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.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +1 -1
- data/CHANGELOG.md +36 -1
- data/CONTRIBUTING.md +2 -6
- data/Gemfile.lock +7 -7
- data/README.md +94 -53
- data/docs/commands.md +22 -5
- data/docs/migrating.md +3 -3
- data/examples/controlplane.yml +63 -4
- data/lib/command/apply_template.rb +30 -54
- data/lib/command/base.rb +43 -0
- data/lib/command/copy_image_from_upstream.rb +5 -2
- data/lib/command/delete.rb +40 -11
- data/lib/command/env.rb +1 -0
- data/lib/command/generate.rb +45 -0
- data/lib/command/info.rb +5 -5
- data/lib/command/latest_image.rb +1 -0
- data/lib/command/maintenance.rb +1 -0
- data/lib/command/no_command.rb +6 -3
- data/lib/command/open_console.rb +26 -0
- data/lib/command/ps.rb +5 -1
- data/lib/command/run.rb +2 -2
- data/lib/command/run_detached.rb +2 -1
- data/lib/command/setup_app.rb +3 -3
- data/lib/command/version.rb +1 -0
- data/lib/core/config.rb +185 -54
- data/lib/core/controlplane.rb +76 -24
- data/lib/core/controlplane_api.rb +9 -1
- data/lib/core/controlplane_api_direct.rb +20 -2
- data/lib/core/helpers.rb +10 -0
- data/lib/core/shell.rb +36 -2
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +28 -2
- data/lib/generator_templates/Dockerfile +27 -0
- data/lib/generator_templates/controlplane.yml +57 -0
- data/lib/generator_templates/entrypoint.sh +8 -0
- data/lib/generator_templates/templates/gvc.yml +21 -0
- data/lib/generator_templates/templates/postgres.yml +176 -0
- data/lib/generator_templates/templates/rails.yml +36 -0
- metadata +14 -5
data/lib/core/config.rb
CHANGED
@@ -1,21 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
-
@
|
15
|
-
|
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
|
-
|
18
|
-
|
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
|
48
|
-
raise "
|
107
|
+
def ensure_config!(global_config)
|
108
|
+
raise "'controlplane.yml' is empty." unless global_config
|
49
109
|
end
|
50
110
|
|
51
|
-
def
|
52
|
-
raise "'controlplane.yml'
|
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 "
|
116
|
+
raise "Can't find config for app '#{app_name}' in 'controlplane.yml'." unless app_options
|
61
117
|
end
|
62
118
|
|
63
|
-
def
|
64
|
-
|
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
|
68
|
-
@
|
69
|
-
@
|
70
|
-
|
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
|
74
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
88
|
-
|
142
|
+
def ensure_org!
|
143
|
+
return if org
|
89
144
|
|
90
|
-
|
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
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
102
|
-
|
164
|
+
def config_file_path # rubocop:disable Metrics/MethodLength
|
165
|
+
@config_file_path ||= begin
|
166
|
+
path = Pathname.new(".").expand_path
|
103
167
|
|
104
|
-
|
105
|
-
|
106
|
-
|
168
|
+
loop do
|
169
|
+
config_file = path + CONFIG_FILE_LOCATION
|
170
|
+
break config_file if File.file?(config_file)
|
107
171
|
|
108
|
-
|
172
|
+
path = path.parent
|
109
173
|
|
110
|
-
|
111
|
-
|
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?
|
data/lib/core/controlplane.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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.
|
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.
|
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}
|
299
|
+
cmd = "cpln apply #{gvc_org} --file #{f.path}"
|
284
300
|
if Shell.tmp_stderr
|
285
|
-
cmd += " 2> #{Shell.tmp_stderr.path}"
|
286
|
-
|
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
|
-
|
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)
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/core/helpers.rb
ADDED
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/
|
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
|