bootscript 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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