bosh_cli 0.18 → 0.19

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.
@@ -8,10 +8,10 @@ module Bosh
8
8
 
9
9
  def initialize(director, task_id, log_type = nil)
10
10
  @director = director
11
- @task_id = task_id
12
- @offset = 0
11
+ @task_id = task_id
12
+ @offset = 0
13
13
  @log_type = log_type
14
- @buf = ""
14
+ @buf = ""
15
15
  end
16
16
 
17
17
  def state
@@ -36,7 +36,7 @@ module Bosh
36
36
 
37
37
  last_nl = @buf.rindex("\n")
38
38
 
39
- if !last_nl
39
+ if last_nl.nil?
40
40
  result = nil
41
41
  elsif last_nl != @buf.size - 1
42
42
  result = @buf[0..last_nl]
@@ -124,8 +124,12 @@ module Bosh::Cli
124
124
  end
125
125
  end
126
126
 
127
+ def duration_known?
128
+ @started_at && @finished_at
129
+ end
130
+
127
131
  def duration
128
- return nil if @started_at.nil? || @finished_at.nil?
132
+ return unless duration_known?
129
133
  @finished_at - @started_at
130
134
  end
131
135
 
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli
4
+ class NullRenderer < TaskLogRenderer
5
+
6
+ def initialize
7
+ end
8
+
9
+ def add_output(output)
10
+ end
11
+
12
+ def refresh
13
+ end
14
+
15
+ def finish(state)
16
+ end
17
+
18
+ end
19
+ end
@@ -4,6 +4,37 @@ module Bosh::Cli
4
4
  class PackageBuilder
5
5
  include PackagingHelper
6
6
 
7
+ class GlobMatch
8
+ # Helper class encapsulating the data we know about the glob. We need
9
+ # both directory and file path, as we match the same path in several
10
+ # directories (src, src_alt, blobs)
11
+ attr_reader :dir
12
+ attr_reader :path
13
+
14
+ def initialize(dir, path)
15
+ @dir = dir
16
+ @path = path
17
+ end
18
+
19
+ def full_path
20
+ File.join(dir, path)
21
+ end
22
+
23
+ def <=>(other)
24
+ @path <=> other.path
25
+ end
26
+
27
+ # GlobMatch will be used as Hash key (as implied by using Set),
28
+ # hence we need to define both eql? and hash
29
+ def eql?(other)
30
+ @path == other.path
31
+ end
32
+
33
+ def hash
34
+ @path.hash
35
+ end
36
+ end
37
+
7
38
  attr_reader :name, :globs, :version, :dependencies, :tarball_path
8
39
  # We have two ways of getting/storing a package:
9
40
  # development versions of packages, kept in release directory
@@ -13,21 +44,28 @@ module Bosh::Cli
13
44
  # final builds metadata should be checked in
14
45
 
15
46
  def initialize(spec, release_dir, final, blobstore,
16
- sources_dir = nil, blobs_dir = nil)
47
+ sources_dir = nil, blobs_dir = nil, alt_src_dir = nil)
17
48
  spec = load_yaml_file(spec) if spec.is_a?(String) && File.file?(spec)
18
49
 
19
50
  @name = spec["name"]
20
51
  @globs = spec["files"]
21
52
  @dependencies = Array(spec["dependencies"])
53
+
22
54
  @release_dir = release_dir
23
55
  @sources_dir = sources_dir || File.join(@release_dir, "src")
56
+ @alt_sources_dir = alt_src_dir || File.join(@release_dir, "src_alt")
24
57
  @blobs_dir = blobs_dir || File.join(@release_dir, "blobs")
58
+
25
59
  @final = final
26
60
  @blobstore = blobstore
27
61
  @artefact_type = "package"
28
62
 
29
63
  @metadata_files = %w(packaging pre_packaging)
30
64
 
65
+ if @final && File.exists?(@alt_sources_dir)
66
+ err("Please remove `#{File.basename(@alt_sources_dir)}' first")
67
+ end
68
+
31
69
  if @name.blank?
32
70
  raise InvalidPackage, "Package name is missing"
33
71
  end
@@ -65,22 +103,10 @@ module Bosh::Cli
65
103
  @fingerprint ||= make_fingerprint
66
104
  end
67
105
 
68
- def resolved_globs
106
+ def glob_matches
69
107
  @resolved_globs ||= resolve_globs
70
108
  end
71
109
 
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
110
  def build_dir
85
111
  @build_dir ||= Dir.mktmpdir
86
112
  end
@@ -92,15 +118,14 @@ module Bosh::Cli
92
118
  def copy_files
93
119
  copied = 0
94
120
 
95
- files.each do |filename|
96
- file_path = get_file_path(filename)
97
- destination = File.join(build_dir, filename)
121
+ glob_matches.each do |match|
122
+ destination = File.join(build_dir, match.path)
98
123
 
99
- if File.directory?(file_path)
124
+ if File.directory?(match.full_path)
100
125
  FileUtils.mkdir_p(destination)
101
126
  else
102
127
  FileUtils.mkdir_p(File.dirname(destination))
103
- FileUtils.cp(file_path, destination, :preserve => true)
128
+ FileUtils.cp(match.full_path, destination, :preserve => true)
104
129
  copied += 1
105
130
  end
106
131
  end
@@ -160,27 +185,18 @@ module Bosh::Cli
160
185
 
161
186
  private
162
187
 
163
- def get_file_path(file)
164
- file_path = File.join(@sources_dir, file)
165
- if !File.exists?(file_path)
166
- file_path = File.join(@blobs_dir, file)
167
- unless File.exists?(file_path)
168
- raise InvalidPackage, "#{file} cannot be found"
169
- end
170
- end
171
- file_path
172
- end
173
-
174
188
  def make_fingerprint
175
189
  contents = ""
176
190
 
177
- signatures = files.map do |file|
178
- path = get_file_path(file)
179
- unless File.directory?(path)
180
- file_digest = Digest::SHA1.file(path).hexdigest
191
+ signatures = glob_matches.map do |match|
192
+ file_digest = nil
193
+
194
+ unless File.directory?(match.full_path)
195
+ file_digest = Digest::SHA1.file(match.full_path).hexdigest
181
196
  end
182
197
 
183
- "%s%s%s" % [file, file_digest, tracked_permissions(path)]
198
+ "%s%s%s" % [match.path, file_digest,
199
+ tracked_permissions(match.full_path)]
184
200
  end
185
201
  contents << signatures.join("")
186
202
 
@@ -198,49 +214,62 @@ module Bosh::Cli
198
214
  Digest::SHA1.hexdigest(contents)
199
215
  end
200
216
 
217
+ # @return Array<GlobMatch>
201
218
  def resolve_globs
202
- glob_map = { :blob => [], :source => [] }
203
- blob_list = []
204
- source_list = []
219
+ matches = Set.new
205
220
 
206
221
  @globs.each do |glob|
207
- matching_source = []
208
- matching_blob = []
222
+ # Alternative source dir completely shadows the source dir, there can be
223
+ # no partial match of a particular glob in both.
224
+ found = false
225
+
226
+ [@alt_sources_dir, @sources_dir].each do |dir|
227
+ next unless File.directory?(dir)
228
+
229
+ Dir.chdir(dir) do
230
+ dir_matches = resolve_glob_in_cwd(glob)
209
231
 
210
- in_sources_dir do
211
- matching_source = Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
212
- [".", ".."].include?(File.basename(fn))
232
+ unless dir_matches.empty?
233
+ matches += dir_matches.map do |path|
234
+ GlobMatch.new(dir, path)
235
+ end
236
+ found = true
237
+ end
213
238
  end
239
+
240
+ break if found
214
241
  end
215
242
 
216
- in_blobs_dir do
217
- matching_blob = Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
218
- [".", ".."].include?(File.basename(fn))
243
+ # Blobs directory is a little bit different: whatever matches a blob
244
+ # will complement already found matches, unless this particular path
245
+ # has already been matched.
246
+ if File.directory?(File.join(@blobs_dir))
247
+ Dir.chdir(@blobs_dir) do
248
+ blob_matches = resolve_glob_in_cwd(glob)
249
+
250
+ unless blob_matches.empty?
251
+ blob_matches.each do |path|
252
+ matches << GlobMatch.new(@blobs_dir, path)
253
+ end
254
+
255
+ found = true
256
+ end
219
257
  end
220
258
  end
221
259
 
222
- if matching_blob.size == 0 && matching_source.size ==0
260
+ unless found
223
261
  raise InvalidPackage, "`#{name}' has a glob that " +
224
- "resolves to an empty file list: #{glob}"
262
+ "resolves to an empty file list: #{glob}"
225
263
  end
226
-
227
- blob_list << matching_blob
228
- source_list << matching_source
229
264
  end
230
- glob_map[:blob] = blob_list.flatten.sort
231
- glob_map[:source] = source_list.flatten.sort
232
- glob_map
233
- end
234
265
 
235
- def in_blobs_dir(&block)
236
- # old release does not have 'blob'
237
- if File.directory?(@blobs_dir)
238
- Dir.chdir(@blobs_dir) { yield }
239
- end
266
+ matches.sort
240
267
  end
241
268
 
242
- def in_sources_dir(&block)
243
- Dir.chdir(@sources_dir) { yield }
269
+ def resolve_glob_in_cwd(glob)
270
+ Dir.glob(glob, File::FNM_DOTMATCH).reject do |fn|
271
+ %w(. ..).include?(File.basename(fn))
272
+ end
244
273
  end
245
274
 
246
275
  def in_build_dir(&block)
@@ -92,7 +92,7 @@ module Bosh::Cli
92
92
  end
93
93
 
94
94
  if need_fetch
95
- say("Downloading `#{name} (#{version})' (#{blobstore_id})".green)
95
+ say("Downloading `#{name} (#{version})'...".green)
96
96
  payload = @blobstore.get(blobstore_id)
97
97
  if Digest::SHA1.hexdigest(payload) == item["sha1"]
98
98
  @tarball_path = @final_index.add_version(fingerprint, item, payload)
@@ -3,34 +3,47 @@
3
3
  module Bosh::Cli
4
4
  class ReleaseBuilder
5
5
  include Bosh::Cli::DependencyHelper
6
+ include Bosh::Cli::VersionCalc
6
7
 
7
8
  DEFAULT_RELEASE_NAME = "bosh_release"
8
9
 
9
- attr_reader :release, :packages, :jobs, :changed_jobs
10
+ attr_reader :release, :packages, :jobs, :version
10
11
 
12
+ # @param [Bosh::Cli::Release] release Current release
13
+ # @param [Array<Bosh::Cli::PackageBuilder>] packages Built packages
14
+ # @param [Array<Bosh::Cli::JobBuilder>] jobs Built jobs
15
+ # @param [Hash] options Release build options
11
16
  def initialize(release, packages, jobs, options = { })
12
- @release = release
13
- @final = options.has_key?(:final) ? !!options[:final] : false
17
+ @release = release
18
+ @final = options.has_key?(:final) ? !!options[:final] : false
14
19
  @packages = packages
15
- @jobs = jobs
20
+ @jobs = jobs
21
+
22
+ @final_index = VersionsIndex.new(final_releases_dir, release_name)
23
+ @dev_index = VersionsIndex.new(dev_releases_dir, release_name)
24
+ @index = @final ? @final_index : @dev_index
16
25
 
17
- @index = VersionsIndex.new(releases_dir, release_name)
18
26
  create_release_build_dir
19
27
  end
20
28
 
29
+ # @return [String] Release name
21
30
  def release_name
22
31
  name = @final ? @release.final_name : @release.dev_name
23
32
  name.blank? ? DEFAULT_RELEASE_NAME : name
24
33
  end
25
34
 
35
+ # @return [String] Release version
26
36
  def version
27
37
  @version ||= assign_version
28
38
  end
29
39
 
40
+ # @return [Boolean] Is release final?
30
41
  def final?
31
42
  @final
32
43
  end
33
44
 
45
+ # @return [Array] List of jobs affected by this release compared
46
+ # to the previous one.
34
47
  def affected_jobs
35
48
  result = Set.new(@jobs.select { |job| job.new_version? })
36
49
  return result if @packages.empty?
@@ -47,6 +60,8 @@ module Bosh::Cli
47
60
  result.to_a
48
61
  end
49
62
 
63
+ # Builds release
64
+ # @param [Hash] options Release build options
50
65
  def build(options = {})
51
66
  options = { :generate_tarball => true }.merge(options)
52
67
 
@@ -59,6 +74,7 @@ module Bosh::Cli
59
74
  @build_complete = true
60
75
  end
61
76
 
77
+ # Copies packages into release
62
78
  def copy_packages
63
79
  packages.each do |package|
64
80
  say("%-40s %s" % [package.name.green,
@@ -70,6 +86,7 @@ module Bosh::Cli
70
86
  @packages_copied = true
71
87
  end
72
88
 
89
+ # Copies jobs into release
73
90
  def copy_jobs
74
91
  jobs.each do |job|
75
92
  say("%-40s %s" % [job.name.green, pretty_size(job.tarball_path)])
@@ -80,24 +97,25 @@ module Bosh::Cli
80
97
  @jobs_copied = true
81
98
  end
82
99
 
100
+ # Generates release manifest
83
101
  def generate_manifest
84
102
  manifest = {}
85
103
  manifest["packages"] = []
86
104
 
87
105
  manifest["packages"] = packages.map do |package|
88
106
  {
89
- "name" => package.name,
90
- "version" => package.version,
91
- "sha1" => package.checksum,
107
+ "name" => package.name,
108
+ "version" => package.version,
109
+ "sha1" => package.checksum,
92
110
  "dependencies" => package.dependencies
93
111
  }
94
112
  end
95
113
 
96
114
  manifest["jobs"] = jobs.map do |job|
97
115
  {
98
- "name" => job.name,
116
+ "name" => job.name,
99
117
  "version" => job.version,
100
- "sha1" => job.checksum,
118
+ "sha1" => job.checksum,
101
119
  }
102
120
  end
103
121
 
@@ -161,7 +179,15 @@ module Bosh::Cli
161
179
  end
162
180
 
163
181
  def releases_dir
164
- File.join(@release.dir, final? ? "releases" : "dev_releases")
182
+ @final ? final_releases_dir : dev_releases_dir
183
+ end
184
+
185
+ def final_releases_dir
186
+ File.join(@release.dir, "releases")
187
+ end
188
+
189
+ def dev_releases_dir
190
+ File.join(@release.dir, "dev_releases")
165
191
  end
166
192
 
167
193
  def tarball_path
@@ -191,8 +217,16 @@ module Bosh::Cli
191
217
  end
192
218
 
193
219
  def assign_version
194
- current_version = @index.latest_version.to_i
195
- current_version + 1
220
+ latest_final_version = @final_index.latest_version.to_i
221
+ latest_dev_version = @dev_index.latest_version(latest_final_version)
222
+
223
+ if @final
224
+ latest_final_version + 1
225
+ else
226
+ major = latest_final_version
227
+ minor = minor_version(latest_dev_version).to_i + 1
228
+ "#{major}.#{minor}-dev"
229
+ end
196
230
  end
197
231
 
198
232
  def build_dir
@@ -25,43 +25,14 @@ module Bosh::Cli
25
25
  end
26
26
 
27
27
  def initialize(args)
28
- trap("SIGINT") {
29
- handle_ctrl_c
30
- }
31
28
  define_commands
32
29
  @args = args
33
30
  @options = {
34
- :director_checks => true,
35
- :colorize => true,
31
+ :director_checks => true,
32
+ :colorize => true,
36
33
  }
37
34
  end
38
35
 
39
- ##
40
- # When user issues ctrl-c it asks if they really want to quit. If so
41
- # then it will cancel the current running task if it exists.
42
- def handle_ctrl_c
43
- if !@runner.task_running?
44
- exit(1)
45
- elsif kill_current_task?
46
- @runner.cancel_current_task
47
- exit(1)
48
- end
49
- end
50
-
51
- ##
52
- # Asks user if they really want to quit and returns the boolean answer.
53
- #
54
- # @return [Boolean] Whether the user wants to quit or not.
55
- def kill_current_task?
56
- # Use say and stdin.gets instead of ask because of 2 bugs in Highline.
57
- # The bug makes it so that if something else has called ask and was in
58
- # the middle of waiting for a response then ctrl-c is issued and it
59
- # calls ask again then highline will re-issue the first question again.
60
- # If the input is a newline character then highline will choke.
61
- say("\nAre you sure you'd like to cancel running tasks? [yN]")
62
- $stdin.gets.chomp.downcase == "y"
63
- end
64
-
65
36
  def prepare
66
37
  define_commands
67
38
  define_plugin_commands
@@ -70,8 +41,10 @@ module Bosh::Cli
70
41
  parse_options!
71
42
 
72
43
  Config.interactive = !@options[:non_interactive]
73
- Config.colorize = @options.delete(:colorize)
74
- Config.output ||= STDOUT unless @options[:quiet]
44
+ Config.colorize = @options.delete(:colorize)
45
+ Config.output ||= STDOUT unless @options[:quiet]
46
+ Config.cache = Bosh::Cli::Cache.new(@options[:cache_dir] ||
47
+ Bosh::Cli::DEFAULT_CACHE_DIR)
75
48
  end
76
49
 
77
50
  def run
@@ -81,10 +54,10 @@ module Bosh::Cli
81
54
  if @namespace && @action
82
55
  ns_class_name = @namespace.to_s.gsub(/(?:_|^)(.)/) { $1.upcase }
83
56
  klass = eval("Bosh::Cli::Command::#{ns_class_name}")
84
- @runner = klass.new(@options)
85
- @runner.usage = @usage
57
+ runner = klass.new(@options)
58
+ runner.usage = @usage
86
59
 
87
- action_arity = @runner.method(@action.to_sym).arity
60
+ action_arity = runner.method(@action.to_sym).arity
88
61
  n_required_args = action_arity >= 0 ? action_arity : -action_arity - 1
89
62
 
90
63
  if n_required_args > @args.size
@@ -94,8 +67,8 @@ module Bosh::Cli
94
67
  err("Too many arguments, correct usage is: bosh #{@usage}")
95
68
  end
96
69
 
97
- @runner.send(@action.to_sym, *@args)
98
- elsif @args.empty? || @args == ["help"]
70
+ runner.send(@action.to_sym, *@args)
71
+ elsif @args.empty? || @args == %w(help)
99
72
  say(help_message)
100
73
  say(plugin_help_message) if @plugins
101
74
  elsif @args[0] == "help"
@@ -246,7 +219,7 @@ module Bosh::Cli
246
219
  usage "scp <job> <--upload | --download> [options] " +
247
220
  "/path/to/source /path/to/destination"
248
221
  desc "upload/download the source file to the given job. " +
249
- "Note: for dowload /path/to/destination is a directory"
222
+ "Note: for download /path/to/destination is a directory"
250
223
  option "--index <job_index>"
251
224
  option "--public_key <file>"
252
225
  option "--gateway_host <host>"
@@ -340,6 +313,15 @@ module Bosh::Cli
340
313
  power_option "--force"
341
314
  end
342
315
 
316
+ command :rename_job do
317
+ usage "rename <old_job_name> <new_job_name>"
318
+ desc "renames a job. NOTE, your deployment manifest must also be " +
319
+ "updated to reflect the new job name."
320
+ power_option "--force"
321
+
322
+ route :job_rename, :rename
323
+ end
324
+
343
325
  command :fetch_logs do
344
326
  usage "logs <job> <index>"
345
327
  desc "Fetch job (default) or agent (if option provided) logs"