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,226 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli
4
+ class JobBuilder
5
+ include PackagingHelper
6
+
7
+ attr_reader :name, :version, :packages, :templates,
8
+ :release_dir, :built_packages, :tarball_path
9
+
10
+ def self.run_prepare_script(script_path)
11
+ unless File.exists?(script_path)
12
+ raise InvalidJob, "Prepare script at `#{script_path}' doesn't exist"
13
+ end
14
+
15
+ unless File.executable?(script_path)
16
+ raise InvalidJob, "Prepare script at `#{script_path}' is not executable"
17
+ end
18
+
19
+ old_env = ENV
20
+
21
+ script_dir = File.dirname(script_path)
22
+ script_name = File.basename(script_path)
23
+
24
+ begin
25
+ # We need to temporarily delete some rubygems related artefacts
26
+ # because preparation scripts shouldn't share any assumptions
27
+ # with CLI itself
28
+ %w{ BUNDLE_GEMFILE RUBYOPT }.each { |key| ENV.delete(key) }
29
+
30
+ Dir.chdir(script_dir) do
31
+ cmd = "./#{script_name} 2>&1"
32
+ say("Running #{cmd}...")
33
+ script_output = `#{cmd}`
34
+ script_output.split("\n").each do |line|
35
+ say("> #{line}")
36
+ end
37
+ end
38
+
39
+ unless $?.exitstatus == 0
40
+ raise InvalidJob, "`#{script_path}' script failed"
41
+ end
42
+ ensure
43
+ ENV.each_pair { |k, v| ENV[k] = old_env[k] }
44
+ end
45
+ end
46
+
47
+ def initialize(spec, release_dir, final, blobstore, built_packages = [])
48
+ spec = load_yaml_file(spec) if spec.is_a?(String) && File.file?(spec)
49
+
50
+ @name = spec["name"]
51
+ @packages = spec["packages"].to_a
52
+ @built_packages = built_packages.to_a
53
+ @release_dir = release_dir
54
+ @templates_dir = File.join(job_dir, "templates")
55
+ @tarballs_dir = File.join(release_dir, "tmp", "jobs")
56
+ @final = final
57
+ @blobstore = blobstore
58
+ @artefact_type = "job"
59
+
60
+ case spec["templates"]
61
+ when Hash
62
+ @templates = spec["templates"].keys
63
+ else
64
+ raise InvalidJob, "Incorrect templates section in `#{@name}' " +
65
+ "job spec (should resolve to a hash)"
66
+ end
67
+
68
+ if @name.blank?
69
+ raise InvalidJob, "Job name is missing"
70
+ end
71
+
72
+ if @templates.nil?
73
+ raise InvalidJob, "Please include templates section with at least 1 " +
74
+ "(possibly dummy) file into `#{@name}' job spec"
75
+ end
76
+
77
+ unless @name.bosh_valid_id?
78
+ raise InvalidJob, "`#{@name}' is not a valid BOSH identifier"
79
+ end
80
+
81
+ unless File.exists?(File.join(job_dir, "spec"))
82
+ raise InvalidJob, "Cannot find spec file for '#{name}'"
83
+ end
84
+
85
+ if missing_packages.size > 0
86
+ raise InvalidJob, "Some packages required by '#{name}' job " +
87
+ "are missing: %s" % [missing_packages.join(", ")]
88
+ end
89
+
90
+ if missing_templates.size > 0
91
+ raise InvalidJob, "Some template files required by '#{name}' job " +
92
+ "are missing: %s" % [missing_templates.join(", ")]
93
+ end
94
+
95
+ if extra_templates.size > 0
96
+ raise InvalidJob, "There are unused template files for job " +
97
+ "'#{name}': %s" % [extra_templates.join(", ")]
98
+ end
99
+
100
+ unless monit_files.size > 0
101
+ raise InvalidJob, "Cannot find monit file for '#{name}'"
102
+ end
103
+
104
+ @dev_builds_dir = File.join(@release_dir, ".dev_builds", "jobs", @name)
105
+ @final_builds_dir = File.join(@release_dir, ".final_builds",
106
+ "jobs", @name)
107
+
108
+ FileUtils.mkdir_p(job_dir)
109
+ FileUtils.mkdir_p(@dev_builds_dir)
110
+ FileUtils.mkdir_p(@final_builds_dir)
111
+
112
+ at_exit { FileUtils.rm_rf(build_dir) }
113
+
114
+ init_indices
115
+ end
116
+
117
+ def copy_files
118
+ FileUtils.mkdir_p(File.join(build_dir, "templates"))
119
+ copied = 0
120
+
121
+ templates.each do |template|
122
+ src = File.join(@templates_dir, template)
123
+ dst = File.join(build_dir, "templates", template)
124
+ FileUtils.mkdir_p(File.dirname(dst))
125
+
126
+ FileUtils.cp(src, dst, :preserve => true)
127
+ copied += 1
128
+ end
129
+
130
+ monit_files.each do |file|
131
+ FileUtils.cp(file, build_dir, :preserve => true)
132
+ copied += 1
133
+ end
134
+
135
+ FileUtils.cp(File.join(job_dir, "spec"), File.join(build_dir, "job.MF"),
136
+ :preserve => true)
137
+ copied += 1
138
+ copied
139
+ end
140
+
141
+ def prepare_files
142
+ preparation_script = File.join(job_dir, "prepare")
143
+ end
144
+
145
+ def build_dir
146
+ @build_dir ||= Dir.mktmpdir
147
+ end
148
+
149
+ def job_dir
150
+ File.join(@release_dir, "jobs", @name)
151
+ end
152
+
153
+ def dev_builds_dir
154
+ File.join(@release_dir, ".dev_builds", "jobs", name)
155
+ end
156
+
157
+ def final_builds_dir
158
+ File.join(@release_dir, ".final_builds", "jobs", name)
159
+ end
160
+
161
+ def fingerprint
162
+ @fingerprint ||= make_fingerprint
163
+ end
164
+
165
+ def monit_files
166
+ glob = File.join(job_dir, '*.monit')
167
+ files = Dir.glob(glob)
168
+ monit = File.join(job_dir, "monit")
169
+ files << monit if File.exist?(monit)
170
+ files
171
+ end
172
+
173
+ def reload
174
+ @fingerprint = nil
175
+ @build_dir = nil
176
+ self
177
+ end
178
+
179
+ private
180
+
181
+ def make_fingerprint
182
+ contents = ""
183
+
184
+ # templates, monit, spec
185
+ files = templates.map do |template|
186
+ File.join(@templates_dir, template)
187
+ end.sort
188
+
189
+ files += monit_files
190
+ files << File.join(job_dir, "spec")
191
+
192
+ files.each do |filename|
193
+ path = File.basename(filename)
194
+ digest = Digest::SHA1.file(filename).hexdigest
195
+ contents << "%s%s%s" % [path, digest, tracked_permissions(filename)]
196
+ end
197
+
198
+ Digest::SHA1.hexdigest(contents)
199
+ end
200
+
201
+ def missing_packages
202
+ @missing_packages ||= packages - built_packages
203
+ end
204
+
205
+ def missing_templates
206
+ templates.select do |template|
207
+ !File.exists?(File.join(@templates_dir, template))
208
+ end
209
+ end
210
+
211
+ def extra_templates
212
+ return [] if !File.directory?(@templates_dir)
213
+
214
+ Dir.chdir(@templates_dir) do
215
+ Dir["**/*"].reject do |file|
216
+ File.directory?(file) || templates.include?(file)
217
+ end
218
+ end
219
+ end
220
+
221
+ def in_build_dir(&block)
222
+ Dir.chdir(build_dir) { yield }
223
+ end
224
+
225
+ end
226
+ end
@@ -0,0 +1,254 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli
4
+ class PackageBuilder
5
+ include PackagingHelper
6
+
7
+ attr_reader :name, :globs, :version, :dependencies, :tarball_path
8
+ # We have two ways of getting/storing a package:
9
+ # development versions of packages, kept in release directory
10
+ # final versions of packages, kept in blobstore
11
+ # development packages and their metadata should always be gitignored
12
+ # final build tarballs should be ignored as well
13
+ # final builds metadata should be checked in
14
+
15
+ def initialize(spec, release_dir, final, blobstore,
16
+ sources_dir = nil, blobs_dir = nil)
17
+ spec = load_yaml_file(spec) if spec.is_a?(String) && File.file?(spec)
18
+
19
+ @name = spec["name"]
20
+ @globs = spec["files"]
21
+ @dependencies = Array(spec["dependencies"])
22
+ @release_dir = release_dir
23
+ @sources_dir = sources_dir || File.join(@release_dir, "src")
24
+ @blobs_dir = blobs_dir || File.join(@release_dir, "blobs")
25
+ @final = final
26
+ @blobstore = blobstore
27
+ @artefact_type = "package"
28
+
29
+ @metadata_files = %w(packaging pre_packaging)
30
+
31
+ if @name.blank?
32
+ raise InvalidPackage, "Package name is missing"
33
+ end
34
+
35
+ unless @name.bosh_valid_id?
36
+ raise InvalidPackage, "Package name should be a valid BOSH identifier"
37
+ end
38
+
39
+ unless @globs.is_a?(Array) && @globs.size > 0
40
+ raise InvalidPackage, "Package '#{@name}' doesn't include any files"
41
+ end
42
+
43
+ @dev_builds_dir = File.join(@release_dir, ".dev_builds",
44
+ "packages", @name)
45
+ @final_builds_dir = File.join(@release_dir, ".final_builds",
46
+ "packages", @name)
47
+
48
+ FileUtils.mkdir_p(package_dir)
49
+ FileUtils.mkdir_p(@dev_builds_dir)
50
+ FileUtils.mkdir_p(@final_builds_dir)
51
+
52
+ at_exit { FileUtils.rm_rf(build_dir) }
53
+
54
+ init_indices
55
+ end
56
+
57
+ def reload # Mostly for tests
58
+ @fingerprint = nil
59
+ @resolved_globs = nil
60
+ init_indices
61
+ self
62
+ end
63
+
64
+ def fingerprint
65
+ @fingerprint ||= make_fingerprint
66
+ end
67
+
68
+ def resolved_globs
69
+ @resolved_globs ||= resolve_globs
70
+ end
71
+
72
+ def source_files
73
+ resolved_globs[:source]
74
+ end
75
+
76
+ def blob_files
77
+ resolved_globs[:blob]
78
+ end
79
+
80
+ def files
81
+ (resolved_globs[:blob] + resolved_globs[:source]).sort
82
+ end
83
+
84
+ def build_dir
85
+ @build_dir ||= Dir.mktmpdir
86
+ end
87
+
88
+ def package_dir
89
+ File.join(@release_dir, "packages", name)
90
+ end
91
+
92
+ def copy_files
93
+ copied = 0
94
+
95
+ files.each do |filename|
96
+ file_path = get_file_path(filename)
97
+ destination = File.join(build_dir, filename)
98
+
99
+ if File.directory?(file_path)
100
+ FileUtils.mkdir_p(destination)
101
+ else
102
+ FileUtils.mkdir_p(File.dirname(destination))
103
+ FileUtils.cp(file_path, destination, :preserve => true)
104
+ copied += 1
105
+ end
106
+ end
107
+
108
+ in_package_dir do
109
+ @metadata_files.each do |filename|
110
+ destination = File.join(build_dir, filename)
111
+ next unless File.exists?(filename)
112
+ if File.exists?(destination)
113
+ raise InvalidPackage, "Package '#{name}' has '#{filename}' file " +
114
+ "which conflicts with BOSH packaging"
115
+ end
116
+ FileUtils.cp(filename, destination, :preserve => true)
117
+ copied += 1
118
+ end
119
+ end
120
+
121
+ pre_package
122
+ copied
123
+ end
124
+
125
+ def pre_package
126
+ pre_packaging_script = File.join(package_dir, "pre_packaging")
127
+
128
+ if File.exists?(pre_packaging_script)
129
+
130
+ say("Pre-packaging...")
131
+ FileUtils.cp(pre_packaging_script, build_dir, :preserve => true)
132
+
133
+ old_env = ENV
134
+
135
+ begin
136
+ ENV.delete_if { |key, _| key[0, 7] == "BUNDLE_" }
137
+ if ENV["RUBYOPT"]
138
+ ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
139
+ end
140
+ ENV["BUILD_DIR"] = build_dir
141
+ in_build_dir do
142
+ pre_packaging_out = `bash -x pre_packaging 2>&1`
143
+ pre_packaging_out.split("\n").each do |line|
144
+ say("> #{line}")
145
+ end
146
+ unless $?.exitstatus == 0
147
+ raise InvalidPackage, "`#{name}' pre-packaging failed"
148
+ end
149
+ end
150
+
151
+ ensure
152
+ ENV.delete("BUILD_DIR")
153
+ old_env.each { |k, v| ENV[k] = old_env[k] }
154
+ end
155
+
156
+ FileUtils.rm(File.join(build_dir, "pre_packaging"))
157
+ end
158
+ end
159
+
160
+ private
161
+
162
+ def get_file_path(file)
163
+ file_path = File.join(@sources_dir, file)
164
+ if !File.exists?(file_path)
165
+ file_path = File.join(@blobs_dir, file)
166
+ unless File.exists?(file_path)
167
+ raise InvalidPackage, "#{file} cannot be found"
168
+ end
169
+ end
170
+ file_path
171
+ end
172
+
173
+ def make_fingerprint
174
+ contents = ""
175
+
176
+ signatures = files.map do |file|
177
+ path = get_file_path(file)
178
+ unless File.directory?(path)
179
+ file_digest = Digest::SHA1.file(path).hexdigest
180
+ end
181
+
182
+ "%s%s%s" % [file, file_digest, tracked_permissions(path)]
183
+ end
184
+ contents << signatures.join("")
185
+
186
+ in_package_dir do
187
+ @metadata_files.each do |file|
188
+ if File.file?(file)
189
+ file_digest = Digest::SHA1.file(file).hexdigest
190
+ contents << "%s%s" % [file, file_digest]
191
+ end
192
+ end
193
+ end
194
+
195
+ contents << @dependencies.sort.join(",")
196
+
197
+ Digest::SHA1.hexdigest(contents)
198
+ end
199
+
200
+ def resolve_globs
201
+ glob_map = { :blob => [], :source => [] }
202
+ blob_list = []
203
+ source_list = []
204
+
205
+ @globs.each do |glob|
206
+ matching_source = []
207
+ matching_blob = []
208
+
209
+ in_sources_dir do
210
+ matching_source = Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
211
+ [".", ".."].include?(File.basename(fn))
212
+ end
213
+ end
214
+
215
+ in_blobs_dir do
216
+ matching_blob = Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
217
+ [".", ".."].include?(File.basename(fn))
218
+ end
219
+ end
220
+
221
+ if matching_blob.size == 0 && matching_source.size ==0
222
+ raise InvalidPackage, "`#{name}' has a glob that " +
223
+ "resolves to an empty file list: #{glob}"
224
+ end
225
+
226
+ blob_list << matching_blob
227
+ source_list << matching_source
228
+ end
229
+ glob_map[:blob] = blob_list.flatten.sort
230
+ glob_map[:source] = source_list.flatten.sort
231
+ glob_map
232
+ end
233
+
234
+ def in_blobs_dir(&block)
235
+ # old release does not have 'blob'
236
+ if File.directory?(@blobs_dir)
237
+ Dir.chdir(@blobs_dir) { yield }
238
+ end
239
+ end
240
+
241
+ def in_sources_dir(&block)
242
+ Dir.chdir(@sources_dir) { yield }
243
+ end
244
+
245
+ def in_build_dir(&block)
246
+ Dir.chdir(build_dir) { yield }
247
+ end
248
+
249
+ def in_package_dir(&block)
250
+ Dir.chdir(package_dir) { yield }
251
+ end
252
+
253
+ end
254
+ end