bootscript 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,126 @@
1
+ echo "Starting Chef installation..."
2
+
3
+ $ChefVers = "<%= (defined? chef_version) ? chef_version : '' %>"
4
+ $ChefPath = "C:\Chef"
5
+ $ChefLog = "$ChefPath\log\bootscript_log.txt"
6
+ $CreateRAMDisk = $<%= create_ramdisk.to_s.upcase %>
7
+ $RAMDiskMount = "<%= ramdisk_mount %>"
8
+
9
+ function main()
10
+ {
11
+ try
12
+ {
13
+ Create-Chef-Directory-Structure
14
+ if ($createRAMDisk) { Move-Chef-Secrets-To-Ramdisk }
15
+ Install-Chef-Client
16
+ Execute-Chef-Client
17
+ }
18
+ catch{
19
+ write-error $error[0]
20
+ exit 1
21
+ }
22
+ }
23
+
24
+ function Create-Chef-Directory-Structure()
25
+ {
26
+ try
27
+ {
28
+ if (!(Test-Path $ChefPath)) {New-Item $ChefPath -type directory | out-null}
29
+ foreach($f in @("etc", "bin", "log", "tmp", "var"))
30
+ {
31
+ $subdir = "$($ChefPath)\$f"
32
+ if (!(Test-Path $subdir)) {New-Item $subdir -type directory | out-null}
33
+ }
34
+ }
35
+ catch {throw $error[0]}
36
+ }
37
+
38
+ function Move-Chef-Secrets-To-Ramdisk()
39
+ {
40
+ echo "Moving Chef secrets to $RAMDiskMount"
41
+ Move-Item c:\chef\validation.pem $RAMDiskMount
42
+ Move-Item c:\chef\encrypted_data_bag_secret $RAMDiskMount
43
+ }
44
+
45
+ function Install-Chef-Client()
46
+ {
47
+ echo "Downloading Chef installer..."
48
+ $sourceFile = "C:\chef-client.msi"
49
+ $wc = new-object System.Net.WebClient
50
+ try
51
+ {
52
+ $installerURL = Chef-URL
53
+ echo "Downloading $installerURL -> $sourceFile"
54
+ $wc.DownloadFile($installerURL, $sourceFile)
55
+ if ((test-path $sourceFile) -ne $true){ throw "File not found: $sourceFile" }
56
+ echo "Installing Chef installer ($sourceFile) with msiexec..."
57
+ Execute-Command("msiexec /qn /i $sourceFile")
58
+ }
59
+ catch{
60
+ throw $error[0]
61
+ }
62
+ }
63
+
64
+ function Chef-Url()
65
+ {
66
+ $major_rev = [System.Environment]::OSVersion.Version.Major
67
+ $minor_rev = [System.Environment]::OSVersion.Version.Minor
68
+ $winrev = "$major_rev.$minor_rev"
69
+ $arch = "x86_64"
70
+ if ($winrev -eq "5.1") { $machineos = "2003" }
71
+ elseif ($winrev -eq "6.0") { $machineos = "2008" }
72
+ elseif ($winrev -eq "6.1") { $machineos = "2008r2" }
73
+ elseif ($winrev -eq "6.2") { $machineos = "2012" }
74
+ else { throw "ERROR: Windows Server 2003, 2008 or 2012 required" }
75
+ $url = "https://www.opscode.com/chef/download?p=windows&pv=$machineos&m=$arch"
76
+ if ($ChefVers -ne "") { $url = "$url&v=$ChefVers" }
77
+ return $url
78
+ }
79
+
80
+ function Execute-Chef-Client()
81
+ {
82
+ try
83
+ {
84
+ $Env:Path = "$Env:Path;C:\opscode\chef\bin;C:\opscode\chef\embedded\bin"
85
+
86
+ echo "Performing initial convergence..."
87
+ $tinyRunlist = "recipe[chef-client::config],recipe[chef-client::service]"
88
+ $logOptions = "-l info -L $ChefLog"
89
+ Execute-Command("chef-client --once --no-color $logOptions -o $tinyRunlist")
90
+
91
+ echo "Performing full convergence..."
92
+ Execute-Command("chef-client --once --no-color $logOptions")
93
+
94
+ echo "Initial Chef runs completed - see $ChefLog"
95
+ }
96
+ catch
97
+ {
98
+ echo "Chef client execution failed"
99
+ echo "Error was: $error[0]"
100
+ throw $error[0]
101
+ }
102
+ }
103
+
104
+ function Execute-Command($cmd)
105
+ {
106
+ if ($cmd -ne "")
107
+ {
108
+ Try {
109
+ echo "Running: $cmd"
110
+ $Env:_THIS_CMD = $cmd
111
+ $proc = Start-Process -FilePath c:\windows\system32\cmd.exe `
112
+ -ArgumentList "/C", "%_THIS_CMD%" `
113
+ -Verbose -Debug -Wait -Passthru
114
+ do { start-sleep -Milliseconds 500 }
115
+ until ($proc.HasExited)
116
+ echo "Finished: $cmd"
117
+ }
118
+ Catch {
119
+ echo "Failed: $cmd"
120
+ echo "Error was: $error[0]"
121
+ throw $error[0]
122
+ }
123
+ }
124
+ }
125
+
126
+ main
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # Installs chef-client via the ombnibus installer (with minimal dependencies),
3
+ # and tries to converge twice: first, with just this runlist -
4
+ # recipe[chef-client::config],recipe[chef-client::service]
5
+ # ; and then again with the full runlist.
6
+ set -e
7
+ test $UID == 0 || (echo "Error: must run as root"; exit 1)
8
+ echo "Installing Chef with ${0}..."
9
+
10
+ ######### STEP 0: CONFIG, rendered by bootscript gem
11
+ NODE_NAME="<%= chef_attributes['chef_client']['config']['node_name'] %>"
12
+ CHEF_URL="<%= chef_attributes['chef_client']['config']['chef_server_url'] %>"
13
+ VALIDATION_PEM='<%= ramdisk_mount %>/chef/validation.pem'
14
+ DATABAG_SECRET='<%= ramdisk_mount %>/chef/encrypted_data_bag_secret'
15
+ CHEF_VERSION="<%= (defined? chef_version) ? chef_version : '' %>"
16
+ CHEF_INITIAL_RUNLIST="<%=
17
+ (defined? chef_initial_runlist) ? chef_initial_runlist : '' %>"
18
+ OMNIBUS_INSTALLER_URL="http://opscode.com/chef/install.sh"
19
+ CHEF_BIN="/usr/bin/chef-client" # Unix Chef omnibus always symlinks here
20
+ PID_FILE="/var/run/chef/client.pid"
21
+
22
+ ######### STEP 1: MOVE CHEF SECRETS INTO PLACE
23
+ chmod 0600 "$VALIDATION_PEM"
24
+ if ! [ "$VALIDATION_PEM" == "/etc/chef/validation.pem" ] ; then
25
+ ln -sf "$VALIDATION_PEM" "/etc/chef/validation.pem"
26
+ fi
27
+ if [ -e "$DATABAG_SECRET" ] ; then
28
+ chmod 0600 "$DATABAG_SECRET"
29
+ if ! [ "$DATABAG_SECRET" == "/etc/chef/encrypted_data_bag_secret" ] ; then
30
+ ln -sf "$DATABAG_SECRET" "/etc/chef/encrypted_data_bag_secret"
31
+ fi
32
+ fi
33
+
34
+ ######### STEP 2: CONFIGURE OPERATING SYSTEM AND INSTALL DEPENDENCIES
35
+ echo "Performing package update..."
36
+ export DEBIAN_FRONTEND=noninteractive
37
+ apt-get update -y
38
+ echo "Installing build-essential and curl..."
39
+ apt-get --force-yes -y install build-essential curl
40
+
41
+ ######### STEP 3: INSTALL CHEF VIA OMNIBUS INSTALLER
42
+ echo "Downloading Chef..."
43
+ installer=$(basename $OMNIBUS_INSTALLER_URL)
44
+ if ! (which $CHEF_BIN >/dev/null 2>&1) ; then
45
+ if (which curl >/dev/null 2>&1); then
46
+ curl -L -o $installer $OMNIBUS_INSTALLER_URL
47
+ else
48
+ echo "Cannot find curl - cannot download Chef!"
49
+ fi
50
+ fi
51
+ cmd="bash $installer"
52
+ if ! [ "$CHEF_VERSION" == "" ] ; then
53
+ cmd="$cmd -v ${CHEF_VERSION}"
54
+ fi
55
+ echo "Installing Chef..."
56
+ $cmd
57
+ rm -f $installer
58
+
59
+ ######### STEP 4: INITIAL, ONE-PASS CHEF CONVERGENCE
60
+ echo "Performing initial convergence..."
61
+ cmd="$CHEF_BIN --once --no-color"
62
+ if [[ "$CHEF_INITIAL_RUNLIST" != "" ]] ; then
63
+ cmd="$cmd -o $CHEF_INITIAL_RUNLIST"
64
+ fi
65
+ echo $cmd
66
+ $cmd
67
+
68
+ ######### STEP 5: SIGNAL SERVICE TO CONVERGE
69
+ echo "Signalling chef service to converge..."
70
+ killall -USR1 chef-client || echo " (...no chef client service detected)"
71
+
72
+ echo "Done."
@@ -0,0 +1,9 @@
1
+ <% %w{node_name chef_server_url validation_client_name environment}.each do |param| %>
2
+ <% if chef_attributes['chef_client']['config'][param] %>
3
+ <%= param %> '<%= chef_attributes['chef_client']['config'][param] %>'
4
+ <% end %>
5
+ <% end %>
6
+ chef_dir = "<%= platform =~ /windows/i ? '/chef' : '/etc/chef' %>"
7
+ Dir.glob(File.join(chef_dir, "client.d", "*.rb")).each do |conf|
8
+ Chef::Config.from_file(conf)
9
+ end
@@ -0,0 +1,4 @@
1
+ attributes_file = "#{::File.dirname(__FILE__)}/../attributes.json"
2
+ if ::File.exists? attributes_file
3
+ json_attribs attributes_file
4
+ end
@@ -0,0 +1,2 @@
1
+ validation_key "<%= ramdisk_mount %>/validation.pem"
2
+ # data bag decryption secret is at <%= ramdisk_mount %>/encrypted_data_bag_secret
@@ -0,0 +1,4 @@
1
+ main
2
+ }.Invoke($args)
3
+ <% if add_script_tags %></script>
4
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <% if add_script_tags %><script>
2
+ <% end -%>@@echo off
3
+ :CheckPowerShellExecutionPolicy
4
+ @@FOR /F "tokens=*" %%i IN ('powershell -noprofile -command Get-ExecutionPolicy') DO Set PSExecMode=%%i
5
+ @@if /I "%PSExecMode%"=="unrestricted" goto :RunPowerShellScript
6
+
7
+ @@NET FILE 1>NUL 2>NUL
8
+ @@if not "%ERRORLEVEL%"=="0" (
9
+ @@echo Elevation required to change PowerShell execution policy from [%PSExecMode%] to Unrestricted
10
+ @@powershell -NoProfile -Command "start-process -Wait -Verb 'RunAs' -FilePath 'powershell.exe' -ArgumentList '-NoProfile Set-ExecutionPolicy Unrestricted'" ) else (
11
+ @@powershell -NoProfile Set-ExecutionPolicy Unrestricted )
12
+
13
+ :RunPowerShellScript
14
+ @@set POWERSHELL_BAT_ARGS=%*
15
+ @@if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%
16
+ @@PowerShell -Command Invoke-Expression $('$args=@(^&{$args} %POWERSHELL_BAT_ARGS%);'+[String]::Join([Environment]::NewLine,$((Get-Content '%~f0') -notmatch '^^@@^|^^:'))) > c:\bootscript.log 2>&1 & goto :EOF
17
+
18
+ {
@@ -0,0 +1,48 @@
1
+ require 'bootscript'
2
+ include Bootscript
3
+
4
+ describe Chef do
5
+
6
+ describe :files do
7
+ context "given a set of ERB template vars" do
8
+ erb_vars = {
9
+ ramdisk_mount: '/mount/myramdisk',
10
+ chef_validation_pem: 'MYPEM',
11
+ chef_databag_secret: 'SECRET',
12
+ }
13
+ it "returns a Hash mapping locations on the boot target to local data" do
14
+ Chef.files(erb_vars).should be_a Hash
15
+ end
16
+ it "maps the Chef Validation data into place on the target's RAMdisk" do
17
+ Chef.files(erb_vars)[
18
+ "#{erb_vars[:ramdisk_mount]}/chef/validation.pem"
19
+ ].should be erb_vars[:chef_validation_pem]
20
+ end
21
+ it "maps the Chef data bag secret into place on the target's RAMdisk" do
22
+ Chef.files(erb_vars)[
23
+ "#{erb_vars[:ramdisk_mount]}/chef/encrypted_data_bag_secret"
24
+ ].should be erb_vars[:chef_databag_secret]
25
+ end
26
+ end
27
+ end
28
+
29
+ describe :included? do
30
+ desired_key = :chef_validation_pem
31
+ context "given a set of ERB template vars with key :#{desired_key}" do
32
+ it "returns true" do
33
+ Chef.included?(chef_validation_pem: 'SOME DATA').should be true
34
+ end
35
+ end
36
+ context "given a set of ERB template vars without key :#{desired_key}" do
37
+ it "returns false" do
38
+ Chef.included?({}).should be false
39
+ end
40
+ end
41
+ context "given nothing" do
42
+ it "returns false" do
43
+ Chef.included?().should be false
44
+ end
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,126 @@
1
+ require 'bootscript'
2
+ require 'logger' # for testing logging functionality
3
+ require 'tmpdir' # for unpacking Script archives
4
+ include Bootscript # for brevity
5
+
6
+ describe Script do
7
+
8
+ #### TEST SETUP
9
+ before :each do
10
+ @script = Script.new()
11
+ end
12
+
13
+ #### TEST PUBLIC INSTANCE MEMBER VARIABLES
14
+
15
+ it "has a public @data_map Hash, for mapping local data to the boot target" do
16
+ Script.new().should respond_to(:data_map)
17
+ Script.new().data_map.should be_a Hash
18
+ end
19
+
20
+ it "exposes a Ruby Logger as its public @log member, to adjust log level" do
21
+ Script.new().should respond_to(:log)
22
+ Script.new().log.should be_a Logger
23
+ end
24
+
25
+ #### TEST PUBLIC METHODS
26
+
27
+ describe :initialize do
28
+ context "when invoked with a logger" do
29
+ it "sets the BootScript's @log to the passed Logger object" do
30
+ my_logger = Logger.new(STDOUT)
31
+ Script.new(my_logger).log.should be my_logger
32
+ end
33
+ end
34
+ context "when invoked with no logger" do
35
+ it "assigns a default Logger the BootScript's @log" do
36
+ Script.new().log.should be_a Logger
37
+ end
38
+ end
39
+ end
40
+
41
+ describe :generate do
42
+ # test arguments / arity
43
+ it "accepts an (optional) Hash of template vars" do
44
+ expect{@script.generate}.to_not raise_error
45
+ expect{@script.generate({some_key: :some_value})}.to_not raise_error
46
+ end
47
+ it "accepts an (optional) destination for the generated text" do
48
+ File.open('/dev/null', 'w') do |outfile| # write to nowhere!
49
+ expect{@script.generate({}, outfile)}.to_not raise_error
50
+ end
51
+ end
52
+ # test output format
53
+ it "produces a Bash script" do
54
+ @script.generate.lines.first.chomp.should eq '#!/usr/bin/env bash'
55
+ end
56
+ # test stripping of empty lines and comments
57
+ context "when invoked with :strip_comments = true (the default)" do
58
+ it "strips all empty lines and comments from the output" do
59
+ lines = @script.generate.lines.to_a
60
+ lines[1..lines.count].each do |line|
61
+ line.should_not match /^#/
62
+ line.should_not match /^\s+$/
63
+ end
64
+ end
65
+ end
66
+ context "when invoked with :strip_comments = false" do
67
+ it "leaves empty lines and comments in the output" do
68
+ lines = @script.generate(strip_comments: false).lines.to_a
69
+ lines.select{|l| l =~ /^\s+$/}.count.should be > 0 # check empty lines
70
+ lines.select{|l| l =~ /^#/}.count.should be > 1 # check comments
71
+ end
72
+ end
73
+
74
+ # test rendering of built-in variables into built-in templates
75
+ vars = {create_ramdisk: false, ramdisk_size: 5,
76
+ ramdisk_mount: '/secrets', update_os: false}
77
+ vars.keys.each do |var|
78
+ it "renders template variable :#{var} as Bash variable #{var.upcase}" do
79
+ rendered_config = Unpacker.new(Script.new.generate(vars)).config
80
+ vars[var].to_s.should eq rendered_config[var.upcase.to_s]
81
+ end
82
+ end
83
+ # test rendering of custom templates
84
+ it "renders custom templates into place with correct ERB values" do
85
+ @script.data_map = {'/hello.sh' => 'echo Hello, <%= my_name %>.'}
86
+ text = @script.generate(my_name: 'H. L. Mencken')
87
+ Dir.mktmpdir do |tmp_dir| # do unarchiving in a temp dir
88
+ Unpacker.new(text).unpack_to tmp_dir
89
+ File.exists?("#{tmp_dir}/hello.sh").should == true
90
+ File.read("#{tmp_dir}/hello.sh").should eq 'echo Hello, H. L. Mencken.'
91
+ end
92
+ end
93
+ # test raw file copying
94
+ it "copies non-template files directly into the generated archive" do
95
+ # insert this test file itself into the BootScript's archive! :-/
96
+ @script.data_map = {File.basename(__FILE__) => File.new(__FILE__)}
97
+ Dir.mktmpdir do |tmp_dir| # do unarchiving in a temp dir
98
+ target_file = "#{tmp_dir}/#{File.basename(__FILE__)}"
99
+ Unpacker.new(@script.generate).unpack_to tmp_dir
100
+ File.exists?(target_file).should == true
101
+ File.read(target_file).should eq File.read(__FILE__)
102
+ end
103
+ end
104
+ # test return values
105
+ context "when invoked without any output destination" do
106
+ it "returns the rendered text of the BootScript" do
107
+ rendered_text = @script.generate
108
+ rendered_text.should be_a String
109
+ rendered_text.lines.first.chomp.should eq '#!/usr/bin/env bash'
110
+ rendered_text.lines.should include("__ARCHIVE_FOLLOWS__\n")
111
+ end
112
+ end
113
+ context "when invoked with a custom output destination" do
114
+ it "returns the number of bytes written to the destination" do
115
+ bytes_written, script_size = 0, @script.generate.bytes.count
116
+ File.open('/dev/null', 'w') do |outfile|
117
+ bytes_written = @script.generate({}, outfile)
118
+ end
119
+ bytes_written.should be_a Fixnum
120
+ bytes_written.should == script_size
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,30 @@
1
+ require 'bootscript'
2
+ include Bootscript # for brevity
3
+
4
+ describe UUWriter do
5
+
6
+ #### TEST PUBLIC INSTANCE MEMBER VARIABLES
7
+
8
+ it "exposes a the number of bytes written as an integer" do
9
+ UUWriter.new(nil).should respond_to(:bytes_written)
10
+ UUWriter.new(nil).bytes_written.should be_a Fixnum
11
+ end
12
+
13
+ #### TEST PUBLIC METHODS
14
+
15
+ describe :initialize do
16
+ it "sets bytes_written to zero" do
17
+ UUWriter.new(nil).bytes_written.should == 0
18
+ end
19
+ end
20
+
21
+ describe :write do
22
+ it "writes the uuencoded version of its argument to its output member" do
23
+ destination = StringIO.open("", 'w')
24
+ UUWriter.new(destination).write("Encode me!")
25
+ destination.close
26
+ destination.string.should == ["Encode me!"].pack('m')
27
+ end
28
+ end
29
+
30
+ end