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,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