bosh-bootstrap 0.6.0 → 0.7.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.
data/.gitignore CHANGED
@@ -15,4 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .DS_Store
18
+ .DS_Store
19
+ .rvmrc
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-19mode
5
+ # - ruby-head - generates "Cannot find Syck parser for YAML"
data/ChangeLog.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Change Log
2
2
 
3
+ ## v0.7
4
+
5
+ Notable:
6
+
7
+ * For existing users: please run "deploy --upgrade-deps" as new inception package (runit) added; and jazor/yaml_command CLIs installed
8
+ * Forces microbosh stemcells 0.8.1 which work with public gems (latest public stemcell does not work with public gems)
9
+
10
+ Added:
11
+
12
+ * `mosh` command - connect to Inception VM on trains over flaky internet connections (use instead of `ssh` or `tmux` command) [thx @mrdavidlang]
13
+ * `upgrade-inception` command - to perform an upgrade of the Inception VM without triggering a re-deploy of microbosh.
14
+
15
+ Changes:
16
+
17
+ * Inception VM now installs rubygems 2.0.0 & bundler 1.3.0
18
+ * Better idempotence for re-deploying microbosh - will delete&deploy after a failure; will deploy after a deletion.
19
+ * Downloads ubuntu 10.04 ISO to speed up custom stemcell builds
20
+ * Using redcard to ensure ruby 1.9 only
21
+ * `manifest.yml` stores the name of the stemcell created from a custom stemcell build; no longer re-creates stemcell each time
22
+ * git color is enabled on inception VM
23
+
24
+ Work in progress:
25
+
26
+ * AWS VPC support was begin by the core BOSH team; though work has stopped sadly.
27
+ * Growing number of specs mostly using Fog.mock! mode; tests being run on travis
28
+
29
+
3
30
  ## v0.6
4
31
 
5
32
  Highlights:
data/README.md CHANGED
@@ -7,7 +7,7 @@ The Stark & Wayne Bosh Bootstrapper is the simplest way to get a Micro BOSH runn
7
7
  Bootstrap a Micro BOSH universe from one CLI command. Also allows SSH access and the ability to delete created Micro BOSHes.
8
8
 
9
9
  ```
10
- $ bosh-bootstrap deploy --latest-stemcell
10
+ $ bosh-bootstrap deploy
11
11
  Creating inception VM...
12
12
  Creating micro BOSH VM...
13
13
 
@@ -22,11 +22,17 @@ It is now very simple to bootstrap a micro BOSH from a single, local CLI. The bo
22
22
 
23
23
  To be cute about it, the Stark & Wayne Bosh Bootstrapper aims to provide lifecycle management for the BOSH lifecycle manager. Zing! See the "Deep dive into deploy command" section below for greater understanding why the Stark & Wayne Bosh Bootstrapper is very useful.
24
24
 
25
+ [![Build Status](https://travis-ci.org/StarkAndWayne/bosh-bootstrap.png?branch=master)](https://travis-ci.org/StarkAndWayne/bosh-bootstrap)
26
+
27
+ [![Code Climate](https://codeclimate.com/github/StarkAndWayne/bosh-bootstrap.png)](https://codeclimate.com/github/StarkAndWayne/bosh-bootstrap)
28
+
25
29
  ## Installation
26
30
 
27
- This bootstrapper for BOSH is distributed as a RubyGem for Ruby 1.8+.
31
+ This bootstrapper tool is distributed as a RubyGem for Ruby 1.9+.
28
32
 
29
33
  ```
34
+ $ ruby -v
35
+ ruby 1.9.3p385 ...
30
36
  $ gem install bosh-bootstrap
31
37
  ```
32
38
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ if defined?(RSpec)
16
16
  desc "Run Unit Tests"
17
17
  unit_rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
18
18
  t.pattern = "spec/unit/**/*_spec.rb"
19
- t.rspec_opts = %w(--format progress --color -d)
19
+ t.rspec_opts = %w(--format progress --color)
20
20
  end
21
21
 
22
22
  desc "Run Integration Tests"
@@ -29,3 +29,5 @@ if defined?(RSpec)
29
29
  desc "Install dependencies and run tests"
30
30
  task :spec => %w(spec:unit spec:functional)
31
31
  end
32
+
33
+ task :default => :spec
@@ -10,26 +10,30 @@ Gem::Specification.new do |gem|
10
10
  gem.email = ["drnicwilliams@gmail.com"]
11
11
  gem.description = %q{Bootstrap a micro BOSH universe from one CLI}
12
12
  gem.summary = <<-EOS
13
- Now very simple to bootstrap a micro BOSH from a single, local CLI.
14
- The bootstrapper first creates an inception VM and then uses
15
- bosh_deployer (bosh micro deploy) to deploy micro BOSH from
16
- an available stemcell.
13
+ bosh-bootstrap is a command line tool that you can run on your laptop and
14
+ automatically get a microbosh (and an inception VM) deployed on either
15
+ AWS or OpenStack.
17
16
  EOS
18
17
  gem.homepage = "https://github.com/StarkAndWayne/bosh-bootstrap"
19
18
 
19
+ gem.required_ruby_version = '>= 1.9'
20
+
20
21
  gem.files = `git ls-files`.split($/)
21
22
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
23
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
24
  gem.require_paths = ["lib"]
24
-
25
+
25
26
  gem.add_dependency "thor"
26
27
  gem.add_dependency "highline"
27
28
  gem.add_dependency "settingslogic"
28
29
  gem.add_dependency "POpen4"
30
+ gem.add_dependency "net-ssh", "~> 2.2.2"
29
31
  gem.add_dependency "net-scp"
30
32
  gem.add_dependency "fog", "~>1.8.0"
31
33
  gem.add_dependency "escape"
34
+ gem.add_dependency "redcard"
32
35
  gem.add_dependency "bosh_cli"
33
36
  gem.add_development_dependency "rake"
34
37
  gem.add_development_dependency "rspec"
38
+ gem.add_development_dependency "activesupport", ">= 3.0.0"
35
39
  end
@@ -24,7 +24,7 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
24
24
  end
25
25
  end
26
26
 
27
- # @return [Hash] e.g. { :bits => 0, :cores => 2, :disk => 0,
27
+ # @return [Hash] e.g. { :bits => 0, :cores => 2, :disk => 0,
28
28
  # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
29
29
  # or nil if +server_flavor_id+ is not a supported flavor ID
30
30
  def fog_compute_flavor(server_flavor_id)
@@ -32,7 +32,7 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
32
32
  end
33
33
 
34
34
  # @return [Array] of [Hash] for each supported compute flavor
35
- # Example [Hash] { :bits => 0, :cores => 2, :disk => 0,
35
+ # Example [Hash] { :bits => 0, :cores => 2, :disk => 0,
36
36
  # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
37
37
  def aws_compute_flavors
38
38
  Fog::Compute::AWS::FLAVORS
@@ -42,10 +42,18 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
42
42
  aws_compute_flavors.map { |fl| fl[:id] }
43
43
  end
44
44
 
45
+ # Provision an EC2 or VPC elastic IP addess.
46
+ # * VPC - provision_public_ip_address(vpc: true)
47
+ # * EC2 - provision_public_ip_address
45
48
  # @return [String] provisions a new public IP address in target region
46
49
  # TODO nil if none available
47
- def provision_public_ip_address
48
- address = fog_compute.addresses.create
50
+ def provision_public_ip_address(options={})
51
+ if options.delete(:vpc)
52
+ options[:domain] = "vpc"
53
+ else
54
+ options[:domain] = options.delete(:domain) || "standard"
55
+ end
56
+ address = fog_compute.addresses.create(options)
49
57
  address.public_ip
50
58
  # TODO catch error and return nil
51
59
  end
@@ -55,8 +63,25 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
55
63
  address.server = server
56
64
  end
57
65
 
66
+ def create_vpc(name, cidr_block)
67
+ vpc = fog_compute.vpcs.create(name: name, cidr_block: cidr_block)
68
+ vpc.id
69
+ end
70
+
71
+ # Creates a VPC subnet
72
+ # @return [String] the subnet_id
73
+ def create_subnet(vpc_id, cidr_block)
74
+ subnet = fog_compute.subnets.create(vpc_id: vpc_id, cidr_block: cidr_block)
75
+ subnet.subnet_id
76
+ end
77
+
78
+ def create_internet_gateway(vpc_id)
79
+ gateway = fog_compute.internet_gateways.create(vpc_id: vpc_id)
80
+ gateway.id
81
+ end
82
+
58
83
  # Creates or reuses an AWS security group and opens ports.
59
- #
84
+ #
60
85
  # +security_group_name+ is the name to be created or reused
61
86
  # +ports+ is a hash of name/port for ports to open, for example:
62
87
  # {
@@ -64,6 +89,19 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
64
89
  # http: 80,
65
90
  # https: 443
66
91
  # }
92
+ # protocol defaults to TCP
93
+ # You can also use a more verbose +ports+ using the format:
94
+ # {
95
+ # ssh: 22,
96
+ # http: { ports: (80..82) },
97
+ # mosh: { protocol: "udp", ports: (60000..60050) }
98
+ # mosh: { protocol: "rdp", ports: (3398..3398), ip_ranges: [ { cidrIp: "196.212.12.34/32" } ] }
99
+ # }
100
+ # In this example,
101
+ # * TCP 22 will be opened for ssh from any ip_range,
102
+ # * TCP ports 80, 81, 82 for http from any ip_range,
103
+ # * UDP 60000 -> 60050 for mosh from any ip_range and
104
+ # * TCP 3398 for RDP from ip range: 96.212.12.34/32
67
105
  def create_security_group(security_group_name, description, ports)
68
106
  unless sg = fog_compute.security_groups.get(security_group_name)
69
107
  sg = fog_compute.security_groups.create(name: security_group_name, description: description)
@@ -73,10 +111,11 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
73
111
  end
74
112
  ip_permissions = sg.ip_permissions
75
113
  ports_opened = 0
76
- ports.each do |name, port|
77
- unless port_open?(ip_permissions, port)
78
- sg.authorize_port_range(port..port)
79
- puts " -> opened #{name} port #{port}"
114
+ ports.each do |name, port_defn|
115
+ (protocol, port_range, ip_range) = extract_port_definition(port_defn)
116
+ unless port_open?(ip_permissions, port_range, protocol, ip_range)
117
+ sg.authorize_port_range(port_range, {:ip_protocol => protocol, :cidr_ip => ip_range})
118
+ puts " -> opened #{name} ports #{protocol.upcase} #{port_range.min}..#{port_range.max} from IP range #{ip_range}"
80
119
  ports_opened += 1
81
120
  end
82
121
  end
@@ -84,8 +123,40 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
84
123
  true
85
124
  end
86
125
 
87
- def port_open?(ip_permissions, port)
88
- ip_permissions && ip_permissions.find {|ip| ip["fromPort"] <= port && ip["toPort"] >= port }
126
+ # Any of the following +port_defn+ can be used:
127
+ # {
128
+ # ssh: 22,
129
+ # http: { ports: (80..82) },
130
+ # mosh: { protocol: "udp", ports: (60000..60050) }
131
+ # mosh: { protocol: "rdp", ports: (3398..3398), ip_range: "196.212.12.34/32" }
132
+ # }
133
+ # In this example,
134
+ # * TCP 22 will be opened for ssh from any ip_range,
135
+ # * TCP ports 80, 81, 82 for http from any ip_range,
136
+ # * UDP 60000 -> 60050 for mosh from any ip_range and
137
+ # * TCP 3398 for RDP from ip range: 96.212.12.34/32
138
+ def extract_port_definition(port_defn)
139
+ protocol = "tcp"
140
+ ip_range = "0.0.0.0/0"
141
+ if port_defn.is_a? Integer
142
+ port_range = (port_defn..port_defn)
143
+ elsif port_defn.is_a? Range
144
+ port_range = port_defn
145
+ elsif port_defn.is_a? Hash
146
+ protocol = port_defn[:protocol] if port_defn[:protocol]
147
+ port_range = port_defn[:ports] if port_defn[:ports]
148
+ ip_range = port_defn[:ip_range] if port_defn[:ip_range]
149
+ end
150
+ [protocol, port_range, ip_range]
151
+ end
152
+
153
+ def port_open?(ip_permissions, port_range, protocol, ip_range)
154
+ ip_permissions && ip_permissions.find do |ip|
155
+ ip["ipProtocol"] == protocol \
156
+ && ip["ipRanges"].detect { |range| range["cidrIp"] == ip_range } \
157
+ && ip["fromPort"] <= port_range.min \
158
+ && ip["toPort"] >= port_range.max
159
+ end
89
160
  end
90
161
 
91
162
  def find_server_device(server, device)
@@ -109,4 +180,43 @@ class Bosh::Providers::AWS < Bosh::Providers::BaseProvider
109
180
  # Instead, using:
110
181
  volume.server = server
111
182
  end
183
+
184
+ def bootstrap(new_attributes = {})
185
+ vpc = new_attributes[:subnet_id]
186
+
187
+ server = fog_compute.servers.new(new_attributes)
188
+
189
+ unless new_attributes[:key_name]
190
+ # first or create fog_#{credential} keypair
191
+ name = Fog.respond_to?(:credential) && Fog.credential || :default
192
+ unless server.key_pair = fog_compute.key_pairs.get("fog_#{name}")
193
+ server.key_pair = fog_compute.key_pairs.create(
194
+ :name => "fog_#{name}",
195
+ :public_key => server.public_key
196
+ )
197
+ end
198
+ end
199
+
200
+ if vpc
201
+ # TODO setup security group on new server
202
+ else
203
+ # make sure port 22 is open in the first security group
204
+ security_group = fog_compute.security_groups.get(server.groups.first)
205
+ authorized = security_group.ip_permissions.detect do |ip_permission|
206
+ ip_permission['ipRanges'].first && ip_permission['ipRanges'].first['cidrIp'] == '0.0.0.0/0' &&
207
+ ip_permission['fromPort'] == 22 &&
208
+ ip_permission['ipProtocol'] == 'tcp' &&
209
+ ip_permission['toPort'] == 22
210
+ end
211
+ unless authorized
212
+ security_group.authorize_port_range(22..22)
213
+ end
214
+ end
215
+
216
+ server.save
217
+ server.wait_for { ready? }
218
+ server.setup(:key_data => [server.private_key])
219
+ server
220
+ end
221
+
112
222
  end
@@ -7,7 +7,7 @@ require "bosh/providers/base_provider"
7
7
  class Bosh::Providers::OpenStack < Bosh::Providers::BaseProvider
8
8
  # @return [String] provisions a new public IP address in target region
9
9
  # TODO nil if none available
10
- def provision_public_ip_address
10
+ def provision_public_ip_address(options={})
11
11
  address = fog_compute.addresses.create
12
12
  address.ip
13
13
  # TODO catch error and return nil
@@ -15,6 +15,7 @@ module Bosh::Bootstrap
15
15
  include Thor::Actions
16
16
  include Bosh::Bootstrap::Helpers::FogSetup
17
17
  include Bosh::Bootstrap::Helpers::Settings
18
+ include Bosh::Bootstrap::Helpers::SettingsSetter
18
19
  include FileUtils
19
20
 
20
21
  attr_reader :fog_credentials
@@ -39,11 +40,20 @@ module Bosh::Bootstrap
39
40
  deploy_stage_6_setup_new_bosh
40
41
  end
41
42
 
43
+ desc "upgrade-inception", "Upgrade inception VM with latest packages, gems, security group ports"
44
+ method_option :"edge-deployer", :type => :boolean, :desc => "Install bosh deployer from git instead of rubygems"
45
+ def upgrade_inception
46
+ load_deploy_options # from method_options above
47
+
48
+ setup_server
49
+ upgrade_inception_stage_1_prepare_inception_vm
50
+ end
51
+
42
52
  # desc "delete", "Delete Micro BOSH"
43
53
  # method_option :all, :type => :boolean, :desc => "Delete all micro-boshes and inception VM [coming soon]"
44
54
  # def delete
45
55
  # delete_stage_1_target_inception_vm
46
- #
56
+ #
47
57
  # if options[:all]
48
58
  # error "I'm sorry; the awesome --all flag is not yet implemented"
49
59
  # delete_all_stage_2_delete_micro_boshes
@@ -52,7 +62,7 @@ module Bosh::Bootstrap
52
62
  # delete_one_stage_2_delete_micro_bosh
53
63
  # end
54
64
  # end
55
- #
65
+ #
56
66
  desc "ssh [COMMAND]", "Open an ssh session to the inception VM [do nothing if local machine is inception VM]"
57
67
  long_desc <<-DESC
58
68
  If a command is supplied, it will be run, otherwise a session will be
@@ -64,13 +74,22 @@ module Bosh::Bootstrap
64
74
 
65
75
  desc "tmux", "Open an ssh (with tmux) session to the inception VM [do nothing if local machine is inception VM]"
66
76
  long_desc <<-DESC
67
- Opens a connection using ssh and attaches to the most recent tmux session;
77
+ Opens a connection using ssh and attaches to the most recent tmux session;
68
78
  giving you persistance across disconnects.
69
79
  DESC
70
80
  def tmux
71
81
  run_ssh_command_or_open_tunnel(["-t", "tmux attach || tmux new-session"])
72
82
  end
73
83
 
84
+ desc "mosh", "Open an mosh session to the inception VM [do nothing if local machine is inception VM]"
85
+ long_desc <<-DESC
86
+ Opens a connection using MOSH (http://mosh.mit.edu/); ideal for those with slow or flakey internet connections.
87
+ Requires outgoing UDP port 60001 to the Inception VM
88
+ DESC
89
+ def mosh
90
+ open_mosh_session
91
+ end
92
+
74
93
  no_tasks do
75
94
  DEFAULT_INCEPTION_VOLUME_SIZE = 32 # Gb
76
95
 
@@ -79,12 +98,25 @@ module Bosh::Bootstrap
79
98
  unless settings[:fog_credentials]
80
99
  choose_fog_provider
81
100
  end
101
+
102
+ unless settings[:bosh_cloud_properties]
103
+ build_cloud_properties
104
+ end
82
105
  confirm "Using infrastructure provider #{settings.fog_credentials.provider}"
83
106
 
107
+ if aws?
108
+ if ENV['VPC']
109
+ choose_aws_vpc_or_ec2
110
+ end
111
+ end
112
+
84
113
  unless settings[:region_code]
85
114
  choose_provider_region
86
115
  end
87
- if settings[:region_code]
116
+ if region = settings[:region_code]
117
+ settings["fog_credentials"]["region"] = region
118
+ settings["bosh_cloud_properties"][settings["bosh_provider"]]["region"] = region
119
+ settings["bosh_cloud_properties"][settings["bosh_provider"]]["ec2_endpoint"] = "ec2.#{region}.amazonaws.com"
88
120
  confirm "Using #{settings.fog_credentials.provider} region #{settings.region_code}"
89
121
  else
90
122
  confirm "No specific region/data center for #{settings.fog_credentials.provider}"
@@ -97,7 +129,7 @@ module Bosh::Bootstrap
97
129
  confirm "Using #{settings.fog_credentials.provider} network labelled #{settings['network_label']}"
98
130
  end
99
131
  end
100
-
132
+
101
133
  def deploy_stage_2_bosh_configuration
102
134
  header "Stage 2: BOSH configuration"
103
135
  unless settings[:bosh_name]
@@ -134,9 +166,13 @@ module Bosh::Bootstrap
134
166
  end
135
167
 
136
168
  unless settings[:bosh]["ip_address"]
137
- say "Acquiring IP address for micro BOSH..."
138
- ip_address = acquire_ip_address
139
- settings[:bosh]["ip_address"] = ip_address
169
+ if vpc?
170
+ settings[:bosh]["ip_address"] = "10.0.0.6"
171
+ else
172
+ say "Acquiring IP address for micro BOSH..."
173
+ ip_address = acquire_ip_address
174
+ settings[:bosh]["ip_address"] = ip_address
175
+ end
140
176
  end
141
177
  unless settings[:bosh]["ip_address"]
142
178
  error "IP address not available/provided currently"
@@ -145,6 +181,10 @@ module Bosh::Bootstrap
145
181
  end
146
182
  save_settings!
147
183
 
184
+ if aws? && vpc?
185
+ create_complete_vpc(settings.bosh_name, "10.0.0.0/16", "10.0.0.0/24")
186
+ end
187
+
148
188
  unless settings[:bosh_security_group]
149
189
  security_group_name = settings.bosh_name
150
190
  create_security_group(security_group_name)
@@ -169,12 +209,12 @@ module Bosh::Bootstrap
169
209
 
170
210
  def deploy_stage_3_create_allocate_inception_vm
171
211
  header "Stage 3: Create/Allocate the Inception VM"
172
- unless settings["inception"] && settings["inception"]["host"]
212
+ unless settings["inception"]
173
213
  hl.choose do |menu|
174
214
  menu.prompt = "Create or specify an Inception VM: "
175
215
  if aws? || openstack?
176
216
  menu.choice("create new inception VM") do
177
- aws? ? boot_aws_inception_vm : boot_openstack_inception_vm
217
+ settings["inception"] = {"create_new" => true}
178
218
  end
179
219
  end
180
220
  menu.choice("use an existing Ubuntu server") do
@@ -191,18 +231,18 @@ module Bosh::Bootstrap
191
231
  end
192
232
  end
193
233
  end
194
- # If successfully validate inception VM, then save those settings.
195
234
  save_settings!
196
235
 
197
- if settings["inception"]["host"]
198
- @server = Commander::RemoteServer.new(settings.inception.host, settings.local.private_key_path)
199
- confirm "Using inception VM #{settings.inception.username}@#{settings.inception.host}"
200
- else
201
- @server = Commander::LocalServer.new
202
- confirm "Using this server as the inception VM"
236
+ if settings["inception"]["create_new"] && !settings["inception"]["host"]
237
+ aws? ? boot_aws_inception_vm : boot_openstack_inception_vm
203
238
  end
239
+ # If successfully validate inception VM, then save those settings.
240
+ save_settings!
241
+
242
+ setup_server
243
+
204
244
  unless settings["inception"]["validated"]
205
- unless server.run(Bosh::Bootstrap::Stages::StageValidateInceptionVm.new(settings).commands)
245
+ unless run_server(Bosh::Bootstrap::Stages::StageValidateInceptionVm.new(settings).commands)
206
246
  error "Failed to complete Stage 3: Create/Allocate the Inception VM"
207
247
  end
208
248
  settings["inception"]["validated"] = true
@@ -255,42 +295,72 @@ module Bosh::Bootstrap
255
295
  confirm "You are now targeting and logged in to your BOSH"
256
296
  end
257
297
 
258
- def delete_stage_1_target_inception_vm
259
- header "Stage 1: Target inception VM to use to delete micro-bosh"
260
- if settings["inception"]["host"]
261
- @server = Commander::RemoteServer.new(settings.inception.host, settings.local.private_key_path)
262
- confirm "Using inception VM #{settings.inception.username}@#{settings.inception.host}"
298
+ def upgrade_inception_stage_1_prepare_inception_vm
299
+ if settings["inception"] && settings["inception"]["prepared"]
300
+ header "Stage 1: Upgrade Inception VM"
301
+ unless run_server(Bosh::Bootstrap::Stages::StagePrepareInceptionVm.new(settings).commands)
302
+ error "Failed to complete Stage 2: Upgrade Inception VM"
303
+ end
263
304
  else
264
- @server = Commander::LocalServer.new
265
- confirm "Using this server as the inception VM"
305
+ error "Please deploy an Inception VM first, using 'bosh-bootstrap deploy' command."
266
306
  end
267
307
  end
268
308
 
309
+ def delete_stage_1_target_inception_vm
310
+ header "Stage 1: Target inception VM to use to delete micro-bosh"
311
+ setup_server
312
+ end
313
+
269
314
  def delete_one_stage_2_delete_micro_bosh
270
315
  header "Stage 2: Deleting micro BOSH"
271
- unless server.run(Bosh::Bootstrap::Stages::MicroBoshDelete.new(settings).commands)
316
+ unless run_server(Bosh::Bootstrap::Stages::MicroBoshDelete.new(settings).commands)
272
317
  error "Failed to complete Stage 1: Delete micro BOSH"
273
318
  end
274
319
  save_settings!
275
320
  end
276
321
 
277
322
  def delete_all_stage_2_delete_micro_boshes
278
-
323
+
279
324
  end
280
325
 
281
326
  def delete_all_stage_3_delete_inception_vm
282
-
327
+
283
328
  end
284
329
 
285
- def run_ssh_command_or_open_tunnel(cmd)
286
- unless settings[:inception]
287
- say "No inception VM being used", :yellow
288
- exit 0
330
+ def setup_server
331
+ if settings["inception"]["host"]
332
+ @server = Commander::RemoteServer.new(settings.inception.host, settings.local.private_key_path)
333
+ confirm "Using inception VM #{settings.inception.username}@#{settings.inception.host}"
334
+ else
335
+ @server = Commander::LocalServer.new
336
+ confirm "Using this server as the inception VM"
289
337
  end
290
- unless host = settings.inception[:host]
291
- exit "Inception VM has not finished launching; run to complete: #{self.class.banner_base} deploy"
338
+ end
339
+
340
+ def create_complete_vpc(name, vpc_range="10.0.0.0/16", subnet_cidr_block="10.0.0.0/24")
341
+ with_setting "vpc" do |setting|
342
+ say "Creating VPC '#{name}'..."
343
+ setting["id"] = provider.create_vpc(name, vpc_range)
344
+ end
345
+
346
+ vpc_id = settings["vpc"]["id"]
347
+ with_setting "internet_gateway" do |setting|
348
+ say "Creating internet gateway..."
349
+ setting["id"] = provider.create_internet_gateway(vpc_id)
292
350
  end
351
+
352
+ with_setting "subnet" do |setting|
353
+ say "Creating subnet #{subnet_cidr_block}..."
354
+ setting["id"] = provider.create_subnet(vpc_id, subnet_cidr_block)
355
+ end
356
+ end
357
+
358
+ def run_ssh_command_or_open_tunnel(cmd)
359
+ ensure_inception_vm
360
+ ensure_inception_vm_has_launched
361
+
293
362
  username = 'vcap'
363
+ host = settings.inception[:host]
294
364
  result = system Escape.shell_command(['ssh', "#{username}@#{host}", cmd].flatten.compact)
295
365
  exit result
296
366
 
@@ -302,6 +372,59 @@ module Bosh::Bootstrap
302
372
  # Warning: Identity file /Users/drnic/.ssh/id_rsa not accessible: No such file or directory.
303
373
  end
304
374
 
375
+ def ensure_inception_vm
376
+ unless settings[:inception]
377
+ say "No inception VM being used", :yellow
378
+ exit 0
379
+ end
380
+ end
381
+ def ensure_inception_vm_has_launched
382
+ unless settings.inception[:host]
383
+ exit "Inception VM has not finished launching; run to complete: #{self.class.banner_base} deploy"
384
+ end
385
+ end
386
+
387
+ def open_mosh_session
388
+ ensure_mosh_installed
389
+ ensure_inception_vm
390
+ ensure_inception_vm_has_launched
391
+ ensure_security_group_allows_mosh
392
+
393
+ username = 'vcap'
394
+ host = settings.inception[:host]
395
+ exit system Escape.shell_command(['mosh', "#{username}@#{host}"])
396
+ end
397
+
398
+ def ensure_mosh_installed
399
+ system 'mosh --version'
400
+ unless $?.exitstatus == 255 #mosh --version returns exit code 255, rather than 0 as one might expect. Grrr.
401
+ say "You must have MOSH installed to use this command. See http://mosh.mit.edu/#getting", :yellow
402
+ exit 0
403
+ end
404
+ end
405
+
406
+ def ensure_security_group_allows_mosh
407
+ ports = {
408
+ mosh: {
409
+ protocol: "udp",
410
+ ports: (60000..60050)
411
+ }
412
+ }
413
+ inception_server = fog_compute.servers.get(settings["inception"]["server_id"])
414
+ security_group_name = inception_server.groups.first
415
+
416
+ say "Ensuring #{ports[:mosh][:protocol]} ports #{ports[:mosh][:ports].to_s} are open", [:yellow, :bold]
417
+ say "on Inception VM's security group (#{security_group_name}) ...", [:yellow, :bold]
418
+
419
+ #TODO - remove this guard once the other providers have been extended
420
+ unless settings['bosh_provider'] == 'aws'
421
+ say "TODO: Non-AWS providers need to be extended to allow creation of UDP ports (60000..60050) in their security groups", :yellow
422
+ exit 0
423
+ end
424
+
425
+ provider.create_security_group(security_group_name, 'not used', ports)
426
+ end
427
+
305
428
  # Display header for a new section of the bootstrapper
306
429
  def header(title, options={})
307
430
  say "" # golden whitespace
@@ -327,6 +450,10 @@ module Bosh::Bootstrap
327
450
  def load_deploy_options
328
451
  settings["fog_path"] = File.expand_path(options[:fog] || "~/.fog")
329
452
 
453
+ settings["git"] ||= {}
454
+ settings["git"]["name"] ||= `git config user.name`.strip
455
+ settings["git"]["email"] ||= `git config user.email`.strip
456
+
330
457
  settings["bosh_git_source"] = options[:"edge-deployer"] # use bosh git repo instead of rubygems
331
458
 
332
459
  # determine which micro-bosh stemcell to download/create
@@ -459,6 +586,10 @@ module Bosh::Bootstrap
459
586
  @fog_credentials.each do |key, value|
460
587
  settings[:fog_credentials][key] = value
461
588
  end
589
+ save_settings!
590
+ end
591
+
592
+ def build_cloud_properties
462
593
  setup_bosh_cloud_properties
463
594
  settings[:bosh_provider] = settings.bosh_cloud_properties.keys.first # aws, vsphere...
464
595
  save_settings!
@@ -506,7 +637,7 @@ module Bosh::Bootstrap
506
637
  props[:access_key_id] = settings.fog_credentials.aws_access_key_id
507
638
  props[:secret_access_key] = settings.fog_credentials.aws_secret_access_key
508
639
  # props[:ec2_endpoint] = "ec2.REGION.amazonaws.com" - via +choose_aws_region+
509
- # props[:region] = REGION - via +choose_aws_region+
640
+ # props[:region] = REGION - via +choose_aws_region+
510
641
  # props[:default_key_name] = "microbosh" - via +create_aws_key_pair+
511
642
  # props[:ec2_private_key] = "/home/vcap/.ssh/microbosh.pem" - via +create_aws_key_pair+
512
643
  # props[:default_security_groups] = ["microbosh"], - via +create_aws_security_group+
@@ -561,6 +692,16 @@ module Bosh::Bootstrap
561
692
  end
562
693
  end
563
694
 
695
+ def choose_aws_vpc_or_ec2
696
+ if settings["use_vpc"].nil?
697
+ settings["use_vpc"] = begin
698
+ answer = hl.ask("You want to use VPC, right? ") {|q| q.default="yes"; q.validate = /(yes|no)/i }.match(/y/)
699
+ !!answer
700
+ end
701
+ save_settings!
702
+ end
703
+ end
704
+
564
705
  def choose_aws_region
565
706
  aws_regions = provider.region_labels
566
707
  default_aws_region = provider.default_region_label
@@ -570,9 +711,6 @@ module Bosh::Bootstrap
570
711
  aws_regions.each do |region|
571
712
  menu.choice(region) do
572
713
  settings["region_code"] = region
573
- settings["fog_credentials"]["region"] = region
574
- settings["bosh_cloud_properties"]["aws"]["region"] = region
575
- settings["bosh_cloud_properties"]["aws"]["ec2_endpoint"] = "ec2.#{region}.amazonaws.com"
576
714
  save_settings!
577
715
  end
578
716
  menu.default = default_aws_region
@@ -682,18 +820,32 @@ module Bosh::Bootstrap
682
820
  def boot_aws_inception_vm
683
821
  say "" # glowing whitespace
684
822
 
823
+ unless settings["inception"]["ip_address"]
824
+ say "Provisioning IP address for inception VM..."
825
+ settings["inception"]["ip_address"] = acquire_ip_address
826
+ save_settings!
827
+ end
828
+
685
829
  public_key_path, private_key_path = local_ssh_key_paths
686
830
  unless settings["inception"] && settings["inception"]["server_id"]
687
831
  username = "ubuntu"
688
832
  size = "m1.small"
833
+ ip_address = settings["inception"]["ip_address"]
689
834
  say "Provisioning #{size} for inception VM..."
690
- server = fog_compute.servers.bootstrap({
835
+ inception_vm_attributes = {
691
836
  :public_key_path => public_key_path,
692
837
  :private_key_path => private_key_path,
693
838
  :flavor_id => size,
694
839
  :bits => 64,
695
- :username => "ubuntu"
696
- })
840
+ :username => "ubuntu",
841
+ :public_ip_address => ip_address
842
+ }
843
+ if vpc?
844
+ raise "must create subnet before creating VPC inception VM" unless settings["subnet"] && settings["subnet"]["id"]
845
+ inception_vm_attributes[:subnet_id] = settings["subnet"]["id"]
846
+ inception_vm_attributes[:private_ip_address] = "10.0.0.5"
847
+ end
848
+ server = provider.bootstrap(inception_vm_attributes)
697
849
  unless server
698
850
  error "Something mysteriously cloudy happened and fog could not provision a VM. Please check your limits."
699
851
  end
@@ -706,16 +858,6 @@ module Bosh::Bootstrap
706
858
 
707
859
  server ||= fog_compute.servers.get(settings["inception"]["server_id"])
708
860
 
709
- unless settings["inception"]["ip_address"]
710
- say "Provisioning IP address for inception VM..."
711
- ip_address = acquire_ip_address
712
- associate_ip_address_with_server(ip_address, server)
713
- host = server.dns_name
714
-
715
- settings["inception"]["ip_address"] = ip_address
716
- save_settings!
717
- end
718
-
719
861
  unless settings["inception"]["disk_size"]
720
862
  disk_size = DEFAULT_INCEPTION_VOLUME_SIZE # Gb
721
863
  device = "/dev/sdi"
@@ -884,7 +1026,7 @@ module Bosh::Bootstrap
884
1026
  # For AWS, it will dynamically provision an elastic IP
885
1027
  # For OpenStack, it will dynamically provision a floating IP
886
1028
  def acquire_ip_address
887
- unless public_ip = provider.provision_public_ip_address
1029
+ unless public_ip = provider.provision_public_ip_address(vpc: vpc?)
888
1030
  say "Unable to acquire a public IP. Please check your account for capacity or service issues.".red
889
1031
  exit 1
890
1032
  end
@@ -908,14 +1050,26 @@ module Bosh::Bootstrap
908
1050
 
909
1051
  # Format and mount the volume
910
1052
  say "Mounting persistent disk as volume on inception VM..."
911
- disk_mounted = false
912
- until disk_mounted
1053
+ run_ssh_command_until_successful server, "sudo mkfs.ext4 #{device} -F"
1054
+ run_ssh_command_until_successful server, "sudo mkdir -p /var/vcap/store"
1055
+ run_ssh_command_until_successful server, "sudo mount #{device} /var/vcap/store"
1056
+ end
1057
+
1058
+ def run_ssh_command_until_successful(server, cmd)
1059
+ completed = false
1060
+ until completed
913
1061
  begin
914
- # TODO catch Errno::ETIMEDOUT and re-run ssh commands
915
- server.ssh(["sudo mkfs.ext4 #{device} -F"])
916
- server.ssh(["sudo mkdir -p /var/vcap/store"])
917
- server.ssh(["sudo mount #{device} /var/vcap/store"])
918
- disk_mounted = true
1062
+ say "Running on inception VM: #{cmd}"
1063
+ result = server.ssh([cmd]).first
1064
+ if result.status == 1
1065
+ result.display_stdout
1066
+ result.display_stderr
1067
+ sleep 1
1068
+ say "trying again..."
1069
+ next
1070
+ else
1071
+ end
1072
+ completed = true
919
1073
  rescue Errno::ETIMEDOUT => e
920
1074
  say "Timeout error/warning mounting volume, retrying...", yellow
921
1075
  end
@@ -953,6 +1107,10 @@ module Bosh::Bootstrap
953
1107
  settings.fog_credentials.provider == "AWS"
954
1108
  end
955
1109
 
1110
+ def vpc?
1111
+ settings["use_vpc"]
1112
+ end
1113
+
956
1114
  def openstack?
957
1115
  settings.fog_credentials.provider == "OpenStack"
958
1116
  end
@@ -967,35 +1125,45 @@ module Bosh::Bootstrap
967
1125
 
968
1126
  # Returns the latest micro-bosh stemcell
969
1127
  # for the target provider (aws, vsphere, openstack)
1128
+ # The name includes the version number.
970
1129
  def micro_bosh_stemcell_name
971
- @micro_bosh_stemcell_name ||= begin
972
- stemcell_filter_tags = ['micro', provider_name]
973
- if settings["micro_bosh_stemcell_type"] == "stable"
974
- unless openstack?
975
- # FIXME remove this if when openstack has its first stable
976
- stemcell_filter_tags << "stable" # latest stable micro-bosh stemcell by default
977
- end
1130
+ @micro_bosh_stemcell_name ||= "micro-bosh-stemcell-#{provider_name}-#{known_stable_micro_bosh_stemcell_version}.tgz"
1131
+ end
1132
+
1133
+ def known_stable_micro_bosh_stemcell_version
1134
+ "0.8.1"
1135
+ end
1136
+
1137
+ def latest_micro_bosh_stemcell_name
1138
+ stemcell_filter_tags = ['micro', provider_name]
1139
+ if settings["micro_bosh_stemcell_type"] == "stable"
1140
+ unless openstack?
1141
+ # FIXME remove this if when openstack has its first stable
1142
+ stemcell_filter_tags << "stable" # latest stable micro-bosh stemcell by default
978
1143
  end
979
- tags = stemcell_filter_tags.join(",")
980
- bosh_stemcells_cmd = "bosh public stemcells --tags #{tags}"
981
- say "Locating micro-bosh stemcell, running '#{bosh_stemcells_cmd}'..."
982
- #
983
- # The +bosh_stemcells_cmd+ has an output that looks like:
984
- # +-----------------------------------+--------------------+
985
- # | Name | Tags |
986
- # +-----------------------------------+--------------------+
987
- # | micro-bosh-stemcell-aws-0.6.4.tgz | aws, micro, stable |
988
- # | micro-bosh-stemcell-aws-0.7.0.tgz | aws, micro, test |
989
- # +-----------------------------------+--------------------+
990
- #
991
- # So to get the latest version for the filter tags,
992
- # get the Name field, reverse sort, and return the first item
993
- # Effectively:
994
- # `#{bosh_stemcells_cmd} | grep micro | awk '{ print $2 }' | sort -r | head -n 1`.strip
995
- stemcell_output = `#{bosh_stemcells_cmd}`
996
- say stemcell_output
997
- stemcell_output.scan(/[\w.-]+\.tgz/).last
998
1144
  end
1145
+ tags = stemcell_filter_tags.join(",")
1146
+ bosh_stemcells_cmd = "bosh public stemcells --tags #{tags}"
1147
+ say "Locating micro-bosh stemcell, running '#{bosh_stemcells_cmd}'..."
1148
+ #
1149
+ # The +bosh_stemcells_cmd+ has an output that looks like:
1150
+ # +----------------------------------------+--------------------+
1151
+ # | Name | Tags |
1152
+ # +----------------------------------------+--------------------+
1153
+ # | micro-bosh-stemcell-aws-0.6.4.tgz | aws, micro, stable |
1154
+ # | micro-bosh-stemcell-aws-0.7.0.tgz | aws, micro, test |
1155
+ # | micro-bosh-stemcell-aws-0.8.1.tgz | aws, micro, test |
1156
+ # | micro-bosh-stemcell-aws-1.5.0.pre1.tgz | aws, micro |
1157
+ # | micro-bosh-stemcell-aws-1.5.0.pre2.tgz | aws, micro |
1158
+ # +----------------------------------------+--------------------+
1159
+ #
1160
+ # So to get the latest version for the filter tags,
1161
+ # get the Name field, reverse sort, and return the first item
1162
+ # Effectively:
1163
+ # `#{bosh_stemcells_cmd} | grep micro | awk '{ print $2 }' | sort -r | head -n 1`.strip
1164
+ stemcell_output = `#{bosh_stemcells_cmd}`
1165
+ say stemcell_output
1166
+ stemcell_output.scan(/[\w.-]+\.tgz/).last
999
1167
  end
1000
1168
 
1001
1169
  def provider_name
@@ -1007,6 +1175,12 @@ module Bosh::Bootstrap
1007
1175
  @provider ||= Bosh::Providers.for_bosh_provider_name(settings.bosh_provider, fog_compute)
1008
1176
  end
1009
1177
 
1178
+ # The micro_bosh.yml that is uploaded to the Inception VM before deploying the
1179
+ # MicroBOSH
1180
+ def micro_bosh_yml
1181
+ Bosh::Bootstrap::Stages::MicroBoshDeploy.new(settings).micro_bosh_manifest
1182
+ end
1183
+
1010
1184
  def cyan; "\033[36m" end
1011
1185
  def clear; "\033[0m" end
1012
1186
  def bold; "\033[1m" end
@@ -1020,4 +1194,4 @@ module Bosh::Bootstrap
1020
1194
  end
1021
1195
  end
1022
1196
  end
1023
- end
1197
+ end