1

00:00:02,580  -->  00:00:07,019
Before I can actually test my script, I
have some setup to do. So, I want to make

2

00:00:07,019  -->  00:00:11,070
sure that I have /etc/hosts entries for
the servers that I'm going to be calling

3

00:00:11,070  -->  00:00:15,700
by name, and one way we can do this is
with echo...

4

00:00:19,300  -->  00:00:24,640
and combine that with sudo tee -a /etc/hosts.

5

00:00:32,300  -->  00:00:34,250
Okay, that looks good.

6

00:00:34,250  -->  00:00:41,260
Let's see if I can ping them by name.
Okay, 10.9.8.11 for server01,

7

00:00:41,260  -->  00:00:46,800
and I can ping server02 as well when
that corresponds to that 10.9.8.12

8

00:00:46,800  -->  00:00:50,810
address, so that looks good. So now,
the next thing I need to do is configure

9

00:00:50,810  -->  00:00:56,090
SSH authentication, if I haven't. And this
is a new server - I just created it from

10

00:00:56,090  -->  00:00:59,690
scratch, as you saw just a couple of
minutes ago - so i need to create a key

11

00:00:59,690  -->  00:01:05,330
pair on my admin01 server. So, I'll do
that with ssh-keygen. I'm just going

12

00:01:05,330  -->  00:01:09,920
to accept the default options here. I'm not
going to use a passphrase because I want

13

00:01:09,920  -->  00:01:15,020
to execute commands on remote systems
without having to enter a passphrase. Now

14

00:01:15,020  -->  00:01:19,450
I need to copy that public key to our
servers, so I'm going to copy it to

15

00:01:19,450  -->  00:01:25,430
server01. Yes, I'm going to trust
that key, and then I'm going to type in

16

00:01:25,430  -->  00:01:28,250
"vagrant" for the password.
I'm going to do the same thing for

17

00:01:28,250  -->  00:01:34,100
server02 - "yes", and the password is
"vagrant". So now I should be able to

18

00:01:34,100  -->  00:01:38,450
execute a command on one of those
systems without a password, so if I do

19

00:01:38,450  -->  00:01:42,460
ssh server01 and i execute the hostname command -

20

00:01:42,460  -->  00:01:48,440
whoops, if I type the hostname correctly -
then it will execute on the remote system

21

00:01:48,440  -->  00:01:52,300
and return its host name, which is server01. Do the same thing with server02.

22

00:01:52,300  -->  00:01:57,740
Connects remotely, executes command,
exits, and we're back to admin01. So, this

23

00:01:57,740  -->  00:02:04,490
is looking good. So here I am in my /vagrant folder, and I want to create this

24

00:02:04,490  -->  00:02:09,380
list of default servers that we coded
for in our script. So I'm just going to

25

00:02:09,380  -->  00:02:18,320
do this: server01, then add server02.
Now, the single greater-than sign

26

00:02:18,320  -->  00:02:22,850
truncates or overrides a file, so if that
servers file existed it would have been

27

00:02:22,850  -->  00:02:26,780
zeroed out and then the contents of the
echo command would have been stored

28

00:02:26,780  -->  00:02:31,400
in that file. With the double
greater-than signs, it appends to

29

00:02:31,400  -->  00:02:37,250
whatever's in that file. So here we have
server01 and server02 in our

30

00:02:37,250  -->  00:02:41,870
file. Now we're ready to test our script. Of course. the first time we execute our

31

00:02:41,870  -->  00:02:46,510
script, we want to make sure it has the
proper permissions.

32

00:02:47,470  -->  00:02:53,450
Let's test to see if our check about
whether or not it was run with root

33

00:02:53,450  -->  00:02:59,400
privileges works. So, we'll run it with
sudo. It says, "Do not execute this script

34

00:02:59,400  -->  00:03:05,330
as root. Use the -s option instead," and
it gives us our usage statement, and of

35

00:03:05,330  -->  00:03:09,410
course it should exit with a non zero
exit status, which it does. So, that looks

36

00:03:09,410  -->  00:03:13,430
like it's working perfectly. Now, let's
make sure the script displays a usage

37

00:03:13,430  -->  00:03:18,770
message if we don't supply a command to
execute on the list of servers. So, if we

38

00:03:18,770  -->  00:03:23,180
run this command without any options or
any commands, it should show us that

39

00:03:23,180  -->  00:03:26,450
usage statement. And it does, so that
looks good.

40

00:03:26,450  -->  00:03:32,120
Now, let's try our script with an invalid
option. Let's do this: run-everywhere.sh -x,

41

00:03:32,120  -->  00:03:36,110
and we'll say the "hostname" command is
what we want to execute everywhere, but

42

00:03:36,110  -->  00:03:41,210
it turns out -x is an invalid option,
and getopts catches that, and our script

43

00:03:41,210  -->  00:03:47,090
looks good. Okay, so this time let's execute
this script the way it's supposed to be

44

00:03:47,090  -->  00:03:50,930
executed. So, we'll give the name of our
script and the command we want executed

45

00:03:50,930  -->  00:03:55,850
on all the remote systems. So, sure enough,
it executes the hostname command on

46

00:03:55,850  -->  00:03:59,930
server01, which returns of course
server01. and the hostname command on

47

00:03:59,930  -->  00:04:05,630
server02, which returns server02. Now
let's execute the script using the dry

48

00:04:05,630  -->  00:04:11,300
run option, which I opted to use the -n
for. By the way, I use that because it's

49

00:04:11,300  -->  00:04:15,650
a familiar option to me with some other
Linux commands - for example, rsync has

50

00:04:15,650  -->  00:04:19,730
-n for dry run, there are some other
examples - so you could have probably used

51

00:04:19,730  -->  00:04:24,680
-d if you wanted to, but it seems to be
a convention, at least, to use -n for a

52

00:04:24,680  -->  00:04:32,900
dry run option. Anyway, enough of that
aside, let's try it out in our script.

53

00:04:32,900  -->  00:04:38,000
So, it would have executed
ssh -o "ConnectTimeout=2" server01

54

00:04:38,000  -->  00:04:41,289
and then the "hostname" command.
Now, you may not notice it here,

55

00:04:41,289  -->  00:04:45,009
but there is actually an additional
space here, and that's because we have a

56

00:04:45,009  -->  00:04:50,530
space here, and then the $SUDO variable (for which
we didn't use -s, so it's not set, so

57

00:04:50,530  -->  00:04:54,340
it doesn't get displayed), and then a
space after that $SUDO variable. So we end

58

00:04:54,340  -->  00:04:58,599
up with two spaces here. So that's how
that's going to work, by the way.

59

00:04:58,599  -->  00:05:04,120
Okay, let's use the -v option, so we'll
do this: run-everywhere.sh -v. Let's use the

60

00:05:04,120  -->  00:05:08,650
"uptime" command that displays how long a
system has been up, and it's load average,

61

00:05:08,650  -->  00:05:11,889
and how many users are logged in, and
that sort of thing - pretty simple command,

62

00:05:11,889  -->  00:05:16,539
can be kind of interesting. Let's see
what happens. Okay, server01 has been

63

00:05:16,539  -->  00:05:20,979
up 42 minutes, server02 has been up 41
minutes, and load average is zero because

64

00:05:20,979  -->  00:05:25,810
nothing's going on on those systems.
Let's try to combine some options like

65

00:05:25,810  -->  00:05:35,050
the -n and -s options. So, -n is a dry run,
-s is for sudo. So "-ns id", and the

66

00:05:35,050  -->  00:05:38,949
command that would have been executed
would have been that ssh with the ssh

67

00:05:38,949  -->  00:05:43,090
options, the server name, and then the
SUDO variable, which evaluates to "sudo",

68

00:05:43,090  -->  00:05:48,550
and then the command. So, again, here we
had two spaces, here we have a space, and

69

00:05:48,550  -->  00:05:53,050
then the contents of that SUDO variable,
and another space. So, again, that's how

70

00:05:53,050  -->  00:06:03,520
that is going to work. This time, let's
run the "id" command with -v. If we didn't

71

00:06:03,520  -->  00:06:07,090
use the -v option in this particular
case the output is the same, so we

72

00:06:07,090  -->  00:06:10,270
wouldn't have known what servers the
output was coming from.

73

00:06:10,270  -->  00:06:14,710
So using this -v option can help with
that, so that's kind of why we coded it

74

00:06:14,710  -->  00:06:20,620
in here in our little script. Let's
create our own little mini server list

75

00:06:20,620  -->  00:06:25,810
here. We'll just put server01 into that
list, and we'll just call this list test,

76

00:06:25,810  -->  00:06:30,370
cat test, okay, it only has one server in
there, and then let's go ahead and use

77

00:06:30,370  -->  00:06:35,550
that list and execute a command on that
list.

78

00:06:35,550  -->  00:06:41,529
So, again, just one server in the list, it
looped over that list - again, just one

79

00:06:41,529  -->  00:06:45,520
doesn't make a lot of sense, but if you
have multiple servers then you can use

80

00:06:45,520  -->  00:06:48,820
different lists if you want - and then it
executed the "hostname" command that we

81

00:06:48,820  -->  00:06:53,380
specified. So, that looks good. Now, let's
test the case where we provide some

82

00:06:53,380  -->  00:07:05,349
invalid data. So, let's say, run-everywhere.sh
to a file that doesn't exist. Okay, it looks

83

00:07:05,349  -->  00:07:11,800
like our check worked: Can't open the
server list file /path/to/nowhere -

84

00:07:11,800  -->  00:07:14,770
of course, that file is made-up and fake and doesn't

85

00:07:14,770  -->  00:07:19,510
exist, so again our script is working as
it should. Now, let's quickly create a

86

00:07:19,510  -->  00:07:24,279
"test1" account on all the servers in
our server list, and of course creating

87

00:07:24,279  -->  00:07:29,440
accounts requires superuser or root privileges. Okay, so we'll run our command with

88

00:07:29,440  -->  00:07:33,610
the superuser privileges, we'll run the useradd command, and we'll pass it the name

89

00:07:33,610  -->  00:07:38,080
of the account we want to create, which
is "test1". So the useradd command

90

00:07:38,080  -->  00:07:42,729
doesn't generate any output unless it
encounters an error, and it looks like no

91

00:07:42,729  -->  00:07:47,320
errors were displayed, so how do we know
if this worked? Well, one way we can figure

92

00:07:47,320  -->  00:07:50,979
it out is by running a command
everywhere, and that command will be "id"

93

00:07:50,979  -->  00:07:56,320
against the username "test1" to see if
it was created. So, sure enough, we get the

94

00:07:56,320  -->  00:08:00,279
exact same output from both of those
systems because the account was created

95

00:08:00,279  -->  00:08:05,169
exactly the same on both of those
systems. Now, let's say you want to create

96

00:08:05,169  -->  00:08:11,680
a "test2" account with a comment of "Test Two" on all the servers. Because we want

97

00:08:11,680  -->  00:08:16,300
to use a quoted string on the remote
system - because we're going to quote "Test Two"

98

00:08:16,300  -->  00:08:21,339
for our comment - we have to put that
command in quotes. And to quote a quote,

99

00:08:21,339  -->  00:08:25,630
use the opposing quotation mark. Now,
let me just demonstrate that to you on

100

00:08:25,630  -->  00:08:30,070
the command line. So, if we do this: echo
double quote, single quote,

101

00:08:30,070  -->  00:08:35,709
Test Two, single quote, double quote, what we
get displayed is, single quote, Test Two,

102

00:08:35,709  -->  00:08:40,930
single quote. So, we're using the opposite
quotation mark if we want single

103

00:08:40,930  -->  00:08:44,169
quotations to be displayed while we
encapsulate those in double quotation

104

00:08:44,169  -->  00:08:49,939
marks, and if we want to do the opposite,
Test Two here with double quotation

105

00:08:49,939  -->  00:08:53,779
marks, we encapsulate those with single
quotation marks. All right, hopefully that

106

00:08:53,779  -->  00:08:57,740
makes some sense. So, if we want to
preserve our quotes for the argument to

107

00:08:57,740  -->  00:09:01,790
the -c option to the useradd command,
we need to make sure that they are

108

00:09:01,790  -->  00:09:07,490
quoted. So how we would do this is this:
run-everywhere.sh - let's do a dry run to see

109

00:09:07,490  -->  00:09:10,970
what would get executed, so we can test
to make sure we have our quotes right on

110

00:09:10,970  -->  00:09:14,720
the command line before we blast this
command on every server and potentially

111

00:09:14,720  -->  00:09:18,319
have an account created that doesn't
have the right information, or we

112

00:09:18,319  -->  00:09:22,220
accidentally create the wrong account
and so on. So, let's do the dry run option.

113

00:09:22,220  -->  00:09:27,000
We need sudo privileges, or we want to
use sudo privileges here.

114

00:09:34,500  -->  00:09:40,220
Okay, so what would have gotten executed would be ssh with our SSH options, the server name,

115

00:09:40,220  -->  00:09:45,379
then "sudo useradd -c", and sure enough,
our comment is enclosed in quotation

116

00:09:45,379  -->  00:09:50,269
marks, just like we need it on the remote
system. So, this is going to work for us.

117

00:09:50,269  -->  00:09:54,649
So now, what we need to do is just go
back up here, eliminate our dry run

118

00:09:54,649  -->  00:10:01,009
option, and let it execute as root on
those remote systems. Now, let's see if it

119

00:10:01,009  -->  00:10:09,470
got created. And sure enough, it did, and
let's run this with the -v option, and you

120

00:10:09,470  -->  00:10:14,269
see exactly what server is generating
this output. So sure enough, server01 says

121

00:10:14,269  -->  00:10:17,089
we have a test2 account with an ID of
1002,

122

00:10:17,089  -->  00:10:22,100
as does server02. We could also do this:
we could run-everywhere.sh,

123

00:10:22,100  -->  00:10:25,939
-v option to display our server name, and
then we'll just tail the last two

124

00:10:25,939  -->  00:10:29,929
entries in the /etc/passwd file, and we
should see our two accounts that we

125

00:10:29,929  -->  00:10:35,120
created: a test1 account that we did
not supply a comment for, and a test2

126

00:10:35,120  -->  00:10:40,279
account for which we had to be careful
about our quotation marks. Now, let's test

127

00:10:40,279  -->  00:10:45,319
our script even further by executing a
command that doesn't exist, and we want

128

00:10:45,319  -->  00:10:49,500
to make sure that our command exits with
a non-zero exit status. So let's

129

00:10:49,500  -->  00:10:53,569
run-everywhere.sh - I hope this command
doesn't exist on your server. If it does,

130

00:10:53,569  -->  00:10:59,860
well, I'll be darned.
Okay, sure enough, "i-like-eggs" is not a

131

00:10:59,860  -->  00:11:05,740
command on server01 or server02, and
let's look at our return status. Sure

132

00:11:05,740  -->  00:11:10,839
enough, our script exited with a nonzero
exit status, so this is exactly what we

133

00:11:10,839  -->  00:11:15,250
want to do. Now, here's another test we
can do. Let's say one of our servers is

134

00:11:15,250  -->  00:11:19,690
down, and what's going to happen when we
try to execute a command to that server?

135

00:11:19,690  -->  00:11:24,220
Well, let's go ahead and exit out of
admin01. Now I'm back to my local

136

00:11:24,220  -->  00:11:32,380
system, be it Mac, Windows, or Linux. Then
I'm going to halt server02. By the way,

137

00:11:32,380  -->  00:11:35,740
if you run "vagrant halt" without
specifying a server, it's going to halt

138

00:11:35,740  -->  00:11:40,000
all the servers, or all the virtual
machines, defined in your Vagrantfile. I

139

00:11:40,000  -->  00:11:43,899
want to limit this command to just one
server, that's why I specified it here on

140

00:11:43,899  -->  00:11:47,680
the command line.
Likewise, with ssh, I need to specify what

141

00:11:47,680  -->  00:11:52,600
server I'm going to. I'm going to get
into admin01. Let's go back to our

142

00:11:52,600  -->  00:11:57,459
shared vagrant folder, and this time
let's do run-everywhere.sh -v and use the

143

00:11:57,459  -->  00:12:03,910
"uptime" command. So, server01 has been up
51 minutes, and server02, the ssh timed

144

00:12:03,910  -->  00:12:07,990
out - remember we set the time out to be 2
seconds here. And let's check the exit

145

00:12:07,990  -->  00:12:12,670
status of our script. So, it exits with a
nonzero exit status. So, if there is a

146

00:12:12,670  -->  00:12:17,019
server along the way that you can't
connect to, then you get a nonzero exit

147

00:12:17,019  -->  00:12:19,690
status with your scripts and you know,
"Hey, I need to look at that output and

148

00:12:19,690  -->  00:12:24,490
make sure to go and check out that
server." Well, that brings us to the end of

149

00:12:24,490  -->  00:12:28,300
this exercise. I hope you enjoyed it! I'll
be curious to see what you come up with,

150

00:12:28,300  -->  00:12:32,500
and of course I hope you find a way
to put these concepts and techniques

151

00:12:32,500  -->  00:12:36,930
into use into your workflow.
