bosh_cli 0.16
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.
- 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,64 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh
|
|
4
|
+
module Cli
|
|
5
|
+
class DirectorTask
|
|
6
|
+
|
|
7
|
+
attr_accessor :offset
|
|
8
|
+
|
|
9
|
+
def initialize(director, task_id, log_type = nil)
|
|
10
|
+
@director = director
|
|
11
|
+
@task_id = task_id
|
|
12
|
+
@offset = 0
|
|
13
|
+
@log_type = log_type
|
|
14
|
+
@buf = ""
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def state
|
|
18
|
+
@director.get_task_state(@task_id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def result
|
|
22
|
+
@director.get_task_result(@task_id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def output
|
|
26
|
+
body, new_offset = @director.get_task_output(@task_id, @offset,
|
|
27
|
+
@log_type)
|
|
28
|
+
|
|
29
|
+
@buf << body if body
|
|
30
|
+
|
|
31
|
+
if new_offset
|
|
32
|
+
@offset = new_offset
|
|
33
|
+
else
|
|
34
|
+
return flush_output
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
last_nl = @buf.rindex("\n")
|
|
38
|
+
|
|
39
|
+
if !last_nl
|
|
40
|
+
result = nil
|
|
41
|
+
elsif last_nl != @buf.size - 1
|
|
42
|
+
result = @buf[0..last_nl]
|
|
43
|
+
@buf = @buf[last_nl+1..-1]
|
|
44
|
+
else
|
|
45
|
+
result = @buf
|
|
46
|
+
@buf = ""
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def flush_output
|
|
53
|
+
out = @buf
|
|
54
|
+
@buf = ""
|
|
55
|
+
out.blank? ? nil : "#{out}\n"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cancel
|
|
59
|
+
@director.cancel_task(@task_id)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
data/lib/cli/errors.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class CliError < StandardError
|
|
5
|
+
attr_reader :exit_code
|
|
6
|
+
|
|
7
|
+
def initialize(*args)
|
|
8
|
+
@exit_code = 1
|
|
9
|
+
super(*args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.error_code(code = nil)
|
|
13
|
+
define_method(:error_code) { code }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.exit_code(code = nil)
|
|
17
|
+
define_method(:exit_code) { code }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class UnknownCommand < CliError; error_code(100); end
|
|
22
|
+
class ConfigError < CliError; error_code(101); end
|
|
23
|
+
class DirectorMissing < CliError; error_code(102); end
|
|
24
|
+
class DirectorInaccessible < CliError; error_code(103); end
|
|
25
|
+
|
|
26
|
+
class DirectorError < CliError; error_code(201); end
|
|
27
|
+
class AuthError < DirectorError; error_code(202); end
|
|
28
|
+
class MissingTask < DirectorError; error_code(203); end
|
|
29
|
+
class TaskTrackError < DirectorError; error_code(204); end
|
|
30
|
+
class DeploymentNotFound < DirectorError; error_code(205); end
|
|
31
|
+
|
|
32
|
+
class CliExit < CliError; error_code(400); end
|
|
33
|
+
class GracefulExit < CliExit; error_code(401); end
|
|
34
|
+
|
|
35
|
+
class CacheDirectoryError < CliError; error_code(301); end
|
|
36
|
+
|
|
37
|
+
class InvalidPackage < CliError; error_code(500); end
|
|
38
|
+
class InvalidJob < CliError; error_code(501); end
|
|
39
|
+
class InvalidRelease < CliError; error_code(503); end
|
|
40
|
+
class MissingDependency < CliError; error_code(504); end
|
|
41
|
+
class CircularDependency < CliError; error_code(505); end
|
|
42
|
+
class InvalidIndex < CliError; error_code(506); end
|
|
43
|
+
class BlobstoreError < CliError; error_code(507); end
|
|
44
|
+
class PackagingError < CliError; error_code(508); end
|
|
45
|
+
class UndefinedProperty < CliError; error_code(509); end
|
|
46
|
+
class MalformedManifest < CliError; error_code(511); end
|
|
47
|
+
class MissingTarget < CliError; error_code(512); end
|
|
48
|
+
end
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class EventLogRenderer < TaskLogRenderer
|
|
5
|
+
|
|
6
|
+
class InvalidEvent < StandardError; end
|
|
7
|
+
|
|
8
|
+
class Task
|
|
9
|
+
attr_accessor :name
|
|
10
|
+
attr_accessor :progress
|
|
11
|
+
attr_accessor :start_time
|
|
12
|
+
attr_accessor :finish_time
|
|
13
|
+
|
|
14
|
+
def initialize(name)
|
|
15
|
+
@name = name
|
|
16
|
+
@progress = 0
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :current_stage
|
|
21
|
+
attr_reader :events_count
|
|
22
|
+
attr_reader :started_at, :finished_at
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@lock = Monitor.new
|
|
26
|
+
@events_count = 0
|
|
27
|
+
@seen_stages = Set.new
|
|
28
|
+
@out = Bosh::Cli::Config.output || $stdout
|
|
29
|
+
@out.sync = true
|
|
30
|
+
@buffer = StringIO.new
|
|
31
|
+
@progress_bars = { }
|
|
32
|
+
@pos = 0
|
|
33
|
+
@time_adjustment = 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_output(output)
|
|
37
|
+
output.to_s.split("\n").each do |line|
|
|
38
|
+
add_event(line)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_event(event)
|
|
43
|
+
event = parse_event(event)
|
|
44
|
+
|
|
45
|
+
@lock.synchronize do
|
|
46
|
+
# One way to handle old stages is to prevent them
|
|
47
|
+
# from appearing on screen altogether. That means
|
|
48
|
+
# that we can always render the current stage only
|
|
49
|
+
# and that simplifies housekeeping around progress
|
|
50
|
+
# bars and messages. However we could always support
|
|
51
|
+
# resuming the older stages rendering if we feel
|
|
52
|
+
# that it's valuable.
|
|
53
|
+
|
|
54
|
+
tags = event["tags"].is_a?(Array) ? event["tags"] : []
|
|
55
|
+
stage_header = event["stage"]
|
|
56
|
+
if tags.size > 0
|
|
57
|
+
stage_header += " " + tags.sort.join(", ").green
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
unless @seen_stages.include?(stage_header)
|
|
61
|
+
done_with_stage if @current_stage
|
|
62
|
+
begin_stage(event, stage_header)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if @current_stage == stage_header
|
|
66
|
+
append_event(event)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
rescue InvalidEvent => e
|
|
71
|
+
# Swallow for the moment
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def begin_stage(event, header)
|
|
75
|
+
@current_stage = header
|
|
76
|
+
@seen_stages << @current_stage
|
|
77
|
+
|
|
78
|
+
@stage_start_time = Time.at(event["time"]) rescue Time.now
|
|
79
|
+
@local_start_time = adjusted_time(@stage_start_time)
|
|
80
|
+
|
|
81
|
+
@tasks = {}
|
|
82
|
+
@done_tasks = []
|
|
83
|
+
|
|
84
|
+
@eta = nil
|
|
85
|
+
# Tracks max_in_flight best guess
|
|
86
|
+
@tasks_batch_size = 0
|
|
87
|
+
@batches_count = 0
|
|
88
|
+
|
|
89
|
+
# Running average of task completion time
|
|
90
|
+
@running_avg = 0
|
|
91
|
+
|
|
92
|
+
append_stage_header
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def render
|
|
96
|
+
@lock.synchronize do
|
|
97
|
+
@buffer.seek(@pos)
|
|
98
|
+
output = @buffer.read
|
|
99
|
+
@out.print output
|
|
100
|
+
@pos = @buffer.tell
|
|
101
|
+
output
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def refresh
|
|
106
|
+
# This is primarily used to refresh timer
|
|
107
|
+
# without advancing rendering buffer
|
|
108
|
+
@lock.synchronize do
|
|
109
|
+
if @in_progress
|
|
110
|
+
progress_bar.label = time_with_eta(Time.now - @local_start_time, @eta)
|
|
111
|
+
progress_bar.refresh
|
|
112
|
+
end
|
|
113
|
+
render
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def finish(state)
|
|
118
|
+
return if @events_count == 0
|
|
119
|
+
|
|
120
|
+
@lock.synchronize do
|
|
121
|
+
@done = true
|
|
122
|
+
done_with_stage(state)
|
|
123
|
+
render
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def duration
|
|
128
|
+
return nil if @started_at.nil? || @finished_at.nil?
|
|
129
|
+
@finished_at - @started_at
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def append_stage_header
|
|
135
|
+
@buffer.print "\n#{@current_stage}\n"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def done_with_stage(state = "done")
|
|
139
|
+
if @last_event
|
|
140
|
+
completion_time = Time.at(@last_event["time"]) rescue Time.now
|
|
141
|
+
else
|
|
142
|
+
completion_time = Time.now
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
case state.to_s
|
|
146
|
+
when "done"
|
|
147
|
+
progress_bar.title = "Done".green
|
|
148
|
+
progress_bar.finished_steps = progress_bar.total
|
|
149
|
+
when "error"
|
|
150
|
+
progress_bar.title = "Error".red
|
|
151
|
+
else
|
|
152
|
+
progress_bar.title = "Not done".yellow
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
progress_bar.bar_visible = false
|
|
156
|
+
progress_bar.label = format_time(completion_time - @stage_start_time)
|
|
157
|
+
progress_bar.refresh
|
|
158
|
+
@buffer.print "\n"
|
|
159
|
+
@in_progress = false
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def progress_bar
|
|
163
|
+
@progress_bars[@current_stage] ||= StageProgressBar.new(@buffer)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# We have to trust the first event in each stage
|
|
167
|
+
# to have correct "total" and "current" fields.
|
|
168
|
+
def append_event(event)
|
|
169
|
+
progress = 0
|
|
170
|
+
total = event["total"].to_i
|
|
171
|
+
|
|
172
|
+
if event["state"] == "started"
|
|
173
|
+
task = Task.new(event["task"])
|
|
174
|
+
else
|
|
175
|
+
task = @tasks[event["index"]]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
event_data = event["data"] || {}
|
|
179
|
+
# Ignoring out-of-order events
|
|
180
|
+
return if task.nil?
|
|
181
|
+
|
|
182
|
+
@events_count += 1
|
|
183
|
+
@last_event = event
|
|
184
|
+
|
|
185
|
+
case event["state"]
|
|
186
|
+
when "started"
|
|
187
|
+
begin
|
|
188
|
+
task.start_time = Time.at(event["time"])
|
|
189
|
+
# Treat first "started" event as task start time
|
|
190
|
+
@started_at = task.start_time if @started_at.nil?
|
|
191
|
+
rescue
|
|
192
|
+
task.start_time = Time.now
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
task.progress = 0
|
|
196
|
+
|
|
197
|
+
@tasks[event["index"]] = task
|
|
198
|
+
|
|
199
|
+
if @tasks.size > @tasks_batch_size
|
|
200
|
+
# Heuristics here: we assume that local maximum of
|
|
201
|
+
# tasks number is a "max_in_flight" value and batches count
|
|
202
|
+
# should only be recalculated once we refresh this maximum.
|
|
203
|
+
# It's unlikely that the first task in a batch will be finished
|
|
204
|
+
# before the last one is started so @done_tasks is expected
|
|
205
|
+
# to only have canaries.
|
|
206
|
+
@tasks_batch_size = @tasks.size
|
|
207
|
+
@non_canary_event_start_time = task.start_time
|
|
208
|
+
@batches_count = ((total - @done_tasks.size) /
|
|
209
|
+
@tasks_batch_size.to_f).ceil
|
|
210
|
+
end
|
|
211
|
+
when "finished", "failed"
|
|
212
|
+
@tasks.delete(event["index"])
|
|
213
|
+
@done_tasks << task
|
|
214
|
+
|
|
215
|
+
begin
|
|
216
|
+
task.finish_time = @finished_at = Time.at(event["time"])
|
|
217
|
+
rescue
|
|
218
|
+
task.finish_time = Time.now
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
task_time = task.finish_time - task.start_time
|
|
222
|
+
|
|
223
|
+
n_done_tasks = @done_tasks.size.to_f
|
|
224
|
+
@running_avg = @running_avg * (n_done_tasks - 1) / n_done_tasks +
|
|
225
|
+
task_time.to_f / n_done_tasks
|
|
226
|
+
|
|
227
|
+
progress = 1
|
|
228
|
+
progress_bar.finished_steps += 1
|
|
229
|
+
progress_bar.label = time_with_eta(task_time, @eta)
|
|
230
|
+
|
|
231
|
+
progress_bar.clear_line
|
|
232
|
+
|
|
233
|
+
task_name = task.name.to_s
|
|
234
|
+
if task_name !~ /^[A-Z]{2}/
|
|
235
|
+
task_name = task_name[0..0].to_s.downcase + task_name[1..-1].to_s
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
if event["state"] == "failed"
|
|
239
|
+
# TODO: truncate?
|
|
240
|
+
status = [task_name.red, event_data["error"]].compact.join(": ")
|
|
241
|
+
else
|
|
242
|
+
status = task_name.yellow
|
|
243
|
+
end
|
|
244
|
+
@buffer.puts(" #{status} (#{format_time(task_time)})")
|
|
245
|
+
when "in_progress"
|
|
246
|
+
progress = [event["progress"].to_f / 100, 1].min
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
if @batches_count > 0 && @non_canary_event_start_time
|
|
250
|
+
@eta = adjusted_time(@non_canary_event_start_time +
|
|
251
|
+
@running_avg * @batches_count)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
progress_bar_gain = progress - task.progress
|
|
255
|
+
task.progress = progress
|
|
256
|
+
|
|
257
|
+
progress_bar.total = total
|
|
258
|
+
progress_bar.title = @tasks.values.map {|t| t.name }.sort.join(", ")
|
|
259
|
+
|
|
260
|
+
progress_bar.current += progress_bar_gain
|
|
261
|
+
progress_bar.refresh
|
|
262
|
+
|
|
263
|
+
@in_progress = true
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def parse_event(event_line)
|
|
267
|
+
event = JSON.parse(event_line)
|
|
268
|
+
|
|
269
|
+
if event["time"] && event["stage"] && event["task"] &&
|
|
270
|
+
event["index"] && event["total"] && event["state"]
|
|
271
|
+
event
|
|
272
|
+
else
|
|
273
|
+
raise InvalidEvent, "Invalid event structure: stage, time, task, " +
|
|
274
|
+
"index, total, state are all required"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
rescue JSON::JSONError
|
|
278
|
+
raise InvalidEvent, "Cannot parse event, invalid JSON"
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Expects time and eta to be adjusted
|
|
282
|
+
def time_with_eta(time, eta)
|
|
283
|
+
time_fmt = format_time(time)
|
|
284
|
+
eta_fmt = eta && eta > Time.now ? format_time(eta - Time.now) : "--:--:--"
|
|
285
|
+
|
|
286
|
+
"#{time_fmt} ETA: #{eta_fmt}"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def adjusted_time(time)
|
|
290
|
+
time + @time_adjustment.to_f
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
class StageProgressBar
|
|
295
|
+
attr_accessor :total
|
|
296
|
+
attr_accessor :title
|
|
297
|
+
attr_accessor :current
|
|
298
|
+
attr_accessor :label
|
|
299
|
+
attr_accessor :bar_visible
|
|
300
|
+
attr_accessor :finished_steps
|
|
301
|
+
attr_accessor :terminal_width
|
|
302
|
+
|
|
303
|
+
def initialize(output)
|
|
304
|
+
@output = output
|
|
305
|
+
@current = 0
|
|
306
|
+
@total = 100
|
|
307
|
+
@bar_visible = true
|
|
308
|
+
@finished_steps = 0
|
|
309
|
+
@filler = "o"
|
|
310
|
+
@terminal_width = calculate_terminal_width
|
|
311
|
+
@bar_width = (0.24 * @terminal_width).to_i # characters
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def refresh
|
|
315
|
+
clear_line
|
|
316
|
+
bar_repr = @bar_visible ? bar : ""
|
|
317
|
+
title_width = (0.35 * @terminal_width).to_i
|
|
318
|
+
title = @title.truncate(title_width).ljust(title_width)
|
|
319
|
+
@output.print "#{title} #{bar_repr} #{@finished_steps}/#{@total}"
|
|
320
|
+
@output.print " #{@label}" if @label
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def bar
|
|
324
|
+
n_fillers = @total == 0 ? 0 : [(@bar_width *
|
|
325
|
+
(@current.to_f / @total.to_f)).floor, 0].max
|
|
326
|
+
|
|
327
|
+
fillers = "#{@filler}" * n_fillers
|
|
328
|
+
spaces = " " * [(@bar_width - n_fillers), 0].max
|
|
329
|
+
"|#{fillers}#{spaces}|"
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def clear_line
|
|
333
|
+
@output.print("\r")
|
|
334
|
+
@output.print(" " * @terminal_width)
|
|
335
|
+
@output.print("\r")
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def calculate_terminal_width
|
|
339
|
+
if !ENV["TERM"].blank?
|
|
340
|
+
width = `tput cols`
|
|
341
|
+
$?.exitstatus == 0 ? [width.to_i, 100].min : 80
|
|
342
|
+
else
|
|
343
|
+
80
|
|
344
|
+
end
|
|
345
|
+
rescue
|
|
346
|
+
80
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
end
|