bosh_cli 0.18 → 0.19

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