cpl 0.4.1 → 0.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.overcommit.yml +10 -0
  3. data/Gemfile.lock +10 -3
  4. data/README.md +6 -0
  5. data/cpl.gemspec +1 -0
  6. data/docs/commands.md +51 -3
  7. data/googlee2da545df05d92f9.html +1 -0
  8. data/lib/command/base.rb +65 -7
  9. data/lib/command/build_image.rb +6 -5
  10. data/lib/command/cleanup_old_images.rb +8 -7
  11. data/lib/command/cleanup_stale_apps.rb +11 -9
  12. data/lib/command/config.rb +30 -15
  13. data/lib/command/copy_image_from_upstream.rb +110 -0
  14. data/lib/command/delete.rb +10 -12
  15. data/lib/command/deploy_image.rb +6 -5
  16. data/lib/command/env.rb +2 -2
  17. data/lib/command/exists.rb +4 -4
  18. data/lib/command/info.rb +233 -0
  19. data/lib/command/latest_image.rb +2 -2
  20. data/lib/command/logs.rb +4 -4
  21. data/lib/command/no_command.rb +3 -3
  22. data/lib/command/open.rb +4 -4
  23. data/lib/command/promote_app_from_upstream.rb +58 -0
  24. data/lib/command/ps.rb +10 -13
  25. data/lib/command/ps_restart.rb +9 -6
  26. data/lib/command/ps_start.rb +7 -6
  27. data/lib/command/ps_stop.rb +7 -6
  28. data/lib/command/run.rb +5 -5
  29. data/lib/command/run_detached.rb +7 -5
  30. data/lib/command/setup.rb +71 -13
  31. data/lib/command/test.rb +2 -2
  32. data/lib/command/version.rb +2 -2
  33. data/lib/core/config.rb +26 -19
  34. data/lib/core/controlplane.rb +77 -11
  35. data/lib/core/controlplane_api.rb +12 -0
  36. data/lib/core/controlplane_api_cli.rb +1 -1
  37. data/lib/core/controlplane_api_direct.rb +2 -2
  38. data/lib/core/shell.rb +25 -3
  39. data/lib/cpl/version.rb +1 -1
  40. data/lib/cpl.rb +19 -10
  41. data/lib/deprecated_commands.json +6 -0
  42. data/script/add_command +37 -0
  43. data/script/generate_commands_docs +5 -5
  44. data/script/rename_command +43 -0
  45. metadata +24 -2
data/lib/command/setup.rb CHANGED
@@ -9,7 +9,7 @@ module Command
9
9
  app_option(required: true)
10
10
  ].freeze
11
11
  DESCRIPTION = "Applies application-specific configs from templates"
12
- LONG_DESCRIPTION = <<~HEREDOC
12
+ LONG_DESCRIPTION = <<~DESC
13
13
  - Applies application-specific configs from templates (e.g., for every review-app)
14
14
  - Publishes (creates or updates) those at Control Plane infrastructure
15
15
  - Picks templates from the `.controlplane/templates` directory
@@ -23,8 +23,8 @@ module Command
23
23
  APP_ORG - organization
24
24
  APP_IMAGE - will use latest app image
25
25
  ```
26
- HEREDOC
27
- EXAMPLES = <<~HEREDOC
26
+ DESC
27
+ EXAMPLES = <<~EX
28
28
  ```sh
29
29
  # Applies single template.
30
30
  cpl setup redis -a $APP_NAME
@@ -32,31 +32,89 @@ module Command
32
32
  # Applies several templates (practically creating full app).
33
33
  cpl setup gvc postgres redis rails -a $APP_NAME
34
34
  ```
35
- HEREDOC
35
+ EX
36
+
37
+ def call # rubocop:disable Metrics/MethodLength
38
+ @app_status = :existing
39
+ @created_workloads = []
40
+ @failed_workloads = []
36
41
 
37
- def call
38
42
  config.args.each do |template|
39
43
  filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
40
- ensure_template!(template, filename)
41
- apply_template(filename)
42
- progress.puts(template)
44
+
45
+ step("Applying template '#{template}'", abort_on_error: false) do
46
+ unless File.exist?(filename)
47
+ report_failure(template)
48
+
49
+ raise "Can't find template '#{template}' at '#{filename}', please create it."
50
+ end
51
+
52
+ apply_template(filename)
53
+ if $CHILD_STATUS.success?
54
+ report_success(template)
55
+ else
56
+ report_failure(template)
57
+ end
58
+
59
+ $CHILD_STATUS.success?
60
+ end
43
61
  end
62
+
63
+ print_app_status
64
+ print_created_workloads
65
+ print_failed_workloads
44
66
  end
45
67
 
46
68
  private
47
69
 
48
- def ensure_template!(template, filename)
49
- Shell.abort("Can't find template '#{template}' at '#{filename}', please create it.") unless File.exist?(filename)
50
- end
51
-
52
70
  def apply_template(filename)
53
71
  data = File.read(filename)
54
72
  .gsub("APP_GVC", config.app)
55
73
  .gsub("APP_LOCATION", config[:default_location])
56
- .gsub("APP_ORG", config[:cpln_org])
74
+ .gsub("APP_ORG", config.org)
57
75
  .gsub("APP_IMAGE", latest_image)
58
76
 
59
77
  cp.apply(YAML.safe_load(data))
60
78
  end
79
+
80
+ def report_success(template)
81
+ if template == "gvc"
82
+ @app_status = :success
83
+ else
84
+ @created_workloads.push(template)
85
+ end
86
+ end
87
+
88
+ def report_failure(template)
89
+ if template == "gvc"
90
+ @app_status = :failure
91
+ else
92
+ @failed_workloads.push(template)
93
+ end
94
+ end
95
+
96
+ def print_app_status
97
+ return if @app_status == :existing
98
+
99
+ if @app_status == :success
100
+ progress.puts("\n#{Shell.color("Created app '#{config.app}'.", :green)}")
101
+ else
102
+ progress.puts("\n#{Shell.color("Failed to create app '#{config.app}'.", :red)}")
103
+ end
104
+ end
105
+
106
+ def print_created_workloads
107
+ return unless @created_workloads.any?
108
+
109
+ workloads = @created_workloads.map { |template| " - #{template}" }.join("\n")
110
+ progress.puts("\n#{Shell.color('Created workloads:', :green)}\n#{workloads}")
111
+ end
112
+
113
+ def print_failed_workloads
114
+ return unless @failed_workloads.any?
115
+
116
+ workloads = @failed_workloads.map { |template| " - #{template}" }.join("\n")
117
+ progress.puts("\n#{Shell.color('Failed to create workloads:', :red)}\n#{workloads}")
118
+ end
61
119
  end
62
120
  end
data/lib/command/test.rb CHANGED
@@ -8,9 +8,9 @@ module Command
8
8
  NAME = "test"
9
9
  OPTIONS = all_options
10
10
  DESCRIPTION = "For debugging purposes"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - For debugging purposes
13
- HEREDOC
13
+ DESC
14
14
  HIDE = true
15
15
 
16
16
  def call
@@ -4,10 +4,10 @@ module Command
4
4
  class Version < Base
5
5
  NAME = "version"
6
6
  DESCRIPTION = "Displays the current version of the CLI"
7
- LONG_DESCRIPTION = <<~HEREDOC
7
+ LONG_DESCRIPTION = <<~DESC
8
8
  - Displays the current version of the CLI
9
9
  - Can also be done with `cpl --version` or `cpl -v`
10
- HEREDOC
10
+ DESC
11
11
 
12
12
  def call
13
13
  puts Cpl::VERSION
data/lib/core/config.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Config
4
4
  attr_reader :config, :current,
5
- :app, :app_dir,
5
+ :org, :app, :apps, :app_dir,
6
6
  # command line options
7
7
  :args, :options
8
8
 
@@ -11,9 +11,13 @@ class Config
11
11
  def initialize(args, options)
12
12
  @args = args
13
13
  @options = options
14
+ @org = options[:org]
14
15
  @app = options[:app]
15
16
 
16
17
  load_app_config
18
+
19
+ @apps = config[:apps]
20
+
17
21
  pick_current_config if app
18
22
  warn_deprecated_options if current
19
23
  end
@@ -27,7 +31,7 @@ class Config
27
31
  elsif old_key && current.key?(old_key)
28
32
  current.fetch(old_key)
29
33
  else
30
- Shell.abort("Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'.")
34
+ raise "Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'."
31
35
  end
32
36
  end
33
37
 
@@ -42,34 +46,33 @@ class Config
42
46
  private
43
47
 
44
48
  def ensure_current_config!
45
- Shell.abort("Can't find current config, please specify an app.") unless current
49
+ raise "Can't find current config, please specify an app." unless current
46
50
  end
47
51
 
48
52
  def ensure_current_config_app!(app)
49
- Shell.abort("Can't find app '#{app}' in 'controlplane.yml'.") unless current
53
+ raise "Can't find app '#{app}' in 'controlplane.yml'." unless current
50
54
  end
51
55
 
52
56
  def ensure_config!
53
- Shell.abort("'controlplane.yml' is empty.") unless config
57
+ raise "'controlplane.yml' is empty." unless config
54
58
  end
55
59
 
56
60
  def ensure_config_apps!
57
- Shell.abort("Can't find key 'apps' in 'controlplane.yml'.") unless config[:apps]
61
+ raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
58
62
  end
59
63
 
60
64
  def ensure_config_app!(app, options)
61
- Shell.abort("App '#{app}' is empty in 'controlplane.yml'.") unless options
65
+ raise "App '#{app}' is empty in 'controlplane.yml'." unless options
62
66
  end
63
67
 
64
68
  def pick_current_config
65
- ensure_config!
66
- ensure_config_apps!
67
69
  config[:apps].each do |c_app, c_data|
68
70
  ensure_config_app!(c_app, c_data)
69
- if c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
70
- @current = c_data
71
- break
72
- end
71
+ next unless c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
72
+
73
+ @current = c_data
74
+ @org = self[:cpln_org]
75
+ break
73
76
  end
74
77
  ensure_current_config_app!(app)
75
78
  end
@@ -78,6 +81,8 @@ class Config
78
81
  config_file = find_app_config_file
79
82
  @config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
80
83
  @app_dir = Pathname.new(config_file).parent.parent.to_s
84
+ ensure_config!
85
+ ensure_config_apps!
81
86
  end
82
87
 
83
88
  def find_app_config_file
@@ -90,7 +95,7 @@ class Config
90
95
  path = path.parent
91
96
 
92
97
  if path.root?
93
- Shell.abort("Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it.")
98
+ raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it."
94
99
  end
95
100
  end
96
101
  end
@@ -104,11 +109,13 @@ class Config
104
109
  end
105
110
 
106
111
  def warn_deprecated_options
107
- old_option_keys.each do |new_key, old_key|
108
- if current.key?(old_key)
109
- Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
110
- "please use '#{new_key}' instead (in 'controlplane.yml').")
111
- end
112
+ deprecated_option_keys = old_option_keys.filter { |_new_key, old_key| current.key?(old_key) }
113
+ return if deprecated_option_keys.empty?
114
+
115
+ deprecated_option_keys.each do |new_key, old_key|
116
+ Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
117
+ "please use '#{new_key}' instead (in 'controlplane.yml').")
112
118
  end
119
+ $stderr.puts
113
120
  end
114
121
  end
@@ -7,7 +7,30 @@ class Controlplane # rubocop:disable Metrics/ClassLength
7
7
  @config = config
8
8
  @api = ControlplaneApi.new
9
9
  @gvc = config.app
10
- @org = config[:cpln_org]
10
+ @org = config.org
11
+ end
12
+
13
+ # profile
14
+
15
+ def profile_switch(profile)
16
+ ENV["CPLN_PROFILE"] = profile
17
+ end
18
+
19
+ def profile_exists?(profile)
20
+ cmd = "cpln profile get #{profile} -o yaml"
21
+ perform_yaml(cmd).length.positive?
22
+ end
23
+
24
+ def profile_create(profile, token)
25
+ cmd = "cpln profile create #{profile} --token #{token}"
26
+ cmd += " > /dev/null" if Shell.tmp_stderr
27
+ perform!(cmd)
28
+ end
29
+
30
+ def profile_delete(profile)
31
+ cmd = "cpln profile delete #{profile}"
32
+ cmd += " > /dev/null" if Shell.tmp_stderr
33
+ perform!(cmd)
11
34
  end
12
35
 
13
36
  # image
@@ -18,8 +41,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
18
41
  perform!(cmd)
19
42
  end
20
43
 
21
- def image_query(app_name = config.app)
22
- cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{app_name}"
44
+ def image_query(app_name = config.app, org_name = config.org)
45
+ cmd = "cpln image query --org #{org_name} -o yaml --max -1 --prop repository=#{app_name}"
23
46
  perform_yaml(cmd)
24
47
  end
25
48
 
@@ -27,8 +50,36 @@ class Controlplane # rubocop:disable Metrics/ClassLength
27
50
  api.image_delete(org: org, image: image)
28
51
  end
29
52
 
53
+ def image_login(org_name = config.org)
54
+ cmd = "cpln image docker-login --org #{org_name}"
55
+ cmd += " > /dev/null 2>&1" if Shell.tmp_stderr
56
+ perform!(cmd)
57
+ end
58
+
59
+ def image_pull(image)
60
+ cmd = "docker pull #{image}"
61
+ cmd += " > /dev/null" if Shell.tmp_stderr
62
+ perform!(cmd)
63
+ end
64
+
65
+ def image_tag(old_tag, new_tag)
66
+ cmd = "docker tag #{old_tag} #{new_tag}"
67
+ cmd += " > /dev/null" if Shell.tmp_stderr
68
+ perform!(cmd)
69
+ end
70
+
71
+ def image_push(image)
72
+ cmd = "docker push #{image}"
73
+ cmd += " > /dev/null" if Shell.tmp_stderr
74
+ perform!(cmd)
75
+ end
76
+
30
77
  # gvc
31
78
 
79
+ def fetch_gvcs
80
+ api.gvc_list(org: org)
81
+ end
82
+
32
83
  def gvc_query(app_name = config.app)
33
84
  # When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
34
85
  # otherwise we query for a gvc with the exact name.
@@ -38,15 +89,15 @@ class Controlplane # rubocop:disable Metrics/ClassLength
38
89
  perform_yaml(cmd)
39
90
  end
40
91
 
41
- def fetch_gvc(a_gvc = gvc)
42
- api.gvc_get(gvc: a_gvc, org: org)
92
+ def fetch_gvc(a_gvc = gvc, a_org = org)
93
+ api.gvc_get(gvc: a_gvc, org: a_org)
43
94
  end
44
95
 
45
96
  def fetch_gvc!(a_gvc = gvc)
46
97
  gvc_data = fetch_gvc(a_gvc)
47
98
  return gvc_data if gvc_data
48
99
 
49
- Shell.abort("Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'.")
100
+ raise "Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'."
50
101
  end
51
102
 
52
103
  def gvc_delete(a_gvc = gvc)
@@ -55,6 +106,14 @@ class Controlplane # rubocop:disable Metrics/ClassLength
55
106
 
56
107
  # workload
57
108
 
109
+ def fetch_workloads(a_gvc = gvc)
110
+ api.workload_list(gvc: a_gvc, org: org)
111
+ end
112
+
113
+ def fetch_workloads_by_org(a_org = org)
114
+ api.workload_list_by_org(org: a_org)
115
+ end
116
+
58
117
  def fetch_workload(workload)
59
118
  api.workload_get(workload: workload, gvc: gvc, org: org)
60
119
  end
@@ -63,7 +122,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
63
122
  workload_data = fetch_workload(workload)
64
123
  return workload_data if workload_data
65
124
 
66
- Shell.abort("Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'.")
125
+ raise "Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'."
67
126
  end
68
127
 
69
128
  def workload_get_replicas(workload, location:)
@@ -73,7 +132,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
73
132
 
74
133
  def workload_set_image_ref(workload, container:, image:)
75
134
  cmd = "cpln workload update #{workload} #{gvc_org}"
76
- cmd += " --set spec.containers.#{container}.image=/org/#{config[:cpln_org]}/image/#{image}"
135
+ cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
136
+ cmd += " > /dev/null" if Shell.tmp_stderr
77
137
  perform!(cmd)
78
138
  end
79
139
 
@@ -85,6 +145,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
85
145
 
86
146
  def workload_force_redeployment(workload)
87
147
  cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
148
+ cmd += " > /dev/null" if Shell.tmp_stderr
88
149
  perform!(cmd)
89
150
  end
90
151
 
@@ -126,12 +187,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength
126
187
 
127
188
  # apply
128
189
 
129
- def apply(data)
190
+ def apply(data) # rubocop:disable Metrics/MethodLength
130
191
  Tempfile.create do |f|
131
192
  f.write(data.to_yaml)
132
193
  f.rewind
133
194
  cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
134
- perform!(cmd)
195
+ if Shell.tmp_stderr
196
+ cmd += " 2> #{Shell.tmp_stderr.path}"
197
+ perform(cmd)
198
+ else
199
+ perform!(cmd)
200
+ end
135
201
  end
136
202
  end
137
203
 
@@ -147,7 +213,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
147
213
 
148
214
  def perform_yaml(cmd)
149
215
  result = `#{cmd}`
150
- $?.success? ? YAML.safe_load(result) : exit(false) # rubocop:disable Style/SpecialGlobalVars
216
+ $CHILD_STATUS.success? ? YAML.safe_load(result) : exit(false)
151
217
  end
152
218
 
153
219
  def gvc_org
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ControlplaneApi
4
+ def gvc_list(org:)
5
+ api_json("/org/#{org}/gvc", method: :get)
6
+ end
7
+
4
8
  def gvc_get(org:, gvc:)
5
9
  api_json("/org/#{org}/gvc/#{gvc}", method: :get)
6
10
  end
@@ -29,6 +33,14 @@ class ControlplaneApi
29
33
  api_json_direct("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
30
34
  end
31
35
 
36
+ def workload_list(org:, gvc:)
37
+ api_json("/org/#{org}/gvc/#{gvc}/workload", method: :get)
38
+ end
39
+
40
+ def workload_list_by_org(org:)
41
+ api_json("/org/#{org}/workload", method: :get)
42
+ end
43
+
32
44
  def workload_get(org:, gvc:, workload:)
33
45
  api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :get)
34
46
  end
@@ -3,7 +3,7 @@
3
3
  class ControlplaneApiCli
4
4
  def call(url, method:)
5
5
  response = `cpln rest #{method} #{url} -o json`
6
- raise(response) unless $?.success? # rubocop:disable Style/SpecialGlobalVars
6
+ raise(response) unless $CHILD_STATUS.success?
7
7
 
8
8
  JSON.parse(response)
9
9
  end
@@ -37,7 +37,7 @@ class ControlplaneApiDirect
37
37
  @@api_token = ENV.fetch("CPLN_TOKEN", `cpln profile token`.chomp) # rubocop:disable Style/ClassVars
38
38
  return @@api_token if @@api_token.match?(API_TOKEN_REGEX)
39
39
 
40
- Shell.abort("Unknown API token format. " \
41
- "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable.")
40
+ raise "Unknown API token format. " \
41
+ "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
42
42
  end
43
43
  end
data/lib/core/shell.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Shell
4
+ class << self
5
+ attr_reader :tmp_stderr
6
+ end
7
+
4
8
  def self.shell
5
9
  @shell ||= Thor::Shell::Color.new
6
10
  end
@@ -9,6 +13,24 @@ class Shell
9
13
  @stderr ||= $stderr
10
14
  end
11
15
 
16
+ def self.use_tmp_stderr
17
+ @tmp_stderr = Tempfile.create
18
+
19
+ yield
20
+
21
+ @tmp_stderr.close
22
+ @tmp_stderr = nil
23
+ end
24
+
25
+ def self.write_to_tmp_stderr(message)
26
+ tmp_stderr.write(message)
27
+ end
28
+
29
+ def self.read_from_tmp_stderr
30
+ tmp_stderr.rewind
31
+ tmp_stderr.read.strip
32
+ end
33
+
12
34
  def self.color(message, color_key)
13
35
  shell.set_color(message, color_key)
14
36
  end
@@ -18,14 +40,14 @@ class Shell
18
40
  end
19
41
 
20
42
  def self.warn(message)
21
- stderr.puts(color("WARNING: #{message}\n", :yellow))
43
+ stderr.puts(color("WARNING: #{message}", :yellow))
22
44
  end
23
45
 
24
46
  def self.warn_deprecated(message)
25
- stderr.puts(color("DEPRECATED: #{message}\n", :yellow))
47
+ stderr.puts(color("DEPRECATED: #{message}", :yellow))
26
48
  end
27
49
 
28
50
  def self.abort(message)
29
- Kernel.abort(color("ERROR: #{message}\n", :red))
51
+ Kernel.abort(color("ERROR: #{message}", :red))
30
52
  end
31
53
  end
data/lib/cpl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/cpl.rb CHANGED
@@ -70,12 +70,17 @@ module Cpl
70
70
  end
71
71
 
72
72
  def self.deprecated_commands
73
- {
74
- build: ::Command::BuildImage,
75
- promote: ::Command::DeployImage,
76
- promote_image: ::Command::DeployImage,
77
- runner: ::Command::RunDetached
78
- }
73
+ @deprecated_commands ||= begin
74
+ deprecated_commands_file_path = "#{__dir__}/deprecated_commands.json"
75
+ deprecated_commands_data = File.binread(deprecated_commands_file_path)
76
+ deprecated_commands = JSON.parse(deprecated_commands_data)
77
+ deprecated_commands.to_h do |old_command_name, new_command_name|
78
+ file_name = new_command_name.gsub(/[^A-Za-z]/, "_")
79
+ class_name = file_name.split("_").map(&:capitalize).join
80
+
81
+ [old_command_name, Object.const_get("::Command::#{class_name}")]
82
+ end
83
+ end
79
84
  end
80
85
 
81
86
  def self.all_base_commands
@@ -113,6 +118,7 @@ module Cpl
113
118
  if deprecated
114
119
  ::Shell.warn_deprecated("Command '#{command_key}' is deprecated, " \
115
120
  "please use '#{name}' instead.")
121
+ $stderr.puts
116
122
  end
117
123
 
118
124
  args = if provided_args.length.positive?
@@ -123,13 +129,16 @@ module Cpl
123
129
 
124
130
  raise_args_error.call(args, nil) if (args.empty? && requires_args) || (!args.empty? && !requires_args)
125
131
 
126
- config = Config.new(args, options)
132
+ begin
133
+ config = Config.new(args, options)
127
134
 
128
- command_class.new(config).call
135
+ command_class.new(config).call
136
+ rescue RuntimeError => e
137
+ ::Shell.abort(e.message)
138
+ end
129
139
  end
130
140
  rescue StandardError => e
131
- logger = $stderr
132
- logger.puts("Unable to load command: #{e.message}")
141
+ ::Shell.abort("Unable to load command: #{e.message}")
133
142
  end
134
143
  end
135
144
  end
@@ -0,0 +1,6 @@
1
+ {
2
+ "build": "build-image",
3
+ "promote": "deploy-image",
4
+ "promote-image": "deploy-image",
5
+ "runner": "run:detached"
6
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ command_name = ARGV[0]&.downcase
5
+
6
+ abort("ERROR: Must provide command name.") unless command_name
7
+
8
+ file_name = command_name.gsub(/[^A-Za-z]/, "_")
9
+ file_path = "#{__dir__}/../lib/command/#{file_name}.rb"
10
+
11
+ abort("ERROR: Command '#{command_name}' already exists.") if File.exist?(file_path)
12
+
13
+ class_name = file_name.split("_").map(&:capitalize).join
14
+
15
+ file_data =
16
+ <<~DATA
17
+ # frozen_string_literal: true
18
+
19
+ module Command
20
+ class #{class_name} < Base
21
+ # See `base.rb` for other constants to add here
22
+ NAME = "#{command_name}"
23
+ OPTIONS = [
24
+ # Add options here
25
+ ].freeze
26
+ DESCRIPTION = "Add description here"
27
+ LONG_DESCRIPTION = <<~DESC
28
+ - Add long description here
29
+ DESC
30
+
31
+ def call
32
+ # Add command logic here
33
+ end
34
+ end
35
+ end
36
+ DATA
37
+ File.binwrite(file_path, file_data)
@@ -41,9 +41,9 @@ end
41
41
 
42
42
  commands_str = commands_str_arr.join("\n\n")
43
43
 
44
- commands_path = "#{__dir__}/../docs/commands.md"
45
- commands_data =
46
- <<~HEREDOC
44
+ file_path = "#{__dir__}/../docs/commands.md"
45
+ file_data =
46
+ <<~DATA
47
47
  <!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
48
48
 
49
49
  ### Common Options
@@ -58,5 +58,5 @@ commands_data =
58
58
  ### Commands
59
59
 
60
60
  #{commands_str}
61
- HEREDOC
62
- File.binwrite(commands_path, commands_data)
61
+ DATA
62
+ File.binwrite(file_path, file_data)