Class: Command::Base

Inherits:
Object
  • Object
show all
Includes:
Helpers
Defined in:
lib/command/base.rb

Overview

rubocop:disable Metrics/ClassLength

Constant Summary collapse

VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS =
%w[config].freeze
VALIDATIONS_WITH_ADDITIONAL_OPTIONS =
%w[templates].freeze
ALL_VALIDATIONS =
VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS + VALIDATIONS_WITH_ADDITIONAL_OPTIONS
USAGE =

Used to call the command (‘cpl NAME`) NAME = “” Displayed when running `cpl help` or `cpl help NAME` (defaults to `NAME`)

""
REQUIRES_ARGS =

Throws error if ‘true` and no arguments are passed to the command or if `false` and arguments are passed to the command

false
DEFAULT_ARGS =

Default arguments if none are passed to the command

[].freeze
OPTIONS =

Options for the command (use option methods below)

[].freeze
ACCEPTS_EXTRA_OPTIONS =

Does not throw error if ‘true` and extra options that are not specified in `OPTIONS` are passed to the command

false
EXAMPLES =

Displayed when running ‘cpl help` DESCRIPTION = “” Displayed when running `cpl help NAME` LONG_DESCRIPTION = “” Displayed along with `LONG_DESCRIPTION` when running `cpl help NAME`

""
HIDE =

If ‘true`, hides the command from `cpl help`

false
WITH_INFO_HEADER =

Whether or not to show key information like ORG and APP name in commands

true
VALIDATIONS =

Which validations to run before the command

%w[config].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

normalize_command_name, normalize_option_name, random_four_digits, strip_str_and_validate

Constructor Details

#initialize(config) ⇒ Base

Returns a new instance of Base.



42
43
44
# File 'lib/command/base.rb', line 42

def initialize(config)
  @config = config
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



7
8
9
# File 'lib/command/base.rb', line 7

def config
  @config
end

Class Method Details

.add_app_identity_option(required: false) ⇒ Object



435
436
437
438
439
440
441
442
443
444
# File 'lib/command/base.rb', line 435

def self.add_app_identity_option(required: false)
  {
    name: :add_app_identity,
    params: {
      desc: "Adds app identity template if it does not exist",
      type: :boolean,
      required: required
    }
  }
end

.all_commandsObject



46
47
48
49
50
51
52
# File 'lib/command/base.rb', line 46

def self.all_commands
  Dir["#{__dir__}/*.rb"].each_with_object({}) do |file, result|
    filename = File.basename(file, ".rb")
    classname = File.read(file).match(/^\s+class (\w+) < Base($| .*$)/)&.captures&.first
    result[filename.to_sym] = Object.const_get("::Command::#{classname}") if classname
  end
end

.all_optionsObject

rubocop:enable Metrics/MethodLength



447
448
449
# File 'lib/command/base.rb', line 447

def self.all_options
  methods.grep(/_option$/).map { |method| send(method.to_s) }
end

.all_options_by_key_nameObject



451
452
453
454
455
456
# File 'lib/command/base.rb', line 451

def self.all_options_by_key_name
  all_options.each_with_object({}) do |option, result|
    option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
    result["--#{option[:name]}"] = option
  end
end

.app_option(required: false) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/command/base.rb', line 72

def self.app_option(required: false)
  {
    name: :app,
    params: {
      aliases: ["-a"],
      banner: "APP_NAME",
      desc: "Application name",
      type: :string,
      required: required
    }
  }
end

.commit_option(required: false) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/command/base.rb', line 138

def self.commit_option(required: false)
  {
    name: :commit,
    params: {
      aliases: ["-c"],
      banner: "COMMIT_HASH",
      desc: "Commit hash",
      type: :string,
      required: required
    }
  }
end

.common_optionsObject



54
55
56
# File 'lib/command/base.rb', line 54

def self.common_options
  [org_option, verbose_option, trace_option]
end

.cpu_option(required: false) ⇒ Object



356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/command/base.rb', line 356

def self.cpu_option(required: false)
  {
    name: :cpu,
    params: {
      banner: "CPU",
      desc: "Overrides CPU millicores " \
            "(e.g., '100m' for 100 millicores, '1' for 1 core)",
      type: :string,
      required: required,
      valid_regex: /^\d+m?$/
    }
  }
end

.detached_option(required: false) ⇒ Object



345
346
347
348
349
350
351
352
353
354
# File 'lib/command/base.rb', line 345

def self.detached_option(required: false)
  {
    name: :detached,
    params: {
      desc: "Runs non-interactive command, detaches, and prints commands to log and stop the job",
      type: :boolean,
      required: required
    }
  }
end

.domain_option(required: false) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/command/base.rb', line 164

def self.domain_option(required: false)
  {
    name: :domain,
    params: {
      banner: "DOMAIN_NAME",
      desc: "Domain name",
      type: :string,
      required: required
    }
  }
end

.entrypoint_option(required: false) ⇒ Object



384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/command/base.rb', line 384

def self.entrypoint_option(required: false)
  {
    name: :entrypoint,
    params: {
      banner: "ENTRYPOINT",
      desc: "Overrides entrypoint " \
            "(must be a single command or a script path that exists in the container)",
      type: :string,
      required: required,
      valid_regex: /^\S+$/
    }
  }
end

.image_option(required: false) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/command/base.rb', line 111

def self.image_option(required: false)
  {
    name: :image,
    params: {
      aliases: ["-i"],
      banner: "IMAGE_NAME",
      desc: "Image name",
      type: :string,
      required: required
    }
  }
end

.interactive_option(required: false) ⇒ Object



334
335
336
337
338
339
340
341
342
343
# File 'lib/command/base.rb', line 334

def self.interactive_option(required: false)
  {
    name: :interactive,
    params: {
      desc: "Runs interactive command",
      type: :boolean,
      required: required
    }
  }
end

.location_option(required: false) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/command/base.rb', line 151

def self.location_option(required: false)
  {
    name: :location,
    params: {
      aliases: ["-l"],
      banner: "LOCATION_NAME",
      desc: "Location name",
      type: :string,
      required: required
    }
  }
end

.log_method_option(required: false) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/command/base.rb', line 124

def self.log_method_option(required: false)
  {
    name: :log_method,
    params: {
      type: :numeric,
      banner: "LOG_METHOD",
      desc: "Log method",
      required: required,
      valid_values: [1, 2, 3],
      default: 3
    }
  }
end

.logs_limit_option(required: false) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/command/base.rb', line 307

def self.logs_limit_option(required: false)
  {
    name: :limit,
    params: {
      banner: "NUMBER",
      desc: "Limit on number of log entries to show",
      type: :numeric,
      required: required,
      default: 200
    }
  }
end

.logs_since_option(required: false) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/command/base.rb', line 320

def self.logs_since_option(required: false)
  {
    name: :since,
    params: {
      banner: "DURATION",
      desc: "Loopback window for showing logs " \
            "(see https://www.npmjs.com/package/parse-duration for the accepted formats, e.g., '1h')",
      type: :string,
      required: required,
      default: "1h"
    }
  }
end

.memory_option(required: false) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/command/base.rb', line 370

def self.memory_option(required: false)
  {
    name: :memory,
    params: {
      banner: "MEMORY",
      desc: "Overrides memory size " \
            "(e.g., '100Mi' for 100 mebibytes, '1Gi' for 1 gibibyte)",
      type: :string,
      required: required,
      valid_regex: /^\d+[MG]i$/
    }
  }
end

.org_option(required: false) ⇒ Object

rubocop:disable Metrics/MethodLength



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/command/base.rb', line 59

def self.org_option(required: false)
  {
    name: :org,
    params: {
      aliases: ["-o"],
      banner: "ORG_NAME",
      desc: "Organization name",
      type: :string,
      required: required
    }
  }
end

.replica_option(required: false) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/command/base.rb', line 98

def self.replica_option(required: false)
  {
    name: :replica,
    params: {
      aliases: ["-r"],
      banner: "REPLICA_NAME",
      desc: "Replica name",
      type: :string,
      required: required
    }
  }
end

.run_release_phase_option(required: false) ⇒ Object



296
297
298
299
300
301
302
303
304
305
# File 'lib/command/base.rb', line 296

def self.run_release_phase_option(required: false)
  {
    name: :run_release_phase,
    params: {
      desc: "Runs release phase",
      type: :boolean,
      required: required
    }
  }
end

.skip_confirm_option(required: false) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/command/base.rb', line 189

def self.skip_confirm_option(required: false)
  {
    name: :yes,
    params: {
      aliases: ["-y"],
      banner: "SKIP_CONFIRM",
      desc: "Skip confirmation",
      type: :boolean,
      required: required
    }
  }
end

.skip_post_creation_hook_option(required: false) ⇒ Object



413
414
415
416
417
418
419
420
421
422
# File 'lib/command/base.rb', line 413

def self.skip_post_creation_hook_option(required: false)
  {
    name: :skip_post_creation_hook,
    params: {
      desc: "Skips post-creation hook",
      type: :boolean,
      required: required
    }
  }
end

.skip_pre_deletion_hook_option(required: false) ⇒ Object



424
425
426
427
428
429
430
431
432
433
# File 'lib/command/base.rb', line 424

def self.skip_pre_deletion_hook_option(required: false)
  {
    name: :skip_pre_deletion_hook,
    params: {
      desc: "Skips pre-deletion hook",
      type: :boolean,
      required: required
    }
  }
end

.skip_secret_access_binding_option(required: false) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
# File 'lib/command/base.rb', line 273

def self.skip_secret_access_binding_option(required: false)
  {
    name: :skip_secret_access_binding,
    new_name: :skip_secrets_setup,
    params: {
      desc: "Skips secret access binding",
      type: :boolean,
      required: required
    }
  }
end

.skip_secrets_setup_option(required: false) ⇒ Object



285
286
287
288
289
290
291
292
293
294
# File 'lib/command/base.rb', line 285

def self.skip_secrets_setup_option(required: false)
  {
    name: :skip_secrets_setup,
    params: {
      desc: "Skips secrets setup",
      type: :boolean,
      required: required
    }
  }
end

.terminal_size_option(required: false) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/command/base.rb', line 226

def self.terminal_size_option(required: false)
  {
    name: :terminal_size,
    params: {
      banner: "ROWS,COLS",
      desc: "Override remote terminal size (e.g. `--terminal-size 10,20`)",
      type: :string,
      required: required,
      valid_regex: /^\d+,\d+$/
    }
  }
end

.trace_option(required: false) ⇒ Object



262
263
264
265
266
267
268
269
270
271
# File 'lib/command/base.rb', line 262

def self.trace_option(required: false)
  {
    name: :trace,
    params: {
      desc: "Shows trace of API calls. WARNING: may contain sensitive data",
      type: :boolean,
      required: required
    }
  }
end

.upstream_token_option(required: false) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/command/base.rb', line 176

def self.upstream_token_option(required: false)
  {
    name: :upstream_token,
    params: {
      aliases: ["-t"],
      banner: "UPSTREAM_TOKEN",
      desc: "Upstream token",
      type: :string,
      required: required
    }
  }
end

.use_local_token_option(required: false) ⇒ Object



215
216
217
218
219
220
221
222
223
224
# File 'lib/command/base.rb', line 215

def self.use_local_token_option(required: false)
  {
    name: :use_local_token,
    params: {
      desc: "Override remote CPLN_TOKEN with local token",
      type: :boolean,
      required: required
    }
  }
end

.validations_option(required: false) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/command/base.rb', line 398

def self.validations_option(required: false)
  {
    name: :validations,
    params: {
      banner: "VALIDATION_1,VALIDATION_2,...",
      desc: "Which validations to run " \
            "(must be separated by a comma)",
      type: :string,
      required: required,
      default: VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS.join(","),
      valid_regex: /^(#{ALL_VALIDATIONS.join("|")})(,(#{ALL_VALIDATIONS.join("|")}))*$/
    }
  }
end

.verbose_option(required: false) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
# File 'lib/command/base.rb', line 250

def self.verbose_option(required: false)
  {
    name: :verbose,
    params: {
      aliases: ["-d"],
      desc: "Shows detailed logs",
      type: :boolean,
      required: required
    }
  }
end

.version_option(required: false) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/command/base.rb', line 202

def self.version_option(required: false)
  {
    name: :version,
    params: {
      aliases: ["-v"],
      banner: "VERSION",
      desc: "Displays the current version of the CLI",
      type: :boolean,
      required: required
    }
  }
end

.wait_option(title = "", required: false) ⇒ Object



239
240
241
242
243
244
245
246
247
248
# File 'lib/command/base.rb', line 239

def self.wait_option(title = "", required: false)
  {
    name: :wait,
    params: {
      desc: "Waits for #{title}",
      type: :boolean,
      required: required
    }
  }
end

.workload_option(required: false) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/command/base.rb', line 85

def self.workload_option(required: false)
  {
    name: :workload,
    params: {
      aliases: ["-w"],
      banner: "WORKLOAD_NAME",
      desc: "Workload name",
      type: :string,
      required: required
    }
  }
end

Instance Method Details

#args_join(args) ⇒ Object

NOTE: use simplified variant atm, as shelljoin do different escaping TODO: most probably need better logic for escaping various quotes



460
461
462
# File 'lib/command/base.rb', line 460

def args_join(args)
  args.join(" ")
end

#cpObject



509
510
511
# File 'lib/command/base.rb', line 509

def cp
  @cp ||= Controlplane.new(config)
end

#ensure_docker_running!Object



513
514
515
516
517
518
# File 'lib/command/base.rb', line 513

def ensure_docker_running!
  result = Shell.cmd("docker", "version", capture_stderr: true)
  return if result[:success]

  raise "Can't run Docker. Please make sure that it's installed and started, then try again."
end

#progressObject



464
465
466
# File 'lib/command/base.rb', line 464

def progress
  $stderr
end

#run_command_in_latest_image(command, title:) ⇒ Object



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/command/base.rb', line 520

def run_command_in_latest_image(command, title:)
  # Need to prefix the command with '.controlplane/'
  # if it's a file in the '.controlplane' directory,
  # for backwards compatibility
  path = Pathname.new("#{config.app_cpln_dir}/#{command}").expand_path
  command = ".controlplane/#{command}" if File.exist?(path)

  progress.puts("Running #{title}...\n\n")

  begin
    Cpl::Cli.start(["run", "-a", config.app, "--image", "latest", "--", command])
  rescue SystemExit => e
    progress.puts

    raise "Failed to run #{title}." if e.status.nonzero?

    progress.puts("Finished running #{title}.\n\n")
  end
end

#step(message, abort_on_error: true, retry_on_failure: false) ⇒ Object

rubocop:disable Metrics/MethodLength



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/command/base.rb', line 486

def step(message, abort_on_error: true, retry_on_failure: false) # rubocop:disable Metrics/MethodLength
  progress.print("#{message}...")

  Shell.use_tmp_stderr do
    success = false

    begin
      if retry_on_failure
        until (success = yield)
          progress.print(".")
          Kernel.sleep(1)
        end
      else
        success = yield
      end
    rescue RuntimeError => e
      step_error(e, abort_on_error: abort_on_error)
    end

    step_finish(success)
  end
end

#step_error(error, abort_on_error: true) ⇒ Object



468
469
470
471
472
473
474
475
476
# File 'lib/command/base.rb', line 468

def step_error(error, abort_on_error: true)
  message = error.message
  if abort_on_error
    progress.puts(" #{Shell.color('failed!', :red)}\n\n")
    Shell.abort(message)
  else
    Shell.write_to_tmp_stderr(message)
  end
end

#step_finish(success) ⇒ Object



478
479
480
481
482
483
484
# File 'lib/command/base.rb', line 478

def step_finish(success)
  if success
    progress.puts(" #{Shell.color('done!', :green)}")
  else
    progress.puts(" #{Shell.color('failed!', :red)}\n\n#{Shell.read_from_tmp_stderr}\n\n")
  end
end