cpl 1.1.2 → 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 +23 -1
- data/CONTRIBUTING.md +2 -6
- data/Gemfile.lock +7 -7
- data/README.md +34 -11
- data/docs/commands.md +13 -5
- data/docs/migrating.md +3 -3
- data/examples/controlplane.yml +63 -4
- data/lib/command/apply_template.rb +2 -1
- data/lib/command/base.rb +31 -0
- data/lib/command/config.rb +0 -5
- 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 -8
- data/lib/command/latest_image.rb +1 -0
- data/lib/command/maintenance.rb +1 -0
- data/lib/command/no_command.rb +1 -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 +180 -66
- data/lib/core/controlplane.rb +15 -3
- data/lib/core/controlplane_api.rb +9 -1
- data/lib/core/controlplane_api_direct.rb +18 -2
- data/lib/core/helpers.rb +10 -0
- data/lib/core/shell.rb +25 -3
- 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 +11 -3
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/
|
84
|
+
def orgs # rubocop:disable Metrics/MethodLength
|
85
85
|
result = []
|
86
86
|
|
87
|
-
if config.
|
88
|
-
result.push(config.
|
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
|
|
data/lib/command/latest_image.rb
CHANGED
data/lib/command/maintenance.rb
CHANGED
@@ -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]
|
data/lib/command/no_command.rb
CHANGED
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:
|
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
|
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
|
|
data/lib/command/run_detached.rb
CHANGED
@@ -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
|
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
|
|
data/lib/command/setup_app.rb
CHANGED
@@ -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 `
|
13
|
-
- This should
|
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[:
|
17
|
+
templates = config[:setup_app_templates]
|
18
18
|
|
19
19
|
app = cp.fetch_gvc
|
20
20
|
if app
|
data/lib/command/version.rb
CHANGED
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 :
|
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
|
-
|
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
|
-
@org_comes_from_env = false
|
16
|
-
@app = options[:app]
|
17
|
+
@required_options = required_options
|
17
18
|
|
18
|
-
|
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
|
-
|
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
|
51
|
-
|
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
|
55
|
-
|
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
|
-
|
58
|
-
|
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
|
62
|
-
|
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
|
-
|
66
|
-
|
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
|
70
|
-
raise "
|
107
|
+
def ensure_config!(global_config)
|
108
|
+
raise "'controlplane.yml' is empty." unless global_config
|
71
109
|
end
|
72
110
|
|
73
|
-
def
|
74
|
-
|
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
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
91
|
-
@
|
92
|
-
|
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
|
-
|
95
|
-
|
96
|
-
new_key ? [new_key, value] : [key, value]
|
97
|
-
end
|
133
|
+
def ensure_app!
|
134
|
+
return if app
|
98
135
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
105
|
-
|
142
|
+
def ensure_org!
|
143
|
+
return if org
|
106
144
|
|
107
|
-
|
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
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
119
|
-
|
164
|
+
def config_file_path # rubocop:disable Metrics/MethodLength
|
165
|
+
@config_file_path ||= begin
|
166
|
+
path = Pathname.new(".").expand_path
|
120
167
|
|
121
|
-
|
122
|
-
|
123
|
-
|
168
|
+
loop do
|
169
|
+
config_file = path + CONFIG_FILE_LOCATION
|
170
|
+
break config_file if File.file?(config_file)
|
124
171
|
|
125
|
-
|
172
|
+
path = path.parent
|
126
173
|
|
127
|
-
|
128
|
-
|
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?
|
data/lib/core/controlplane.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/core/helpers.rb
ADDED
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/
|
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
|
-
|
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
|