cpflow 3.0.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 +7 -0
- data/.github/workflows/check_cpln_links.yml +19 -0
- data/.github/workflows/command_docs.yml +24 -0
- data/.github/workflows/rspec-shared.yml +56 -0
- data/.github/workflows/rspec.yml +28 -0
- data/.github/workflows/rubocop.yml +24 -0
- data/.gitignore +18 -0
- data/.overcommit.yml +16 -0
- data/.rubocop.yml +22 -0
- data/.simplecov_spawn.rb +10 -0
- data/CHANGELOG.md +259 -0
- data/CONTRIBUTING.md +73 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +126 -0
- data/LICENSE +21 -0
- data/README.md +546 -0
- data/Rakefile +21 -0
- data/bin/cpflow +6 -0
- data/cpflow +6 -0
- data/cpflow.gemspec +41 -0
- data/docs/assets/grafana-alert.png +0 -0
- data/docs/assets/memcached.png +0 -0
- data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
- data/docs/commands.md +454 -0
- data/docs/dns.md +15 -0
- data/docs/migrating.md +262 -0
- data/docs/postgres.md +436 -0
- data/docs/redis.md +128 -0
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/tips.md +150 -0
- data/docs/troubleshooting.md +6 -0
- data/examples/circleci.yml +104 -0
- data/examples/controlplane.yml +159 -0
- data/lib/command/apply_template.rb +209 -0
- data/lib/command/base.rb +540 -0
- data/lib/command/build_image.rb +49 -0
- data/lib/command/cleanup_images.rb +136 -0
- data/lib/command/cleanup_stale_apps.rb +79 -0
- data/lib/command/config.rb +48 -0
- data/lib/command/copy_image_from_upstream.rb +108 -0
- data/lib/command/delete.rb +149 -0
- data/lib/command/deploy_image.rb +56 -0
- data/lib/command/doctor.rb +47 -0
- data/lib/command/env.rb +22 -0
- data/lib/command/exists.rb +23 -0
- data/lib/command/generate.rb +45 -0
- data/lib/command/info.rb +222 -0
- data/lib/command/latest_image.rb +19 -0
- data/lib/command/logs.rb +49 -0
- data/lib/command/maintenance.rb +42 -0
- data/lib/command/maintenance_off.rb +62 -0
- data/lib/command/maintenance_on.rb +62 -0
- data/lib/command/maintenance_set_page.rb +34 -0
- data/lib/command/no_command.rb +23 -0
- data/lib/command/open.rb +33 -0
- data/lib/command/open_console.rb +26 -0
- data/lib/command/promote_app_from_upstream.rb +38 -0
- data/lib/command/ps.rb +41 -0
- data/lib/command/ps_restart.rb +37 -0
- data/lib/command/ps_start.rb +51 -0
- data/lib/command/ps_stop.rb +82 -0
- data/lib/command/ps_wait.rb +40 -0
- data/lib/command/run.rb +573 -0
- data/lib/command/setup_app.rb +113 -0
- data/lib/command/test.rb +23 -0
- data/lib/command/version.rb +18 -0
- data/lib/constants/exit_code.rb +7 -0
- data/lib/core/config.rb +316 -0
- data/lib/core/controlplane.rb +552 -0
- data/lib/core/controlplane_api.rb +170 -0
- data/lib/core/controlplane_api_direct.rb +112 -0
- data/lib/core/doctor_service.rb +104 -0
- data/lib/core/helpers.rb +26 -0
- data/lib/core/shell.rb +100 -0
- data/lib/core/template_parser.rb +76 -0
- data/lib/cpflow/version.rb +6 -0
- data/lib/cpflow.rb +288 -0
- data/lib/deprecated_commands.json +9 -0
- data/lib/generator_templates/Dockerfile +27 -0
- data/lib/generator_templates/controlplane.yml +62 -0
- data/lib/generator_templates/entrypoint.sh +8 -0
- data/lib/generator_templates/templates/app.yml +21 -0
- data/lib/generator_templates/templates/postgres.yml +176 -0
- data/lib/generator_templates/templates/rails.yml +36 -0
- data/rakelib/create_release.rake +81 -0
- data/script/add_command +37 -0
- data/script/check_command_docs +3 -0
- data/script/check_cpln_links +45 -0
- data/script/rename_command +43 -0
- data/script/update_command_docs +62 -0
- data/templates/app.yml +13 -0
- data/templates/daily-task.yml +32 -0
- data/templates/maintenance.yml +25 -0
- data/templates/memcached.yml +24 -0
- data/templates/postgres.yml +32 -0
- data/templates/rails.yml +27 -0
- data/templates/redis.yml +21 -0
- data/templates/redis2.yml +37 -0
- data/templates/sidekiq.yml +38 -0
- metadata +341 -0
@@ -0,0 +1,552 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Controlplane # rubocop:disable Metrics/ClassLength
|
4
|
+
attr_reader :config, :api, :gvc, :org
|
5
|
+
|
6
|
+
NO_IMAGE_AVAILABLE = "NO_IMAGE_AVAILABLE"
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
@api = ControlplaneApi.new
|
11
|
+
@gvc = config.app
|
12
|
+
@org = config.org
|
13
|
+
|
14
|
+
ensure_org_exists! if org
|
15
|
+
end
|
16
|
+
|
17
|
+
# profile
|
18
|
+
|
19
|
+
def profile_switch(profile)
|
20
|
+
ENV["CPLN_PROFILE"] = profile
|
21
|
+
ControlplaneApiDirect.reset_api_token
|
22
|
+
end
|
23
|
+
|
24
|
+
def profile_exists?(profile)
|
25
|
+
cmd = "cpln profile get #{profile} -o yaml"
|
26
|
+
perform_yaml!(cmd).length.positive?
|
27
|
+
end
|
28
|
+
|
29
|
+
def profile_create(profile, token)
|
30
|
+
sensitive_data_pattern = /(?<=--token )(\S+)/
|
31
|
+
cmd = "cpln profile create #{profile} --token #{token}"
|
32
|
+
perform!(cmd, sensitive_data_pattern: sensitive_data_pattern)
|
33
|
+
end
|
34
|
+
|
35
|
+
def profile_delete(profile)
|
36
|
+
cmd = "cpln profile delete #{profile}"
|
37
|
+
perform!(cmd)
|
38
|
+
end
|
39
|
+
|
40
|
+
# image
|
41
|
+
|
42
|
+
def latest_image(a_gvc = gvc, a_org = org)
|
43
|
+
@latest_image ||= {}
|
44
|
+
@latest_image[a_gvc] ||=
|
45
|
+
begin
|
46
|
+
items = query_images(a_gvc, a_org)["items"]
|
47
|
+
latest_image_from(items, app_name: a_gvc)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def latest_image_next(a_gvc = gvc, a_org = org, commit: nil)
|
52
|
+
commit ||= config.options[:commit]
|
53
|
+
|
54
|
+
@latest_image_next ||= {}
|
55
|
+
@latest_image_next[a_gvc] ||= begin
|
56
|
+
latest_image_name = latest_image(a_gvc, a_org)
|
57
|
+
image = latest_image_name.split(":").first
|
58
|
+
image += ":#{extract_image_number(latest_image_name) + 1}"
|
59
|
+
image += "_#{commit}" if commit
|
60
|
+
image
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def latest_image_from(items, app_name: gvc, name_only: true)
|
65
|
+
matching_items = items.select { |item| item["name"].start_with?("#{app_name}:") }
|
66
|
+
|
67
|
+
# Or special string to indicate no image available
|
68
|
+
if matching_items.empty?
|
69
|
+
name_only ? "#{app_name}:#{NO_IMAGE_AVAILABLE}" : nil
|
70
|
+
else
|
71
|
+
latest_item = matching_items.max_by { |item| DateTime.parse(item["created"]) }
|
72
|
+
name_only ? latest_item["name"] : latest_item
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def extract_image_number(image_name)
|
77
|
+
return 0 if image_name.end_with?(NO_IMAGE_AVAILABLE)
|
78
|
+
|
79
|
+
image_name.match(/:(\d+)/)&.captures&.first.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_image_commit(image_name)
|
83
|
+
image_name.match(/_(\h+)$/)&.captures&.first
|
84
|
+
end
|
85
|
+
|
86
|
+
def query_images(a_gvc = gvc, a_org = org, partial_gvc_match: nil)
|
87
|
+
partial_gvc_match = config.should_app_start_with?(a_gvc) if partial_gvc_match.nil?
|
88
|
+
gvc_op = partial_gvc_match ? "~" : "="
|
89
|
+
|
90
|
+
api.query_images(org: a_org, gvc: a_gvc, gvc_op_type: gvc_op)
|
91
|
+
end
|
92
|
+
|
93
|
+
def image_build(image, dockerfile:, docker_args: [], build_args: [], push: true)
|
94
|
+
# https://docs.controlplane.com/guides/push-image#step-2
|
95
|
+
# Might need to use `docker buildx build` if compatiblitity issues arise
|
96
|
+
cmd = "docker build --platform=linux/amd64 -t #{image} -f #{dockerfile}"
|
97
|
+
cmd += " --progress=plain" if ControlplaneApiDirect.trace
|
98
|
+
|
99
|
+
cmd += " #{docker_args.join(' ')}" if docker_args.any?
|
100
|
+
build_args.each { |build_arg| cmd += " --build-arg #{build_arg}" }
|
101
|
+
cmd += " #{config.app_dir}"
|
102
|
+
perform!(cmd)
|
103
|
+
|
104
|
+
image_push(image) if push
|
105
|
+
end
|
106
|
+
|
107
|
+
def fetch_image_details(image)
|
108
|
+
api.fetch_image_details(org: org, image: image)
|
109
|
+
end
|
110
|
+
|
111
|
+
def image_delete(image)
|
112
|
+
api.image_delete(org: org, image: image)
|
113
|
+
end
|
114
|
+
|
115
|
+
def image_login(org_name = config.org)
|
116
|
+
cmd = "cpln image docker-login --org #{org_name}"
|
117
|
+
perform!(cmd, output_mode: :none)
|
118
|
+
end
|
119
|
+
|
120
|
+
def image_pull(image)
|
121
|
+
cmd = "docker pull #{image}"
|
122
|
+
perform!(cmd, output_mode: :none)
|
123
|
+
end
|
124
|
+
|
125
|
+
def image_tag(old_tag, new_tag)
|
126
|
+
cmd = "docker tag #{old_tag} #{new_tag}"
|
127
|
+
perform!(cmd)
|
128
|
+
end
|
129
|
+
|
130
|
+
def image_push(image)
|
131
|
+
cmd = "docker push #{image}"
|
132
|
+
perform!(cmd)
|
133
|
+
end
|
134
|
+
|
135
|
+
# gvc
|
136
|
+
|
137
|
+
def fetch_gvcs
|
138
|
+
api.gvc_list(org: org)
|
139
|
+
end
|
140
|
+
|
141
|
+
def gvc_query(app_name = config.app)
|
142
|
+
# When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
|
143
|
+
# otherwise we query for a gvc with the exact name.
|
144
|
+
op = config.should_app_start_with?(app_name) ? "~" : "="
|
145
|
+
|
146
|
+
cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
|
147
|
+
perform_yaml!(cmd)
|
148
|
+
end
|
149
|
+
|
150
|
+
def fetch_gvc(a_gvc = gvc, a_org = org)
|
151
|
+
api.gvc_get(gvc: a_gvc, org: a_org)
|
152
|
+
end
|
153
|
+
|
154
|
+
def fetch_gvc!(a_gvc = gvc)
|
155
|
+
gvc_data = fetch_gvc(a_gvc)
|
156
|
+
return gvc_data if gvc_data
|
157
|
+
|
158
|
+
raise "Can't find app '#{gvc}', please create it with 'cpflow setup-app -a #{config.app}'."
|
159
|
+
end
|
160
|
+
|
161
|
+
def gvc_delete(a_gvc = gvc)
|
162
|
+
api.gvc_delete(gvc: a_gvc, org: org)
|
163
|
+
end
|
164
|
+
|
165
|
+
# workload
|
166
|
+
|
167
|
+
def fetch_workloads(a_gvc = gvc)
|
168
|
+
api.workload_list(gvc: a_gvc, org: org)
|
169
|
+
end
|
170
|
+
|
171
|
+
def fetch_workloads_by_org(a_org = org)
|
172
|
+
api.workload_list_by_org(org: a_org)
|
173
|
+
end
|
174
|
+
|
175
|
+
def fetch_workload(workload)
|
176
|
+
api.workload_get(workload: workload, gvc: gvc, org: org)
|
177
|
+
end
|
178
|
+
|
179
|
+
def fetch_workload!(workload)
|
180
|
+
workload_data = fetch_workload(workload)
|
181
|
+
return workload_data if workload_data
|
182
|
+
|
183
|
+
raise "Can't find workload '#{workload}', " \
|
184
|
+
"please create it with 'cpflow apply-template #{workload} -a #{config.app}'."
|
185
|
+
end
|
186
|
+
|
187
|
+
def query_workloads(workload, a_gvc = gvc, a_org = org, partial_workload_match: false, partial_gvc_match: nil)
|
188
|
+
partial_gvc_match = config.should_app_start_with?(a_gvc) if partial_gvc_match.nil?
|
189
|
+
gvc_op = partial_gvc_match ? "~" : "="
|
190
|
+
workload_op = partial_workload_match ? "~" : "="
|
191
|
+
|
192
|
+
api.query_workloads(org: a_org, gvc: a_gvc, workload: workload, gvc_op_type: gvc_op, workload_op_type: workload_op)
|
193
|
+
end
|
194
|
+
|
195
|
+
def fetch_workload_replicas(workload, location:)
|
196
|
+
cmd = "cpln workload replica get #{workload} #{gvc_org} --location #{location} -o yaml"
|
197
|
+
perform_yaml(cmd)
|
198
|
+
end
|
199
|
+
|
200
|
+
def stop_workload_replica(workload, replica, location:)
|
201
|
+
cmd = "cpln workload replica stop #{workload} #{gvc_org} --replica-name #{replica} --location #{location}"
|
202
|
+
perform(cmd, output_mode: :none)
|
203
|
+
end
|
204
|
+
|
205
|
+
def fetch_workload_deployments(workload)
|
206
|
+
api.workload_deployments(workload: workload, gvc: gvc, org: org)
|
207
|
+
end
|
208
|
+
|
209
|
+
def workload_deployment_version_ready?(version, next_version)
|
210
|
+
return false unless version["workload"] == next_version
|
211
|
+
|
212
|
+
version["containers"]&.all? do |_, container|
|
213
|
+
container.dig("resources", "replicas") == container.dig("resources", "replicasReady")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def workload_deployments_ready?(workload, location:, expected_status:)
|
218
|
+
deployed_replicas = fetch_workload_replicas(workload, location: location)["items"].length
|
219
|
+
return deployed_replicas.zero? if expected_status == false
|
220
|
+
|
221
|
+
deployments = fetch_workload_deployments(workload)["items"]
|
222
|
+
deployments.all? do |deployment|
|
223
|
+
next_version = deployment.dig("status", "expectedDeploymentVersion")
|
224
|
+
|
225
|
+
deployment.dig("status", "versions")&.all? do |version|
|
226
|
+
workload_deployment_version_ready?(version, next_version)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def workload_set_image_ref(workload, container:, image:)
|
232
|
+
cmd = "cpln workload update #{workload} #{gvc_org}"
|
233
|
+
cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
|
234
|
+
perform!(cmd)
|
235
|
+
end
|
236
|
+
|
237
|
+
def set_workload_env_var(workload, container:, name:, value:)
|
238
|
+
data = fetch_workload!(workload)
|
239
|
+
data["spec"]["containers"].each do |container_data|
|
240
|
+
next unless container_data["name"] == container
|
241
|
+
|
242
|
+
container_data["env"].each do |env_data|
|
243
|
+
next unless env_data["name"] == name
|
244
|
+
|
245
|
+
env_data["value"] = value
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
|
250
|
+
end
|
251
|
+
|
252
|
+
def set_workload_suspend(workload, value)
|
253
|
+
data = fetch_workload!(workload)
|
254
|
+
data["spec"]["defaultOptions"]["suspend"] = value
|
255
|
+
|
256
|
+
api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
|
257
|
+
end
|
258
|
+
|
259
|
+
def workload_suspended?(workload)
|
260
|
+
details = fetch_workload!(workload)
|
261
|
+
details["spec"]["defaultOptions"]["suspend"]
|
262
|
+
end
|
263
|
+
|
264
|
+
def workload_force_redeployment(workload)
|
265
|
+
cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
|
266
|
+
perform!(cmd)
|
267
|
+
end
|
268
|
+
|
269
|
+
def delete_workload(workload, a_gvc = gvc)
|
270
|
+
api.delete_workload(org: org, gvc: a_gvc, workload: workload)
|
271
|
+
end
|
272
|
+
|
273
|
+
def workload_connect(workload, location:, container: nil, shell: nil)
|
274
|
+
cmd = "cpln workload connect #{workload} #{gvc_org} --location #{location}"
|
275
|
+
cmd += " --container #{container}" if container
|
276
|
+
cmd += " --shell #{shell}" if shell
|
277
|
+
perform!(cmd, output_mode: :all)
|
278
|
+
end
|
279
|
+
|
280
|
+
def workload_exec(workload, replica, location:, container: nil, command: nil)
|
281
|
+
cmd = "cpln workload exec #{workload} #{gvc_org} --replica #{replica} --location #{location}"
|
282
|
+
cmd += " --container #{container}" if container
|
283
|
+
cmd += " -- #{command}"
|
284
|
+
perform!(cmd, output_mode: :all)
|
285
|
+
end
|
286
|
+
|
287
|
+
def start_cron_workload(workload, job_start_yaml, location:)
|
288
|
+
Tempfile.create do |f|
|
289
|
+
f.write(job_start_yaml)
|
290
|
+
f.rewind
|
291
|
+
|
292
|
+
cmd = "cpln workload cron start #{workload} #{gvc_org} --file #{f.path} --location #{location} -o yaml"
|
293
|
+
perform_yaml(cmd)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def fetch_cron_workload(workload, location:)
|
298
|
+
cmd = "cpln workload cron get #{workload} #{gvc_org} --location #{location} -o yaml"
|
299
|
+
perform_yaml(cmd)
|
300
|
+
end
|
301
|
+
|
302
|
+
def cron_workload_deployed_version(workload)
|
303
|
+
current_deployment = fetch_workload_deployments(workload)&.dig("items")&.first
|
304
|
+
return nil unless current_deployment
|
305
|
+
|
306
|
+
ready = current_deployment.dig("status", "ready")
|
307
|
+
last_processed_version = current_deployment.dig("status", "lastProcessedVersion")
|
308
|
+
|
309
|
+
ready ? last_processed_version : nil
|
310
|
+
end
|
311
|
+
|
312
|
+
# volumeset
|
313
|
+
|
314
|
+
def fetch_volumesets(a_gvc = gvc)
|
315
|
+
api.list_volumesets(org: org, gvc: a_gvc)
|
316
|
+
end
|
317
|
+
|
318
|
+
def delete_volumeset(volumeset, a_gvc = gvc)
|
319
|
+
api.delete_volumeset(org: org, gvc: a_gvc, volumeset: volumeset)
|
320
|
+
end
|
321
|
+
|
322
|
+
# domain
|
323
|
+
|
324
|
+
def find_domain_route(data)
|
325
|
+
port = data["spec"]["ports"].find { |current_port| current_port["number"] == 80 || current_port["number"] == 443 }
|
326
|
+
return nil if port.nil? || port["routes"].nil?
|
327
|
+
|
328
|
+
route = port["routes"].find { |current_route| current_route["prefix"] == "/" }
|
329
|
+
return nil if route.nil?
|
330
|
+
|
331
|
+
route
|
332
|
+
end
|
333
|
+
|
334
|
+
def find_domain_for(workloads)
|
335
|
+
domains = api.list_domains(org: org)["items"]
|
336
|
+
domains.find do |domain_data|
|
337
|
+
route = find_domain_route(domain_data)
|
338
|
+
next false if route.nil?
|
339
|
+
|
340
|
+
workloads.any? { |workload| route["workloadLink"].match?(%r{/org/#{org}/gvc/#{gvc}/workload/#{workload}}) }
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def fetch_domain(domain)
|
345
|
+
domain_data = api.fetch_domain(org: org, domain: domain)
|
346
|
+
route = find_domain_route(domain_data)
|
347
|
+
return nil if route.nil?
|
348
|
+
|
349
|
+
domain_data
|
350
|
+
end
|
351
|
+
|
352
|
+
def domain_workload_matches?(data, workload)
|
353
|
+
route = find_domain_route(data)
|
354
|
+
route["workloadLink"].match?(%r{/org/#{org}/gvc/#{gvc}/workload/#{workload}})
|
355
|
+
end
|
356
|
+
|
357
|
+
def set_domain_workload(data, workload)
|
358
|
+
route = find_domain_route(data)
|
359
|
+
route["workloadLink"] = "/org/#{org}/gvc/#{gvc}/workload/#{workload}"
|
360
|
+
|
361
|
+
api.update_domain(org: org, domain: data["name"], data: data)
|
362
|
+
end
|
363
|
+
|
364
|
+
# logs
|
365
|
+
|
366
|
+
def logs(workload:, limit:, since:, replica: nil)
|
367
|
+
query_parts = ["gvc=\"#{gvc}\"", "workload=\"#{workload}\""]
|
368
|
+
query_parts.push("replica=\"#{replica}\"") if replica
|
369
|
+
query = "{#{query_parts.join(',')}}"
|
370
|
+
|
371
|
+
cmd = "cpln logs '#{query}' --org #{org} -t -o raw --limit #{limit} --since #{since}"
|
372
|
+
perform!(cmd, output_mode: :all)
|
373
|
+
end
|
374
|
+
|
375
|
+
def log_get(workload:, from:, to:, replica: nil)
|
376
|
+
api.log_get(org: org, gvc: gvc, workload: workload, replica: replica, from: from, to: to)
|
377
|
+
end
|
378
|
+
|
379
|
+
# secrets
|
380
|
+
|
381
|
+
def fetch_secret(secret)
|
382
|
+
api.fetch_secret(org: org, secret: secret)
|
383
|
+
end
|
384
|
+
|
385
|
+
# identities
|
386
|
+
|
387
|
+
def fetch_identity(identity, a_gvc = gvc)
|
388
|
+
api.fetch_identity(org: org, gvc: a_gvc, identity: identity)
|
389
|
+
end
|
390
|
+
|
391
|
+
# policies
|
392
|
+
|
393
|
+
def fetch_policy(policy)
|
394
|
+
api.fetch_policy(org: org, policy: policy)
|
395
|
+
end
|
396
|
+
|
397
|
+
def bind_identity_to_policy(identity_link, policy)
|
398
|
+
cmd = "cpln policy add-binding #{policy} --org #{org} --identity #{identity_link} --permission reveal"
|
399
|
+
perform!(cmd)
|
400
|
+
end
|
401
|
+
|
402
|
+
def unbind_identity_from_policy(identity_link, policy)
|
403
|
+
cmd = "cpln policy remove-binding #{policy} --org #{org} --identity #{identity_link} --permission reveal"
|
404
|
+
perform!(cmd)
|
405
|
+
end
|
406
|
+
|
407
|
+
# apply
|
408
|
+
def apply_template(data) # rubocop:disable Metrics/MethodLength
|
409
|
+
Tempfile.create do |f|
|
410
|
+
f.write(data)
|
411
|
+
f.rewind
|
412
|
+
cmd = "cpln apply #{gvc_org} --file #{f.path}"
|
413
|
+
if Shell.tmp_stderr
|
414
|
+
cmd += " 2> #{Shell.tmp_stderr.path}" if Shell.should_hide_output?
|
415
|
+
|
416
|
+
Shell.debug("CMD", cmd)
|
417
|
+
|
418
|
+
result = Shell.cmd(cmd)
|
419
|
+
parse_apply_result(result[:output]) if result[:success]
|
420
|
+
else
|
421
|
+
Shell.debug("CMD", cmd)
|
422
|
+
|
423
|
+
result = Shell.cmd(cmd)
|
424
|
+
if result[:success]
|
425
|
+
parse_apply_result(result[:output])
|
426
|
+
else
|
427
|
+
Shell.abort("Command exited with non-zero status.")
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def apply_hash(data)
|
434
|
+
apply_template(data.to_yaml)
|
435
|
+
end
|
436
|
+
|
437
|
+
def parse_apply_result(result) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
438
|
+
items = []
|
439
|
+
|
440
|
+
lines = result.split("\n")
|
441
|
+
lines.each do |line|
|
442
|
+
# The line can be in one of these formats:
|
443
|
+
# - "Created /org/shakacode-open-source-examples/gvc/my-app-staging"
|
444
|
+
# - "Created /org/shakacode-open-source-examples/gvc/my-app-staging/workload/redis"
|
445
|
+
# - "Updated gvc 'tutorial-app-test-1'"
|
446
|
+
# - "Updated workload 'redis'"
|
447
|
+
if line.start_with?("Created")
|
448
|
+
matches = line.match(%r{Created\s/org/[^/]+/gvc/([^/]+)($|(/([^/]+)/([^/]+)$))})&.captures
|
449
|
+
next unless matches
|
450
|
+
|
451
|
+
app, _, __, kind, name = matches
|
452
|
+
if kind
|
453
|
+
items.push({ kind: kind, name: name })
|
454
|
+
else
|
455
|
+
items.push({ kind: "app", name: app })
|
456
|
+
end
|
457
|
+
else
|
458
|
+
matches = line.match(/Updated\s([^\s]+)\s'([^\s]+)'$/)&.captures
|
459
|
+
next unless matches
|
460
|
+
|
461
|
+
kind, name = matches
|
462
|
+
kind = "app" if kind == "gvc"
|
463
|
+
items.push({ kind: kind, name: name })
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
items
|
468
|
+
end
|
469
|
+
|
470
|
+
private
|
471
|
+
|
472
|
+
def org_exists?
|
473
|
+
items = api.list_orgs["items"]
|
474
|
+
items.any? { |item| item["name"] == org }
|
475
|
+
end
|
476
|
+
|
477
|
+
def ensure_org_exists!
|
478
|
+
return if org_exists?
|
479
|
+
|
480
|
+
raise "Can't find org '#{org}', please create it in the Control Plane dashboard " \
|
481
|
+
"or ensure that the name is correct."
|
482
|
+
end
|
483
|
+
|
484
|
+
# `output_mode` can be :all, :errors_only or :none.
|
485
|
+
# If not provided, it will be determined based on the `HIDE_COMMAND_OUTPUT` env var
|
486
|
+
# or the return value of `Shell.should_hide_output?`.
|
487
|
+
def build_command(cmd, output_mode: nil) # rubocop:disable Metrics/MethodLength
|
488
|
+
output_mode ||= determine_command_output_mode
|
489
|
+
|
490
|
+
case output_mode
|
491
|
+
when :all
|
492
|
+
cmd
|
493
|
+
when :errors_only
|
494
|
+
"#{cmd} > /dev/null"
|
495
|
+
when :none
|
496
|
+
"#{cmd} > /dev/null 2>&1"
|
497
|
+
else
|
498
|
+
raise "Invalid command output mode '#{output_mode}'."
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def determine_command_output_mode
|
503
|
+
if ENV.fetch("HIDE_COMMAND_OUTPUT", nil) == "true"
|
504
|
+
:none
|
505
|
+
elsif Shell.should_hide_output?
|
506
|
+
:errors_only
|
507
|
+
else
|
508
|
+
:all
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def perform(cmd, output_mode: nil, sensitive_data_pattern: nil)
|
513
|
+
cmd = build_command(cmd, output_mode: output_mode)
|
514
|
+
|
515
|
+
Shell.debug("CMD", cmd, sensitive_data_pattern: sensitive_data_pattern)
|
516
|
+
|
517
|
+
kernel_system_with_pid_handling(cmd)
|
518
|
+
end
|
519
|
+
|
520
|
+
# NOTE: full analogue of Kernel.system which returns pids and saves it to child_pids for proper killing
|
521
|
+
def kernel_system_with_pid_handling(cmd)
|
522
|
+
pid = Process.spawn(cmd)
|
523
|
+
$child_pids << pid # rubocop:disable Style/GlobalVars
|
524
|
+
|
525
|
+
_, status = Process.wait2(pid)
|
526
|
+
$child_pids.delete(pid) # rubocop:disable Style/GlobalVars
|
527
|
+
|
528
|
+
status.exited? ? status.success? : nil
|
529
|
+
rescue SystemCallError
|
530
|
+
nil
|
531
|
+
end
|
532
|
+
|
533
|
+
def perform!(cmd, output_mode: nil, sensitive_data_pattern: nil)
|
534
|
+
success = perform(cmd, output_mode: output_mode, sensitive_data_pattern: sensitive_data_pattern)
|
535
|
+
success || Shell.abort("Command exited with non-zero status.")
|
536
|
+
end
|
537
|
+
|
538
|
+
def perform_yaml(cmd)
|
539
|
+
Shell.debug("CMD", cmd)
|
540
|
+
|
541
|
+
result = Shell.cmd(cmd)
|
542
|
+
YAML.safe_load(result[:output], permitted_classes: [Time]) if result[:success]
|
543
|
+
end
|
544
|
+
|
545
|
+
def perform_yaml!(cmd)
|
546
|
+
perform_yaml(cmd) || Shell.abort("Command exited with non-zero status.")
|
547
|
+
end
|
548
|
+
|
549
|
+
def gvc_org
|
550
|
+
"--gvc #{gvc} --org #{org}"
|
551
|
+
end
|
552
|
+
end
|