bosh_cli 0.18 → 0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/bosh +7 -4
- data/lib/cli.rb +2 -0
- data/lib/cli/commands/base.rb +1 -18
- data/lib/cli/commands/biff.rb +3 -7
- data/lib/cli/commands/cloudcheck.rb +1 -0
- data/lib/cli/commands/deployment.rb +10 -6
- data/lib/cli/commands/job_rename.rb +116 -0
- data/lib/cli/commands/stemcell.rb +3 -3
- data/lib/cli/commands/task.rb +52 -143
- data/lib/cli/commands/vms.rb +2 -2
- data/lib/cli/config.rb +1 -0
- data/lib/cli/deployment_helper.rb +62 -22
- data/lib/cli/director.rb +85 -196
- data/lib/cli/director_task.rb +4 -4
- data/lib/cli/event_log_renderer.rb +5 -1
- data/lib/cli/null_renderer.rb +19 -0
- data/lib/cli/package_builder.rb +91 -62
- data/lib/cli/packaging_helper.rb +1 -1
- data/lib/cli/release_builder.rb +47 -13
- data/lib/cli/runner.rb +21 -39
- data/lib/cli/task_log_renderer.rb +9 -0
- data/lib/cli/task_tracker.rb +168 -0
- data/lib/cli/templates/help_message.erb +1 -0
- data/lib/cli/version.rb +1 -1
- data/lib/cli/versions_index.rb +3 -3
- data/spec/unit/biff_spec.rb +5 -0
- data/spec/unit/director_spec.rb +96 -192
- data/spec/unit/job_rename_spec.rb +195 -0
- data/spec/unit/package_builder_spec.rb +188 -186
- data/spec/unit/release_builder_spec.rb +27 -9
- data/spec/unit/runner_spec.rb +0 -25
- data/spec/unit/task_tracker_spec.rb +154 -0
- metadata +11 -4
data/lib/cli/director_task.rb
CHANGED
@@ -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
|
12
|
-
@offset
|
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
|
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
|
132
|
+
return unless duration_known?
|
129
133
|
@finished_at - @started_at
|
130
134
|
end
|
131
135
|
|
data/lib/cli/package_builder.rb
CHANGED
@@ -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
|
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
|
-
|
96
|
-
|
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?(
|
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(
|
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 =
|
178
|
-
|
179
|
-
|
180
|
-
|
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" % [
|
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
|
-
|
203
|
-
blob_list = []
|
204
|
-
source_list = []
|
219
|
+
matches = Set.new
|
205
220
|
|
206
221
|
@globs.each do |glob|
|
207
|
-
|
208
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
260
|
+
unless found
|
223
261
|
raise InvalidPackage, "`#{name}' has a glob that " +
|
224
|
-
|
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
|
-
|
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
|
243
|
-
Dir.
|
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)
|
data/lib/cli/packaging_helper.rb
CHANGED
@@ -92,7 +92,7 @@ module Bosh::Cli
|
|
92
92
|
end
|
93
93
|
|
94
94
|
if need_fetch
|
95
|
-
say("Downloading `#{name} (#{version})'
|
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)
|
data/lib/cli/release_builder.rb
CHANGED
@@ -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, :
|
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
|
13
|
-
@final
|
17
|
+
@release = release
|
18
|
+
@final = options.has_key?(:final) ? !!options[:final] : false
|
14
19
|
@packages = packages
|
15
|
-
@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"
|
90
|
-
"version"
|
91
|
-
"sha1"
|
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"
|
116
|
+
"name" => job.name,
|
99
117
|
"version" => job.version,
|
100
|
-
"sha1"
|
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
|
-
|
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
|
-
|
195
|
-
|
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
|
data/lib/cli/runner.rb
CHANGED
@@ -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
|
-
|
35
|
-
|
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
|
74
|
-
Config.output
|
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
|
-
|
85
|
-
|
57
|
+
runner = klass.new(@options)
|
58
|
+
runner.usage = @usage
|
86
59
|
|
87
|
-
action_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
|
-
|
98
|
-
elsif @args.empty? || @args ==
|
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
|
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"
|