cpl 1.1.2 → 1.2.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.
data/lib/command/info.rb CHANGED
@@ -4,7 +4,6 @@ module Command
4
4
  class Info < Base # rubocop:disable Metrics/ClassLength
5
5
  NAME = "info"
6
6
  OPTIONS = [
7
- org_option,
8
7
  app_option
9
8
  ].freeze
10
9
  DESCRIPTION = "Displays the diff between defined/available apps/workloads (apps equal GVCs)"
@@ -17,7 +16,7 @@ module Command
17
16
  DESC
18
17
  EXAMPLES = <<~EX
19
18
  ```sh
20
- # Shows diff for all apps in all orgs.
19
+ # Shows diff for all apps in all orgs (based on `.controlplane/controlplane.yml`).
21
20
  cpl info
22
21
 
23
22
  # Shows diff for all apps in a specific org.
@@ -27,6 +26,7 @@ module Command
27
26
  cpl info -a $APP_NAME
28
27
  ```
29
28
  EX
29
+ WITH_INFO_HEADER = false
30
30
 
31
31
  def call
32
32
  @missing_apps_workloads = {}
@@ -81,15 +81,12 @@ module Command
81
81
  end
82
82
  end
83
83
 
84
- def orgs # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
84
+ def orgs # rubocop:disable Metrics/MethodLength
85
85
  result = []
86
86
 
87
- if config.options[:org]
88
- result.push(config.options[:org])
87
+ if config.org
88
+ result.push(config.org)
89
89
  else
90
- org_from_env = ENV.fetch("CPLN_ORG", nil)
91
- result.push(org_from_env) if org_from_env
92
-
93
90
  config.apps.each do |app_name, app_options|
94
91
  next if config.app && !app_matches?(config.app, app_name, app_options)
95
92
 
@@ -10,6 +10,7 @@ module Command
10
10
  LONG_DESCRIPTION = <<~DESC
11
11
  - Displays the latest image name
12
12
  DESC
13
+ WITH_INFO_HEADER = false
13
14
 
14
15
  def call
15
16
  puts latest_image
@@ -14,6 +14,7 @@ module Command
14
14
  - Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
15
15
  - Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
16
16
  DESC
17
+ WITH_INFO_HEADER = false
17
18
 
18
19
  def call # rubocop:disable Metrics/MethodLength
19
20
  one_off_workload = config[:one_off_workload]
@@ -9,6 +9,7 @@ module Command
9
9
  - Called when no command was specified
10
10
  DESC
11
11
  HIDE = true
12
+ WITH_INFO_HEADER = false
12
13
 
13
14
  def call
14
15
  if config.options[:version]
data/lib/command/ps.rb CHANGED
@@ -5,6 +5,7 @@ module Command
5
5
  NAME = "ps"
6
6
  OPTIONS = [
7
7
  app_option(required: true),
8
+ location_option,
8
9
  workload_option
9
10
  ].freeze
10
11
  DESCRIPTION = "Shows running replicas in app"
@@ -20,16 +21,19 @@ module Command
20
21
  cpl ps -a $APP_NAME -w $WORKLOAD_NAME
21
22
  ```
22
23
  EX
24
+ WITH_INFO_HEADER = false
23
25
 
24
26
  def call
25
27
  cp.fetch_gvc!
26
28
 
29
+ location = config.location
30
+
27
31
  workloads = [config.options[:workload]] if config.options[:workload]
28
32
  workloads ||= config[:app_workloads] + config[:additional_workloads]
29
33
  workloads.each do |workload|
30
34
  cp.fetch_workload!(workload)
31
35
 
32
- result = cp.workload_get_replicas(workload, location: config[:default_location])
36
+ result = cp.workload_get_replicas(workload, location: location)
33
37
  result["items"].each { |replica| puts replica }
34
38
  end
35
39
  end
data/lib/command/run.rb CHANGED
@@ -10,6 +10,7 @@ module Command
10
10
  app_option(required: true),
11
11
  image_option,
12
12
  workload_option,
13
+ location_option,
13
14
  use_local_token_option,
14
15
  terminal_size_option
15
16
  ].freeze
@@ -17,7 +18,6 @@ module Command
17
18
  LONG_DESCRIPTION = <<~DESC
18
19
  - Runs one-off **_interactive_** replicas (analog of `heroku run`)
19
20
  - Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
20
- - May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
21
21
  - If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file, the remote terminal size will be fixed to match the local terminal size (may also be overriden through `--terminal-size`)
22
22
 
23
23
  > **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
@@ -57,7 +57,7 @@ module Command
57
57
  attr_reader :location, :workload, :one_off, :container
58
58
 
59
59
  def call # rubocop:disable Metrics/MethodLength
60
- @location = config[:default_location]
60
+ @location = config.location
61
61
  @workload = config.options["workload"] || config[:one_off_workload]
62
62
  @one_off = "#{workload}-run-#{rand(1000..9999)}"
63
63
 
@@ -9,6 +9,7 @@ module Command
9
9
  app_option(required: true),
10
10
  image_option,
11
11
  workload_option,
12
+ location_option,
12
13
  use_local_token_option
13
14
  ].freeze
14
15
  DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
@@ -47,7 +48,7 @@ module Command
47
48
  attr_reader :location, :workload, :one_off, :container
48
49
 
49
50
  def call # rubocop:disable Metrics/MethodLength
50
- @location = config[:default_location]
51
+ @location = config.location
51
52
  @workload = config.options["workload"] || config[:one_off_workload]
52
53
  @one_off = "#{workload}-runner-#{rand(1000..9999)}"
53
54
 
@@ -9,12 +9,12 @@ module Command
9
9
  DESCRIPTION = "Creates an app and all its workloads"
10
10
  LONG_DESCRIPTION = <<~DESC
11
11
  - Creates an app and all its workloads
12
- - Specify the templates for the app and workloads through `setup` in the `.controlplane/controlplane.yml` file
13
- - This should should only be used for temporary apps like review apps, never for persistent apps like production (to update workloads for those, use 'cpl apply-template' instead)
12
+ - Specify the templates for the app and workloads through `setup_app_templates` in the `.controlplane/controlplane.yml` file
13
+ - This should only be used for temporary apps like review apps, never for persistent apps like production (to update workloads for those, use 'cpl apply-template' instead)
14
14
  DESC
15
15
 
16
16
  def call
17
- templates = config[:setup]
17
+ templates = config[:setup_app_templates]
18
18
 
19
19
  app = cp.fetch_gvc
20
20
  if app
@@ -8,6 +8,7 @@ module Command
8
8
  - Displays the current version of the CLI
9
9
  - Can also be done with `cpl --version` or `cpl -v`
10
10
  DESC
11
+ WITH_INFO_HEADER = false
11
12
 
12
13
  def call
13
14
  puts Cpl::VERSION
data/lib/core/config.rb CHANGED
@@ -1,24 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "helpers"
4
+
3
5
  class Config # rubocop:disable Metrics/ClassLength
4
- attr_reader :config, :current,
5
- :org, :org_comes_from_env, :app, :apps, :app_dir,
6
+ attr_reader :org_comes_from_env, :app_comes_from_env,
6
7
  # command line options
7
- :args, :options
8
+ :args, :options, :required_options
9
+
10
+ include Helpers
8
11
 
9
- CONFIG_FILE_LOCATIION = ".controlplane/controlplane.yml"
12
+ CONFIG_FILE_LOCATION = ".controlplane/controlplane.yml"
10
13
 
11
- def initialize(args, options)
14
+ def initialize(args, options, required_options)
12
15
  @args = args
13
16
  @options = options
14
- @org = options[:org]
15
- @org_comes_from_env = false
16
- @app = options[:app]
17
+ @required_options = required_options
17
18
 
18
- load_app_config
19
- load_apps
19
+ ensure_required_options!
20
20
 
21
21
  Shell.verbose_mode(options[:verbose])
22
+ trace_mode = options[:trace]
23
+ return unless trace_mode
24
+
25
+ ControlplaneApiDirect.trace = trace_mode
26
+ Shell.warn("Trace mode is enabled, this will print sensitive information to the console.")
27
+ end
28
+
29
+ def org
30
+ @org ||= load_org_from_options || load_org_from_env || load_org_from_file
31
+ end
32
+
33
+ def app
34
+ @app ||= load_app_from_options || load_app_from_env
35
+ end
36
+
37
+ def location
38
+ @location ||= load_location_from_options || load_location_from_env || load_location_from_file
22
39
  end
23
40
 
24
41
  def [](key)
@@ -41,91 +58,122 @@ class Config # rubocop:disable Metrics/ClassLength
41
58
  apps[app_name.to_sym]&.dig(:match_if_app_name_starts_with) || false
42
59
  end
43
60
 
44
- private
45
-
46
- def ensure_current_config!
47
- raise "Can't find current config, please specify an app." unless current
61
+ def app_dir
62
+ Pathname.new(config_file_path).parent.parent.to_s
48
63
  end
49
64
 
50
- def ensure_current_config_app!(app_name)
51
- raise "Can't find app '#{app_name}' in 'controlplane.yml'." unless current
65
+ def config
66
+ @config ||= begin
67
+ global_config = YAML.safe_load_file(config_file_path, symbolize_names: true, aliases: true)
68
+ ensure_config!(global_config)
69
+ ensure_config_apps!(global_config)
70
+
71
+ global_config
72
+ end
52
73
  end
53
74
 
54
- def ensure_current_config_org!(app_name)
55
- return if @org
75
+ def apps
76
+ @apps ||= config[:apps].to_h do |app_name, app_options|
77
+ ensure_config_app!(app_name, app_options)
56
78
 
57
- raise "Can't find option 'cpln_org' for app '#{app_name}' in 'controlplane.yml', " \
58
- "and CPLN_ORG env var is not set."
79
+ app_options_with_new_keys = app_options.to_h do |key, value|
80
+ new_key = new_option_keys[key]
81
+ new_key ? [new_key, value] : [key, value]
82
+ end
83
+
84
+ [app_name, app_options_with_new_keys]
85
+ end
59
86
  end
60
87
 
61
- def ensure_config!
62
- raise "'controlplane.yml' is empty." unless config
88
+ def current
89
+ return unless app
90
+
91
+ @current ||= begin
92
+ app_config = find_app_config(app)
93
+ ensure_config_app!(app, app_config)
94
+
95
+ warn_deprecated_options(app_config)
96
+
97
+ app_config
98
+ end
63
99
  end
64
100
 
65
- def ensure_config_apps!
66
- raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
101
+ private
102
+
103
+ def ensure_current_config!
104
+ raise "Can't find current config, please specify an app." unless current
67
105
  end
68
106
 
69
- def ensure_config_app!(app_name, app_options)
70
- raise "App '#{app_name}' is empty in 'controlplane.yml'." unless app_options
107
+ def ensure_config!(global_config)
108
+ raise "'controlplane.yml' is empty." unless global_config
71
109
  end
72
110
 
73
- def app_matches_current?(app_name, app_options)
74
- app && (app_name.to_s == app || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s)))
111
+ def ensure_config_apps!(global_config)
112
+ raise "Can't find key 'apps' in 'controlplane.yml'." unless global_config[:apps]
75
113
  end
76
114
 
77
- def pick_current_config(app_name, app_options)
78
- @current = app_options
79
- ensure_current_config_app!(app_name)
115
+ def ensure_config_app!(app_name, app_options)
116
+ raise "Can't find config for app '#{app_name}' in 'controlplane.yml'." unless app_options
117
+ end
80
118
 
81
- if current.key?(:cpln_org)
82
- @org = current.fetch(:cpln_org)
83
- else
84
- @org = ENV.fetch("CPLN_ORG", nil)
85
- @org_comes_from_env = true
86
- end
87
- ensure_current_config_org!(app_name)
119
+ def app_matches?(app_name1, app_name2, app_options)
120
+ app_name1 && app_name2 &&
121
+ (app_name1.to_s == app_name2.to_s ||
122
+ (app_options[:match_if_app_name_starts_with] && app_name1.to_s.start_with?(app_name2.to_s))
123
+ )
88
124
  end
89
125
 
90
- def load_apps # rubocop:disable Metrics/MethodLength
91
- @apps = config[:apps].to_h do |app_name, app_options|
92
- ensure_config_app!(app_name, app_options)
126
+ def find_app_config(app_name1)
127
+ @app_configs ||= {}
128
+ @app_configs[app_name1] ||= apps.find do |app_name2, app_config|
129
+ app_matches?(app_name1, app_name2, app_config)
130
+ end&.last
131
+ end
93
132
 
94
- app_options_with_new_keys = app_options.to_h do |key, value|
95
- new_key = new_option_keys[key]
96
- new_key ? [new_key, value] : [key, value]
97
- end
133
+ def ensure_app!
134
+ return if app
98
135
 
99
- if app_matches_current?(app_name, app_options_with_new_keys)
100
- pick_current_config(app_name, app_options_with_new_keys)
101
- warn_deprecated_options(app_options)
102
- end
136
+ raise "No app provided. " \
137
+ "The app can be provided either through the CPLN_APP env var " \
138
+ "('allow_app_override_by_env' must be set to true in 'controlplane.yml'), " \
139
+ "or the --app command option."
140
+ end
103
141
 
104
- [app_name, app_options_with_new_keys]
105
- end
142
+ def ensure_org!
143
+ return if org
106
144
 
107
- ensure_current_config_app!(app) if app
145
+ raise "No org provided. " \
146
+ "The org can be provided either through the CPLN_ORG env var " \
147
+ "('allow_org_override_by_env' must be set to true in 'controlplane.yml'), " \
148
+ "the --org command option, " \
149
+ "or the 'cpln_org' key in 'controlplane.yml'."
108
150
  end
109
151
 
110
- def load_app_config
111
- config_file = find_app_config_file
112
- @config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
113
- @app_dir = Pathname.new(config_file).parent.parent.to_s
114
- ensure_config!
115
- ensure_config_apps!
152
+ def ensure_required_options! # rubocop:disable Metrics/CyclomaticComplexity
153
+ ensure_app! if required_options.include?(:app)
154
+ ensure_org! if required_options.include?(:org) || app
155
+
156
+ missing_str = required_options
157
+ .reject { |option_name| %i[org app].include?(option_name) || options.key?(option_name) }
158
+ .map { |option_name| "--#{option_name}" }
159
+ .join(", ")
160
+
161
+ raise "Required options missing: #{missing_str}" unless missing_str.empty?
116
162
  end
117
163
 
118
- def find_app_config_file
119
- path = Pathname.new(".").expand_path
164
+ def config_file_path # rubocop:disable Metrics/MethodLength
165
+ @config_file_path ||= begin
166
+ path = Pathname.new(".").expand_path
120
167
 
121
- loop do
122
- config_file = path + CONFIG_FILE_LOCATIION
123
- break config_file if File.file?(config_file)
168
+ loop do
169
+ config_file = path + CONFIG_FILE_LOCATION
170
+ break config_file if File.file?(config_file)
124
171
 
125
- path = path.parent
172
+ path = path.parent
126
173
 
127
- if path.root?
128
- raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it."
174
+ if path.root?
175
+ raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATION}', please create it."
176
+ end
129
177
  end
130
178
  end
131
179
  end
@@ -135,10 +183,76 @@ class Config # rubocop:disable Metrics/ClassLength
135
183
  org: :cpln_org,
136
184
  location: :default_location,
137
185
  prefix: :match_if_app_name_starts_with,
186
+ setup: :setup_app_templates,
138
187
  old_image_retention_days: :image_retention_days
139
188
  }
140
189
  end
141
190
 
191
+ def load_app_from_env
192
+ app_from_env = strip_str_and_validate(ENV.fetch("CPLN_APP", nil))
193
+ return unless app_from_env
194
+
195
+ app_config = find_app_config(app_from_env)
196
+ ensure_config_app!(app_from_env, app_config)
197
+
198
+ key_exists = app_config.key?(:allow_app_override_by_env)
199
+ allowed_locally = key_exists && app_config[:allow_app_override_by_env]
200
+ allowed_globally = !key_exists && config[:allow_app_override_by_env]
201
+ return unless allowed_locally || allowed_globally
202
+
203
+ @app_comes_from_env = true
204
+
205
+ app_from_env
206
+ end
207
+
208
+ def load_app_from_options
209
+ app_from_options = strip_str_and_validate(options[:app])
210
+ return unless app_from_options
211
+
212
+ app_config = find_app_config(app_from_options)
213
+ ensure_config_app!(app_from_options, app_config)
214
+
215
+ app_from_options
216
+ end
217
+
218
+ def load_org_from_env
219
+ org_from_env = strip_str_and_validate(ENV.fetch("CPLN_ORG", nil))
220
+ return unless org_from_env
221
+
222
+ key_exists = current&.key?(:allow_org_override_by_env)
223
+ allowed_locally = key_exists && current[:allow_org_override_by_env]
224
+ allowed_globally = !key_exists && config[:allow_org_override_by_env]
225
+ return unless allowed_locally || allowed_globally
226
+
227
+ @org_comes_from_env = true
228
+
229
+ org_from_env
230
+ end
231
+
232
+ def load_org_from_options
233
+ strip_str_and_validate(options[:org])
234
+ end
235
+
236
+ def load_org_from_file
237
+ return unless current&.key?(:cpln_org)
238
+
239
+ strip_str_and_validate(current[:cpln_org])
240
+ end
241
+
242
+ def load_location_from_options
243
+ strip_str_and_validate(options[:location])
244
+ end
245
+
246
+ def load_location_from_env
247
+ strip_str_and_validate(ENV.fetch("CPLN_LOCATION", nil))
248
+ end
249
+
250
+ def load_location_from_file
251
+ return unless current&.key?(:default_location)
252
+
253
+ strip_str_and_validate(current.fetch(:default_location))
254
+ end
255
+
142
256
  def warn_deprecated_options(app_options)
143
257
  deprecated_option_keys = new_option_keys.select { |old_key| app_options.key?(old_key) }
144
258
  return if deprecated_option_keys.empty?
@@ -23,9 +23,10 @@ class Controlplane # rubocop:disable Metrics/ClassLength
23
23
  end
24
24
 
25
25
  def profile_create(profile, token)
26
+ sensitive_data_pattern = /(?<=--token )(\S+)/
26
27
  cmd = "cpln profile create #{profile} --token #{token}"
27
28
  cmd += " > /dev/null" if Shell.should_hide_output?
28
- perform!(cmd)
29
+ perform!(cmd, sensitive_data_pattern: sensitive_data_pattern)
29
30
  end
30
31
 
31
32
  def profile_delete(profile)
@@ -47,6 +48,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
47
48
  # https://docs.controlplane.com/guides/push-image#step-2
48
49
  # Might need to use `docker buildx build` if compatiblitity issues arise
49
50
  cmd = "docker build --platform=linux/amd64 -t #{image} -f #{dockerfile}"
51
+ cmd += " --progress=plain" if ControlplaneApiDirect.trace
50
52
 
51
53
  build_args.each { |build_arg| cmd += " --build-arg #{build_arg}" }
52
54
  cmd += " #{config.app_dir}"
@@ -234,6 +236,16 @@ class Controlplane # rubocop:disable Metrics/ClassLength
234
236
  perform!(cmd)
235
237
  end
236
238
 
239
+ # volumeset
240
+
241
+ def fetch_volumesets(a_gvc = gvc)
242
+ api.list_volumesets(org: org, gvc: a_gvc)
243
+ end
244
+
245
+ def delete_volumeset(volumeset, a_gvc = gvc)
246
+ api.delete_volumeset(org: org, gvc: a_gvc, volumeset: volumeset)
247
+ end
248
+
237
249
  # domain
238
250
 
239
251
  def find_domain_route(data)
@@ -346,8 +358,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
346
358
  system(cmd)
347
359
  end
348
360
 
349
- def perform!(cmd)
350
- Shell.debug("CMD", cmd)
361
+ def perform!(cmd, sensitive_data_pattern: nil)
362
+ Shell.debug("CMD", cmd, sensitive_data_pattern: sensitive_data_pattern)
351
363
 
352
364
  system(cmd) || exit(false)
353
365
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ControlplaneApi
3
+ class ControlplaneApi # rubocop:disable Metrics/ClassLength
4
4
  def gvc_list(org:)
5
5
  api_json("/org/#{org}/gvc", method: :get)
6
6
  end
@@ -86,6 +86,14 @@ class ControlplaneApi
86
86
  api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :delete)
87
87
  end
88
88
 
89
+ def list_volumesets(org:, gvc:)
90
+ api_json("/org/#{org}/gvc/#{gvc}/volumeset", method: :get)
91
+ end
92
+
93
+ def delete_volumeset(org:, gvc:, volumeset:)
94
+ api_json("/org/#{org}/gvc/#{gvc}/volumeset/#{volumeset}", method: :delete)
95
+ end
96
+
89
97
  def list_domains(org:)
90
98
  api_json("/org/#{org}/domain", method: :get)
91
99
  end
@@ -17,7 +17,12 @@ class ControlplaneApiDirect
17
17
 
18
18
  API_TOKEN_REGEX = /^[\w\-._]+$/.freeze
19
19
 
20
- def call(url, method:, host: :api, body: nil) # rubocop:disable Metrics/MethodLength
20
+ class << self
21
+ attr_accessor :trace
22
+ end
23
+
24
+ def call(url, method:, host: :api, body: nil) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
25
+ trace = ControlplaneApiDirect.trace
21
26
  uri = URI("#{api_host(host)}#{url}")
22
27
  request = API_METHODS[method].new(uri)
23
28
  request["Content-Type"] = "application/json"
@@ -26,7 +31,11 @@ class ControlplaneApiDirect
26
31
 
27
32
  Shell.debug(method.upcase, "#{uri} #{body&.to_json}")
28
33
 
29
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) }
34
+ http = Net::HTTP.new(uri.hostname, uri.port)
35
+ http.use_ssl = uri.scheme == "https"
36
+ http.set_debug_output($stdout) if trace
37
+
38
+ response = http.start { |ht| ht.request(request) }
30
39
 
31
40
  case response
32
41
  when Net::HTTPOK
@@ -35,6 +44,9 @@ class ControlplaneApiDirect
35
44
  true
36
45
  when Net::HTTPNotFound
37
46
  nil
47
+ when Net::HTTPForbidden
48
+ org = self.class.parse_org(url)
49
+ raise("Double check your org #{org}. #{response} #{response.body}")
38
50
  else
39
51
  raise("#{response} #{response.body}")
40
52
  end
@@ -65,4 +77,8 @@ class ControlplaneApiDirect
65
77
  remove_class_variable(:@@api_token) if defined?(@@api_token)
66
78
  end
67
79
  # rubocop:enable Style/ClassVars
80
+
81
+ def self.parse_org(url)
82
+ url.match(%r{^/org/([^/]+)})[1]
83
+ end
68
84
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Helpers
4
+ def strip_str_and_validate(str)
5
+ return str if str.nil?
6
+
7
+ str = str.strip
8
+ str.empty? ? nil : str
9
+ end
10
+ end
data/lib/core/shell.rb CHANGED
@@ -36,7 +36,7 @@ class Shell
36
36
  end
37
37
 
38
38
  def self.confirm(message)
39
- shell.yes?("#{message} (y/n)")
39
+ shell.yes?("#{message} (y/N)")
40
40
  end
41
41
 
42
42
  def self.warn(message)
@@ -55,11 +55,33 @@ class Shell
55
55
  @verbose = verbose
56
56
  end
57
57
 
58
- def self.debug(prefix, message)
59
- stderr.puts("\n[#{color(prefix, :red)}] #{message}") if verbose
58
+ def self.debug(prefix, message, sensitive_data_pattern: nil)
59
+ return unless verbose
60
+
61
+ filtered_message = hide_sensitive_data(message, sensitive_data_pattern)
62
+ stderr.puts("\n[#{color(prefix, :red)}] #{filtered_message}")
60
63
  end
61
64
 
62
65
  def self.should_hide_output?
63
66
  tmp_stderr && !verbose
64
67
  end
68
+
69
+ #
70
+ # Hide sensitive data based on the passed pattern
71
+ #
72
+ # @param [String] message
73
+ # The message to get processed.
74
+ # @param [Regexp, nil] pattern
75
+ # The regular expression to be used. If not provided, no filter gets applied.
76
+ #
77
+ # @return [String]
78
+ # Filtered message.
79
+ #
80
+ # @example
81
+ # hide_sensitive_data("--token abcd", /(?<=--token )(\S+)/)
82
+ def self.hide_sensitive_data(message, pattern = nil)
83
+ return message unless pattern.is_a?(Regexp)
84
+
85
+ message.gsub(pattern, "XXXXXXX")
86
+ end
65
87
  end
data/lib/cpl/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "1.1.2"
4
+ VERSION = "1.2.0"
5
5
  MIN_CPLN_VERSION = "0.0.71"
6
6
  end