bosh_cli 0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/README +4 -0
  2. data/Rakefile +55 -0
  3. data/bin/bosh +17 -0
  4. data/lib/cli.rb +76 -0
  5. data/lib/cli/cache.rb +44 -0
  6. data/lib/cli/changeset_helper.rb +142 -0
  7. data/lib/cli/command_definition.rb +52 -0
  8. data/lib/cli/commands/base.rb +245 -0
  9. data/lib/cli/commands/biff.rb +300 -0
  10. data/lib/cli/commands/blob.rb +125 -0
  11. data/lib/cli/commands/cloudcheck.rb +169 -0
  12. data/lib/cli/commands/deployment.rb +147 -0
  13. data/lib/cli/commands/job.rb +42 -0
  14. data/lib/cli/commands/job_management.rb +117 -0
  15. data/lib/cli/commands/log_management.rb +81 -0
  16. data/lib/cli/commands/maintenance.rb +131 -0
  17. data/lib/cli/commands/misc.rb +240 -0
  18. data/lib/cli/commands/package.rb +112 -0
  19. data/lib/cli/commands/property_management.rb +125 -0
  20. data/lib/cli/commands/release.rb +469 -0
  21. data/lib/cli/commands/ssh.rb +271 -0
  22. data/lib/cli/commands/stemcell.rb +184 -0
  23. data/lib/cli/commands/task.rb +213 -0
  24. data/lib/cli/commands/user.rb +28 -0
  25. data/lib/cli/commands/vms.rb +53 -0
  26. data/lib/cli/config.rb +154 -0
  27. data/lib/cli/core_ext.rb +145 -0
  28. data/lib/cli/dependency_helper.rb +62 -0
  29. data/lib/cli/deployment_helper.rb +263 -0
  30. data/lib/cli/deployment_manifest_compiler.rb +28 -0
  31. data/lib/cli/director.rb +633 -0
  32. data/lib/cli/director_task.rb +64 -0
  33. data/lib/cli/errors.rb +48 -0
  34. data/lib/cli/event_log_renderer.rb +351 -0
  35. data/lib/cli/job_builder.rb +226 -0
  36. data/lib/cli/package_builder.rb +254 -0
  37. data/lib/cli/packaging_helper.rb +248 -0
  38. data/lib/cli/release.rb +176 -0
  39. data/lib/cli/release_builder.rb +215 -0
  40. data/lib/cli/release_compiler.rb +178 -0
  41. data/lib/cli/release_tarball.rb +272 -0
  42. data/lib/cli/runner.rb +771 -0
  43. data/lib/cli/stemcell.rb +83 -0
  44. data/lib/cli/task_log_renderer.rb +40 -0
  45. data/lib/cli/templates/help_message.erb +75 -0
  46. data/lib/cli/validation.rb +42 -0
  47. data/lib/cli/version.rb +7 -0
  48. data/lib/cli/version_calc.rb +48 -0
  49. data/lib/cli/versions_index.rb +126 -0
  50. data/lib/cli/yaml_helper.rb +62 -0
  51. data/spec/assets/biff/bad_gateway_config.yml +28 -0
  52. data/spec/assets/biff/good_simple_config.yml +63 -0
  53. data/spec/assets/biff/good_simple_golden_config.yml +63 -0
  54. data/spec/assets/biff/good_simple_template.erb +69 -0
  55. data/spec/assets/biff/multiple_subnets_config.yml +40 -0
  56. data/spec/assets/biff/network_only_template.erb +34 -0
  57. data/spec/assets/biff/no_cc_config.yml +27 -0
  58. data/spec/assets/biff/no_range_config.yml +27 -0
  59. data/spec/assets/biff/no_subnet_config.yml +16 -0
  60. data/spec/assets/biff/ok_network_config.yml +30 -0
  61. data/spec/assets/biff/properties_template.erb +6 -0
  62. data/spec/assets/deployment.MF +0 -0
  63. data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
  64. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
  65. data/spec/assets/release/jobs/cacher.tgz +0 -0
  66. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  67. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  68. data/spec/assets/release/jobs/cacher/job.MF +6 -0
  69. data/spec/assets/release/jobs/cacher/monit +1 -0
  70. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  71. data/spec/assets/release/jobs/cleaner/job.MF +4 -0
  72. data/spec/assets/release/jobs/cleaner/monit +1 -0
  73. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  74. data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
  75. data/spec/assets/release/jobs/sweeper/job.MF +5 -0
  76. data/spec/assets/release/jobs/sweeper/monit +1 -0
  77. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  78. data/spec/assets/release/packages/stuff.tgz +0 -0
  79. data/spec/assets/release/release.MF +17 -0
  80. data/spec/assets/release_invalid_checksum.tgz +0 -0
  81. data/spec/assets/release_invalid_jobs.tgz +0 -0
  82. data/spec/assets/release_no_name.tgz +0 -0
  83. data/spec/assets/release_no_version.tgz +0 -0
  84. data/spec/assets/stemcell/image +1 -0
  85. data/spec/assets/stemcell/stemcell.MF +6 -0
  86. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  87. data/spec/assets/stemcell_no_image.tgz +0 -0
  88. data/spec/assets/valid_release.tgz +0 -0
  89. data/spec/assets/valid_stemcell.tgz +0 -0
  90. data/spec/spec_helper.rb +25 -0
  91. data/spec/unit/base_command_spec.rb +66 -0
  92. data/spec/unit/biff_spec.rb +135 -0
  93. data/spec/unit/cache_spec.rb +36 -0
  94. data/spec/unit/cli_commands_spec.rb +481 -0
  95. data/spec/unit/config_spec.rb +139 -0
  96. data/spec/unit/core_ext_spec.rb +77 -0
  97. data/spec/unit/dependency_helper_spec.rb +52 -0
  98. data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
  99. data/spec/unit/director_spec.rb +511 -0
  100. data/spec/unit/director_task_spec.rb +48 -0
  101. data/spec/unit/event_log_renderer_spec.rb +171 -0
  102. data/spec/unit/hash_changeset_spec.rb +73 -0
  103. data/spec/unit/job_builder_spec.rb +454 -0
  104. data/spec/unit/package_builder_spec.rb +567 -0
  105. data/spec/unit/release_builder_spec.rb +65 -0
  106. data/spec/unit/release_spec.rb +66 -0
  107. data/spec/unit/release_tarball_spec.rb +33 -0
  108. data/spec/unit/runner_spec.rb +140 -0
  109. data/spec/unit/ssh_spec.rb +78 -0
  110. data/spec/unit/stemcell_spec.rb +17 -0
  111. data/spec/unit/version_calc_spec.rb +27 -0
  112. data/spec/unit/versions_index_spec.rb +132 -0
  113. metadata +338 -0
@@ -0,0 +1,147 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Deployment < Base
5
+ include Bosh::Cli::DeploymentHelper
6
+
7
+ def show_current
8
+ say(deployment ?
9
+ "Current deployment is '#{deployment.green}'" :
10
+ "Deployment not set")
11
+ end
12
+
13
+ def set_current(name)
14
+ manifest_filename = find_deployment(name)
15
+
16
+ unless File.exists?(manifest_filename)
17
+ err("Missing manifest for #{name} (tried '#{manifest_filename}')")
18
+ end
19
+
20
+ manifest = load_yaml_file(manifest_filename)
21
+
22
+ unless manifest.is_a?(Hash)
23
+ err("Invalid manifest format")
24
+ end
25
+
26
+ unless manifest["target"].blank?
27
+ err(manifest_target_upgrade_notice)
28
+ end
29
+
30
+ if manifest["director_uuid"].blank?
31
+ err("Director UUID is not defined in deployment manifest")
32
+ end
33
+
34
+ if target
35
+ old_director = Bosh::Cli::Director.new(target, username, password)
36
+ old_director_uuid = old_director.get_status["uuid"] rescue nil
37
+ else
38
+ old_director_uuid = nil
39
+ end
40
+
41
+ if old_director_uuid != manifest["director_uuid"]
42
+ new_target_url = config.resolve_alias(:target,
43
+ manifest["director_uuid"])
44
+ if new_target_url.blank?
45
+ err("Cannot find director url for " +
46
+ "UUID '#{manifest["director_uuid"]}'")
47
+ end
48
+
49
+ new_director = Bosh::Cli::Director.new(new_target_url,
50
+ username, password)
51
+ status = new_director.get_status
52
+
53
+ config.target = new_target_url
54
+ config.target_name = status["name"]
55
+ config.target_version = status["version"]
56
+ config.target_uuid = status["uuid"]
57
+ say("#{"WARNING!".red} Your target has been" +
58
+ "changed to `#{target.red}'!")
59
+ end
60
+
61
+ say("Deployment set to '#{manifest_filename.green}'")
62
+ config.set_deployment(manifest_filename)
63
+ config.save
64
+ end
65
+
66
+ def perform(*options)
67
+ auth_required
68
+ recreate = options.include?("--recreate")
69
+
70
+ manifest_yaml = prepare_deployment_manifest(:yaml => true,
71
+ :resolve_properties => true)
72
+
73
+ if interactive?
74
+ inspect_deployment_changes(YAML.load(manifest_yaml))
75
+ say("Please review all changes carefully".yellow)
76
+ end
77
+
78
+ desc = "`#{File.basename(deployment).green}' to `#{target_name.green}'"
79
+
80
+ unless confirmed?("Deploying #{desc}")
81
+ cancel_deployment
82
+ end
83
+
84
+ status, body = director.deploy(manifest_yaml, :recreate => recreate)
85
+
86
+ responses = {
87
+ :done => "Deployed #{desc}",
88
+ :non_trackable => "Started deployment but director at `#{target}' " +
89
+ "doesn't support deployment tracking",
90
+ :track_timeout => "Started deployment but timed out out " +
91
+ "while tracking status",
92
+ :error => "Started deployment but received an error " +
93
+ "while tracking status",
94
+ :invalid => "Deployment is invalid, please fix it and deploy again"
95
+ }
96
+
97
+ say(responses[status] || "Cannot deploy: #{body}")
98
+ end
99
+
100
+ def delete(name, *options)
101
+ auth_required
102
+ force = options.include?("--force")
103
+
104
+ say("\nYou are going to delete deployment `#{name}'.\n\n")
105
+ say("THIS IS A VERY DESTRUCTIVE OPERATION AND IT CANNOT BE UNDONE!\n".red)
106
+
107
+ unless confirmed?
108
+ say("Canceled deleting deployment".green)
109
+ return
110
+ end
111
+
112
+ status, message = director.delete_deployment(name, :force => force)
113
+
114
+ responses = {
115
+ :done => "Deleted deployment '#{name}'",
116
+ :non_trackable => "Deployment delete in progress but director " +
117
+ "at '#{target}' doesn't support task tracking",
118
+ :track_timeout => "Timed out out while tracking deployment " +
119
+ "deletion progress",
120
+ :error => "Attempted to delete deployment but received " +
121
+ "an error while tracking status",
122
+ }
123
+
124
+ say(responses[status] || "Cannot delete deployment: #{message}")
125
+ end
126
+
127
+ def list
128
+ auth_required
129
+
130
+ deployments = director.list_deployments
131
+
132
+ err("No deployments") if deployments.size == 0
133
+
134
+ deployments_table = table do |t|
135
+ t.headings = ["Name"]
136
+ deployments.each do |r|
137
+ t << [r["name"]]
138
+ end
139
+ end
140
+
141
+ say("\n")
142
+ say(deployments_table)
143
+ say("\n")
144
+ say("Deployments total: %d" % deployments.size)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Job < Base
5
+
6
+ def generate(name)
7
+ check_if_release_dir
8
+
9
+ unless name.bosh_valid_id?
10
+ err("`#{name}' is not a vaild BOSH id")
11
+ end
12
+
13
+ job_dir = File.join("jobs", name)
14
+
15
+ if File.exists?(job_dir)
16
+ err("Job `#{name}' already exists, please pick another name")
17
+ end
18
+
19
+ say("create\t#{job_dir}")
20
+ FileUtils.mkdir_p(job_dir)
21
+
22
+ templates_dir = File.join(job_dir, "templates")
23
+ say("create\t#{templates_dir}")
24
+ FileUtils.mkdir_p(templates_dir)
25
+
26
+ spec_file = File.join(job_dir, "spec")
27
+ say("create\t#{spec_file}")
28
+ FileUtils.touch(spec_file)
29
+
30
+ monit_file = File.join(job_dir, "monit")
31
+ say("create\t#{monit_file}")
32
+ FileUtils.touch(monit_file)
33
+
34
+ File.open(spec_file, "w") do |f|
35
+ f.write("---\nname: #{name}\ntemplates:\n\npackages:\n")
36
+ end
37
+
38
+ say("\nGenerated skeleton for `#{name}' job in `#{job_dir}'")
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,117 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class JobManagement < Base
5
+ include Bosh::Cli::DeploymentHelper
6
+
7
+ def start_job(*args)
8
+ change_job_state(:start, *args)
9
+ end
10
+
11
+ def stop_job(*args)
12
+ change_job_state(:stop, *args)
13
+ end
14
+
15
+ def restart_job(*args)
16
+ change_job_state(:restart, *args)
17
+ end
18
+
19
+ def recreate_job(*args)
20
+ change_job_state(:recreate, *args)
21
+ end
22
+
23
+ def change_job_state(operation, *args)
24
+ auth_required
25
+ manifest_yaml = prepare_deployment_manifest(:yaml => true)
26
+ manifest = YAML.load(manifest_yaml)
27
+
28
+ unless [:start, :stop, :restart, :recreate].include?(operation)
29
+ err("Unknown operation `#{operation}': supported operations are " +
30
+ "`start', `stop', `restart', `recreate'")
31
+ end
32
+
33
+ args = args.dup
34
+ hard = args.delete("--hard")
35
+ soft = args.delete("--soft")
36
+ force = args.delete("--force")
37
+
38
+ if hard && soft
39
+ err("Cannot handle both --hard and --soft options, please choose one")
40
+ end
41
+
42
+ if operation != :stop && (hard || soft)
43
+ err("--hard and --soft options only make sense for `stop' operation")
44
+ end
45
+
46
+ job = args.shift
47
+ index = args.shift
48
+ deployment_desc = "`#{deployment.green}' to `#{target_name.green}'"
49
+ job_desc = index ? "#{job}(#{index})" : "#{job}"
50
+
51
+ case operation
52
+ when :start
53
+ op_desc = "start #{job_desc}"
54
+ new_state = "started"
55
+ completion_desc = "#{job_desc.green} has been started"
56
+ when :stop
57
+ if hard
58
+ op_desc = "stop #{job_desc} and power off its VM(s)"
59
+ completion_desc = "#{job_desc.green} has been stopped, " +
60
+ "VM(s) powered off"
61
+ new_state = "detached"
62
+ else
63
+ op_desc = "stop #{job_desc}"
64
+ completion_desc = "#{job_desc.green} has been stopped, " +
65
+ "VM(s) still running"
66
+ new_state = "stopped"
67
+ end
68
+ when :restart
69
+ op_desc = "restart #{job_desc}"
70
+ new_state = "restart"
71
+ completion_desc = "#{job_desc.green} has been restarted"
72
+ when :recreate
73
+ op_desc = "recreate #{job_desc}"
74
+ new_state = "recreate"
75
+ completion_desc = "#{job_desc.green} has been recreated"
76
+ end
77
+
78
+ say("You are about to #{op_desc.green}")
79
+
80
+ if interactive?
81
+ # TODO: refactor inspect_deployment_changes
82
+ # to decouple changeset structure and rendering
83
+ other_changes_present = inspect_deployment_changes(
84
+ manifest, :show_empty_changeset => false)
85
+
86
+ if other_changes_present && !force
87
+ err("Cannot perform job management when other deployment changes " +
88
+ "are present. Please use `--force' to override.")
89
+ end
90
+ unless confirmed?("#{op_desc.capitalize}?")
91
+ cancel_deployment
92
+ end
93
+ end
94
+ nl
95
+
96
+ say("Performing `#{op_desc}'...")
97
+
98
+ status, body = director.change_job_state(manifest["name"],
99
+ manifest_yaml,
100
+ job, index, new_state)
101
+
102
+ responses = {
103
+ :done => completion_desc,
104
+ :non_trackable => "Started deployment but director at '#{target}' " +
105
+ "doesn't support deployment tracking",
106
+ :track_timeout => "Started deployment but timed out out "+
107
+ "while tracking status",
108
+ :error => "Started deployment but received an error " +
109
+ "while tracking status",
110
+ :invalid => "Deployment is invalid, please fix it and deploy again"
111
+ }
112
+
113
+ say(responses[status] || "Cannot deploy: #{body}")
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,81 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class LogManagement < Base
5
+ include Bosh::Cli::DeploymentHelper
6
+
7
+ def fetch_logs(*args)
8
+ auth_required
9
+ target_required
10
+
11
+ job = args.shift
12
+ index = args.shift
13
+ filters = nil
14
+
15
+ for_job = args.delete("--job")
16
+ for_agent = args.delete("--agent")
17
+
18
+ if for_job && for_agent
19
+ err("Please specify which logs you want, job or agent")
20
+ elsif for_agent
21
+ log_type = "agent"
22
+ else # default log type is 'job'
23
+ log_type = "job"
24
+ end
25
+
26
+ if args.include?("--only")
27
+ pos = args.index("--only")
28
+ filters = args[pos+1]
29
+ if filters.nil?
30
+ err("Please provide a list of filters separated by comma")
31
+ end
32
+ args.delete("--only")
33
+ args.delete(filters)
34
+ elsif args.include?("--all")
35
+ args.delete("--all")
36
+ filters = "all"
37
+ end
38
+
39
+ if for_agent && !filters.nil? && filters != "all"
40
+ err("Custom filtering is not supported for agent logs")
41
+ end
42
+
43
+ if index !~ /^\d+$/
44
+ err("Job index is expected to be a positive integer")
45
+ end
46
+
47
+ if args.size > 0
48
+ err("Unknown arguments: #{args.join(", ")}")
49
+ end
50
+
51
+ manifest = prepare_deployment_manifest
52
+
53
+ resource_id = director.fetch_logs(manifest["name"], job, index,
54
+ log_type, filters)
55
+
56
+ if resource_id.nil?
57
+ err("Error retrieving logs")
58
+ end
59
+
60
+ nl
61
+ say("Downloading log bundle (#{resource_id.to_s.green})...")
62
+
63
+ begin
64
+ time = Time.now.strftime("%Y-%m-%d@%H-%M-%S")
65
+ log_file = File.join(Dir.pwd, "#{job}.#{index}.#{time}.tgz")
66
+
67
+ tmp_file = director.download_resource(resource_id)
68
+
69
+ FileUtils.mv(tmp_file, log_file)
70
+ say("Logs saved in `#{log_file.green}'")
71
+ rescue Bosh::Cli::DirectorError => e
72
+ err("Unable to download logs from director: #{e}")
73
+ ensure
74
+ FileUtils.rm_rf(tmp_file) if File.exists?(tmp_file)
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
81
+
@@ -0,0 +1,131 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Maintenance < Base
5
+ include Bosh::Cli::VersionCalc
6
+
7
+ RELEASES_TO_KEEP = 2
8
+ STEMCELLS_TO_KEEP = 2
9
+
10
+ def cleanup
11
+ target_required
12
+ auth_required
13
+
14
+ releases_to_keep = RELEASES_TO_KEEP
15
+ stemcells_to_keep = STEMCELLS_TO_KEEP
16
+
17
+ release_wording = pluralize(releases_to_keep, "latest version")
18
+ stemcell_wording = pluralize(stemcells_to_keep, "latest version")
19
+
20
+ desc = <<-EOS.gsub(/^ */, "")
21
+ Cleanup command will attempt to delete old unused
22
+ release versions and stemcells from your currently
23
+ targeted director at #{target_name.green}.
24
+
25
+ Only #{release_wording.green} of each release
26
+ and #{stemcell_wording.green} of each stemcell will be kept.
27
+
28
+ Releases and stemcells that are in use will not be affected.
29
+ EOS
30
+
31
+ say("\n#{desc}\n")
32
+
33
+ err("Cleanup canceled") unless confirmed?
34
+
35
+ nl
36
+ cleanup_stemcells(stemcells_to_keep)
37
+ nl
38
+ cleanup_releases(releases_to_keep)
39
+
40
+ nl
41
+ say("Cleanup complete".green)
42
+ end
43
+
44
+ private
45
+
46
+ def cleanup_stemcells(n_to_keep)
47
+ stemcells_by_name = director.list_stemcells.inject({}) do |h, stemcell|
48
+ h[stemcell["name"]] ||= []
49
+ h[stemcell["name"]] << stemcell
50
+ h
51
+ end
52
+
53
+ delete_list = []
54
+ say("Deleting old stemcells")
55
+
56
+ stemcells_by_name.each_pair do |name, stemcells|
57
+ stemcells.sort! do |sc1, sc2|
58
+ version_cmp(sc1["version"], sc2["version"])
59
+ end
60
+ delete_list += stemcells[0...(-n_to_keep)]
61
+ end
62
+
63
+ if delete_list.size > 0
64
+ delete_list.each do |stemcell|
65
+ name, version = stemcell["name"], stemcell["version"]
66
+ desc = "#{name}/#{version}"
67
+ perform(desc) do
68
+ director.delete_stemcell(name, version, :quiet => true)
69
+ end
70
+ end
71
+ else
72
+ say(" none found".yellow)
73
+ end
74
+ end
75
+
76
+ def cleanup_releases(n_to_keep)
77
+ delete_list = []
78
+ say("Deleting old release versions")
79
+
80
+ director.list_releases.each do |release|
81
+ name = release["name"]
82
+ versions = release["versions"].sort { |v1, v2| version_cmp(v1, v2) }
83
+
84
+ versions[0...(-n_to_keep)].each do |version|
85
+ delete_list << [name, version]
86
+ end
87
+ end
88
+
89
+ if delete_list.size > 0
90
+ delete_list.each do |name, version|
91
+ desc = "#{name}/#{version}"
92
+ perform(desc) do
93
+ director.delete_release(name, :force => false,
94
+ :version => version, :quiet => true)
95
+ end
96
+ end
97
+ else
98
+ say(" none found".yellow)
99
+ end
100
+ end
101
+
102
+ def refresh(message)
103
+ say("\r", "")
104
+ say(" " * 80, "")
105
+ say("\r#{message}", "")
106
+ end
107
+
108
+ def perform(desc)
109
+ say(" #{desc.yellow.ljust(40)}", "")
110
+ say("IN PROGRESS...".yellow, "")
111
+
112
+ status, task_id = yield
113
+ responses = {
114
+ :done => "DELETED".green,
115
+ :non_trackable => "CANNOT TRACK".red,
116
+ :track_timeout => "TIMED OUT".red,
117
+ :error => "ERROR".red,
118
+ }
119
+
120
+ refresh(" #{desc.yellow.ljust(40)}#{responses[status]}\n")
121
+
122
+ if status == :error
123
+ task = director.get_task(task_id)
124
+ say(" #{task["result"].red}")
125
+ end
126
+
127
+ status == :done
128
+ end
129
+
130
+ end
131
+ end