bosh-bootstrap 0.5.0

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.
Files changed (47) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +318 -0
  5. data/Rakefile +1 -0
  6. data/bin/bosh-bootstrap +8 -0
  7. data/bosh-bootstrap.gemspec +34 -0
  8. data/lib/bosh-bootstrap.rb +10 -0
  9. data/lib/bosh-bootstrap/cli.rb +1024 -0
  10. data/lib/bosh-bootstrap/commander.rb +9 -0
  11. data/lib/bosh-bootstrap/commander/README.md +47 -0
  12. data/lib/bosh-bootstrap/commander/command.rb +25 -0
  13. data/lib/bosh-bootstrap/commander/commands.rb +80 -0
  14. data/lib/bosh-bootstrap/commander/local_server.rb +68 -0
  15. data/lib/bosh-bootstrap/commander/remote_script_command.rb +48 -0
  16. data/lib/bosh-bootstrap/commander/remote_server.rb +121 -0
  17. data/lib/bosh-bootstrap/commander/upload_command.rb +17 -0
  18. data/lib/bosh-bootstrap/helpers.rb +2 -0
  19. data/lib/bosh-bootstrap/helpers/fog_setup.rb +50 -0
  20. data/lib/bosh-bootstrap/helpers/settings.rb +36 -0
  21. data/lib/bosh-bootstrap/stages.rb +8 -0
  22. data/lib/bosh-bootstrap/stages/stage_micro_bosh_delete.rb +90 -0
  23. data/lib/bosh-bootstrap/stages/stage_micro_bosh_delete/bosh_micro_delete +19 -0
  24. data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy.rb +135 -0
  25. data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy/bosh_micro_deploy +36 -0
  26. data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy/download_micro_bosh_stemcell +132 -0
  27. data/lib/bosh-bootstrap/stages/stage_micro_bosh_deploy/install_key_pair_for_user +23 -0
  28. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm.rb +52 -0
  29. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/convert_salted_password +9 -0
  30. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/create_vcap_user +79 -0
  31. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_base_packages +13 -0
  32. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_bosh +54 -0
  33. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_ruby +33 -0
  34. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/install_useful_gems +24 -0
  35. data/lib/bosh-bootstrap/stages/stage_prepare_inception_vm/validate_bosh_deployer +21 -0
  36. data/lib/bosh-bootstrap/stages/stage_setup_new_bosh.rb +52 -0
  37. data/lib/bosh-bootstrap/stages/stage_setup_new_bosh/cleanup_permissions +14 -0
  38. data/lib/bosh-bootstrap/stages/stage_setup_new_bosh/setup_bosh_user +29 -0
  39. data/lib/bosh-bootstrap/stages/stage_validate_inception_vm.rb +39 -0
  40. data/lib/bosh-bootstrap/stages/stage_validate_inception_vm/validate_ubuntu +6 -0
  41. data/lib/bosh-bootstrap/version.rb +5 -0
  42. data/lib/bosh/providers.rb +21 -0
  43. data/lib/bosh/providers/README.md +5 -0
  44. data/lib/bosh/providers/aws.rb +77 -0
  45. data/lib/bosh/providers/base_provider.rb +20 -0
  46. data/lib/bosh/providers/openstack.rb +40 -0
  47. metadata +239 -0
@@ -0,0 +1,9 @@
1
+ module Bosh::Bootstrap::Commander
2
+ end
3
+
4
+ require "bosh-bootstrap/commander/command"
5
+ require "bosh-bootstrap/commander/remote_script_command"
6
+ require "bosh-bootstrap/commander/upload_command"
7
+ require "bosh-bootstrap/commander/commands"
8
+ require "bosh-bootstrap/commander/local_server"
9
+ require "bosh-bootstrap/commander/remote_server"
@@ -0,0 +1,47 @@
1
+ # Bosh::Bootstrap::Commander
2
+
3
+ The sequence of commands that are run on an Inception VM can be either invoked locally on the Inception VM or from a remote machine. The `Commander` provides a DSL for describing the commands to be run and allows them to be sequentially run against a local or remote server.
4
+
5
+ Remote servers are accessed via SSH commands.
6
+
7
+ Example commands:
8
+ ``` ruby
9
+ commands = Bosh::Bootstrap::Commander::Commands.new do |server|
10
+ server.create "vcap user", <<-BASH
11
+ #!/usr/bin/env bash
12
+
13
+ groupadd vcap
14
+ useradd vcap -m -g vcap
15
+ mkdir -p /home/vcap/.ssh
16
+ chown -R vcap:vcap /home/vcap/.ssh
17
+ BASH
18
+
19
+ server.install "rvm & ruby", <<-BASH
20
+ #!/usr/bin/env bash
21
+
22
+ if [[ -x rvm ]]
23
+ then
24
+ rvm get stable
25
+ else
26
+ curl -L get.rvm.io | bash -s stable
27
+ source /etc/profile.d/rvm.sh
28
+ fi
29
+ command rvm install 1.9.3 # oh god this takes a long time
30
+ rvm 1.9.3
31
+ rvm alias create default 1.9.3
32
+ BASH
33
+ end
34
+
35
+ @local_machine.run(commands)
36
+ @server.run(commands)
37
+ ```
38
+
39
+ There is a set of predefined command methods which give nicer text output. You can also invoke any method upon `server` and it will be supported as the command name. The name of the method invoked is semantically meaningless; it is a convenience.
40
+
41
+ * `assign`
42
+ * `create`
43
+ * `download`
44
+ * `install`
45
+ * `provision`
46
+ * `store`
47
+ * `validate`
@@ -0,0 +1,25 @@
1
+ # A single command/script to be run on a local/remote server
2
+ # For the display, it has an active ("installing") and
3
+ # past tense ("installed") verb and a noub/description ("packages")
4
+ module Bosh::Bootstrap::Commander
5
+ class Command
6
+ attr_reader :command # verb e.g. "install"
7
+ attr_reader :description # noun phrase, e.g. "packages"
8
+
9
+ attr_reader :full_present_tense # e.g. "installing packages"
10
+ attr_reader :full_past_tense # e.g. "installed packages"
11
+
12
+ def initialize(command, description, full_present_tense=nil, full_past_tense=nil)
13
+ @command = command
14
+ @description = description
15
+ @full_present_tense = full_present_tense || "#{command} #{description}"
16
+ @full_past_tense = full_past_tense || "#{command} #{description}"
17
+ end
18
+
19
+ # Invoke this command (subclass) to call back upon
20
+ # +server+ to perform a server helper
21
+ def perform(server)
22
+ raise "please implement this method to call back upon `server`"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,80 @@
1
+ module Bosh::Bootstrap::Commander
2
+ class Commands
3
+ attr_reader :commands
4
+
5
+ def initialize(&block)
6
+ @commands = []
7
+ yield self
8
+ end
9
+
10
+ def upload_file(target_path, file_contents)
11
+ @commands << UploadCommand.new(target_path, file_contents)
12
+ end
13
+
14
+ #
15
+ # Generic remote script commands with custom phrases
16
+ #
17
+
18
+ def assign(description, script, options={})
19
+ @commands << RemoteScriptCommand.new(
20
+ "assign", description, script,
21
+ "assigning #{description}", "assigned #{description}", options)
22
+ end
23
+
24
+ # Runs a script on target server, and stores the (stripped) STDOUT into
25
+ # settings.
26
+ #
27
+ # Usage:
28
+ # server.capture_value "salted password", script("convert_salted_password", "PASSWORD" => settings.bosh.password),
29
+ # :settings => "bosh.salted_password"
30
+ #
31
+ # Would store the returned STDOUT into settings[:bosh][:salted_password]
32
+ def capture_value(description, script, options)
33
+ @commands << RemoteScriptCommand.new(
34
+ "capture value", description, script,
35
+ "captures value of #{description}", "captured value of #{description}", options)
36
+ end
37
+
38
+ def create(description, script, options={})
39
+ @commands << RemoteScriptCommand.new(
40
+ "create", description, script,
41
+ "creating #{description}", "created #{description}", options)
42
+ end
43
+
44
+ def download(description, script, options={})
45
+ @commands << RemoteScriptCommand.new(
46
+ "download", description, script,
47
+ "downloading #{description}", "downloaded #{description}", options)
48
+ end
49
+
50
+ def install(description, script, options={})
51
+ @commands << RemoteScriptCommand.new(
52
+ "install", description, script,
53
+ "installing #{description}", "installed #{description}", options)
54
+ end
55
+
56
+ def provision(description, script, options={})
57
+ @commands << RemoteScriptCommand.new(
58
+ "provision", description, script,
59
+ "provisioning #{description}", "provisioned #{description}", options)
60
+ end
61
+
62
+ def store(description, script, options={})
63
+ @commands << RemoteScriptCommand.new(
64
+ "store", description, script,
65
+ "storing #{description}", "stored #{description}", options)
66
+ end
67
+
68
+ def validate(description, script, options={})
69
+ @commands << RemoteScriptCommand.new(
70
+ "validate", description, script,
71
+ "validating #{description}", "validated #{description}", options)
72
+ end
73
+
74
+ # catch-all for commands with generic active/past tense phrases
75
+ def method_missing(command, *args, &blk)
76
+ description, script = args[0..1]
77
+ @commands << RemoteScriptCommand.new(command.to_s, description, script)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,68 @@
1
+ require "popen4"
2
+
3
+ class Bosh::Bootstrap::Commander::LocalServer
4
+ attr_reader :logfile
5
+
6
+ def initialize(logfile=STDERR)
7
+ @logfile = logfile
8
+ end
9
+
10
+ # Execute the +Command+ objects, in sequential order
11
+ # upon the local server
12
+ # +commands+ is a +Commands+ container
13
+ #
14
+ # Returns false once any subcommand fails to execute successfully
15
+ def run(commands)
16
+ commands.commands.each do |command|
17
+ puts command.full_present_tense
18
+ if command.perform(self)
19
+ Thor::Base.shell.new.say "Successfully #{command.full_past_tense}", :green
20
+ else
21
+ Thor::Base.shell.new.say_status "error", "#{command.full_present_tense}", :red
22
+ return false
23
+ end
24
+ end
25
+ end
26
+
27
+ #
28
+ # Commands performed on local server
29
+ # These map to Command subclasses, which then callback to these
30
+ # local server specific implementations
31
+ #
32
+
33
+ # Run a script
34
+ def run_script(command, script, options={})
35
+ command.as_file do |execution_command|
36
+ `chmod +x #{execution_command}`
37
+ status = POpen4::popen4(execution_command) do |stdout, stderr, stdin, pid|
38
+ out = stdout.read
39
+ logfile.puts out unless out.strip == ""
40
+ err = stderr.read
41
+ if err.strip != ""
42
+ logfile.puts err
43
+ STDERR.puts err if logfile != STDERR
44
+ end
45
+ logfile.flush
46
+ end
47
+ status.success?
48
+ end
49
+ rescue StandardError => e
50
+ logfile.puts e.message
51
+ false
52
+ end
53
+
54
+ # Upload a file (put a file into local filesystem)
55
+ def upload_file(command, path, contents, upload_as_user=nil)
56
+ basedir = File.dirname(path)
57
+ unless File.directory?(basedir)
58
+ logfile.puts "creating micro-bosh manifest folder: #{basedir}"
59
+ FileUtils.mkdir_p(basedir)
60
+ end
61
+ logfile.puts "creating micro-bosh manifest: #{path}"
62
+ File.open(path, "w") { |file| file << contents }
63
+ true
64
+ rescue StandardError => e
65
+ logfile.puts e.message
66
+ false
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ # A single command/script to be run on a local/remote server
2
+ # For the display, it has an active ("installing") and
3
+ # past tense ("installed") verb and a noub/description ("packages")
4
+ module Bosh::Bootstrap::Commander
5
+ class RemoteScriptCommand < Command
6
+ attr_reader :command # verb e.g. "install"
7
+ attr_reader :description # noun phrase, e.g. "packages"
8
+ attr_reader :script
9
+
10
+ attr_reader :full_present_tense # e.g. "installing packages"
11
+ attr_reader :full_past_tense # e.g. "installed packages"
12
+
13
+ # Optional:
14
+ attr_reader :specific_run_as_user # e.g. ubuntu
15
+ attr_reader :settings # settings manifest (result of script might get stored back)
16
+ attr_reader :save_output_to_settings_key # e.g. bosh.salted_password
17
+
18
+ def initialize(command, description, script, full_present_tense=nil, full_past_tense=nil, options={})
19
+ super(command, description, full_present_tense, full_past_tense)
20
+ @script = script
21
+ @specific_run_as_user = options[:user]
22
+ @settings = options[:settings]
23
+ @save_output_to_settings_key = options[:save_output_to_settings_key]
24
+ end
25
+
26
+ # Invoke this command to call back upon +server.run_script+
27
+ def perform(server)
28
+ server.run_script(self, script, :user => specific_run_as_user,
29
+ :settings => settings, :save_output_to_settings_key => save_output_to_settings_key)
30
+ end
31
+
32
+ # Provide a filename that represents this Command
33
+ def to_filename
34
+ @to_filename ||= "#{command} #{description}".gsub(/\W+/, '_')
35
+ end
36
+
37
+ # Stores the script on the local filesystem in a temporary directory
38
+ # Returns path
39
+ def as_file(&block)
40
+ tmpdir = ENV['TMPDIR'] || "/tmp"
41
+ script_path = File.join(tmpdir, to_filename)
42
+ File.open(script_path, "w") do |f|
43
+ f << @script
44
+ end
45
+ yield script_path
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,121 @@
1
+ require "net/scp"
2
+ require "tempfile"
3
+
4
+ class Bosh::Bootstrap::Commander::RemoteServer
5
+
6
+ attr_reader :host
7
+ attr_reader :default_username
8
+ attr_reader :logfile
9
+
10
+ def initialize(host, logfile=STDERR)
11
+ @host, @logfile = host, logfile
12
+ @default_username = "vcap" # unless overridden by a Command (before vcap exists)
13
+ end
14
+
15
+ # Execute the +Command+ objects, in sequential order
16
+ # upon the local server
17
+ # +commands+ is a +Commands+ container
18
+ #
19
+ # Returns false once any subcommand fails to execute successfully
20
+ def run(commands)
21
+ commands.commands.each do |command|
22
+ puts command.full_present_tense
23
+ if command.perform(self)
24
+ Thor::Base.shell.new.say "Successfully #{command.full_past_tense}", :green
25
+ else
26
+ Thor::Base.shell.new.say_status "error", "#{command.full_present_tense}", :red
27
+ return false
28
+ end
29
+ end
30
+ end
31
+
32
+ #
33
+ # Commands performed on remote server
34
+ # These map to Command subclasses, which then callback to these
35
+ # remote server specific implementations
36
+ #
37
+
38
+ # Run a script
39
+ #
40
+ # Uploads it to the remote server, makes it executable, then executes
41
+ # Stores the last line of stripped STDOUT/STDERR into a settings field,
42
+ # if :settings & :save_output_to_settings_key => "x.y.z" provided
43
+ def run_script(command, script, options={})
44
+ run_as_user = options[:user] || default_username
45
+ settings = options[:settings]
46
+ settings_key = options[:save_output_to_settings_key]
47
+
48
+ remote_path = remote_tmp_script_path(command)
49
+ upload_file(command, remote_path, script, run_as_user)
50
+ output, status = run_remote_script(remote_path, run_as_user)
51
+ output =~ /^(.*)\Z/
52
+ last_line = $1
53
+ # store output into a settings field, if requested
54
+ if settings_key
55
+ settings_key_portions = settings_key.split(".")
56
+ parent_key_portions, final_key = settings_key_portions[0..-2], settings_key_portions[-1]
57
+ target_settings_field = settings
58
+ parent_key_portions.each do |key_portion|
59
+ target_settings_field[key_portion] ||= {}
60
+ target_settings_field = target_settings_field[key_portion]
61
+ end
62
+ target_settings_field[final_key] = last_line.strip
63
+ end
64
+ status
65
+ rescue StandardError => e
66
+ logfile.puts e.message
67
+ false
68
+ end
69
+
70
+ # Upload a file (put a file into the remote server's filesystem)
71
+ def upload_file(command, remote_path, contents, upload_as_user=nil)
72
+ upload_as_user ||= default_username
73
+ run_remote_command("mkdir -p #{File.dirname(remote_path)}", upload_as_user)
74
+ Tempfile.open("remote_script") do |file|
75
+ file << contents
76
+ file.flush
77
+ logfile.puts "uploading #{remote_path} to Inception VM"
78
+ Net::SCP.upload!(host, upload_as_user, file.path, remote_path)
79
+ end
80
+ true
81
+ rescue StandardError => e
82
+ logfile.puts e.message
83
+ false
84
+ end
85
+
86
+ private
87
+ def remote_tmp_script_path(command)
88
+ "/tmp/remote_script_#{command.to_filename}"
89
+ end
90
+
91
+ # Makes +remote_path+ executable, then runs it
92
+ # Returns:
93
+ # * a String of all STDOUT/STDERR; which is also appended to +logfile+
94
+ # * status (true = success)
95
+ #
96
+ # TODO catch exceptions http://learnonthejob.blogspot.com/2010/08/exception-handling-for-netssh.html
97
+ def run_remote_script(remote_path, username)
98
+ commands = [
99
+ "chmod +x #{remote_path}",
100
+ "bash -lc 'sudo /usr/bin/env PATH=$PATH #{remote_path}'"
101
+ ]
102
+ script_output = ""
103
+ results = Fog::SSH.new(host, username).run(commands) do |stdout, stderr|
104
+ [stdout, stderr].flatten.each do |data|
105
+ logfile << data
106
+ script_output << data
107
+ end
108
+ end
109
+ result = results.last
110
+ result_success = result.status == 0
111
+ [script_output, result_success]
112
+ end
113
+
114
+ def run_remote_command(command, username)
115
+ Net::SSH.start(host, username) do |ssh|
116
+ ssh.exec!("bash -lc '#{command}'") do |channel, stream, data|
117
+ logfile << data
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ # A single command/script to be run on a local/remote server
2
+ # For the display, it has an active ("installing") and
3
+ # past tense ("installed") verb and a noub/description ("packages")
4
+ module Bosh::Bootstrap::Commander
5
+ class UploadCommand < Command
6
+ def initialize(target_path, file_contents)
7
+ super("upload", "file", "uploading file", "uploaded file")
8
+ @target_path = target_path
9
+ @file_contents = file_contents
10
+ end
11
+
12
+ # Invoke this command to call back upon +server.upload_file+
13
+ def perform(server)
14
+ server.upload_file(self, @target_path, @file_contents)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,2 @@
1
+ require "bosh-bootstrap/helpers/settings"
2
+ require "bosh-bootstrap/helpers/fog_setup"
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ require "fog"
4
+ module Bosh; module Bootstrap; module Helpers; end; end; end
5
+
6
+ # A collection of methods related to getting fog_compute
7
+ # credentials for creating the inception VM
8
+ # and to provide to the MicroBOSH for its uses.
9
+ #
10
+ # Attempts to look in +settings.fog_path+ to see if there is a .fog file.
11
+ module Bosh::Bootstrap::Helpers::FogSetup
12
+
13
+ # fog connection object to Compute tasks (VMs, IP addresses)
14
+ def fog_compute
15
+ @fog_compute ||= begin
16
+ # Fog::Compute.new requires Hash with keys that are symbols
17
+ # but Settings converts all keys to strings
18
+ # So create a version of settings.fog_credentials with symbol keys
19
+ credentials_with_symbols = settings.fog_credentials.inject({}) do |creds, key_pair|
20
+ key, value = key_pair
21
+ creds[key.to_sym] = value
22
+ creds
23
+ end
24
+ Fog::Compute.new(credentials_with_symbols)
25
+ end
26
+ end
27
+
28
+ def reset_fog_compute
29
+ @fog_compute = nil
30
+ @provider = nil # in cli.rb - I don't like this; need one wrapper for all CPI/compute calls
31
+ # or don't create fog_compute until we know all IaaS details
32
+ end
33
+
34
+ def fog_config
35
+ @fog_config ||= begin
36
+ if File.exists?(fog_config_path)
37
+ say "Found infrastructure API credentials at #{fog_config_path} (override with --fog)"
38
+ YAML.load_file(fog_config_path)
39
+ else
40
+ say "No existing #{fog_config_path} fog configuration file", :yellow
41
+ {}
42
+ end
43
+ end
44
+ end
45
+
46
+ def fog_config_path
47
+ settings.fog_path
48
+ end
49
+
50
+ end