bosh_cli 1.2831.0 → 1.2839.0

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.
@@ -1,316 +0,0 @@
1
- module Bosh::Cli
2
- class PackageBuilder
3
- include PackagingHelper
4
-
5
- class GlobMatch
6
- # Helper class encapsulating the data we know about the glob. We need
7
- # both directory and file path, as we match the same path in several
8
- # directories (src, src_alt, blobs)
9
- attr_reader :dir
10
- attr_reader :path
11
-
12
- def initialize(dir, path)
13
- @dir = dir
14
- @path = path
15
- end
16
-
17
- def full_path
18
- File.join(dir, path)
19
- end
20
-
21
- def <=>(other)
22
- @path <=> other.path
23
- end
24
-
25
- # GlobMatch will be used as Hash key (as implied by using Set),
26
- # hence we need to define both eql? and hash
27
- def eql?(other)
28
- @path == other.path
29
- end
30
-
31
- def hash
32
- @path.hash
33
- end
34
- end
35
-
36
- attr_reader :name, :globs, :version, :dependencies, :tarball_path
37
- # We have two ways of getting/storing a package:
38
- # development versions of packages, kept in release directory
39
- # final versions of packages, kept in blobstore
40
- # development packages and their metadata should always be gitignored
41
- # final build tarballs should be ignored as well
42
- # final builds metadata should be checked in
43
-
44
- # @param [String] directory Release directory
45
- # @param [Hash] options Package build options
46
- def self.discover(directory, options = {})
47
- builders = []
48
-
49
- Dir[File.join(directory, "packages", "*")].each do |package_dir|
50
- next unless File.directory?(package_dir)
51
- package_dirname = File.basename(package_dir)
52
- package_spec = load_yaml_file(File.join(package_dir, "spec"))
53
-
54
- if package_spec["name"] != package_dirname
55
- raise InvalidPackage,
56
- "Found `#{package_spec["name"]}' package in " +
57
- "`#{package_dirname}' directory, please fix it"
58
- end
59
-
60
- is_final = options[:final]
61
- blobstore = options[:blobstore]
62
- dry_run = options[:dry_run]
63
-
64
- builder = new(package_spec, directory, is_final, blobstore)
65
- builder.dry_run = true if dry_run
66
-
67
- builders << builder
68
- end
69
-
70
- builders
71
- end
72
-
73
- def initialize(spec, release_dir, final, blobstore,
74
- sources_dir = nil, blobs_dir = nil, alt_src_dir = nil)
75
- spec = load_yaml_file(spec) if spec.is_a?(String) && File.file?(spec)
76
-
77
- @name = spec["name"]
78
- @globs = spec["files"]
79
- @excluded_globs = spec["excluded_files"] || []
80
- @dependencies = Array(spec["dependencies"])
81
-
82
- @release_dir = release_dir
83
- @sources_dir = sources_dir || File.join(@release_dir, "src")
84
- @alt_sources_dir = alt_src_dir || File.join(@release_dir, "src_alt")
85
- @blobs_dir = blobs_dir || File.join(@release_dir, "blobs")
86
-
87
- @final = final
88
- @blobstore = blobstore
89
- @artefact_type = "package"
90
-
91
- @metadata_files = %w(packaging pre_packaging)
92
-
93
- if @final && File.exists?(@alt_sources_dir)
94
- err("Please remove `#{File.basename(@alt_sources_dir)}' first")
95
- end
96
-
97
- if @name.blank?
98
- raise InvalidPackage, "Package name is missing"
99
- end
100
-
101
- unless @name.bosh_valid_id?
102
- raise InvalidPackage, "Package name should be a valid BOSH identifier"
103
- end
104
-
105
- unless @globs.is_a?(Array) && @globs.size > 0
106
- raise InvalidPackage, "Package '#{@name}' doesn't include any files"
107
- end
108
-
109
- @dev_builds_dir = File.join(@release_dir, ".dev_builds",
110
- "packages", @name)
111
- @final_builds_dir = File.join(@release_dir, ".final_builds",
112
- "packages", @name)
113
-
114
- FileUtils.mkdir_p(package_dir)
115
- FileUtils.mkdir_p(@dev_builds_dir)
116
- FileUtils.mkdir_p(@final_builds_dir)
117
-
118
- init_indices
119
- end
120
-
121
- def reload # Mostly for tests
122
- @fingerprint = nil
123
- @resolved_globs = nil
124
- init_indices
125
- self
126
- end
127
-
128
- def fingerprint
129
- @fingerprint ||= make_fingerprint
130
- end
131
-
132
- def glob_matches
133
- @resolved_globs ||= resolve_globs
134
- end
135
-
136
- def build_dir
137
- @build_dir ||= Dir.mktmpdir
138
- end
139
-
140
- def package_dir
141
- File.join(@release_dir, "packages", name)
142
- end
143
-
144
- def copy_files
145
- copied = 0
146
-
147
- glob_matches.each do |match|
148
- destination = File.join(build_dir, match.path)
149
-
150
- if File.directory?(match.full_path)
151
- FileUtils.mkdir_p(destination)
152
- else
153
- FileUtils.mkdir_p(File.dirname(destination))
154
- FileUtils.cp(match.full_path, destination, :preserve => true)
155
- copied += 1
156
- end
157
- end
158
-
159
- in_package_dir do
160
- @metadata_files.each do |filename|
161
- destination = File.join(build_dir, filename)
162
- next unless File.exists?(filename)
163
- if File.exists?(destination)
164
- raise InvalidPackage, "Package '#{name}' has '#{filename}' file " +
165
- "which conflicts with BOSH packaging"
166
- end
167
- FileUtils.cp(filename, destination, :preserve => true)
168
- copied += 1
169
- end
170
- end
171
-
172
- pre_package
173
- copied
174
- end
175
-
176
- def pre_package
177
- pre_packaging_script = File.join(package_dir, "pre_packaging")
178
-
179
- if File.exists?(pre_packaging_script)
180
-
181
- say("Pre-packaging...")
182
- FileUtils.cp(pre_packaging_script, build_dir, :preserve => true)
183
-
184
- old_env = ENV
185
-
186
- begin
187
- ENV.delete_if { |key, _| key[0, 7] == "BUNDLE_" }
188
- if ENV["RUBYOPT"]
189
- ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
190
- end
191
- ENV["BUILD_DIR"] = build_dir
192
- ENV["RELEASE_DIR"] = @release_dir
193
- in_build_dir do
194
- pre_packaging_out = `bash -x pre_packaging 2>&1`
195
- unless $?.exitstatus == 0
196
- pre_packaging_out.split("\n").each do |line|
197
- say("> #{line}")
198
- end
199
- raise InvalidPackage, "`#{name}' pre-packaging failed"
200
- end
201
- end
202
-
203
- ensure
204
- ENV.delete("BUILD_DIR")
205
- old_env.each { |k, v| ENV[k] = old_env[k] }
206
- end
207
-
208
- FileUtils.rm(File.join(build_dir, "pre_packaging"))
209
- end
210
- end
211
-
212
- private
213
-
214
- def make_fingerprint
215
- versioning_scheme = 2
216
- contents = "v#{versioning_scheme}"
217
-
218
- signatures = glob_matches.map do |match|
219
- file_digest = nil
220
-
221
- unless File.directory?(match.full_path)
222
- file_digest = Digest::SHA1.file(match.full_path).hexdigest
223
- end
224
-
225
- "%s%s%s" % [match.path, file_digest,
226
- tracked_permissions(match.full_path)]
227
- end
228
- contents << signatures.join("")
229
-
230
- in_package_dir do
231
- @metadata_files.each do |file|
232
- if File.file?(file)
233
- file_digest = Digest::SHA1.file(file).hexdigest
234
- contents << "%s%s" % [file, file_digest]
235
- end
236
- end
237
- end
238
-
239
- contents << @dependencies.sort.join(",")
240
-
241
- Digest::SHA1.hexdigest(contents)
242
- end
243
-
244
- # @return Array<GlobMatch>
245
- def resolve_globs
246
- all_matches = Set.new
247
-
248
- @globs.each do |glob|
249
- matches = Set.new
250
-
251
- src_matches = resolve_glob_in_dir(glob, @sources_dir)
252
- src_alt_matches = []
253
- if File.directory?(@alt_sources_dir)
254
- src_alt_matches = resolve_glob_in_dir(glob, @alt_sources_dir)
255
- end
256
-
257
- # Glob like core/dea/**/* might not yield anything in alt source even
258
- # when `src_alt/core' exists. That's error prone, so we don't lookup
259
- # in `src' if `src_alt' contains any part of the glob hierarchy.
260
- top_dir = glob.split(File::SEPARATOR)[0]
261
- top_dir_in_src_alt_exists = top_dir && File.exists?(File.join(@alt_sources_dir, top_dir))
262
-
263
- if top_dir_in_src_alt_exists && src_alt_matches.empty? && src_matches.any?
264
- raise InvalidPackage, "Package `#{name}' has a glob that " +
265
- "doesn't match in `#{File.basename(@alt_sources_dir)}' " +
266
- "but matches in `#{File.basename(@sources_dir)}'. " +
267
- "However `#{File.basename(@alt_sources_dir)}/#{top_dir}' " +
268
- "exists, so this might be an error."
269
- end
270
-
271
- # First add src_alt matches since src_alt takes priority over src matches
272
- matches += src_alt_matches.map { |path| GlobMatch.new(@alt_sources_dir, path) }
273
-
274
- # Only add if top-level-dir does not exist in src_alt. No partial matches.
275
- if !top_dir_in_src_alt_exists
276
- matches += src_matches.map { |path| GlobMatch.new(@sources_dir, path) }
277
- end
278
-
279
- # Blobs directory is a little bit different: whatever matches a blob
280
- # will complement already found matches, unless this particular path
281
- # has already been matched.
282
- if File.directory?(File.join(@blobs_dir))
283
- resolve_glob_in_dir(glob, @blobs_dir).each { |path| matches << GlobMatch.new(@blobs_dir, path) }
284
- end
285
-
286
- if matches.empty?
287
- raise InvalidPackage, "Package `#{name}' has a glob that resolves to an empty file list: #{glob}"
288
- end
289
-
290
- all_matches += matches
291
- end
292
-
293
- all_matches.reject! do |match|
294
- @excluded_globs.detect { |excluded_glob| File.fnmatch(excluded_glob, match.path) }
295
- end
296
- all_matches.sort
297
- end
298
-
299
- def resolve_glob_in_dir(glob, dir)
300
- Dir.chdir(dir) do
301
- Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
302
- %w(. ..).include?(File.basename(fn))
303
- end
304
- end
305
- end
306
-
307
- def in_build_dir(&block)
308
- Dir.chdir(build_dir) { yield }
309
- end
310
-
311
- def in_package_dir(&block)
312
- Dir.chdir(package_dir) { yield }
313
- end
314
-
315
- end
316
- end
@@ -1,217 +0,0 @@
1
- # This relies on having the following instance variables in a host class:
2
- # @dev_builds_dir, @final_builds_dir, @blobstore,
3
- # @name, @version, @tarball_path, @final, @artefact_type
4
-
5
- module Bosh::Cli
6
- module PackagingHelper
7
- attr_accessor :dry_run
8
-
9
- def init_indices
10
- @dev_index = Versions::VersionsIndex.new(@dev_builds_dir)
11
- @dev_storage = Versions::LocalVersionStorage.new(@dev_builds_dir)
12
-
13
- @final_index = Versions::VersionsIndex.new(@final_builds_dir)
14
- @final_storage = Versions::LocalVersionStorage.new(@final_builds_dir)
15
-
16
- @final_resolver = Versions::VersionFileResolver.new(@final_storage, @blobstore)
17
- end
18
-
19
- def final?
20
- @final
21
- end
22
-
23
- def dry_run?
24
- @dry_run
25
- end
26
-
27
- def new_version?
28
- @tarball_generated || @promoted || @will_be_promoted
29
- end
30
-
31
- def notes
32
- notes = []
33
-
34
- if @will_be_promoted
35
- new_final_version = @version
36
- notes << "new final version #{new_final_version}"
37
- elsif new_version?
38
- notes << 'new version'
39
- end
40
-
41
- notes
42
- end
43
-
44
- def build
45
- with_indent(' ') do
46
- use_final_version || use_dev_version || generate_tarball
47
- end
48
- upload_tarball(@tarball_path) if final? && !dry_run?
49
- @will_be_promoted = true if final? && dry_run? && @used_dev_version
50
- end
51
-
52
- def use_final_version
53
- say('Final version:', ' ')
54
-
55
- item = @final_index[fingerprint]
56
-
57
- if item.nil?
58
- say('NOT FOUND'.make_red)
59
- return nil
60
- end
61
-
62
- blobstore_id = item['blobstore_id']
63
- version = item['version'] || fingerprint
64
- sha1 = item['sha1']
65
-
66
- if blobstore_id.nil?
67
- say('No blobstore id'.make_red)
68
- return nil
69
- end
70
-
71
- desc = "#{name} (#{version})"
72
-
73
- @tarball_path = @final_resolver.find_file(blobstore_id, sha1, version, "package #{desc}")
74
-
75
- @version = version
76
- @used_final_version = true
77
- true
78
- rescue Bosh::Blobstore::NotFound
79
- raise BlobstoreError, "Final version of `#{name}' not found in blobstore"
80
- rescue Bosh::Blobstore::BlobstoreError => e
81
- raise BlobstoreError, "Blobstore error: #{e}"
82
- end
83
-
84
- def use_dev_version
85
- say('Dev version:', ' ')
86
- item = @dev_index[fingerprint]
87
-
88
- if item.nil?
89
- say('NOT FOUND'.make_red)
90
- return nil
91
- end
92
-
93
- version = @dev_index['version'] || fingerprint
94
-
95
- if !@dev_storage.has_file?(version)
96
- say('TARBALL MISSING'.make_red)
97
- return nil
98
- end
99
-
100
- say('FOUND LOCAL'.make_green)
101
- @tarball_path = @dev_storage.get_file(version)
102
-
103
- if file_checksum(@tarball_path) != item['sha1']
104
- say("`#{name} (#{version})' tarball corrupted".make_red)
105
- return nil
106
- end
107
-
108
- if final? && !dry_run?
109
- # copy from dev index/storage to final index/storage
110
- @final_index.add_version(fingerprint, item)
111
- @tarball_path = @final_storage.put_file(version, @tarball_path)
112
- item['sha1'] = Digest::SHA1.file(@tarball_path).hexdigest
113
- @final_index.update_version(fingerprint, item)
114
- end
115
-
116
- @version = version
117
- @used_dev_version = true
118
- end
119
-
120
- def generate_tarball
121
- version = fingerprint
122
- tmp_file = Tempfile.new(name)
123
-
124
- say('Generating...')
125
-
126
- copy_files
127
-
128
- in_build_dir do
129
- tar_out = `tar -chzf #{tmp_file.path} . 2>&1`
130
- unless $?.exitstatus == 0
131
- raise PackagingError, "Cannot create tarball: #{tar_out}"
132
- end
133
- end
134
-
135
- item = {
136
- 'version' => version
137
- }
138
-
139
- if final?
140
- # add version (with its validation) before adding sha1
141
- @final_index.add_version(fingerprint, item)
142
- @tarball_path = @final_storage.put_file(fingerprint, tmp_file.path)
143
- item['sha1'] = file_checksum(@tarball_path)
144
- @final_index.update_version(fingerprint, item)
145
- elsif dry_run?
146
- else
147
- # add version (with its validation) before adding sha1
148
- @dev_index.add_version(fingerprint, item)
149
- @tarball_path = @dev_storage.put_file(fingerprint, tmp_file.path)
150
- item['sha1'] = file_checksum(@tarball_path)
151
- @dev_index.update_version(fingerprint, item)
152
- end
153
-
154
- @version = version
155
- @tarball_generated = true
156
- say("Generated version #{version}".make_green)
157
- true
158
- end
159
-
160
- def upload_tarball(path)
161
- item = @final_index[fingerprint]
162
-
163
- unless item
164
- say("Failed to find entry `#{fingerprint}' in index, check local storage")
165
- return
166
- end
167
-
168
- if item['blobstore_id']
169
- return
170
- end
171
-
172
- say("Uploading final version `#{version}'...")
173
-
174
- blobstore_id = nil
175
- File.open(path, 'r') do |f|
176
- blobstore_id = @blobstore.create(f)
177
- end
178
-
179
- say("Uploaded, blobstore id `#{blobstore_id}'")
180
- item['blobstore_id'] = blobstore_id
181
- @final_index.update_version(fingerprint, item)
182
- @promoted = true
183
- true
184
- rescue Bosh::Blobstore::BlobstoreError => e
185
- raise BlobstoreError, "Blobstore error: #{e}"
186
- end
187
-
188
- def file_checksum(path)
189
- Digest::SHA1.file(path).hexdigest
190
- end
191
-
192
- def checksum
193
- if @tarball_path && File.exists?(@tarball_path)
194
- file_checksum(@tarball_path)
195
- else
196
- raise RuntimeError,
197
- 'cannot read checksum for not yet generated package/job'
198
- end
199
- end
200
-
201
- # Git doesn't really track file permissions, it just looks at executable
202
- # bit and uses 0755 if it's set or 0644 if not. We have to mimic that
203
- # behavior in the fingerprint calculation to avoid the situation where
204
- # seemingly clean working copy would trigger new fingerprints for
205
- # artifacts with changed permissions. Also we don't want current
206
- # fingerprints to change, hence the exact values below.
207
- def tracked_permissions(path)
208
- if File.directory?(path)
209
- '40755'
210
- elsif File.executable?(path)
211
- '100755'
212
- else
213
- '100644'
214
- end
215
- end
216
- end
217
- end