cpl 2.0.2 → 2.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/.gitignore +1 -0
- data/CHANGELOG.md +39 -1
- data/Gemfile.lock +3 -3
- data/README.md +24 -3
- data/docs/commands.md +37 -8
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/tips.md +1 -40
- data/examples/controlplane.yml +24 -3
- data/lib/command/apply_template.rb +70 -80
- data/lib/command/base.rb +82 -71
- data/lib/command/build_image.rb +2 -2
- data/lib/command/cleanup_images.rb +1 -1
- data/lib/command/cleanup_stale_apps.rb +1 -1
- data/lib/command/copy_image_from_upstream.rb +3 -3
- data/lib/command/delete.rb +17 -5
- data/lib/command/deploy_image.rb +6 -21
- data/lib/command/doctor.rb +47 -0
- data/lib/command/latest_image.rb +1 -1
- data/lib/command/no_command.rb +1 -0
- data/lib/command/promote_app_from_upstream.rb +1 -1
- data/lib/command/ps_wait.rb +7 -3
- data/lib/command/run.rb +87 -34
- data/lib/command/setup_app.rb +80 -16
- data/lib/command/test.rb +1 -0
- data/lib/command/version.rb +1 -0
- data/lib/core/config.rb +40 -12
- data/lib/core/controlplane.rb +58 -0
- data/lib/core/controlplane_api.rb +13 -7
- data/lib/core/controlplane_api_direct.rb +1 -1
- data/lib/core/doctor_service.rb +104 -0
- data/lib/core/helpers.rb +10 -0
- data/lib/core/template_parser.rb +76 -0
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +24 -6
- data/templates/app.yml +0 -5
- metadata +6 -4
- data/lib/core/controlplane_api_cli.rb +0 -10
- data/templates/secrets.yml +0 -11
data/lib/command/run.rb
CHANGED
@@ -36,12 +36,17 @@ module Command
|
|
36
36
|
- - log async fetching for non-interactive mode
|
37
37
|
- The Dockerfile entrypoint is used as the command by default, which assumes `exec "${@}"` to be present,
|
38
38
|
and the args ["bash", "-c", cmd_to_run] are passed
|
39
|
-
- The entrypoint can be
|
39
|
+
- The entrypoint can be overridden through `--entrypoint`, which must be a single command or a script path that exists in the container,
|
40
40
|
and the args ["bash", "-c", cmd_to_run] are passed,
|
41
41
|
unless the entrypoint is `bash`, in which case the args ["-c", cmd_to_run] are passed
|
42
42
|
- Providing `--entrypoint none` sets the entrypoint to `bash` by default
|
43
43
|
- If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file,
|
44
|
-
the remote terminal size will be fixed to match the local terminal size (may also be
|
44
|
+
the remote terminal size will be fixed to match the local terminal size (may also be overridden through `--terminal-size`)
|
45
|
+
- By default, all jobs use a CPU size of 1 (1 core) and a memory size of 2Gi (2 gibibytes)
|
46
|
+
(can be configured through `runner_job_default_cpu` and `runner_job_default_memory` in `controlplane.yml`,
|
47
|
+
and also overridden per job through `--cpu` and `--memory`)
|
48
|
+
- By default, the job is stopped if it takes longer than 6 hours to finish
|
49
|
+
(can be configured though `runner_job_timeout` in `controlplane.yml`)
|
45
50
|
DESC
|
46
51
|
EXAMPLES = <<~EX
|
47
52
|
```sh
|
@@ -84,12 +89,17 @@ module Command
|
|
84
89
|
```
|
85
90
|
EX
|
86
91
|
|
92
|
+
DEFAULT_JOB_CPU = "1"
|
93
|
+
DEFAULT_JOB_MEMORY = "2Gi"
|
94
|
+
DEFAULT_JOB_TIMEOUT = 21_600 # 6 hours
|
95
|
+
DEFAULT_JOB_HISTORY_LIMIT = 10
|
87
96
|
MAGIC_END = "---cpl run command finished---"
|
88
97
|
|
89
98
|
attr_reader :interactive, :detached, :location, :original_workload, :runner_workload,
|
99
|
+
:default_image, :default_cpu, :default_memory, :job_timeout, :job_history_limit,
|
90
100
|
:container, :expected_deployed_version, :job, :replica, :command
|
91
101
|
|
92
|
-
def call # rubocop:disable Metrics/MethodLength
|
102
|
+
def call # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
93
103
|
@interactive = config.options[:interactive] || interactive_command?
|
94
104
|
@detached = config.options[:detached]
|
95
105
|
@log_method = config.options[:log_method]
|
@@ -97,6 +107,11 @@ module Command
|
|
97
107
|
@location = config.location
|
98
108
|
@original_workload = config.options[:workload] || config[:one_off_workload]
|
99
109
|
@runner_workload = "#{original_workload}-runner"
|
110
|
+
@default_image = "#{config.app}:#{Controlplane::NO_IMAGE_AVAILABLE}"
|
111
|
+
@default_cpu = config.current[:runner_job_default_cpu] || DEFAULT_JOB_CPU
|
112
|
+
@default_memory = config.current[:runner_job_default_memory] || DEFAULT_JOB_MEMORY
|
113
|
+
@job_timeout = config.current[:runner_job_timeout] || DEFAULT_JOB_TIMEOUT
|
114
|
+
@job_history_limit = DEFAULT_JOB_HISTORY_LIMIT
|
100
115
|
|
101
116
|
unless interactive
|
102
117
|
@internal_sigint = false
|
@@ -110,12 +125,10 @@ module Command
|
|
110
125
|
end
|
111
126
|
end
|
112
127
|
|
113
|
-
if cp.fetch_workload(runner_workload).nil?
|
114
|
-
|
115
|
-
wait_for_runner_workload_create
|
116
|
-
end
|
128
|
+
create_runner_workload if cp.fetch_workload(runner_workload).nil?
|
129
|
+
wait_for_runner_workload_deploy
|
117
130
|
update_runner_workload
|
118
|
-
wait_for_runner_workload_update
|
131
|
+
wait_for_runner_workload_update if expected_deployed_version
|
119
132
|
|
120
133
|
start_job
|
121
134
|
wait_for_replica_for_job
|
@@ -154,6 +167,11 @@ module Command
|
|
154
167
|
container_spec.delete("livenessProbe")
|
155
168
|
container_spec.delete("readinessProbe")
|
156
169
|
|
170
|
+
# Set image, CPU, and memory to default values
|
171
|
+
container_spec["image"] = default_image
|
172
|
+
container_spec["cpu"] = default_cpu
|
173
|
+
container_spec["memory"] = default_memory
|
174
|
+
|
157
175
|
# Ensure cron workload won't run per schedule
|
158
176
|
spec["defaultOptions"]["suspend"] = true
|
159
177
|
|
@@ -163,9 +181,14 @@ module Command
|
|
163
181
|
|
164
182
|
# Set cron job props
|
165
183
|
spec["type"] = "cron"
|
184
|
+
spec["job"] = {
|
185
|
+
# Next job set to run on January 1st, 2029
|
186
|
+
"schedule" => "0 0 1 1 1",
|
166
187
|
|
167
|
-
|
168
|
-
|
188
|
+
"restartPolicy" => "Never",
|
189
|
+
"activeDeadlineSeconds" => job_timeout,
|
190
|
+
"historyLimit" => job_history_limit
|
191
|
+
}
|
169
192
|
|
170
193
|
# Create runner workload
|
171
194
|
cp.apply_hash("kind" => "workload", "name" => runner_workload, "spec" => spec)
|
@@ -173,46 +196,60 @@ module Command
|
|
173
196
|
end
|
174
197
|
|
175
198
|
def update_runner_workload # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
176
|
-
|
177
|
-
|
178
|
-
should_update = false
|
199
|
+
should_update = false
|
200
|
+
spec = nil
|
179
201
|
|
202
|
+
step("Checking if runner workload '#{runner_workload}' needs to be updated") do # rubocop:disable Metrics/BlockLength
|
180
203
|
_, original_container_spec = base_workload_specs(original_workload)
|
181
204
|
spec, container_spec = base_workload_specs(runner_workload)
|
182
205
|
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
if container_spec["image"] !=
|
192
|
-
container_spec["image"] =
|
206
|
+
# Keep ENV synced between original and runner workloads
|
207
|
+
original_env_str = original_container_spec["env"]&.sort_by { |env| env["name"] }.to_s
|
208
|
+
env_str = container_spec["env"]&.sort_by { |env| env["name"] }.to_s
|
209
|
+
if original_env_str != env_str
|
210
|
+
container_spec["env"] = original_container_spec["env"]
|
211
|
+
should_update = true
|
212
|
+
end
|
213
|
+
|
214
|
+
if container_spec["image"] != default_image
|
215
|
+
container_spec["image"] = default_image
|
193
216
|
should_update = true
|
194
217
|
end
|
195
218
|
|
196
|
-
|
197
|
-
|
198
|
-
container_spec["cpu"] = config.options[:cpu]
|
219
|
+
if container_spec["cpu"] != default_cpu
|
220
|
+
container_spec["cpu"] = default_cpu
|
199
221
|
should_update = true
|
200
222
|
end
|
201
|
-
|
202
|
-
|
223
|
+
|
224
|
+
if container_spec["memory"] != default_memory
|
225
|
+
container_spec["memory"] = default_memory
|
203
226
|
should_update = true
|
204
227
|
end
|
205
228
|
|
206
|
-
|
229
|
+
if spec["job"]["activeDeadlineSeconds"] != job_timeout
|
230
|
+
spec["job"]["activeDeadlineSeconds"] = job_timeout
|
231
|
+
should_update = true
|
232
|
+
end
|
207
233
|
|
234
|
+
if spec["job"]["historyLimit"] != job_history_limit
|
235
|
+
spec["job"]["historyLimit"] = job_history_limit
|
236
|
+
should_update = true
|
237
|
+
end
|
238
|
+
|
239
|
+
true
|
240
|
+
end
|
241
|
+
|
242
|
+
return unless should_update
|
243
|
+
|
244
|
+
step("Updating runner workload '#{runner_workload}'") do
|
208
245
|
# Update runner workload
|
209
|
-
@expected_deployed_version
|
246
|
+
@expected_deployed_version = cp.cron_workload_deployed_version(runner_workload) + 1
|
210
247
|
cp.apply_hash("kind" => "workload", "name" => runner_workload, "spec" => spec)
|
211
248
|
end
|
212
249
|
end
|
213
250
|
|
214
|
-
def
|
215
|
-
step("Waiting for runner workload '#{runner_workload}' to be
|
251
|
+
def wait_for_runner_workload_deploy
|
252
|
+
step("Waiting for runner workload '#{runner_workload}' to be deployed", retry_on_failure: true) do
|
216
253
|
!cp.cron_workload_deployed_version(runner_workload).nil?
|
217
254
|
end
|
218
255
|
end
|
@@ -302,12 +339,14 @@ module Command
|
|
302
339
|
def base_workload_specs(workload)
|
303
340
|
spec = cp.fetch_workload!(workload).fetch("spec")
|
304
341
|
container_spec = spec["containers"].detect { _1["name"] == original_workload } || spec["containers"].first
|
305
|
-
@container = container_spec["name"]
|
306
342
|
|
307
343
|
[spec, container_spec]
|
308
344
|
end
|
309
345
|
|
310
346
|
def build_job_start_yaml # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
347
|
+
_, original_container_spec = base_workload_specs(original_workload)
|
348
|
+
@container = original_container_spec["name"]
|
349
|
+
|
311
350
|
job_start_hash = { "name" => container }
|
312
351
|
|
313
352
|
if config.options[:use_local_token]
|
@@ -335,6 +374,18 @@ module Command
|
|
335
374
|
job_start_hash["args"].push('eval "$CPL_RUNNER_SCRIPT"')
|
336
375
|
end
|
337
376
|
|
377
|
+
image = config.options[:image]
|
378
|
+
image_link = if image
|
379
|
+
image = cp.latest_image if image == "latest"
|
380
|
+
"/org/#{config.org}/image/#{image}"
|
381
|
+
else
|
382
|
+
original_container_spec["image"]
|
383
|
+
end
|
384
|
+
|
385
|
+
job_start_hash["image"] = image_link
|
386
|
+
job_start_hash["cpu"] = config.options[:cpu] if config.options[:cpu]
|
387
|
+
job_start_hash["memory"] = config.options[:memory] if config.options[:memory]
|
388
|
+
|
338
389
|
job_start_hash.to_yaml
|
339
390
|
end
|
340
391
|
|
@@ -434,6 +485,8 @@ module Command
|
|
434
485
|
end
|
435
486
|
|
436
487
|
def print_detached_commands
|
488
|
+
return unless replica
|
489
|
+
|
437
490
|
app_workload_replica_config = app_workload_replica_args.join(" ")
|
438
491
|
progress.puts(
|
439
492
|
"\n\n" \
|
@@ -451,7 +504,7 @@ module Command
|
|
451
504
|
Shell.debug("JOB STATUS", status)
|
452
505
|
|
453
506
|
case status
|
454
|
-
when "active"
|
507
|
+
when "active", "pending"
|
455
508
|
sleep 1
|
456
509
|
when "successful"
|
457
510
|
break ExitCode::SUCCESS
|
data/lib/command/setup_app.rb
CHANGED
@@ -5,18 +5,27 @@ module Command
|
|
5
5
|
NAME = "setup-app"
|
6
6
|
OPTIONS = [
|
7
7
|
app_option(required: true),
|
8
|
-
skip_secret_access_binding_option
|
8
|
+
skip_secret_access_binding_option,
|
9
|
+
skip_secrets_setup_option,
|
10
|
+
skip_post_creation_hook_option
|
9
11
|
].freeze
|
10
12
|
DESCRIPTION = "Creates an app and all its workloads"
|
11
13
|
LONG_DESCRIPTION = <<~DESC
|
12
14
|
- Creates an app and all its workloads
|
13
15
|
- Specify the templates for the app and workloads through `setup_app_templates` in the `.controlplane/controlplane.yml` file
|
14
|
-
- 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)
|
15
|
-
-
|
16
|
-
|
16
|
+
- This should only be used for temporary apps like review apps, never for persistent apps like production or staging (to update workloads for those, use 'cpl apply-template' instead)
|
17
|
+
- Configures app to have org-level secrets with default name "{APP_PREFIX}-secrets"
|
18
|
+
using org-level policy with default name "{APP_PREFIX}-secrets-policy" (names can be customized, see docs)
|
19
|
+
- Creates identity for secrets if it does not exist
|
20
|
+
- Use `--skip-secrets-setup` to prevent the automatic setup of secrets,
|
21
|
+
or set it through `skip_secrets_setup` in the `.controlplane/controlplane.yml` file
|
22
|
+
- Runs a post-creation hook after the app is created if `hooks.post_creation` is specified in the `.controlplane/controlplane.yml` file
|
23
|
+
- If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
24
|
+
- Use `--skip-post-creation-hook` to skip the hook if specified in `controlplane.yml`
|
17
25
|
DESC
|
26
|
+
VALIDATIONS = %w[config templates].freeze
|
18
27
|
|
19
|
-
def call # rubocop:disable Metrics/MethodLength
|
28
|
+
def call # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
20
29
|
templates = config[:setup_app_templates]
|
21
30
|
|
22
31
|
app = cp.fetch_gvc
|
@@ -26,24 +35,79 @@ module Command
|
|
26
35
|
"or run 'cpl apply-template #{templates.join(' ')} -a #{config.app}'."
|
27
36
|
end
|
28
37
|
|
29
|
-
|
38
|
+
skip_secrets_setup = config.options[:skip_secret_access_binding] ||
|
39
|
+
config.options[:skip_secrets_setup] || config.current[:skip_secrets_setup]
|
30
40
|
|
31
|
-
|
41
|
+
create_secret_and_policy_if_not_exist unless skip_secrets_setup
|
42
|
+
|
43
|
+
args = []
|
44
|
+
args.push("--add-app-identity") unless skip_secrets_setup
|
45
|
+
Cpl::Cli.start(["apply-template", *templates, "-a", config.app, *args])
|
46
|
+
|
47
|
+
bind_identity_to_policy unless skip_secrets_setup
|
48
|
+
run_post_creation_hook unless config.options[:skip_post_creation_hook]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def create_secret_and_policy_if_not_exist
|
54
|
+
create_secret_if_not_exists
|
55
|
+
create_policy_if_not_exists
|
32
56
|
|
33
57
|
progress.puts
|
58
|
+
end
|
34
59
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
60
|
+
def create_secret_if_not_exists
|
61
|
+
if cp.fetch_secret(config.secrets)
|
62
|
+
progress.puts("Secret '#{config.secrets}' already exists. Skipping creation...")
|
63
|
+
else
|
64
|
+
step("Creating secret '#{config.secrets}'") do
|
65
|
+
cp.apply_hash(build_secret_hash)
|
66
|
+
end
|
42
67
|
end
|
68
|
+
end
|
43
69
|
|
44
|
-
|
45
|
-
|
70
|
+
def create_policy_if_not_exists
|
71
|
+
if cp.fetch_policy(config.secrets_policy)
|
72
|
+
progress.puts("Policy '#{config.secrets_policy}' already exists. Skipping creation...")
|
73
|
+
else
|
74
|
+
step("Creating policy '#{config.secrets_policy}'") do
|
75
|
+
cp.apply_hash(build_policy_hash)
|
76
|
+
end
|
46
77
|
end
|
47
78
|
end
|
79
|
+
|
80
|
+
def build_secret_hash
|
81
|
+
{
|
82
|
+
"kind" => "secret",
|
83
|
+
"name" => config.secrets,
|
84
|
+
"type" => "dictionary",
|
85
|
+
"data" => {}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_policy_hash
|
90
|
+
{
|
91
|
+
"kind" => "policy",
|
92
|
+
"name" => config.secrets_policy,
|
93
|
+
"targetKind" => "secret",
|
94
|
+
"targetLinks" => ["//secret/#{config.secrets}"]
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def bind_identity_to_policy
|
99
|
+
progress.puts
|
100
|
+
|
101
|
+
step("Binding identity '#{config.identity}' to policy '#{config.secrets_policy}'") do
|
102
|
+
cp.bind_identity_to_policy(config.identity_link, config.secrets_policy)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_post_creation_hook
|
107
|
+
post_creation_hook = config.current.dig(:hooks, :post_creation)
|
108
|
+
return unless post_creation_hook
|
109
|
+
|
110
|
+
run_command_in_latest_image(post_creation_hook, title: "post-creation hook")
|
111
|
+
end
|
48
112
|
end
|
49
113
|
end
|
data/lib/command/test.rb
CHANGED
data/lib/command/version.rb
CHANGED
data/lib/core/config.rb
CHANGED
@@ -18,6 +18,8 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
18
18
|
|
19
19
|
ensure_required_options!
|
20
20
|
|
21
|
+
warn_deprecated_options
|
22
|
+
|
21
23
|
Shell.verbose_mode(options[:verbose])
|
22
24
|
trace_mode = options[:trace]
|
23
25
|
return unless trace_mode
|
@@ -38,10 +40,34 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
38
40
|
current&.fetch(:name)
|
39
41
|
end
|
40
42
|
|
43
|
+
def identity
|
44
|
+
"#{app}-identity"
|
45
|
+
end
|
46
|
+
|
47
|
+
def identity_link
|
48
|
+
"/org/#{org}/gvc/#{app}/identity/#{identity}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def secrets
|
52
|
+
current&.dig(:secrets_name) || "#{app_prefix}-secrets"
|
53
|
+
end
|
54
|
+
|
55
|
+
def secrets_policy
|
56
|
+
current&.dig(:secrets_policy_name) || "#{secrets}-policy"
|
57
|
+
end
|
58
|
+
|
41
59
|
def location
|
42
60
|
@location ||= load_location_from_options || load_location_from_env || load_location_from_file
|
43
61
|
end
|
44
62
|
|
63
|
+
def location_link
|
64
|
+
"/org/#{org}/location/#{location}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def image_link(image)
|
68
|
+
"/org/#{org}/image/#{image}"
|
69
|
+
end
|
70
|
+
|
45
71
|
def domain
|
46
72
|
@domain ||= load_domain_from_options || load_domain_from_file
|
47
73
|
end
|
@@ -84,6 +110,8 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
84
110
|
@apps ||= config[:apps].to_h do |app_name, app_options|
|
85
111
|
ensure_config_app!(app_name, app_options)
|
86
112
|
|
113
|
+
check_deprecated_options(app_options)
|
114
|
+
|
87
115
|
app_options_with_new_keys = app_options.to_h do |key, value|
|
88
116
|
new_key = new_option_keys[key]
|
89
117
|
new_key ? [new_key, value] : [key, value]
|
@@ -96,14 +124,7 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
96
124
|
def current
|
97
125
|
return unless app
|
98
126
|
|
99
|
-
@current ||=
|
100
|
-
app_config = find_app_config(app)
|
101
|
-
ensure_config_app!(app, app_config)
|
102
|
-
|
103
|
-
warn_deprecated_options(app_config)
|
104
|
-
|
105
|
-
app_config
|
106
|
-
end
|
127
|
+
@current ||= find_app_config(app)
|
107
128
|
end
|
108
129
|
|
109
130
|
def app_matches?(app_name1, app_name2, app_options)
|
@@ -275,11 +296,18 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
275
296
|
strip_str_and_validate(current.fetch(:default_domain))
|
276
297
|
end
|
277
298
|
|
278
|
-
def
|
279
|
-
deprecated_option_keys
|
280
|
-
|
299
|
+
def check_deprecated_options(app_options)
|
300
|
+
@deprecated_option_keys ||= {}
|
301
|
+
|
302
|
+
new_option_keys.each do |old_key, new_key|
|
303
|
+
@deprecated_option_keys[old_key] = new_key if app_options.key?(old_key)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def warn_deprecated_options
|
308
|
+
return if !@deprecated_option_keys || @deprecated_option_keys.empty?
|
281
309
|
|
282
|
-
deprecated_option_keys.each do |old_key, new_key|
|
310
|
+
@deprecated_option_keys.each do |old_key, new_key|
|
283
311
|
Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
|
284
312
|
"please use '#{new_key}' instead (in 'controlplane.yml').")
|
285
313
|
end
|
data/lib/core/controlplane.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
class Controlplane # rubocop:disable Metrics/ClassLength
|
4
4
|
attr_reader :config, :api, :gvc, :org
|
5
5
|
|
6
|
+
NO_IMAGE_AVAILABLE = "NO_IMAGE_AVAILABLE"
|
7
|
+
|
6
8
|
def initialize(config)
|
7
9
|
@config = config
|
8
10
|
@api = ControlplaneApi.new
|
@@ -37,6 +39,51 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
37
39
|
|
38
40
|
# image
|
39
41
|
|
42
|
+
def latest_image(a_gvc = gvc, a_org = org, refresh: false)
|
43
|
+
@latest_image ||= {}
|
44
|
+
@latest_image[a_gvc] = nil if refresh
|
45
|
+
@latest_image[a_gvc] ||=
|
46
|
+
begin
|
47
|
+
items = query_images(a_gvc, a_org)["items"]
|
48
|
+
latest_image_from(items, app_name: a_gvc)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def latest_image_next(a_gvc = gvc, a_org = org, commit: nil)
|
53
|
+
commit ||= config.options[:commit]
|
54
|
+
|
55
|
+
@latest_image_next ||= {}
|
56
|
+
@latest_image_next[a_gvc] ||= begin
|
57
|
+
latest_image_name = latest_image(a_gvc, a_org)
|
58
|
+
image = latest_image_name.split(":").first
|
59
|
+
image += ":#{extract_image_number(latest_image_name) + 1}"
|
60
|
+
image += "_#{commit}" if commit
|
61
|
+
image
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def latest_image_from(items, app_name: gvc, name_only: true)
|
66
|
+
matching_items = items.select { |item| item["name"].start_with?("#{app_name}:") }
|
67
|
+
|
68
|
+
# Or special string to indicate no image available
|
69
|
+
if matching_items.empty?
|
70
|
+
name_only ? "#{app_name}:#{NO_IMAGE_AVAILABLE}" : nil
|
71
|
+
else
|
72
|
+
latest_item = matching_items.max_by { |item| extract_image_number(item["name"]) }
|
73
|
+
name_only ? latest_item["name"] : latest_item
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_image_number(image_name)
|
78
|
+
return 0 if image_name.end_with?(NO_IMAGE_AVAILABLE)
|
79
|
+
|
80
|
+
image_name.match(/:(\d+)/)&.captures&.first.to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
def extract_image_commit(image_name)
|
84
|
+
image_name.match(/_(\h+)$/)&.captures&.first
|
85
|
+
end
|
86
|
+
|
40
87
|
def query_images(a_gvc = gvc, a_org = org, partial_gvc_match: nil)
|
41
88
|
partial_gvc_match = config.should_app_start_with?(a_gvc) if partial_gvc_match.nil?
|
42
89
|
gvc_op = partial_gvc_match ? "~" : "="
|
@@ -209,6 +256,11 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
209
256
|
api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
|
210
257
|
end
|
211
258
|
|
259
|
+
def workload_suspended?(workload)
|
260
|
+
details = fetch_workload!(workload)
|
261
|
+
details["spec"]["defaultOptions"]["suspend"]
|
262
|
+
end
|
263
|
+
|
212
264
|
def workload_force_redeployment(workload)
|
213
265
|
cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
|
214
266
|
perform!(cmd)
|
@@ -324,6 +376,12 @@ class Controlplane # rubocop:disable Metrics/ClassLength
|
|
324
376
|
api.log_get(org: org, gvc: gvc, workload: workload, replica: replica, from: from, to: to)
|
325
377
|
end
|
326
378
|
|
379
|
+
# secrets
|
380
|
+
|
381
|
+
def fetch_secret(secret)
|
382
|
+
api.fetch_secret(org: org, secret: secret)
|
383
|
+
end
|
384
|
+
|
327
385
|
# identities
|
328
386
|
|
329
387
|
def fetch_identity(identity, a_gvc = gvc)
|
@@ -52,7 +52,7 @@ class ControlplaneApi # rubocop:disable Metrics/ClassLength
|
|
52
52
|
# params << "direction=forward"
|
53
53
|
params = params.map { |k, v| %(#{k}=#{CGI.escape(v)}) }.join("&")
|
54
54
|
|
55
|
-
|
55
|
+
api_json("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
|
56
56
|
end
|
57
57
|
|
58
58
|
def query_workloads(org:, gvc:, workload:, gvc_op_type:, workload_op_type:) # rubocop:disable Metrics/MethodLength
|
@@ -116,6 +116,14 @@ class ControlplaneApi # rubocop:disable Metrics/ClassLength
|
|
116
116
|
api_json("/org/#{org}/domain/#{domain}", method: :patch, body: data)
|
117
117
|
end
|
118
118
|
|
119
|
+
def fetch_secret(org:, secret:)
|
120
|
+
api_json("/org/#{org}/secret/#{secret}", method: :get)
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_secret(org:, secret:)
|
124
|
+
api_json("/org/#{org}/secret/#{secret}", method: :delete)
|
125
|
+
end
|
126
|
+
|
119
127
|
def fetch_identity(org:, gvc:, identity:)
|
120
128
|
api_json("/org/#{org}/gvc/#{gvc}/identity/#{identity}", method: :get)
|
121
129
|
end
|
@@ -124,6 +132,10 @@ class ControlplaneApi # rubocop:disable Metrics/ClassLength
|
|
124
132
|
api_json("/org/#{org}/policy/#{policy}", method: :get)
|
125
133
|
end
|
126
134
|
|
135
|
+
def delete_policy(org:, policy:)
|
136
|
+
api_json("/org/#{org}/policy/#{policy}", method: :delete)
|
137
|
+
end
|
138
|
+
|
127
139
|
private
|
128
140
|
|
129
141
|
def fetch_query_pages(result)
|
@@ -152,13 +164,7 @@ class ControlplaneApi # rubocop:disable Metrics/ClassLength
|
|
152
164
|
result
|
153
165
|
end
|
154
166
|
|
155
|
-
# switch between cpln rest and api
|
156
167
|
def api_json(...)
|
157
168
|
ControlplaneApiDirect.new.call(...)
|
158
169
|
end
|
159
|
-
|
160
|
-
# only for api (where not impelemented in cpln rest)
|
161
|
-
def api_json_direct(...)
|
162
|
-
ControlplaneApiDirect.new.call(...)
|
163
|
-
end
|
164
170
|
end
|