bosh-bootstrap 0.6.0 → 0.7.0

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