cpl 1.1.2.rc.0 → 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/core/config.rb CHANGED
@@ -1,21 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Config
4
- attr_reader :config, :current,
5
- :org, :app, :apps, :app_dir,
3
+ require_relative "helpers"
4
+
5
+ class Config # rubocop:disable Metrics/ClassLength
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
- @app = options[:app]
17
+ @required_options = required_options
18
+
19
+ ensure_required_options!
20
+
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
16
36
 
17
- load_app_config
18
- load_apps
37
+ def location
38
+ @location ||= load_location_from_options || load_location_from_env || load_location_from_file
19
39
  end
20
40
 
21
41
  def [](key)
@@ -38,77 +58,122 @@ class Config
38
58
  apps[app_name.to_sym]&.dig(:match_if_app_name_starts_with) || false
39
59
  end
40
60
 
61
+ def app_dir
62
+ Pathname.new(config_file_path).parent.parent.to_s
63
+ end
64
+
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
73
+ end
74
+
75
+ def apps
76
+ @apps ||= config[:apps].to_h do |app_name, app_options|
77
+ ensure_config_app!(app_name, app_options)
78
+
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
86
+ end
87
+
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
99
+ end
100
+
41
101
  private
42
102
 
43
103
  def ensure_current_config!
44
104
  raise "Can't find current config, please specify an app." unless current
45
105
  end
46
106
 
47
- def ensure_current_config_app!(app_name)
48
- raise "Can't find app '#{app_name}' in 'controlplane.yml'." unless current
107
+ def ensure_config!(global_config)
108
+ raise "'controlplane.yml' is empty." unless global_config
49
109
  end
50
110
 
51
- def ensure_config!
52
- raise "'controlplane.yml' is empty." unless config
53
- end
54
-
55
- def ensure_config_apps!
56
- raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
111
+ def ensure_config_apps!(global_config)
112
+ raise "Can't find key 'apps' in 'controlplane.yml'." unless global_config[:apps]
57
113
  end
58
114
 
59
115
  def ensure_config_app!(app_name, app_options)
60
- raise "App '#{app_name}' is empty in 'controlplane.yml'." unless app_options
116
+ raise "Can't find config for app '#{app_name}' in 'controlplane.yml'." unless app_options
61
117
  end
62
118
 
63
- def app_matches_current?(app_name, app_options)
64
- app && (app_name.to_s == app || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s)))
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
+ )
65
124
  end
66
125
 
67
- def pick_current_config(app_name, app_options)
68
- @current = app_options
69
- @org = self[:cpln_org]
70
- ensure_current_config_app!(app_name)
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
71
131
  end
72
132
 
73
- def load_apps # rubocop:disable Metrics/MethodLength
74
- @apps = config[:apps].to_h do |app_name, app_options|
75
- ensure_config_app!(app_name, app_options)
76
-
77
- app_options_with_new_keys = app_options.to_h do |key, value|
78
- new_key = new_option_keys[key]
79
- new_key ? [new_key, value] : [key, value]
80
- end
133
+ def ensure_app!
134
+ return if app
81
135
 
82
- if app_matches_current?(app_name, app_options_with_new_keys)
83
- pick_current_config(app_name, app_options_with_new_keys)
84
- warn_deprecated_options(app_options)
85
- 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
86
141
 
87
- [app_name, app_options_with_new_keys]
88
- end
142
+ def ensure_org!
143
+ return if org
89
144
 
90
- 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'."
91
150
  end
92
151
 
93
- def load_app_config
94
- config_file = find_app_config_file
95
- @config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
96
- @app_dir = Pathname.new(config_file).parent.parent.to_s
97
- ensure_config!
98
- 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?
99
162
  end
100
163
 
101
- def find_app_config_file
102
- 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
103
167
 
104
- loop do
105
- config_file = path + CONFIG_FILE_LOCATIION
106
- 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)
107
171
 
108
- path = path.parent
172
+ path = path.parent
109
173
 
110
- if path.root?
111
- 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
112
177
  end
113
178
  end
114
179
  end
@@ -118,10 +183,76 @@ class Config
118
183
  org: :cpln_org,
119
184
  location: :default_location,
120
185
  prefix: :match_if_app_name_starts_with,
186
+ setup: :setup_app_templates,
121
187
  old_image_retention_days: :image_retention_days
122
188
  }
123
189
  end
124
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
+
125
256
  def warn_deprecated_options(app_options)
126
257
  deprecated_option_keys = new_option_keys.select { |old_key| app_options.key?(old_key) }
127
258
  return if deprecated_option_keys.empty?
@@ -23,14 +23,15 @@ 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
- cmd += " > /dev/null" if Shell.tmp_stderr
28
- perform!(cmd)
28
+ cmd += " > /dev/null" if Shell.should_hide_output?
29
+ perform!(cmd, sensitive_data_pattern: sensitive_data_pattern)
29
30
  end
30
31
 
31
32
  def profile_delete(profile)
32
33
  cmd = "cpln profile delete #{profile}"
33
- cmd += " > /dev/null" if Shell.tmp_stderr
34
+ cmd += " > /dev/null" if Shell.should_hide_output?
34
35
  perform!(cmd)
35
36
  end
36
37
 
@@ -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}"
@@ -61,25 +63,25 @@ class Controlplane # rubocop:disable Metrics/ClassLength
61
63
 
62
64
  def image_login(org_name = config.org)
63
65
  cmd = "cpln image docker-login --org #{org_name}"
64
- cmd += " > /dev/null 2>&1" if Shell.tmp_stderr
66
+ cmd += " > /dev/null 2>&1" if Shell.should_hide_output?
65
67
  perform!(cmd)
66
68
  end
67
69
 
68
70
  def image_pull(image)
69
71
  cmd = "docker pull #{image}"
70
- cmd += " > /dev/null" if Shell.tmp_stderr
72
+ cmd += " > /dev/null" if Shell.should_hide_output?
71
73
  perform!(cmd)
72
74
  end
73
75
 
74
76
  def image_tag(old_tag, new_tag)
75
77
  cmd = "docker tag #{old_tag} #{new_tag}"
76
- cmd += " > /dev/null" if Shell.tmp_stderr
78
+ cmd += " > /dev/null" if Shell.should_hide_output?
77
79
  perform!(cmd)
78
80
  end
79
81
 
80
82
  def image_push(image)
81
83
  cmd = "docker push #{image}"
82
- cmd += " > /dev/null" if Shell.tmp_stderr
84
+ cmd += " > /dev/null" if Shell.should_hide_output?
83
85
  perform!(cmd)
84
86
  end
85
87
 
@@ -148,7 +150,11 @@ class Controlplane # rubocop:disable Metrics/ClassLength
148
150
  end
149
151
 
150
152
  def workload_get_replicas_safely(workload, location:)
151
- cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml 2> /dev/null"
153
+ cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
154
+ cmd += " 2> /dev/null" if Shell.should_hide_output?
155
+
156
+ Shell.debug("CMD", cmd)
157
+
152
158
  result = `#{cmd}`
153
159
  $CHILD_STATUS.success? ? YAML.safe_load(result) : nil
154
160
  end
@@ -180,7 +186,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
180
186
  def workload_set_image_ref(workload, container:, image:)
181
187
  cmd = "cpln workload update #{workload} #{gvc_org}"
182
188
  cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
183
- cmd += " > /dev/null" if Shell.tmp_stderr
189
+ cmd += " > /dev/null" if Shell.should_hide_output?
184
190
  perform!(cmd)
185
191
  end
186
192
 
@@ -208,7 +214,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
208
214
 
209
215
  def workload_force_redeployment(workload)
210
216
  cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
211
- cmd += " > /dev/null" if Shell.tmp_stderr
217
+ cmd += " > /dev/null" if Shell.should_hide_output?
212
218
  perform!(cmd)
213
219
  end
214
220
 
@@ -230,6 +236,16 @@ class Controlplane # rubocop:disable Metrics/ClassLength
230
236
  perform!(cmd)
231
237
  end
232
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
+
233
249
  # domain
234
250
 
235
251
  def find_domain_route(data)
@@ -280,41 +296,77 @@ class Controlplane # rubocop:disable Metrics/ClassLength
280
296
  Tempfile.create do |f|
281
297
  f.write(data)
282
298
  f.rewind
283
- cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
299
+ cmd = "cpln apply #{gvc_org} --file #{f.path}"
284
300
  if Shell.tmp_stderr
285
- cmd += " 2> #{Shell.tmp_stderr.path}"
286
- perform(cmd)
301
+ cmd += " 2> #{Shell.tmp_stderr.path}" if Shell.should_hide_output?
302
+
303
+ Shell.debug("CMD", cmd)
304
+
305
+ result = `#{cmd}`
306
+ $CHILD_STATUS.success? ? parse_apply_result(result) : false
287
307
  else
288
- perform!(cmd)
308
+ Shell.debug("CMD", cmd)
309
+
310
+ result = `#{cmd}`
311
+ $CHILD_STATUS.success? ? parse_apply_result(result) : exit(false)
289
312
  end
290
313
  end
291
314
  end
292
315
 
293
- def apply_hash(data) # rubocop:disable Metrics/MethodLength
294
- Tempfile.create do |f|
295
- f.write(data.to_yaml)
296
- f.rewind
297
- cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
298
- if Shell.tmp_stderr
299
- cmd += " 2> #{Shell.tmp_stderr.path}"
300
- perform(cmd)
316
+ def apply_hash(data)
317
+ apply_template(data.to_yaml)
318
+ end
319
+
320
+ def parse_apply_result(result) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
321
+ items = []
322
+
323
+ lines = result.split("\n")
324
+ lines.each do |line|
325
+ # The line can be in one of these formats:
326
+ # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging"
327
+ # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging/workload/redis"
328
+ # - "Updated gvc 'tutorial-app-test-1'"
329
+ # - "Updated workload 'redis'"
330
+ if line.start_with?("Created")
331
+ matches = line.match(%r{Created\s/org/[^/]+/gvc/([^/]+)($|(/([^/]+)/([^/]+)$))})&.captures
332
+ next unless matches
333
+
334
+ app, _, __, kind, name = matches
335
+ if kind
336
+ items.push({ kind: kind, name: name })
337
+ else
338
+ items.push({ kind: "app", name: app })
339
+ end
301
340
  else
302
- perform!(cmd)
341
+ matches = line.match(/Updated\s([^\s]+)\s'([^\s]+)'$/)&.captures
342
+ next unless matches
343
+
344
+ kind, name = matches
345
+ kind = "app" if kind == "gvc"
346
+ items.push({ kind: kind, name: name })
303
347
  end
304
348
  end
349
+
350
+ items
305
351
  end
306
352
 
307
353
  private
308
354
 
309
355
  def perform(cmd)
356
+ Shell.debug("CMD", cmd)
357
+
310
358
  system(cmd)
311
359
  end
312
360
 
313
- def perform!(cmd)
361
+ def perform!(cmd, sensitive_data_pattern: nil)
362
+ Shell.debug("CMD", cmd, sensitive_data_pattern: sensitive_data_pattern)
363
+
314
364
  system(cmd) || exit(false)
315
365
  end
316
366
 
317
367
  def perform_yaml(cmd)
368
+ Shell.debug("CMD", cmd)
369
+
318
370
  result = `#{cmd}`
319
371
  $CHILD_STATUS.success? ? YAML.safe_load(result) : exit(false)
320
372
  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,14 +17,25 @@ 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"
24
29
  request["Authorization"] = api_token
25
30
  request.body = body.to_json if body
26
31
 
27
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) }
32
+ Shell.debug(method.upcase, "#{uri} #{body&.to_json}")
33
+
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) }
28
39
 
29
40
  case response
30
41
  when Net::HTTPOK
@@ -33,6 +44,9 @@ class ControlplaneApiDirect
33
44
  true
34
45
  when Net::HTTPNotFound
35
46
  nil
47
+ when Net::HTTPForbidden
48
+ org = self.class.parse_org(url)
49
+ raise("Double check your org #{org}. #{response} #{response.body}")
36
50
  else
37
51
  raise("#{response} #{response.body}")
38
52
  end
@@ -63,4 +77,8 @@ class ControlplaneApiDirect
63
77
  remove_class_variable(:@@api_token) if defined?(@@api_token)
64
78
  end
65
79
  # rubocop:enable Style/ClassVars
80
+
81
+ def self.parse_org(url)
82
+ url.match(%r{^/org/([^/]+)})[1]
83
+ end
66
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Shell
4
4
  class << self
5
- attr_reader :tmp_stderr
5
+ attr_reader :tmp_stderr, :verbose
6
6
  end
7
7
 
8
8
  def self.shell
@@ -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)
@@ -50,4 +50,38 @@ class Shell
50
50
  def self.abort(message)
51
51
  Kernel.abort(color("ERROR: #{message}", :red))
52
52
  end
53
+
54
+ def self.verbose_mode(verbose)
55
+ @verbose = verbose
56
+ end
57
+
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}")
63
+ end
64
+
65
+ def self.should_hide_output?
66
+ tmp_stderr && !verbose
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
53
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.rc.0"
4
+ VERSION = "1.2.0"
5
5
  MIN_CPLN_VERSION = "0.0.71"
6
6
  end