bosh_cli 1.3215.4.0 → 1.3232.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cli.rb +1 -0
  3. data/lib/cli/base_command.rb +1 -1
  4. data/lib/cli/basic_login_strategy.rb +3 -3
  5. data/lib/cli/blob_manager.rb +14 -14
  6. data/lib/cli/client/director.rb +16 -5
  7. data/lib/cli/commands/backup.rb +2 -2
  8. data/lib/cli/commands/cloudcheck.rb +2 -2
  9. data/lib/cli/commands/deployment.rb +22 -22
  10. data/lib/cli/commands/deployment_diff.rb +7 -3
  11. data/lib/cli/commands/disks.rb +1 -1
  12. data/lib/cli/commands/errand.rb +2 -2
  13. data/lib/cli/commands/events.rb +55 -0
  14. data/lib/cli/commands/help.rb +1 -1
  15. data/lib/cli/commands/job.rb +3 -3
  16. data/lib/cli/commands/job_management.rb +1 -1
  17. data/lib/cli/commands/locks.rb +6 -4
  18. data/lib/cli/commands/login.rb +1 -1
  19. data/lib/cli/commands/misc.rb +4 -4
  20. data/lib/cli/commands/package.rb +3 -3
  21. data/lib/cli/commands/property_management.rb +8 -8
  22. data/lib/cli/commands/release/delete_release.rb +3 -3
  23. data/lib/cli/commands/release/export_release.rb +2 -2
  24. data/lib/cli/commands/release/finalize_release.rb +26 -12
  25. data/lib/cli/commands/release/upload_release.rb +2 -2
  26. data/lib/cli/commands/release/verify_release.rb +2 -2
  27. data/lib/cli/commands/restore.rb +3 -3
  28. data/lib/cli/commands/snapshot.rb +5 -5
  29. data/lib/cli/commands/ssh.rb +2 -2
  30. data/lib/cli/commands/stemcell.rb +8 -8
  31. data/lib/cli/commands/task.rb +4 -4
  32. data/lib/cli/commands/user.rb +3 -3
  33. data/lib/cli/commands/vms.rb +1 -1
  34. data/lib/cli/config.rb +1 -1
  35. data/lib/cli/core_ext.rb +4 -4
  36. data/lib/cli/deployment_manifest_compiler.rb +1 -1
  37. data/lib/cli/file_with_progress_bar.rb +2 -0
  38. data/lib/cli/job_property_collection.rb +2 -2
  39. data/lib/cli/job_state.rb +2 -2
  40. data/lib/cli/logs_downloader.rb +1 -1
  41. data/lib/cli/manifest.rb +3 -3
  42. data/lib/cli/public_stemcell_presenter.rb +3 -3
  43. data/lib/cli/release.rb +3 -3
  44. data/lib/cli/release_compiler.rb +2 -2
  45. data/lib/cli/release_tarball.rb +2 -1
  46. data/lib/cli/runner.rb +1 -1
  47. data/lib/cli/sorted_release_archiver.rb +1 -12
  48. data/lib/cli/uaa_login_strategy.rb +1 -1
  49. data/lib/cli/version.rb +1 -1
  50. data/lib/cli/versions/local_artifact_storage.rb +2 -2
  51. data/lib/cli/versions/releases_dir_migrator.rb +3 -3
  52. data/lib/cli/versions/version_file_resolver.rb +1 -1
  53. data/lib/cli/versions/versions_index.rb +10 -11
  54. metadata +9 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3b79ac5a8377c99cad46de0f2f788e3859310d4
4
- data.tar.gz: b8f9b134f53385092d33cf8c2a5ae09ed786ea78
3
+ metadata.gz: f4e190149bfe9bd224bbe10c2be32ad11eeeef44
4
+ data.tar.gz: a43e138fbea15832c7233b3f41dbf7949478a2e2
5
5
  SHA512:
6
- metadata.gz: 8dcc139f7050d386586bf31b9a768f30339deb6da2fd115b692a0477ef0ce1c9bbe2c7931e8fe8abbea010b46416bf7b7f750d633d71dad2f5fa9bef064bf11e
7
- data.tar.gz: 3322806005d9b5560c50cb3d9fe1c5604016f061b5cbf9a3738e140ed36e44f9e9709374ac9560d8ab8fb87cd5c9b657de628821743da02619f94cc9bc461aa9
6
+ metadata.gz: b48f84f6cb4c4659978f95d039616b4bf21f8e04288e17fc9da4b4de4cdb6c506b0af956254698ee9e142c49bd6ec529c4fcc8983a6ed1447b86e18ce068ce4a
7
+ data.tar.gz: 7d53af233d4ecf0320865ca91b6217ebfb51d42e45c5870cfded371d81d5122aaf3833a3e3be6ae2ef28b9aea720f62e5103c321b231e9faec9e5616b1cd7c62
data/lib/cli.rb CHANGED
@@ -43,6 +43,7 @@ end
43
43
 
44
44
  require 'common/common'
45
45
  require 'common/exec'
46
+ require 'common/release/release_directory'
46
47
  require 'common/version/release_version'
47
48
  require 'common/version/release_version_list'
48
49
  require 'common/version/bosh_version'
@@ -208,7 +208,7 @@ module Bosh::Cli
208
208
 
209
209
  def no_track_unsupported
210
210
  if @options.delete(:no_track)
211
- say('Ignoring `' + '--no-track'.make_yellow + "' option")
211
+ say("Ignoring '" + '--no-track'.make_yellow + "' option")
212
212
  end
213
213
  end
214
214
 
@@ -22,7 +22,7 @@ module Bosh
22
22
  end
23
23
 
24
24
  if @director.login(username, password)
25
- @terminal.say_green("Logged in as `#{username}'")
25
+ @terminal.say_green("Logged in as '#{username}'")
26
26
  @config.set_credentials(target, {
27
27
  "username" => username,
28
28
  "password" => password
@@ -30,10 +30,10 @@ module Bosh
30
30
  @config.save
31
31
  else
32
32
  if @interactive
33
- @terminal.say_red("Cannot log in as `#{username}', please try again")
33
+ @terminal.say_red("Cannot log in as '#{username}', please try again")
34
34
  login(target, username, '')
35
35
  else
36
- err("Cannot log in as `#{username}'")
36
+ err("Cannot log in as '#{username}'")
37
37
  end
38
38
  end
39
39
  end
@@ -36,7 +36,7 @@ module Bosh::Cli
36
36
 
37
37
  @src_dir = File.join(@release.dir, "src")
38
38
  unless File.directory?(@src_dir)
39
- err("`src' directory is missing")
39
+ err("'src' directory is missing")
40
40
  end
41
41
 
42
42
  @storage_dir = File.join(@release.dir, ".blobs")
@@ -96,7 +96,7 @@ module Bosh::Cli
96
96
  end
97
97
 
98
98
  nl
99
- say("When ready please run `#{"bosh upload blobs".make_green}'")
99
+ say("When ready please run '#{"bosh upload blobs".make_green}'")
100
100
  end
101
101
 
102
102
  # Registers a file as BOSH blob
@@ -105,11 +105,11 @@ module Bosh::Cli
105
105
  # @return [void]
106
106
  def add_blob(local_path, blob_path)
107
107
  unless File.exists?(local_path)
108
- err("File `#{local_path}' not found")
108
+ err("File '#{local_path}' not found")
109
109
  end
110
110
 
111
111
  if File.directory?(local_path)
112
- err("`#{local_path}' is a directory")
112
+ err("'#{local_path}' is a directory")
113
113
  end
114
114
 
115
115
  if blob_path[0..0] == "/"
@@ -117,19 +117,19 @@ module Bosh::Cli
117
117
  end
118
118
 
119
119
  if blob_path[0..5] == "blobs/"
120
- err("Blob path should not start with `blobs/'")
120
+ err("Blob path should not start with 'blobs/'")
121
121
  end
122
122
 
123
123
  blob_dst = File.join(@blobs_dir, blob_path)
124
124
 
125
125
  if File.directory?(blob_dst)
126
- err("`#{blob_dst}' is a directory, please pick a different path")
126
+ err("'#{blob_dst}' is a directory, please pick a different path")
127
127
  end
128
128
 
129
129
  update = false
130
130
  if File.exists?(blob_dst)
131
131
  if file_checksum(blob_dst) == file_checksum(local_path)
132
- err("Already tracking the same version of `#{blob_path}'")
132
+ err("Already tracking the same version of '#{blob_path}'")
133
133
  end
134
134
  update = true
135
135
  FileUtils.rm(blob_dst)
@@ -145,7 +145,7 @@ module Bosh::Cli
145
145
  end
146
146
 
147
147
  say("When you are done testing the new blob, please run\n" +
148
- "`#{"bosh upload blobs".make_green}' and commit changes.")
148
+ "'#{"bosh upload blobs".make_green}' and commit changes.")
149
149
  end
150
150
 
151
151
  # Synchronizes the contents of blobs directory with blobs index.
@@ -173,7 +173,7 @@ module Bosh::Cli
173
173
  path = strip_blobs_dir(file)
174
174
 
175
175
  if File.exists?(File.join(@src_dir, path))
176
- err("File `#{path}' is in both `blobs' and `src' directory.\n" +
176
+ err("File '#{path}' is in both 'blobs' and 'src' directory.\n" +
177
177
  "Please fix release repo before proceeding")
178
178
  end
179
179
 
@@ -213,7 +213,7 @@ module Bosh::Cli
213
213
  missing_blobs = []
214
214
  @index.each_pair do |path, entry|
215
215
  if File.exists?(File.join(@src_dir, path))
216
- err("File `#{path}' is in both blob index and src directory.\n" +
216
+ err("File '#{path}' is in both blob index and src directory.\n" +
217
217
  "Please fix release repo before proceeding")
218
218
  end
219
219
 
@@ -256,11 +256,11 @@ module Bosh::Cli
256
256
  blob_path = File.join(@blobs_dir, path)
257
257
 
258
258
  unless File.exists?(blob_path)
259
- err("Cannot upload blob, local file `#{blob_path}' doesn't exist")
259
+ err("Cannot upload blob, local file '#{blob_path}' doesn't exist")
260
260
  end
261
261
 
262
262
  if File.symlink?(blob_path)
263
- err("`#{blob_path}' is a symlink")
263
+ err("'#{blob_path}' is a symlink")
264
264
  end
265
265
 
266
266
  checksum = file_checksum(blob_path)
@@ -288,7 +288,7 @@ module Bosh::Cli
288
288
  end
289
289
 
290
290
  unless @index.has_key?(path)
291
- err("Unknown blob path `#{path}'")
291
+ err("Unknown blob path '#{path}'")
292
292
  end
293
293
 
294
294
  blob = @index[path]
@@ -355,7 +355,7 @@ module Bosh::Cli
355
355
  if blob_path[0..blobs_dir.size] == blobs_dir + "/"
356
356
  blob_path[blobs_dir.size+1..-1]
357
357
  else
358
- err("File `#{blob_path}' is not under `blobs' directory")
358
+ err("File '#{blob_path}' is not under 'blobs' directory")
359
359
  end
360
360
  end
361
361
 
@@ -124,6 +124,18 @@ module Bosh
124
124
  get_json('/deployments')
125
125
  end
126
126
 
127
+ def list_events(options={})
128
+ query_string = "/events"
129
+ delimeter = "?"
130
+ [:before_id, :deployment, :instance, :task].each do |param|
131
+ if options[param]
132
+ query_string += "#{delimeter}#{ param.to_s}=#{options[param]}"
133
+ delimeter = "&"
134
+ end
135
+ end
136
+ get_json(query_string)
137
+ end
138
+
127
139
  def list_errands(deployment_name)
128
140
  get_json("/deployments/#{deployment_name}/errands")
129
141
  end
@@ -275,15 +287,14 @@ module Bosh
275
287
  request_and_track(:post, add_query_string(url, extras), options)
276
288
  end
277
289
 
278
- def diff_deployment(name, manifest_yaml, no_redact = false)
279
- redact_param = no_redact ? '?redact=false' : ''
290
+ def diff_deployment(name, manifest_yaml, redact_diff = true)
291
+ redact_param = redact_diff ? '' : '?redact=false'
280
292
  uri = "/deployments/#{name}/diff#{redact_param}"
281
293
  status, body = post(uri, 'text/yaml', manifest_yaml)
282
-
283
294
  if status == 200
284
295
  JSON.parse(body)
285
296
  else
286
- err(parse_error_message(status, body, uri))
297
+ err(parse_error_message(status, body))
287
298
  end
288
299
  end
289
300
 
@@ -411,7 +422,7 @@ module Bosh
411
422
  tmp_file
412
423
  else
413
424
  raise DirectorError,
414
- "Cannot download resource `#{id}': HTTP status #{status}"
425
+ "Cannot download resource '#{id}': HTTP status #{status}"
415
426
  end
416
427
  end
417
428
 
@@ -15,7 +15,7 @@ module Bosh::Cli::Command
15
15
  if status == :done
16
16
  tmp_path = director.fetch_backup
17
17
  FileUtils.mv(tmp_path, path)
18
- say("Backup of BOSH director was put in `#{path.make_green}'.")
18
+ say("Backup of BOSH director was put in '#{path.make_green}'.")
19
19
  else
20
20
  [status, task_id]
21
21
  end
@@ -31,7 +31,7 @@ module Bosh::Cli::Command
31
31
  path = Bosh::Cli::BackupDestinationPath.new(director).create_from_path(dest_path)
32
32
 
33
33
  if File.exists?(path) && !force?
34
- err("There is already an existing file at `#{path}'. " +
34
+ err("There is already an existing file at '#{path}'. " +
35
35
  'To overwrite it use the --force option.')
36
36
  end
37
37
 
@@ -21,8 +21,8 @@ module Bosh::Cli::Command
21
21
 
22
22
  if non_interactive? && !(@report_mode || @auto_mode)
23
23
  err ("Cloudcheck cannot be run in non-interactive mode\n" +
24
- "Please use `--auto' flag if you want automated resolutions " +
25
- "or `--report' if you just want a report of the errors")
24
+ "Please use '--auto' flag if you want automated resolutions " +
25
+ "or '--report' if you just want a report of the errors")
26
26
  end
27
27
 
28
28
  if @auto_mode && @report_mode
@@ -13,7 +13,7 @@ module Bosh::Cli::Command
13
13
  manifest_filename = find_deployment(filename)
14
14
 
15
15
  unless File.exists?(manifest_filename)
16
- err("Missing manifest for `#{filename}'")
16
+ err("Missing manifest for '#{filename}'")
17
17
  end
18
18
 
19
19
  manifest = load_yaml_file(manifest_filename)
@@ -60,10 +60,10 @@ module Bosh::Cli::Command
60
60
  config.target_version = status['version']
61
61
  config.target_uuid = status['uuid']
62
62
  say("#{'WARNING!'.make_red} Your target has been " +
63
- "changed to `#{target.make_red}'!")
63
+ "changed to '#{target.make_red}'!")
64
64
  end
65
65
 
66
- say("Deployment set to `#{manifest_filename.make_green}'")
66
+ say("Deployment set to '#{manifest_filename.make_green}'")
67
67
  config.set_deployment(manifest_filename)
68
68
  config.save
69
69
  end
@@ -81,21 +81,21 @@ module Bosh::Cli::Command
81
81
  usage 'deploy'
82
82
  desc 'Deploy according to the currently selected deployment manifest'
83
83
  option '--recreate', 'Recreate all VMs in deployment'
84
- option '--redact-diff', 'Redact manifest value changes in deployment'
85
- option '--no-redact', 'do not redact'
84
+ option '--no-redact', 'Redact manifest value changes in deployment'
86
85
  option '--skip-drain [job1,job2]', String, 'Skip drain script for either specific or all jobs'
87
86
  def perform
88
87
  auth_required
88
+
89
89
  recreate = !!options[:recreate]
90
- redact_diff = !!options[:redact_diff]
91
- no_redact = !options[:no_redact].nil?
90
+ redact_diff = !!options[:no_redact].nil?
91
+
92
92
  manifest = build_manifest
93
93
 
94
94
  if manifest.hash['releases']
95
95
  manifest.hash['releases'].each do |release|
96
96
  if release['url'].blank?
97
97
  if release['version'] == 'create'
98
- err("Expected URL when specifying release version `create'")
98
+ err("Expected URL when specifying release version 'create'")
99
99
  end
100
100
  else
101
101
  parsed_uri = URI.parse(release['url'])
@@ -110,11 +110,11 @@ module Bosh::Cli::Command
110
110
  run_nested_command "upload", "release", parsed_uri.path, '--name', release['name'], '--version', release['version'].to_s
111
111
  end
112
112
  when 'http', 'https'
113
- err('Path must be a local release directory when version is `create\'') if release['version'] == 'create'
114
- err("Expected SHA1 when specifying remote URL for release `#{release["name"]}'") if release['sha1'].blank?
113
+ err("Path must be a local release directory when version is 'create'") if release['version'] == 'create'
114
+ err("Expected SHA1 when specifying remote URL for release '#{release["name"]}'") if release['sha1'].blank?
115
115
  run_nested_command "upload", "release", release['url'], "--sha1", release['sha1'], "--name", release['name'], "--version", release['version'].to_s
116
116
  else
117
- err("Invalid URL format for release `#{release['name']}' with URL `#{release['url']}'. Supported schemes: file, http, https.")
117
+ err("Invalid URL format for release '#{release['name']}' with URL '#{release['url']}'. Supported schemes: file, http, https.")
118
118
  end
119
119
  end
120
120
  end
@@ -128,11 +128,11 @@ module Bosh::Cli::Command
128
128
  when 'file'
129
129
  run_nested_command "upload", "stemcell", parsed_uri.path, "--name", resource_pool['stemcell']['name'], "--version", resource_pool['stemcell']['version'].to_s, "--skip-if-exists"
130
130
  when 'http', 'https'
131
- err("Expected SHA1 when specifying remote URL for stemcell `#{resource_pool['stemcell']['name']}'") if resource_pool['stemcell']['sha1'].blank?
131
+ err("Expected SHA1 when specifying remote URL for stemcell '#{resource_pool['stemcell']['name']}'") if resource_pool['stemcell']['sha1'].blank?
132
132
  run_nested_command "upload", "stemcell", resource_pool['stemcell']['url'], "--sha1", resource_pool['stemcell']['sha1'],
133
133
  "--name", resource_pool['stemcell']['name'], "--version", resource_pool['stemcell']['version'].to_s, "--skip-if-exists"
134
134
  else
135
- err("Invalid URL format for stemcell `#{resource_pool['stemcell']['name']}' with URL `#{resource_pool['stemcell']['url']}'. Supported schemes: file, http, https.")
135
+ err("Invalid URL format for stemcell '#{resource_pool['stemcell']['name']}' with URL '#{resource_pool['stemcell']['url']}'. Supported schemes: file, http, https.")
136
136
  end
137
137
  end
138
138
  end
@@ -140,7 +140,7 @@ module Bosh::Cli::Command
140
140
 
141
141
  manifest = prepare_deployment_manifest(resolve_properties: true, show_state: true)
142
142
 
143
- context = DeploymentDiff.new(director, manifest).print({redact_diff: redact_diff, no_redact: no_redact})
143
+ context = DeploymentDiff.new(director, manifest).print({redact_diff: redact_diff})
144
144
  say('Please review all changes carefully'.make_yellow) if interactive?
145
145
 
146
146
  header('Deploying')
@@ -160,7 +160,7 @@ module Bosh::Cli::Command
160
160
 
161
161
  status, task_id = director.deploy(manifest.yaml, deploy_options)
162
162
 
163
- task_report(status, task_id, "Deployed `#{manifest.name.make_green}' to `#{target_name.make_green}'")
163
+ task_report(status, task_id, "Deployed '#{manifest.name.make_green}' to '#{target_name.make_green}'")
164
164
  end
165
165
 
166
166
  # bosh delete deployment
@@ -173,7 +173,7 @@ module Bosh::Cli::Command
173
173
 
174
174
  force = !!options[:force]
175
175
 
176
- say("\nYou are going to delete deployment `#{deployment_name}'.".make_red)
176
+ say("\nYou are going to delete deployment '#{deployment_name}'.".make_red)
177
177
  nl
178
178
  say("THIS IS A VERY DESTRUCTIVE OPERATION AND IT CANNOT BE UNDONE!\n".make_red)
179
179
 
@@ -184,9 +184,9 @@ module Bosh::Cli::Command
184
184
 
185
185
  begin
186
186
  status, result = director.delete_deployment(deployment_name, :force => force)
187
- task_report(status, result, "Deleted deployment `#{deployment_name}'")
187
+ task_report(status, result, "Deleted deployment '#{deployment_name}'")
188
188
  rescue Bosh::Cli::ResourceNotFound
189
- task_report(:done, nil, "Skipped delete of missing deployment `#{deployment_name}'")
189
+ task_report(:done, nil, "Skipped delete of missing deployment '#{deployment_name}'")
190
190
  end
191
191
  end
192
192
 
@@ -223,7 +223,7 @@ module Bosh::Cli::Command
223
223
  show_current_state(deployment_name)
224
224
 
225
225
  if save_as && File.exists?(save_as) &&
226
- !confirmed?("Overwrite `#{save_as}'?")
226
+ !confirmed?("Overwrite '#{save_as}'?")
227
227
  err('Please choose another file to save the manifest to')
228
228
  end
229
229
 
@@ -233,7 +233,7 @@ module Bosh::Cli::Command
233
233
  File.open(save_as, 'w') do |f|
234
234
  f.write(deployment['manifest'])
235
235
  end
236
- say("Deployment manifest saved to `#{save_as}'".make_green)
236
+ say("Deployment manifest saved to '#{save_as}'".make_green)
237
237
  else
238
238
  say(deployment['manifest'])
239
239
  end
@@ -244,7 +244,7 @@ module Bosh::Cli::Command
244
244
  config.target = target
245
245
  if config.deployment
246
246
  if interactive?
247
- say("Current deployment is `#{config.deployment.make_green}'")
247
+ say("Current deployment is '#{config.deployment.make_green}'")
248
248
  else
249
249
  say(config.deployment)
250
250
  end
@@ -269,7 +269,7 @@ module Bosh::Cli::Command
269
269
  def path_is_reasonable!(path)
270
270
  #path is actually to a directory, not a file
271
271
  unless File.directory?(path)
272
- err "Path must be a release directory when version is `create'"
272
+ err "Path must be a release directory when version is 'create'"
273
273
  end
274
274
  end
275
275
 
@@ -6,10 +6,12 @@ module Bosh::Cli::Command
6
6
  end
7
7
 
8
8
  def print(options)
9
+ redact_diff = options[:redact_diff]
10
+
9
11
  begin
10
- no_redact = options[:no_redact]
11
- changes = @director.diff_deployment(@manifest.name, @manifest.yaml, no_redact)
12
+ changes = @director.diff_deployment(@manifest.name, @manifest.yaml, redact_diff)
12
13
  diff = changes['diff']
14
+ error = changes['error']
13
15
 
14
16
  header('Detecting deployment changes')
15
17
 
@@ -39,11 +41,13 @@ module Bosh::Cli::Command
39
41
  end
40
42
  end
41
43
 
44
+ say(error) if error
45
+
42
46
  changes['context']
43
47
  rescue Bosh::Cli::ResourceNotFound
44
48
  inspect_deployment_changes(
45
49
  @manifest,
46
- redact_diff: options[:redact_diff]
50
+ redact_diff: redact_diff
47
51
  )
48
52
 
49
53
  nil
@@ -6,7 +6,7 @@ module Bosh::Cli::Command
6
6
  def list
7
7
  auth_required
8
8
  unless options[:orphaned]
9
- err('Only `bosh disks --orphaned` is supported')
9
+ err("Only 'bosh disks --orphaned' is supported")
10
10
  end
11
11
 
12
12
  disks = sort(director.list_orphan_disks)
@@ -44,7 +44,7 @@ module Bosh::Cli::Command
44
44
  status, task_id, errand_result = errands_client.run_errand(deployment_name, errand_name, options[:keep_alive] || FALSE)
45
45
 
46
46
  unless errand_result
47
- task_report(status, task_id, nil, "Errand `#{errand_name}' did not complete")
47
+ task_report(status, task_id, nil, "Errand '#{errand_name}' did not complete")
48
48
  return
49
49
  end
50
50
 
@@ -69,7 +69,7 @@ module Bosh::Cli::Command
69
69
  end
70
70
  end
71
71
 
72
- title_prefix = "Errand `#{errand_name}'"
72
+ title_prefix = "Errand '#{errand_name}'"
73
73
  exit_code_suffix = "(exit code #{errand_result.exit_code})"
74
74
 
75
75
  if errand_result.exit_code == 0