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