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.
- data/README +4 -0
- data/Rakefile +55 -0
- data/bin/bosh +17 -0
- data/lib/cli.rb +76 -0
- data/lib/cli/cache.rb +44 -0
- data/lib/cli/changeset_helper.rb +142 -0
- data/lib/cli/command_definition.rb +52 -0
- data/lib/cli/commands/base.rb +245 -0
- data/lib/cli/commands/biff.rb +300 -0
- data/lib/cli/commands/blob.rb +125 -0
- data/lib/cli/commands/cloudcheck.rb +169 -0
- data/lib/cli/commands/deployment.rb +147 -0
- data/lib/cli/commands/job.rb +42 -0
- data/lib/cli/commands/job_management.rb +117 -0
- data/lib/cli/commands/log_management.rb +81 -0
- data/lib/cli/commands/maintenance.rb +131 -0
- data/lib/cli/commands/misc.rb +240 -0
- data/lib/cli/commands/package.rb +112 -0
- data/lib/cli/commands/property_management.rb +125 -0
- data/lib/cli/commands/release.rb +469 -0
- data/lib/cli/commands/ssh.rb +271 -0
- data/lib/cli/commands/stemcell.rb +184 -0
- data/lib/cli/commands/task.rb +213 -0
- data/lib/cli/commands/user.rb +28 -0
- data/lib/cli/commands/vms.rb +53 -0
- data/lib/cli/config.rb +154 -0
- data/lib/cli/core_ext.rb +145 -0
- data/lib/cli/dependency_helper.rb +62 -0
- data/lib/cli/deployment_helper.rb +263 -0
- data/lib/cli/deployment_manifest_compiler.rb +28 -0
- data/lib/cli/director.rb +633 -0
- data/lib/cli/director_task.rb +64 -0
- data/lib/cli/errors.rb +48 -0
- data/lib/cli/event_log_renderer.rb +351 -0
- data/lib/cli/job_builder.rb +226 -0
- data/lib/cli/package_builder.rb +254 -0
- data/lib/cli/packaging_helper.rb +248 -0
- data/lib/cli/release.rb +176 -0
- data/lib/cli/release_builder.rb +215 -0
- data/lib/cli/release_compiler.rb +178 -0
- data/lib/cli/release_tarball.rb +272 -0
- data/lib/cli/runner.rb +771 -0
- data/lib/cli/stemcell.rb +83 -0
- data/lib/cli/task_log_renderer.rb +40 -0
- data/lib/cli/templates/help_message.erb +75 -0
- data/lib/cli/validation.rb +42 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/version_calc.rb +48 -0
- data/lib/cli/versions_index.rb +126 -0
- data/lib/cli/yaml_helper.rb +62 -0
- data/spec/assets/biff/bad_gateway_config.yml +28 -0
- data/spec/assets/biff/good_simple_config.yml +63 -0
- data/spec/assets/biff/good_simple_golden_config.yml +63 -0
- data/spec/assets/biff/good_simple_template.erb +69 -0
- data/spec/assets/biff/multiple_subnets_config.yml +40 -0
- data/spec/assets/biff/network_only_template.erb +34 -0
- data/spec/assets/biff/no_cc_config.yml +27 -0
- data/spec/assets/biff/no_range_config.yml +27 -0
- data/spec/assets/biff/no_subnet_config.yml +16 -0
- data/spec/assets/biff/ok_network_config.yml +30 -0
- data/spec/assets/biff/properties_template.erb +6 -0
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
- data/spec/assets/release/jobs/cacher.tgz +0 -0
- data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
- data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
- data/spec/assets/release/jobs/cacher/job.MF +6 -0
- data/spec/assets/release/jobs/cacher/monit +1 -0
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +4 -0
- data/spec/assets/release/jobs/cleaner/monit +1 -0
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
- data/spec/assets/release/jobs/sweeper/job.MF +5 -0
- data/spec/assets/release/jobs/sweeper/monit +1 -0
- data/spec/assets/release/packages/mutator.tar.gz +0 -0
- data/spec/assets/release/packages/stuff.tgz +0 -0
- data/spec/assets/release/release.MF +17 -0
- data/spec/assets/release_invalid_checksum.tgz +0 -0
- data/spec/assets/release_invalid_jobs.tgz +0 -0
- data/spec/assets/release_no_name.tgz +0 -0
- data/spec/assets/release_no_version.tgz +0 -0
- data/spec/assets/stemcell/image +1 -0
- data/spec/assets/stemcell/stemcell.MF +6 -0
- data/spec/assets/stemcell_invalid_mf.tgz +0 -0
- data/spec/assets/stemcell_no_image.tgz +0 -0
- data/spec/assets/valid_release.tgz +0 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/base_command_spec.rb +66 -0
- data/spec/unit/biff_spec.rb +135 -0
- data/spec/unit/cache_spec.rb +36 -0
- data/spec/unit/cli_commands_spec.rb +481 -0
- data/spec/unit/config_spec.rb +139 -0
- data/spec/unit/core_ext_spec.rb +77 -0
- data/spec/unit/dependency_helper_spec.rb +52 -0
- data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
- data/spec/unit/director_spec.rb +511 -0
- data/spec/unit/director_task_spec.rb +48 -0
- data/spec/unit/event_log_renderer_spec.rb +171 -0
- data/spec/unit/hash_changeset_spec.rb +73 -0
- data/spec/unit/job_builder_spec.rb +454 -0
- data/spec/unit/package_builder_spec.rb +567 -0
- data/spec/unit/release_builder_spec.rb +65 -0
- data/spec/unit/release_spec.rb +66 -0
- data/spec/unit/release_tarball_spec.rb +33 -0
- data/spec/unit/runner_spec.rb +140 -0
- data/spec/unit/ssh_spec.rb +78 -0
- data/spec/unit/stemcell_spec.rb +17 -0
- data/spec/unit/version_calc_spec.rb +27 -0
- data/spec/unit/versions_index_spec.rb +132 -0
- metadata +338 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
# This relies on having the following instance variables in a host class:
|
|
4
|
+
# @dev_builds_dir, @final_builds_dir, @blobstore,
|
|
5
|
+
# @name, @version, @tarball_path, @final, @artefact_type
|
|
6
|
+
|
|
7
|
+
module Bosh::Cli
|
|
8
|
+
module PackagingHelper
|
|
9
|
+
include Bosh::Cli::VersionCalc
|
|
10
|
+
|
|
11
|
+
attr_accessor :dry_run
|
|
12
|
+
|
|
13
|
+
def init_indices
|
|
14
|
+
@dev_index = VersionsIndex.new(@dev_builds_dir)
|
|
15
|
+
@final_index = VersionsIndex.new(@final_builds_dir)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def final?
|
|
19
|
+
@final
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dry_run?
|
|
23
|
+
@dry_run
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def new_version?
|
|
27
|
+
@tarball_generated || @promoted || @will_be_promoted
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def older_version?
|
|
31
|
+
if @tarball_generated
|
|
32
|
+
false
|
|
33
|
+
elsif @used_final_version
|
|
34
|
+
version_cmp(@version, @final_index.latest_version) < 0
|
|
35
|
+
else
|
|
36
|
+
version_cmp(@version, @dev_index.latest_version) < 0
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def notes
|
|
41
|
+
notes = []
|
|
42
|
+
|
|
43
|
+
if @will_be_promoted
|
|
44
|
+
new_final_version = @final_index.latest_version.to_i + 1
|
|
45
|
+
notes << "new final version #{new_final_version}"
|
|
46
|
+
elsif new_version?
|
|
47
|
+
notes << "new version"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
notes << "older than latest" if older_version?
|
|
51
|
+
notes
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build
|
|
55
|
+
with_indent(" ") do
|
|
56
|
+
use_final_version || use_dev_version || generate_tarball
|
|
57
|
+
end
|
|
58
|
+
upload_tarball(@tarball_path) if final? && !dry_run?
|
|
59
|
+
@will_be_promoted = true if final? && dry_run? && @used_dev_version
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def use_final_version
|
|
63
|
+
say("Final version:", " ")
|
|
64
|
+
|
|
65
|
+
item = @final_index[fingerprint]
|
|
66
|
+
|
|
67
|
+
if item.nil?
|
|
68
|
+
say("NOT FOUND".red)
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
blobstore_id = item["blobstore_id"]
|
|
73
|
+
version = item["version"]
|
|
74
|
+
|
|
75
|
+
if blobstore_id.nil?
|
|
76
|
+
say("No blobstore id".red)
|
|
77
|
+
return nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
filename = @final_index.filename(version)
|
|
81
|
+
need_fetch = true
|
|
82
|
+
|
|
83
|
+
if File.exists?(filename)
|
|
84
|
+
say("FOUND LOCAL".green)
|
|
85
|
+
if file_checksum(filename) == item["sha1"]
|
|
86
|
+
@tarball_path = filename
|
|
87
|
+
need_fetch = false
|
|
88
|
+
else
|
|
89
|
+
say("LOCAL CHECKSUM MISMATCH".red)
|
|
90
|
+
need_fetch = true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if need_fetch
|
|
95
|
+
say("Downloading `#{name} (#{version})' (#{blobstore_id})".green)
|
|
96
|
+
payload = @blobstore.get(blobstore_id)
|
|
97
|
+
if Digest::SHA1.hexdigest(payload) == item["sha1"]
|
|
98
|
+
@tarball_path = @final_index.add_version(fingerprint, item, payload)
|
|
99
|
+
else
|
|
100
|
+
err("`#{name}' (#{version}) is corrupted in blobstore " +
|
|
101
|
+
"(id=#{blobstore_id}), " +
|
|
102
|
+
"please remove it manually and re-generate the final release")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@version = version
|
|
107
|
+
@used_final_version = true
|
|
108
|
+
true
|
|
109
|
+
rescue Bosh::Blobstore::NotFound => e
|
|
110
|
+
raise BlobstoreError, "Final version of `#{name}' not found in blobstore"
|
|
111
|
+
rescue Bosh::Blobstore::BlobstoreError => e
|
|
112
|
+
raise BlobstoreError, "Blobstore error: #{e}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def use_dev_version
|
|
116
|
+
say("Dev version:", " ")
|
|
117
|
+
item = @dev_index[fingerprint]
|
|
118
|
+
|
|
119
|
+
if item.nil?
|
|
120
|
+
say("NOT FOUND".red)
|
|
121
|
+
return nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
version = item["version"]
|
|
125
|
+
filename = @dev_index.filename(version)
|
|
126
|
+
|
|
127
|
+
if File.exists?(filename)
|
|
128
|
+
say("FOUND LOCAL".green)
|
|
129
|
+
else
|
|
130
|
+
say("TARBALL MISSING".red)
|
|
131
|
+
return nil
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if file_checksum(filename) == item["sha1"]
|
|
135
|
+
@tarball_path = filename
|
|
136
|
+
@version = version
|
|
137
|
+
@used_dev_version = true
|
|
138
|
+
else
|
|
139
|
+
say("`#{name} (#{version})' tarball corrupted".red)
|
|
140
|
+
return nil
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def generate_tarball
|
|
145
|
+
if final?
|
|
146
|
+
err_message = "No matching build found for " +
|
|
147
|
+
"`#{@name}' #{@artefact_type}.\n" +
|
|
148
|
+
"Please consider creating a dev release first.\n" +
|
|
149
|
+
"The fingerprint is `#{fingerprint}'."
|
|
150
|
+
err(err_message)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
current_final = @final_index.latest_version.to_i
|
|
154
|
+
new_minor = minor_version(@dev_index.latest_version(current_final)) + 1
|
|
155
|
+
|
|
156
|
+
version = "#{current_final}.#{new_minor}-dev"
|
|
157
|
+
tmp_file = Tempfile.new(name)
|
|
158
|
+
|
|
159
|
+
say("Generating...")
|
|
160
|
+
|
|
161
|
+
copy_files
|
|
162
|
+
|
|
163
|
+
in_build_dir do
|
|
164
|
+
tar_out = `tar -chzf #{tmp_file.path} . 2>&1`
|
|
165
|
+
unless $?.exitstatus == 0
|
|
166
|
+
raise PackagingError, "Cannot create tarball: #{tar_out}"
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
payload = tmp_file.read
|
|
171
|
+
|
|
172
|
+
item = {
|
|
173
|
+
"version" => version
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
unless dry_run?
|
|
177
|
+
@dev_index.add_version(fingerprint, item, payload)
|
|
178
|
+
@tarball_path = @dev_index.filename(version)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
@version = version
|
|
182
|
+
@tarball_generated = true
|
|
183
|
+
say("Generated version #{version}".green)
|
|
184
|
+
true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def upload_tarball(path)
|
|
188
|
+
item = @final_index[fingerprint]
|
|
189
|
+
|
|
190
|
+
say("Uploading final version #{version}...")
|
|
191
|
+
|
|
192
|
+
if !item.nil?
|
|
193
|
+
version = item["version"]
|
|
194
|
+
say("This package has already been uploaded")
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
version = @final_index.latest_version.to_i + 1
|
|
199
|
+
payload = File.read(path)
|
|
200
|
+
|
|
201
|
+
blobstore_id = @blobstore.create(payload)
|
|
202
|
+
|
|
203
|
+
item = {
|
|
204
|
+
"blobstore_id" => blobstore_id,
|
|
205
|
+
"version" => version
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
say("Uploaded, blobstore id #{blobstore_id}")
|
|
209
|
+
@final_index.add_version(fingerprint, item, payload)
|
|
210
|
+
@tarball_path = @final_index.filename(version)
|
|
211
|
+
@version = version
|
|
212
|
+
@promoted = true
|
|
213
|
+
true
|
|
214
|
+
rescue Bosh::Blobstore::BlobstoreError => e
|
|
215
|
+
raise BlobstoreError, "Blobstore error: #{e}"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def file_checksum(path)
|
|
219
|
+
Digest::SHA1.file(path).hexdigest
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def checksum
|
|
223
|
+
if @tarball_path && File.exists?(@tarball_path)
|
|
224
|
+
file_checksum(@tarball_path)
|
|
225
|
+
else
|
|
226
|
+
raise RuntimeError,
|
|
227
|
+
"cannot read checksum for not yet generated package/job"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Git doesn't really track file permissions, it just looks at executable
|
|
232
|
+
# bit and uses 0755 if it's set or 0644 if not. We have to mimic that
|
|
233
|
+
# behavior in the fingerprint calculation to avoid the situation where
|
|
234
|
+
# seemingly clean working copy would trigger new fingerprints for
|
|
235
|
+
# artifacts with changed permissions. Also we don't want current
|
|
236
|
+
# fingerprints to change, hence the exact values below.
|
|
237
|
+
def tracked_permissions(path)
|
|
238
|
+
if File.directory?(path)
|
|
239
|
+
"40755"
|
|
240
|
+
elsif File.executable?(path)
|
|
241
|
+
"100755"
|
|
242
|
+
else
|
|
243
|
+
"100644"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
data/lib/cli/release.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
# This class encapsulates the details of handling dev and final releases:
|
|
5
|
+
# also it partitions release metadata between final config (which is
|
|
6
|
+
# under version control) and user dev config.
|
|
7
|
+
class Release
|
|
8
|
+
attr_reader :dir
|
|
9
|
+
|
|
10
|
+
def initialize(dir)
|
|
11
|
+
@dir = dir
|
|
12
|
+
config_dir = File.join(dir, "config")
|
|
13
|
+
@final_config_file = File.join(config_dir, "final.yml")
|
|
14
|
+
@dev_config_file = File.join(config_dir, "dev.yml")
|
|
15
|
+
|
|
16
|
+
@private_config_file = File.join(config_dir, "private.yml")
|
|
17
|
+
|
|
18
|
+
unless File.directory?(dir)
|
|
19
|
+
err("Cannot find release directory `#{dir}'")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
unless File.directory?(config_dir)
|
|
23
|
+
err("Cannot find release config directory `#{config_dir}'")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@final_config = load_config(@final_config_file)
|
|
27
|
+
@dev_config = load_config(@dev_config_file)
|
|
28
|
+
@private_config = load_config(@private_config_file)
|
|
29
|
+
|
|
30
|
+
migrate_legacy_configs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Devbox-specific attributes, gitignored
|
|
34
|
+
[:dev_name, :latest_release_filename].each do |attr|
|
|
35
|
+
define_method(attr) do
|
|
36
|
+
@dev_config[attr.to_s]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
define_method("#{attr}=".to_sym) do |value|
|
|
40
|
+
@dev_config[attr.to_s] = value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Shared attributes, present in repo
|
|
45
|
+
[:final_name, :min_cli_version].each do |attr|
|
|
46
|
+
define_method(attr) do
|
|
47
|
+
@final_config[attr.to_s]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
define_method("#{attr}=".to_sym) do |value|
|
|
51
|
+
@final_config[attr.to_s] = value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if the blobstore secret is provided in the private config file
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def has_blobstore_secret?
|
|
59
|
+
@private_config.has_key?("blobstore_secret")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Picks blobstore client to use with current release.
|
|
63
|
+
#
|
|
64
|
+
# @return [Bosh::Blobstore::Client] blobstore client
|
|
65
|
+
def blobstore
|
|
66
|
+
return @blobstore if @blobstore
|
|
67
|
+
blobstore_config = Marshal.load(Marshal.dump(@final_config["blobstore"]))
|
|
68
|
+
|
|
69
|
+
if blobstore_config.nil?
|
|
70
|
+
err("Missing blobstore configuration, please update your release")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
provider = blobstore_config["provider"]
|
|
74
|
+
options = blobstore_config["options"] || {}
|
|
75
|
+
|
|
76
|
+
if has_blobstore_secret?
|
|
77
|
+
options["secret"] = @private_config["blobstore_secret"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@blobstore = Bosh::Blobstore::Client.create(provider,
|
|
81
|
+
symbolize_keys(options))
|
|
82
|
+
|
|
83
|
+
rescue Bosh::Blobstore::BlobstoreError => e
|
|
84
|
+
err("Cannot initialize blobstore: #{e}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def save_config
|
|
88
|
+
# TODO: introduce write_yaml helper
|
|
89
|
+
File.open(@dev_config_file, "w") do |f|
|
|
90
|
+
YAML.dump(@dev_config, f)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
File.open(@final_config_file, "w") do |f|
|
|
94
|
+
YAML.dump(@final_config, f)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Upgrade path for legacy clients that kept release metadata
|
|
101
|
+
# in config/dev.yml and config/final.yml
|
|
102
|
+
#
|
|
103
|
+
def migrate_legacy_configs
|
|
104
|
+
# We're using blobstore_options as old config marker.
|
|
105
|
+
# Unfortunately old CLI won't tell you to upgrade because it checks
|
|
106
|
+
# for valid blobstore options first, so instead of removing
|
|
107
|
+
# blobstore_options we mark it as deprecated, so new CLI proceeds
|
|
108
|
+
# to migrate while the old one tells you to upgrade.
|
|
109
|
+
if @dev_config.has_key?("blobstore_options") &&
|
|
110
|
+
@dev_config["blobstore_options"] != "deprecated"
|
|
111
|
+
say("Found legacy dev config file `#{@dev_config_file}'".yellow)
|
|
112
|
+
|
|
113
|
+
new_dev_config = {
|
|
114
|
+
"dev_name" => @dev_config["name"],
|
|
115
|
+
"latest_release_filename" =>
|
|
116
|
+
@dev_config["latest_release_filename"],
|
|
117
|
+
|
|
118
|
+
# Following two options are only needed for older clients
|
|
119
|
+
# to fail gracefully and never actually read by a new client
|
|
120
|
+
"blobstore_options" => "deprecated",
|
|
121
|
+
"min_cli_version" => "0.12"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@dev_config = new_dev_config
|
|
125
|
+
|
|
126
|
+
File.open(@dev_config_file, "w") do |f|
|
|
127
|
+
YAML.dump(@dev_config, f)
|
|
128
|
+
end
|
|
129
|
+
say("Migrated dev config file format".green)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if @final_config.has_key?("blobstore_options") &&
|
|
133
|
+
@final_config["blobstore_options"] != "deprecated"
|
|
134
|
+
say("Found legacy config file `#{@final_config_file}'".yellow)
|
|
135
|
+
|
|
136
|
+
unless @final_config["blobstore_options"]["provider"] == "atmos" &&
|
|
137
|
+
@final_config["blobstore_options"].has_key?("atmos_options")
|
|
138
|
+
err("Please update your release to the version " +
|
|
139
|
+
"that uses Atmos blobstore")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
new_final_config = {
|
|
143
|
+
"final_name" => @final_config["name"],
|
|
144
|
+
"min_cli_version" => @final_config["min_cli_version"],
|
|
145
|
+
"blobstore" => {
|
|
146
|
+
"provider" => "atmos",
|
|
147
|
+
"options" => @final_config["blobstore_options"]["atmos_options"]
|
|
148
|
+
},
|
|
149
|
+
"blobstore_options" => "deprecated"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@final_config = new_final_config
|
|
153
|
+
|
|
154
|
+
File.open(@final_config_file, "w") { |f| YAML.dump(@final_config, f) }
|
|
155
|
+
say("Migrated final config file format".green)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def symbolize_keys(hash)
|
|
160
|
+
hash.inject({}) do |h, (key, value)|
|
|
161
|
+
h[key.to_sym] = value
|
|
162
|
+
h
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def load_config(file)
|
|
167
|
+
if File.exists?(file)
|
|
168
|
+
load_yaml_file(file)
|
|
169
|
+
else
|
|
170
|
+
{}
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class ReleaseBuilder
|
|
5
|
+
include Bosh::Cli::DependencyHelper
|
|
6
|
+
|
|
7
|
+
DEFAULT_RELEASE_NAME = "bosh_release"
|
|
8
|
+
|
|
9
|
+
attr_reader :release, :packages, :jobs, :changed_jobs
|
|
10
|
+
|
|
11
|
+
def initialize(release, packages, jobs, options = { })
|
|
12
|
+
@release = release
|
|
13
|
+
@final = options.has_key?(:final) ? !!options[:final] : false
|
|
14
|
+
@packages = packages
|
|
15
|
+
@jobs = jobs
|
|
16
|
+
|
|
17
|
+
@index = VersionsIndex.new(releases_dir, release_name)
|
|
18
|
+
create_release_build_dir
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def release_name
|
|
22
|
+
name = @final ? @release.final_name : @release.dev_name
|
|
23
|
+
name.blank? ? DEFAULT_RELEASE_NAME : name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def version
|
|
27
|
+
@version ||= assign_version
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def final?
|
|
31
|
+
@final
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def affected_jobs
|
|
35
|
+
result = Set.new(@jobs.select { |job| job.new_version? })
|
|
36
|
+
return result if @packages.empty?
|
|
37
|
+
|
|
38
|
+
new_package_names = @packages.inject([]) do |list, package|
|
|
39
|
+
list << package.name if package.new_version?
|
|
40
|
+
list
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@jobs.each do |job|
|
|
44
|
+
result << job if (new_package_names & job.packages).size > 0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
result.to_a
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build(options = {})
|
|
51
|
+
options = { :generate_tarball => true }.merge(options)
|
|
52
|
+
|
|
53
|
+
header("Generating manifest...")
|
|
54
|
+
generate_manifest
|
|
55
|
+
if options[:generate_tarball]
|
|
56
|
+
header("Generating tarball...")
|
|
57
|
+
generate_tarball
|
|
58
|
+
end
|
|
59
|
+
@build_complete = true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def copy_packages
|
|
63
|
+
packages.each do |package|
|
|
64
|
+
say("%-40s %s" % [package.name.green,
|
|
65
|
+
pretty_size(package.tarball_path)])
|
|
66
|
+
FileUtils.cp(package.tarball_path,
|
|
67
|
+
File.join(build_dir, "packages", "#{package.name}.tgz"),
|
|
68
|
+
:preserve => true)
|
|
69
|
+
end
|
|
70
|
+
@packages_copied = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def copy_jobs
|
|
74
|
+
jobs.each do |job|
|
|
75
|
+
say("%-40s %s" % [job.name.green, pretty_size(job.tarball_path)])
|
|
76
|
+
FileUtils.cp(job.tarball_path,
|
|
77
|
+
File.join(build_dir, "jobs", "#{job.name}.tgz"),
|
|
78
|
+
:preserve => true)
|
|
79
|
+
end
|
|
80
|
+
@jobs_copied = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def generate_manifest
|
|
84
|
+
manifest = {}
|
|
85
|
+
manifest["packages"] = []
|
|
86
|
+
|
|
87
|
+
manifest["packages"] = packages.map do |package|
|
|
88
|
+
{
|
|
89
|
+
"name" => package.name,
|
|
90
|
+
"version" => package.version,
|
|
91
|
+
"sha1" => package.checksum,
|
|
92
|
+
"dependencies" => package.dependencies
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
manifest["jobs"] = jobs.map do |job|
|
|
97
|
+
{
|
|
98
|
+
"name" => job.name,
|
|
99
|
+
"version" => job.version,
|
|
100
|
+
"sha1" => job.checksum,
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
manifest["name"] = release_name
|
|
105
|
+
|
|
106
|
+
unless manifest["name"].bosh_valid_id?
|
|
107
|
+
raise InvalidRelease, "Release name `#{manifest["name"]}' " +
|
|
108
|
+
"is not a valid BOSH identifier"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
fingerprint = make_fingerprint(manifest)
|
|
112
|
+
|
|
113
|
+
if @index[fingerprint]
|
|
114
|
+
old_version = @index[fingerprint]["version"]
|
|
115
|
+
say("This version is no different from version #{old_version}")
|
|
116
|
+
@version = old_version
|
|
117
|
+
else
|
|
118
|
+
@version = assign_version
|
|
119
|
+
@index.add_version(fingerprint, { "version" => @version })
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
manifest["version"] = @version
|
|
123
|
+
manifest_yaml = YAML.dump(manifest)
|
|
124
|
+
|
|
125
|
+
say("Writing manifest...")
|
|
126
|
+
File.open(File.join(build_dir, "release.MF"), "w") do |f|
|
|
127
|
+
f.write(manifest_yaml)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
File.open(manifest_path, "w") do |f|
|
|
131
|
+
f.write(manifest_yaml)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
@manifest_generated = true
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def generate_tarball
|
|
138
|
+
generate_manifest unless @manifest_generated
|
|
139
|
+
return if @index.version_exists?(@version)
|
|
140
|
+
|
|
141
|
+
unless @jobs_copied
|
|
142
|
+
header("Copying jobs...")
|
|
143
|
+
copy_jobs
|
|
144
|
+
nl
|
|
145
|
+
end
|
|
146
|
+
unless @packages_copied
|
|
147
|
+
header("Copying packages...")
|
|
148
|
+
copy_packages
|
|
149
|
+
nl
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
FileUtils.mkdir_p(File.dirname(tarball_path))
|
|
153
|
+
|
|
154
|
+
in_build_dir do
|
|
155
|
+
`tar -czf #{tarball_path} . 2>&1`
|
|
156
|
+
unless $?.exitstatus == 0
|
|
157
|
+
raise InvalidRelease, "Cannot create release tarball"
|
|
158
|
+
end
|
|
159
|
+
say("Generated #{tarball_path}")
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def releases_dir
|
|
164
|
+
File.join(@release.dir, final? ? "releases" : "dev_releases")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def tarball_path
|
|
168
|
+
File.join(releases_dir, "#{release_name}-#{version}.tgz")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def manifest_path
|
|
172
|
+
File.join(releases_dir, "#{release_name}-#{version}.yml")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def make_fingerprint(item)
|
|
176
|
+
case item
|
|
177
|
+
when Array
|
|
178
|
+
source = item.map { |e| make_fingerprint(e) }.sort.join("")
|
|
179
|
+
when Hash
|
|
180
|
+
source = item.keys.sort.map{ |k| make_fingerprint(item[k]) }.join("")
|
|
181
|
+
else
|
|
182
|
+
source = item.to_s
|
|
183
|
+
end
|
|
184
|
+
Digest::SHA1.hexdigest(source)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def version=(version)
|
|
190
|
+
@version = version
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def assign_version
|
|
194
|
+
current_version = @index.latest_version.to_i
|
|
195
|
+
current_version + 1
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def build_dir
|
|
199
|
+
@build_dir ||= Dir.mktmpdir
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def create_release_build_dir
|
|
203
|
+
in_build_dir do
|
|
204
|
+
FileUtils.mkdir("packages")
|
|
205
|
+
FileUtils.mkdir("jobs")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def in_build_dir(&block)
|
|
210
|
+
Dir.chdir(build_dir) { yield }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|