bosh_cli_plugin_micro 1.5.0.pre.1113
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/README.rdoc +145 -0
- data/config/aws_defaults.yml +53 -0
- data/config/openstack_defaults.yml +52 -0
- data/config/vcloud_defaults.yml +41 -0
- data/config/vsphere_defaults.yml +44 -0
- data/lib/bosh/cli/commands/micro.rb +442 -0
- data/lib/deployer/config.rb +157 -0
- data/lib/deployer/helpers.rb +115 -0
- data/lib/deployer/instance_manager/aws.rb +181 -0
- data/lib/deployer/instance_manager/openstack.rb +174 -0
- data/lib/deployer/instance_manager/vcloud.rb +41 -0
- data/lib/deployer/instance_manager/vsphere.rb +46 -0
- data/lib/deployer/instance_manager.rb +555 -0
- data/lib/deployer/models/instance.rb +6 -0
- data/lib/deployer/specification.rb +97 -0
- data/lib/deployer/version.rb +7 -0
- data/lib/deployer.rb +23 -0
- metadata +225 -0
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'deployer'
|
3
|
+
|
4
|
+
module Bosh::Cli::Command
|
5
|
+
class Micro < Base
|
6
|
+
include Bosh::Deployer::Helpers
|
7
|
+
|
8
|
+
MICRO_DIRECTOR_PORT = 25555
|
9
|
+
DEFAULT_CONFIG_PATH = File.expand_path("~/.bosh_deployer_config")
|
10
|
+
MICRO_BOSH_YAML = "micro_bosh.yml"
|
11
|
+
|
12
|
+
def initialize(runner)
|
13
|
+
super(runner)
|
14
|
+
options[:config] ||= DEFAULT_CONFIG_PATH #hijack Cli::Config
|
15
|
+
end
|
16
|
+
|
17
|
+
usage "micro"
|
18
|
+
desc "show micro bosh sub-commands"
|
19
|
+
def micro_help
|
20
|
+
say("bosh micro sub-commands:")
|
21
|
+
nl
|
22
|
+
cmds = Bosh::Cli::Config.commands.values.find_all {|c|
|
23
|
+
c.usage =~ /^micro/
|
24
|
+
}
|
25
|
+
Bosh::Cli::Command::Help.list_commands(cmds)
|
26
|
+
end
|
27
|
+
|
28
|
+
usage "micro deployment"
|
29
|
+
desc "Choose micro deployment to work with, or display current deployment"
|
30
|
+
def micro_deployment(name=nil)
|
31
|
+
if name
|
32
|
+
set_current(name)
|
33
|
+
else
|
34
|
+
show_current
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_current(name)
|
39
|
+
manifest_filename = find_deployment(name)
|
40
|
+
|
41
|
+
if !File.exists?(manifest_filename)
|
42
|
+
err "Missing manifest for #{name} (tried '#{manifest_filename}')"
|
43
|
+
end
|
44
|
+
|
45
|
+
manifest = load_yaml_file(manifest_filename)
|
46
|
+
|
47
|
+
unless manifest.is_a?(Hash)
|
48
|
+
err "Invalid manifest format"
|
49
|
+
end
|
50
|
+
|
51
|
+
if manifest["network"].blank?
|
52
|
+
err "network is not defined in deployment manifest"
|
53
|
+
end
|
54
|
+
ip = deployer(manifest_filename).discover_bosh_ip || name
|
55
|
+
|
56
|
+
if target
|
57
|
+
old_director_ip = URI.parse(target).host
|
58
|
+
else
|
59
|
+
old_director_ip = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
if old_director_ip != ip
|
63
|
+
set_target(ip)
|
64
|
+
say "#{"WARNING!".make_red} Your target has been changed to `#{target.make_red}'!"
|
65
|
+
end
|
66
|
+
|
67
|
+
say "Deployment set to '#{manifest_filename.make_green}'"
|
68
|
+
config.set_deployment(manifest_filename)
|
69
|
+
config.save
|
70
|
+
end
|
71
|
+
|
72
|
+
def show_current
|
73
|
+
say(deployment ? "Current deployment is '#{deployment.make_green}'" : "Deployment not set")
|
74
|
+
end
|
75
|
+
|
76
|
+
usage "micro status"
|
77
|
+
desc "Display micro BOSH deployment status"
|
78
|
+
def status
|
79
|
+
stemcell_cid = deployer_state(:stemcell_cid)
|
80
|
+
stemcell_name = deployer_state(:stemcell_name)
|
81
|
+
vm_cid = deployer_state(:vm_cid)
|
82
|
+
disk_cid = deployer_state(:disk_cid)
|
83
|
+
deployment = config.deployment ? config.deployment.make_green : "not set".make_red
|
84
|
+
|
85
|
+
say("Stemcell CID".ljust(15) + stemcell_cid)
|
86
|
+
say("Stemcell name".ljust(15) + stemcell_name)
|
87
|
+
say("VM CID".ljust(15) + vm_cid)
|
88
|
+
say("Disk CID".ljust(15) + disk_cid)
|
89
|
+
say("Micro BOSH CID".ljust(15) + Bosh::Deployer::Config.uuid)
|
90
|
+
say("Deployment".ljust(15) + deployment)
|
91
|
+
|
92
|
+
update_target
|
93
|
+
|
94
|
+
target_name = target ? target.make_green : "not set".make_red
|
95
|
+
say("Target".ljust(15) + target_name)
|
96
|
+
end
|
97
|
+
|
98
|
+
usage "micro deploy"
|
99
|
+
desc "Deploy a micro BOSH instance to the currently selected deployment"
|
100
|
+
option "--update", "update existing instance"
|
101
|
+
option "--update-if-exists", "create new or update existing instance"
|
102
|
+
def perform(stemcell=nil)
|
103
|
+
update = !!options[:update]
|
104
|
+
|
105
|
+
err "No deployment set" unless deployment
|
106
|
+
|
107
|
+
manifest = load_yaml_file(deployment)
|
108
|
+
|
109
|
+
if stemcell.nil?
|
110
|
+
unless manifest.is_a?(Hash)
|
111
|
+
err("Invalid manifest format")
|
112
|
+
end
|
113
|
+
|
114
|
+
stemcell = dig_hash(manifest, "resources", "cloud_properties", "image_id")
|
115
|
+
|
116
|
+
if stemcell.nil?
|
117
|
+
err "No stemcell provided"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
deployer.check_dependencies
|
122
|
+
|
123
|
+
rel_path = strip_relative_path(deployment)
|
124
|
+
|
125
|
+
desc = "`#{rel_path.make_green}' to `#{target_name.make_green}'"
|
126
|
+
|
127
|
+
if deployer.exists?
|
128
|
+
if !options[:update_if_exists] && !update
|
129
|
+
err "Instance exists. Did you mean to --update?"
|
130
|
+
end
|
131
|
+
confirmation = "Updating"
|
132
|
+
method = :update_deployment
|
133
|
+
else
|
134
|
+
prefered_dir = File.dirname(File.dirname(deployment))
|
135
|
+
|
136
|
+
unless prefered_dir == Dir.pwd
|
137
|
+
confirm_deployment("\n#{'No `bosh-deployments.yml` file found in current directory.'.make_red}\n\n" +
|
138
|
+
"Conventionally, `bosh-deployments.yml` should be saved in " +
|
139
|
+
"#{prefered_dir.make_green}.\n" +
|
140
|
+
"Is #{Dir.pwd.make_yellow} a directory where you can save state?")
|
141
|
+
end
|
142
|
+
|
143
|
+
err "No existing instance to update" if update
|
144
|
+
confirmation = "Deploying new micro BOSH instance"
|
145
|
+
method = :create_deployment
|
146
|
+
|
147
|
+
# make sure the user knows a persistent disk is required
|
148
|
+
unless dig_hash(manifest, "resources", "persistent_disk")
|
149
|
+
quit("No persistent disk configured in #{MICRO_BOSH_YAML}".make_red)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
confirm_deployment("#{confirmation} #{desc}")
|
154
|
+
|
155
|
+
if is_tgz?(stemcell)
|
156
|
+
stemcell_file = Bosh::Cli::Stemcell.new(stemcell, cache)
|
157
|
+
|
158
|
+
say("\nVerifying stemcell...")
|
159
|
+
stemcell_file.validate
|
160
|
+
say("\n")
|
161
|
+
|
162
|
+
unless stemcell_file.valid?
|
163
|
+
err("Stemcell is invalid, please fix, verify and upload again")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
renderer = DeployerRenderer.new
|
168
|
+
renderer.start
|
169
|
+
deployer.renderer = renderer
|
170
|
+
|
171
|
+
start_time = Time.now
|
172
|
+
|
173
|
+
deployer.send(method, stemcell)
|
174
|
+
|
175
|
+
renderer.finish("done")
|
176
|
+
|
177
|
+
duration = renderer.duration || (Time.now - start_time)
|
178
|
+
|
179
|
+
update_target
|
180
|
+
|
181
|
+
say("Deployed #{desc}, took #{format_time(duration).make_green} to complete")
|
182
|
+
end
|
183
|
+
|
184
|
+
usage "micro delete"
|
185
|
+
desc "Delete micro BOSH instance (including persistent disk)"
|
186
|
+
def delete
|
187
|
+
unless deployer.exists?
|
188
|
+
err "No existing instance to delete"
|
189
|
+
end
|
190
|
+
|
191
|
+
name = deployer.state.name
|
192
|
+
|
193
|
+
say "\nYou are going to delete micro BOSH deployment `#{name}'.\n\n" \
|
194
|
+
"THIS IS A VERY DESTRUCTIVE OPERATION AND IT CANNOT BE UNDONE!\n".make_red
|
195
|
+
|
196
|
+
unless confirmed?
|
197
|
+
say "Canceled deleting deployment".make_green
|
198
|
+
return
|
199
|
+
end
|
200
|
+
|
201
|
+
renderer = DeployerRenderer.new
|
202
|
+
renderer.start
|
203
|
+
deployer.renderer = renderer
|
204
|
+
|
205
|
+
start_time = Time.now
|
206
|
+
|
207
|
+
deployer.delete_deployment
|
208
|
+
|
209
|
+
renderer.finish("done")
|
210
|
+
|
211
|
+
duration = renderer.duration || (Time.now - start_time)
|
212
|
+
|
213
|
+
say("Deleted deployment '#{name}', took #{format_time(duration).make_green} to complete")
|
214
|
+
end
|
215
|
+
|
216
|
+
usage "micro deployments"
|
217
|
+
desc "Show the list of deployments"
|
218
|
+
def list
|
219
|
+
file = File.join(work_dir, DEPLOYMENTS_FILE)
|
220
|
+
if File.exists?(file)
|
221
|
+
deployments = load_yaml_file(file)["instances"]
|
222
|
+
else
|
223
|
+
deployments = []
|
224
|
+
end
|
225
|
+
|
226
|
+
err("No deployments") if deployments.size == 0
|
227
|
+
|
228
|
+
na = "n/a"
|
229
|
+
|
230
|
+
deployments_table = table do |t|
|
231
|
+
t.headings = [ "Name", "VM name", "Stemcell name" ]
|
232
|
+
deployments.each do |r|
|
233
|
+
t << [ r[:name], r[:vm_cid] || na, r[:stemcell_cid] || na ]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
say("\n")
|
238
|
+
say(deployments_table)
|
239
|
+
say("\n")
|
240
|
+
say("Deployments total: %d" % deployments.size)
|
241
|
+
end
|
242
|
+
|
243
|
+
usage "micro agent <args>"
|
244
|
+
desc <<-AGENT_HELP
|
245
|
+
Send agent messages
|
246
|
+
|
247
|
+
Message Types:
|
248
|
+
|
249
|
+
start - Start all jobs on MicroBOSH
|
250
|
+
|
251
|
+
stop - Stop all jobs on MicroBOSH
|
252
|
+
|
253
|
+
ping - Check to see if the agent is responding
|
254
|
+
|
255
|
+
drain TYPE SPEC - Tell the agent to begin draining
|
256
|
+
TYPE - One of 'shutdown', 'update' or 'status'.
|
257
|
+
SPEC - The drain spec to use.
|
258
|
+
|
259
|
+
state [full] - Get the state of a system
|
260
|
+
full - Get additional information about system vitals
|
261
|
+
|
262
|
+
list_disk - List disk CIDs mounted on the system
|
263
|
+
|
264
|
+
migrate_disk OLD NEW - Migrate a disk
|
265
|
+
OLD - The CID of the source disk.
|
266
|
+
NEW - The CID of the destination disk.
|
267
|
+
|
268
|
+
mount_disk CID - Mount a disk on the system
|
269
|
+
CID - The cloud ID of the disk to mount.
|
270
|
+
|
271
|
+
unmount_disk CID - Unmount a disk from the system
|
272
|
+
CID - The cloud ID of the disk to unmount.
|
273
|
+
|
274
|
+
AGENT_HELP
|
275
|
+
def agent(*args)
|
276
|
+
message = args.shift
|
277
|
+
args = args.map do |arg|
|
278
|
+
if File.exists?(arg)
|
279
|
+
load_yaml_file(arg)
|
280
|
+
else
|
281
|
+
arg
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
say(deployer.agent.send(message.to_sym, *args).pretty_inspect)
|
286
|
+
end
|
287
|
+
|
288
|
+
usage "micro apply"
|
289
|
+
desc "Apply spec"
|
290
|
+
def apply(spec)
|
291
|
+
deployer.apply(Bosh::Deployer::Specification.new(load_yaml_file(spec)))
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
295
|
+
|
296
|
+
def deployer(manifest_filename=nil)
|
297
|
+
deployment_required unless manifest_filename
|
298
|
+
|
299
|
+
if @deployer.nil?
|
300
|
+
manifest_filename ||= deployment
|
301
|
+
|
302
|
+
if !File.exists?(manifest_filename)
|
303
|
+
err("Cannot find deployment manifest in `#{manifest_filename}'")
|
304
|
+
end
|
305
|
+
|
306
|
+
manifest = load_yaml_file(manifest_filename)
|
307
|
+
|
308
|
+
manifest["dir"] ||= work_dir
|
309
|
+
manifest["logging"] ||= {}
|
310
|
+
unless manifest["logging"]["file"]
|
311
|
+
log_file = File.join(File.dirname(manifest_filename),
|
312
|
+
"bosh_micro_deploy.log")
|
313
|
+
manifest["logging"]["file"] = log_file
|
314
|
+
end
|
315
|
+
|
316
|
+
@deployer = Bosh::Deployer::InstanceManager.create(manifest)
|
317
|
+
end
|
318
|
+
|
319
|
+
@deployer
|
320
|
+
end
|
321
|
+
|
322
|
+
def find_deployment(name)
|
323
|
+
if File.directory?(name)
|
324
|
+
filename = File.join("#{name}", MICRO_BOSH_YAML)
|
325
|
+
else
|
326
|
+
filename = name
|
327
|
+
end
|
328
|
+
|
329
|
+
File.expand_path(filename, Dir.pwd)
|
330
|
+
end
|
331
|
+
|
332
|
+
def deployment_name
|
333
|
+
File.basename(File.dirname(deployment))
|
334
|
+
end
|
335
|
+
|
336
|
+
# set new target and clear out cached values
|
337
|
+
# does not persist the new values (set_current() does this)
|
338
|
+
def set_target(ip)
|
339
|
+
config.target = "https://#{ip}:#{MICRO_DIRECTOR_PORT}"
|
340
|
+
config.target_name = nil
|
341
|
+
config.target_version = nil
|
342
|
+
config.target_uuid = nil
|
343
|
+
end
|
344
|
+
|
345
|
+
def update_target
|
346
|
+
if deployer.exists?
|
347
|
+
bosh_ip = deployer.discover_bosh_ip
|
348
|
+
if URI.parse(target).host != bosh_ip
|
349
|
+
set_current(deployment)
|
350
|
+
end
|
351
|
+
|
352
|
+
director = Bosh::Cli::Client::Director.new(target)
|
353
|
+
|
354
|
+
if options[:director_checks]
|
355
|
+
begin
|
356
|
+
status = director.get_status
|
357
|
+
rescue Bosh::Cli::AuthError
|
358
|
+
status = {}
|
359
|
+
rescue Bosh::Cli::DirectorError
|
360
|
+
err("Cannot talk to director at '#{target}', please set correct target")
|
361
|
+
end
|
362
|
+
else
|
363
|
+
status = { "name" => "Unknown Director", "version" => "n/a" }
|
364
|
+
end
|
365
|
+
else
|
366
|
+
status = {}
|
367
|
+
end
|
368
|
+
|
369
|
+
config.target_name = status["name"]
|
370
|
+
config.target_version = status["version"]
|
371
|
+
config.target_uuid = status["uuid"]
|
372
|
+
|
373
|
+
config.save
|
374
|
+
end
|
375
|
+
|
376
|
+
def confirm_deployment(msg)
|
377
|
+
unless confirmed?(msg)
|
378
|
+
cancel_deployment
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def deployer_state(column)
|
383
|
+
if value = deployer.state.send(column)
|
384
|
+
value.make_green
|
385
|
+
else
|
386
|
+
"n/a".make_red
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class DeployerRenderer < Bosh::Cli::EventLogRenderer
|
391
|
+
attr_accessor :stage, :total, :index
|
392
|
+
|
393
|
+
DEFAULT_POLL_INTERVAL = 1
|
394
|
+
|
395
|
+
def interval_poll
|
396
|
+
Bosh::Cli::Config.poll_interval || DEFAULT_POLL_INTERVAL
|
397
|
+
end
|
398
|
+
|
399
|
+
def start
|
400
|
+
@thread = Thread.new do
|
401
|
+
loop do
|
402
|
+
refresh
|
403
|
+
sleep(interval_poll)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def finish(state)
|
409
|
+
@thread.kill
|
410
|
+
super(state)
|
411
|
+
end
|
412
|
+
|
413
|
+
def enter_stage(stage, total)
|
414
|
+
@stage = stage
|
415
|
+
@total = total
|
416
|
+
@index = 0
|
417
|
+
end
|
418
|
+
|
419
|
+
def parse_event(event)
|
420
|
+
event
|
421
|
+
end
|
422
|
+
|
423
|
+
def update(state, task)
|
424
|
+
event = {
|
425
|
+
"time" => Time.now,
|
426
|
+
"stage" => @stage,
|
427
|
+
"task" => task,
|
428
|
+
"tags" => [],
|
429
|
+
"index" => @index+1,
|
430
|
+
"total" => @total,
|
431
|
+
"state" => state.to_s,
|
432
|
+
"progress" => state == :finished ? 100 : 0
|
433
|
+
}
|
434
|
+
|
435
|
+
add_event(event)
|
436
|
+
|
437
|
+
@index += 1 if state == :finished
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh; end
|
4
|
+
|
5
|
+
module Bosh::Deployer
|
6
|
+
class Config
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
include Helpers
|
11
|
+
|
12
|
+
attr_accessor :logger, :db, :uuid, :resources, :cloud_options,
|
13
|
+
:spec_properties, :agent_properties, :bosh_ip, :env, :name, :net_conf
|
14
|
+
|
15
|
+
def configure(config)
|
16
|
+
plugin = cloud_plugin(config)
|
17
|
+
|
18
|
+
config = deep_merge(load_defaults(plugin), config)
|
19
|
+
|
20
|
+
@base_dir = config["dir"]
|
21
|
+
FileUtils.mkdir_p(@base_dir)
|
22
|
+
|
23
|
+
@name = config["name"]
|
24
|
+
@cloud_options = config["cloud"]
|
25
|
+
@net_conf = config["network"]
|
26
|
+
@bosh_ip = @net_conf["ip"]
|
27
|
+
@resources = config["resources"]
|
28
|
+
@env = config["env"]
|
29
|
+
|
30
|
+
@logger = Logger.new(config["logging"]["file"] || STDOUT)
|
31
|
+
@logger.level = Logger.const_get(config["logging"]["level"].upcase)
|
32
|
+
@logger.formatter = ThreadFormatter.new
|
33
|
+
|
34
|
+
apply_spec = config["apply_spec"]
|
35
|
+
@spec_properties = apply_spec["properties"]
|
36
|
+
@agent_properties = apply_spec["agent"]
|
37
|
+
|
38
|
+
@db = Sequel.sqlite
|
39
|
+
|
40
|
+
migrate_cpi
|
41
|
+
|
42
|
+
@db.create_table :instances do
|
43
|
+
primary_key :id
|
44
|
+
column :name, :text, :unique => true, :null => false
|
45
|
+
column :uuid, :text
|
46
|
+
column :stemcell_cid, :text
|
47
|
+
column :stemcell_name, :text
|
48
|
+
column :vm_cid, :text
|
49
|
+
column :disk_cid, :text
|
50
|
+
end
|
51
|
+
|
52
|
+
Sequel::Model.plugin :validation_helpers
|
53
|
+
|
54
|
+
Bosh::Clouds::Config.configure(self)
|
55
|
+
|
56
|
+
require "deployer/models/instance"
|
57
|
+
|
58
|
+
@cloud_options["properties"]["agent"]["mbus"] ||=
|
59
|
+
"https://vcap:b00tstrap@0.0.0.0:6868"
|
60
|
+
|
61
|
+
@disk_model = nil
|
62
|
+
@cloud = nil
|
63
|
+
@networks = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def cloud
|
67
|
+
if @cloud.nil?
|
68
|
+
@cloud = Bosh::Clouds::Provider.create(@cloud_options["plugin"],
|
69
|
+
@cloud_options["properties"])
|
70
|
+
end
|
71
|
+
@cloud
|
72
|
+
end
|
73
|
+
|
74
|
+
def agent
|
75
|
+
uri = URI.parse(agent_url)
|
76
|
+
user, password = uri.userinfo.split(":", 2)
|
77
|
+
uri.userinfo = nil
|
78
|
+
uri.host = bosh_ip
|
79
|
+
Bosh::Agent::HTTPClient.new(uri.to_s,
|
80
|
+
{ "user" => user,
|
81
|
+
"password" => password,
|
82
|
+
"reply_to" => uuid })
|
83
|
+
end
|
84
|
+
|
85
|
+
def agent_url
|
86
|
+
@cloud_options["properties"]["agent"]["mbus"]
|
87
|
+
end
|
88
|
+
|
89
|
+
def networks
|
90
|
+
return @networks if @networks
|
91
|
+
|
92
|
+
@networks = {
|
93
|
+
"bosh" => {
|
94
|
+
"cloud_properties" => @net_conf["cloud_properties"],
|
95
|
+
"netmask" => @net_conf["netmask"],
|
96
|
+
"gateway" => @net_conf["gateway"],
|
97
|
+
"ip" => @net_conf["ip"],
|
98
|
+
"dns" => @net_conf["dns"],
|
99
|
+
"type" => @net_conf["type"],
|
100
|
+
"default" => ["dns", "gateway"]
|
101
|
+
}
|
102
|
+
}
|
103
|
+
if @net_conf["vip"]
|
104
|
+
@networks["vip"] = {
|
105
|
+
"ip" => @net_conf["vip"],
|
106
|
+
"type" => "vip",
|
107
|
+
"cloud_properties" => {}
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
@networks
|
112
|
+
end
|
113
|
+
|
114
|
+
def task_checkpoint
|
115
|
+
# Bosh::Clouds::Config (bosh_cli >= 0.5.1) delegates task_checkpoint
|
116
|
+
# method to periodically check if director task is cancelled,
|
117
|
+
# so we need to define a void method in Bosh::Deployer::Config to avoid
|
118
|
+
# NoMethodError exceptions.
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def migrate_cpi
|
124
|
+
cpi = @cloud_options["plugin"]
|
125
|
+
require_path = File.join("cloud", cpi)
|
126
|
+
cpi_path = $LOAD_PATH.find { |p| File.exist?(
|
127
|
+
File.join(p, require_path)) }
|
128
|
+
migrations = File.expand_path("../db/migrations", cpi_path)
|
129
|
+
|
130
|
+
if File.directory?(migrations)
|
131
|
+
Sequel.extension :migration
|
132
|
+
Sequel::TimestampMigrator.new(
|
133
|
+
@db, migrations, :table => "#{cpi}_cpi_schema").run
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def deep_merge(src, dst)
|
138
|
+
src.merge(dst) do |key, old, new|
|
139
|
+
if new.respond_to?(:blank) && new.blank?
|
140
|
+
old
|
141
|
+
elsif old.kind_of?(Hash) and new.kind_of?(Hash)
|
142
|
+
deep_merge(old, new)
|
143
|
+
elsif old.kind_of?(Array) and new.kind_of?(Array)
|
144
|
+
old.concat(new).uniq
|
145
|
+
else
|
146
|
+
new
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def load_defaults(provider)
|
152
|
+
file = File.join(File.dirname(File.expand_path(__FILE__)), "../../config/#{provider}_defaults.yml")
|
153
|
+
Psych.load_file(file)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require 'net/ssh'
|
4
|
+
|
5
|
+
module Bosh::Deployer
|
6
|
+
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
DEPLOYMENTS_FILE = "bosh-deployments.yml"
|
10
|
+
|
11
|
+
def is_tgz?(path)
|
12
|
+
File.extname(path) == ".tgz"
|
13
|
+
end
|
14
|
+
|
15
|
+
def cloud_plugin(config)
|
16
|
+
err "No cloud properties defined" if config["cloud"].nil?
|
17
|
+
err "No cloud plugin defined" if config["cloud"]["plugin"].nil?
|
18
|
+
|
19
|
+
config["cloud"]["plugin"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def dig_hash(hash, *path)
|
23
|
+
path.inject(hash) do |location, key|
|
24
|
+
location.respond_to?(:keys) ? location[key] : nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_exists?(pid)
|
29
|
+
begin
|
30
|
+
Process.kill(0, pid)
|
31
|
+
rescue Errno::ESRCH
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def socket_readable?(ip, port)
|
37
|
+
socket = TCPSocket.new(ip, port)
|
38
|
+
if IO.select([socket], nil, nil, 5)
|
39
|
+
logger.debug("tcp socket #{ip}:#{port} is readable")
|
40
|
+
yield
|
41
|
+
true
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
rescue SocketError => e
|
46
|
+
logger.debug("tcp socket #{ip}:#{port} SocketError: #{e.inspect}")
|
47
|
+
sleep 1
|
48
|
+
false
|
49
|
+
rescue SystemCallError => e
|
50
|
+
logger.debug("tcp socket #{ip}:#{port} SystemCallError: #{e.inspect}")
|
51
|
+
sleep 1
|
52
|
+
false
|
53
|
+
ensure
|
54
|
+
socket.close if socket
|
55
|
+
end
|
56
|
+
|
57
|
+
def remote_tunnel(port)
|
58
|
+
@sessions ||= {}
|
59
|
+
return if @sessions[port]
|
60
|
+
|
61
|
+
ip = Config.bosh_ip
|
62
|
+
|
63
|
+
loop until socket_readable?(ip, @ssh_port) do
|
64
|
+
#sshd is up, sleep while host keys are generated
|
65
|
+
sleep @ssh_wait
|
66
|
+
end
|
67
|
+
|
68
|
+
if @sessions[port].nil?
|
69
|
+
logger.info("Starting SSH session for port forwarding to #{@ssh_user}@#{ip}...")
|
70
|
+
loop do
|
71
|
+
begin
|
72
|
+
@sessions[port] = Net::SSH.start(ip, @ssh_user, :keys => [@ssh_key],
|
73
|
+
:paranoid => false)
|
74
|
+
logger.debug("ssh #{@ssh_user}@#{ip}: ESTABLISHED")
|
75
|
+
break
|
76
|
+
rescue => e
|
77
|
+
logger.debug("ssh start #{@ssh_user}@#{ip} failed: #{e.inspect}")
|
78
|
+
sleep 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
lo = "127.0.0.1"
|
84
|
+
@sessions[port].forward.remote(port, lo, port)
|
85
|
+
|
86
|
+
logger.info("SSH forwarding for port #{port} started: OK")
|
87
|
+
|
88
|
+
Thread.new do
|
89
|
+
while @sessions[port]
|
90
|
+
begin
|
91
|
+
@sessions[port].loop { true }
|
92
|
+
rescue IOError => e
|
93
|
+
logger.debug("SSH session #{@sessions[port].inspect} forwarding for port #{port} terminated: #{e.inspect}")
|
94
|
+
@sessions.delete(port)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
at_exit do
|
100
|
+
status = $!.is_a?(::SystemExit) ? $!.status : nil
|
101
|
+
close_ssh_sessions
|
102
|
+
exit status if status
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def close_ssh_sessions
|
107
|
+
@sessions.each_value { |s| s.close }
|
108
|
+
end
|
109
|
+
|
110
|
+
def strip_relative_path(path)
|
111
|
+
path[/#{Regexp.escape File.join(Dir.pwd, '')}(.*)/, 1] || path
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|