cpl 1.1.2.rc.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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