bosh_cli 0.16

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.
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,213 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Task < Base
5
+
6
+ # Tracks a running task or outputs the logs from an old task. Triggered
7
+ # with 'bosh task <task_num>'. Check parse_flags to see what flags can be
8
+ # used with this.
9
+ #
10
+ # @param [Array] args The arguments from the command line command.
11
+ def track(*args)
12
+ auth_required
13
+
14
+ task_id, log_type, no_cache, raw_output = parse_flags(args)
15
+
16
+ err("Task id must be a positive integer") unless task_id.to_i > 0
17
+
18
+ task = Bosh::Cli::DirectorTask.new(director, task_id, log_type)
19
+ say("Task state: #{task.state}")
20
+
21
+ cached_output = get_cached_task_output(task_id, log_type) unless no_cache
22
+
23
+ if raw_output
24
+ renderer = Bosh::Cli::TaskLogRenderer.new
25
+ else
26
+ renderer = Bosh::Cli::TaskLogRenderer.create_for_log_type(log_type)
27
+ renderer.time_adjustment = director.get_time_difference
28
+ end
29
+
30
+ say("Task log:")
31
+
32
+ if cached_output
33
+ renderer.add_output(cached_output)
34
+ # renderer.finish calls render which prints the output.
35
+ renderer.finish(task.state)
36
+ else
37
+ # This calls renderer.finish which calls render and prints the output.
38
+ fetch_print_and_save_output(task, task_id, log_type, renderer)
39
+ end
40
+
41
+ nl
42
+
43
+ print_task_state_and_timing(task, task_id, renderer)
44
+ end
45
+
46
+ # Whether the bosh user has asked for the last (most recently run) task.
47
+ #
48
+ # @param [String] task_id The task id specified by the user. Could be a
49
+ # number as a string or it could be "last" or "latest".
50
+ # @return [Boolean] Whether the user is asking for the most recent task.
51
+ def asking_for_last_task?(task_id)
52
+ task_id.nil? || ["last", "latest"].include?(task_id)
53
+ end
54
+
55
+ # Returns the task id of the most recently run task.
56
+ #
57
+ # @return [String] The task id of the most recently run task.
58
+ def get_last_task_id
59
+ last = director.list_recent_tasks(1)
60
+ if last.size == 0
61
+ err("No tasks found")
62
+ end
63
+
64
+ last[0]["id"]
65
+ end
66
+
67
+ # Returns what type of log output the user is asking for.
68
+ #
69
+ # @param [Array] flags The args that were passed in from the command line.
70
+ # @return [String] The type of log output the user is asking for.
71
+ def get_log_type(flags)
72
+ if flags.include?("--soap")
73
+ "soap"
74
+ elsif flags.include?("--event")
75
+ "event"
76
+ else
77
+ "debug"
78
+ end
79
+ end
80
+
81
+ # Parses the command line args to see what options have been specified.
82
+ #
83
+ # @param [Array] flags The args that were passed in from the command line.
84
+ # @return [String, String, Boolean, Boolean] The task id, the type of log
85
+ # output, whether to use cache or not, whether to output the raw log.
86
+ def parse_flags(flags)
87
+ task_id = flags.shift
88
+ task_id = get_last_task_id if asking_for_last_task?(task_id)
89
+
90
+ log_type = get_log_type(flags)
91
+
92
+ no_cache = flags.include?("--no-cache")
93
+
94
+ raw_output = flags.include?("--raw")
95
+
96
+ [task_id, log_type, no_cache, raw_output]
97
+ end
98
+
99
+ # Grabs the log output for a task, prints it, then saves it to the cache.
100
+ #
101
+ # @param [Bosh::Cli::DirectorTask] task The director task that has all of
102
+ # the methods for retrieving/parsing the director's task JSON.
103
+ # @param [String] task_id The ID of the task to get logs on.
104
+ # @param [String] log_type The type of log output.
105
+ # @param [Bosh::Cli::TaskLogRenderer] renderer The renderer that renders the
106
+ # parsed task JSON.
107
+ def fetch_print_and_save_output(task, task_id, log_type, renderer)
108
+ complete_output = ""
109
+
110
+ begin
111
+ state, output = task.state, task.output
112
+
113
+ if output
114
+ renderer.add_output(output)
115
+ complete_output << output
116
+ end
117
+
118
+ renderer.refresh
119
+ sleep(0.5)
120
+
121
+ end while ["queued", "processing", "cancelling"].include?(state)
122
+
123
+ final_out = task.flush_output
124
+
125
+ if final_out
126
+ renderer.add_output(final_out)
127
+ complete_output << final_out << "\n"
128
+ end
129
+
130
+ renderer.finish(state)
131
+ save_task_output(task_id, log_type, complete_output)
132
+ end
133
+
134
+ # Prints the task state and timing information at the end of the output.
135
+ #
136
+ # @param [Bosh::Cli::DirectorTask] task The director task that has all of
137
+ # the methods for retrieving/parsing the director's task JSON.
138
+ # @param [String] task_id The ID of the task to get logs on.
139
+ # @param [Bosh::Cli::TaskLogRenderer] renderer The renderer that renders the
140
+ # parsed task JSON.
141
+ def print_task_state_and_timing(task, task_id, renderer)
142
+ final_state = task.state
143
+ color = {
144
+ "done" => :green,
145
+ "error" => :red,
146
+ }[final_state] || :yellow
147
+ status = "Task %s state is %s" %
148
+ [task_id.to_s.green, final_state.colorize(color)]
149
+
150
+ duration = renderer.duration
151
+ if final_state == "done" && duration && duration.kind_of?(Numeric)
152
+ status += ", started: %s, ended: %s (%s)" %
153
+ [renderer.started_at.to_s.green, renderer.finished_at.to_s.green,
154
+ format_time(duration).green]
155
+ end
156
+
157
+ say(status)
158
+ end
159
+
160
+ def list_running
161
+ auth_required
162
+ tasks = director.list_running_tasks
163
+ err("No running tasks") if tasks.empty?
164
+ show_tasks_table(tasks.sort_by { |t| t["id"].to_i * -1 })
165
+ say("Total tasks running now: %d" % [tasks.size])
166
+ end
167
+
168
+ def list_recent(count = 30)
169
+ auth_required
170
+ tasks = director.list_recent_tasks(count)
171
+ err("No recent tasks") if tasks.empty?
172
+ show_tasks_table(tasks)
173
+ say("Showing %d recent %s" % [tasks.size,
174
+ tasks.size == 1 ? "task" : "tasks"])
175
+ end
176
+
177
+ def cancel(task_id)
178
+ task = Bosh::Cli::DirectorTask.new(director, task_id)
179
+ task.cancel
180
+ say("Cancelling task #{task_id}")
181
+ end
182
+
183
+ private
184
+
185
+ def show_tasks_table(tasks)
186
+ return if tasks.empty?
187
+ tasks_table = table do |t|
188
+ t.headings = "#", "State", "Timestamp", "Description", "Result"
189
+ tasks.map do |task|
190
+ t << [task["id"], task["state"], Time.at(task["timestamp"]).utc,
191
+ task["description"].to_s, task["result"].to_s.truncate(80)]
192
+ end
193
+ end
194
+
195
+ say("\n")
196
+ say(tasks_table)
197
+ say("\n")
198
+ end
199
+
200
+ def get_cached_task_output(task_id, log_type)
201
+ cache.read(task_cache_key(task_id, log_type))
202
+ end
203
+
204
+ def save_task_output(task_id, log_type, output)
205
+ cache.write(task_cache_key(task_id, log_type), output)
206
+ end
207
+
208
+ def task_cache_key(task_id, log_type)
209
+ "task/#{target}/#{task_id}/#{log_type}"
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class User < Base
5
+
6
+ def create(username = nil, password = nil)
7
+ auth_required
8
+
9
+ unless options[:non_interactive]
10
+ username = ask("Enter username: ") if username.blank?
11
+ if password.blank?
12
+ password = ask("Enter password: ") { |q| q.echo = "*" }
13
+ end
14
+ end
15
+
16
+ if username.blank? || password.blank?
17
+ err("Please enter username and password")
18
+ end
19
+
20
+ if director.create_user(username, password)
21
+ say("User #{username} has been created")
22
+ else
23
+ say("Error creating user")
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Vms < Base
5
+ include Bosh::Cli::DeploymentHelper
6
+
7
+ def list(*args)
8
+ auth_required
9
+
10
+ show_full_stats = !args.delete("--full").nil?
11
+ name = args.first
12
+
13
+ if name.nil?
14
+ deployment_required
15
+ manifest = prepare_deployment_manifest
16
+ name = manifest["name"]
17
+ end
18
+
19
+ say("Deployment `#{name.green}'")
20
+
21
+ vms = director.fetch_vm_state(name)
22
+ err("No VMs") if vms.size == 0
23
+
24
+ sorted = vms.sort do |a, b|
25
+ s = a["job_name"].to_s <=> b["job_name"].to_s
26
+ s = a["index"].to_i <=> b["index"].to_i if s == 0
27
+ s = a["resource_pool"].to_s <=> b["resource_pool"].to_s if s == 0
28
+ s
29
+ end
30
+
31
+ vms_table = table do |t|
32
+ headings = ["Job/index", "State", "Resource Pool", "IPs"]
33
+ headings += ["Agent ID", "CID"] if show_full_stats
34
+
35
+ t.headings = headings
36
+
37
+ sorted.each do |vm|
38
+ job = "#{vm["job_name"]}/#{vm["index"]}" if vm["job_name"]
39
+ row = [job, vm["job_state"],
40
+ vm["resource_pool"], Array(vm["ips"]).join(", ")]
41
+ row += [vm["vm_cid"], vm["agent_id"]] if show_full_stats
42
+ t << row
43
+ end
44
+ end
45
+
46
+ say("\n")
47
+ say(vms_table)
48
+ say("\n")
49
+ say("VMs total: %d" % vms.size)
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,154 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli
4
+ class Config
5
+ VALID_ID = /^[-a-z0-9_.]+$/i
6
+
7
+ class << self
8
+ attr_accessor :colorize
9
+ attr_accessor :output
10
+ attr_accessor :interactive
11
+ end
12
+
13
+ def initialize(filename, work_dir = Dir.pwd)
14
+ @filename = File.expand_path(filename)
15
+ @work_dir = work_dir
16
+
17
+ unless File.exists?(@filename)
18
+ File.open(@filename, "w") { |f| YAML.dump({}, f) }
19
+ File.chmod(0600, @filename)
20
+ end
21
+
22
+ @config_file = load_yaml_file(@filename, nil)
23
+
24
+ unless @config_file.is_a?(Hash)
25
+ @config_file = { } # Just ignore it if it's malformed
26
+ end
27
+
28
+ rescue SystemCallError => e
29
+ raise ConfigError, "Cannot read config file: #{e.message}"
30
+ end
31
+
32
+ def auth
33
+ if @config_file.has_key?("auth") && @config_file["auth"].is_a?(Hash)
34
+ @config_file["auth"][target]
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ def set_credentials(target, username, password)
41
+ @config_file["auth"] ||= { }
42
+ @config_file["auth"][target] = { "username" => username,
43
+ "password" => password }
44
+ end
45
+
46
+ def set_alias(category, alias_name, value)
47
+ @config_file["aliases"] ||= { }
48
+ @config_file["aliases"][category.to_s] ||= { }
49
+ @config_file["aliases"][category.to_s][alias_name] = value
50
+ end
51
+
52
+ def resolve_alias(category, alias_name)
53
+ category = category.to_s
54
+
55
+ if @config_file.has_key?("aliases") &&
56
+ @config_file["aliases"].is_a?(Hash) &&
57
+ @config_file["aliases"].has_key?(category) &&
58
+ @config_file["aliases"][category].is_a?(Hash) &&
59
+ !@config_file["aliases"][category][alias_name].blank?
60
+ @config_file["aliases"][category][alias_name].to_s
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ def username
67
+ auth ? auth["username"] : nil
68
+ end
69
+
70
+ def password
71
+ auth ? auth["password"] : nil
72
+ end
73
+
74
+ # Deployment used to be a string that was only stored for your
75
+ # current target. As soon as you switched targets, the deployment
76
+ # was erased. If the user has the old config we convert it to the
77
+ # new config.
78
+ #
79
+ # @return [Boolean] Whether config is using the old deployment format.
80
+ def is_old_deployment_config?
81
+ @config_file["deployment"].is_a?(String)
82
+ end
83
+
84
+ # Read the deployment configuration. Return the deployment for the
85
+ # current target.
86
+ #
87
+ # @return [String?] The deployment path for the current target.
88
+ def deployment
89
+ return nil if target.nil?
90
+ if @config_file.has_key?("deployment")
91
+ if is_old_deployment_config?
92
+ set_deployment(@config_file["deployment"])
93
+ save
94
+ end
95
+ if @config_file["deployment"].is_a?(Hash)
96
+ return @config_file["deployment"][target]
97
+ end
98
+ end
99
+ end
100
+
101
+ # Sets the deployment file for the current target. If the deployment is
102
+ # the old deployment configuration, it will turn it into the format.
103
+ #
104
+ # @raise [MissingTarget] If there is no target set.
105
+ # @param [String] deployment_file_path The string path to the
106
+ # deployment file.
107
+ def set_deployment(deployment_file_path)
108
+ raise MissingTarget, "Must have a target set." if target.nil?
109
+ @config_file["deployment"] = { } if is_old_deployment_config?
110
+ @config_file["deployment"] ||= { }
111
+ @config_file["deployment"][target] = deployment_file_path
112
+ end
113
+
114
+ [:target, :target_name, :target_version, :release,
115
+ :target_uuid, :status_timeout].each do |attr|
116
+ define_method attr do
117
+ read(attr, false)
118
+ end
119
+
120
+ define_method "#{attr}=" do |value|
121
+ write_global(attr, value)
122
+ end
123
+ end
124
+
125
+ def read(attr, try_local_first = true)
126
+ attr = attr.to_s
127
+ if try_local_first && @config_file[@work_dir].is_a?(Hash) &&
128
+ @config_file[@work_dir].has_key?(attr)
129
+ @config_file[@work_dir][attr]
130
+ else
131
+ @config_file[attr]
132
+ end
133
+ end
134
+
135
+ def write(attr, value)
136
+ @config_file[@work_dir] ||= {}
137
+ @config_file[@work_dir][attr.to_s] = value
138
+ end
139
+
140
+ def write_global(attr, value)
141
+ @config_file[attr.to_s] = value
142
+ end
143
+
144
+ def save
145
+ File.open(@filename, "w") do |f|
146
+ YAML.dump(@config_file, f)
147
+ end
148
+
149
+ rescue SystemCallError => e
150
+ raise ConfigError, e.message
151
+ end
152
+
153
+ end
154
+ end