bosh-bootstrap 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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