bosh_cli 0.16
Sign up to get free protection for your applications and to get access to all the features.
- 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
|