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.
- 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
|