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
data/lib/command/base.rb
ADDED
@@ -0,0 +1,540 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../core/helpers"
|
4
|
+
|
5
|
+
module Command
|
6
|
+
class Base # rubocop:disable Metrics/ClassLength
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
include Helpers
|
10
|
+
|
11
|
+
VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS = %w[config].freeze
|
12
|
+
VALIDATIONS_WITH_ADDITIONAL_OPTIONS = %w[templates].freeze
|
13
|
+
ALL_VALIDATIONS = VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS + VALIDATIONS_WITH_ADDITIONAL_OPTIONS
|
14
|
+
|
15
|
+
# Used to call the command (`cpflow NAME`)
|
16
|
+
# NAME = ""
|
17
|
+
# Displayed when running `cpflow help` or `cpflow help NAME` (defaults to `NAME`)
|
18
|
+
USAGE = ""
|
19
|
+
# Throws error if `true` and no arguments are passed to the command
|
20
|
+
# or if `false` and arguments are passed to the command
|
21
|
+
REQUIRES_ARGS = false
|
22
|
+
# Default arguments if none are passed to the command
|
23
|
+
DEFAULT_ARGS = [].freeze
|
24
|
+
# Options for the command (use option methods below)
|
25
|
+
OPTIONS = [].freeze
|
26
|
+
# Does not throw error if `true` and extra options
|
27
|
+
# that are not specified in `OPTIONS` are passed to the command
|
28
|
+
ACCEPTS_EXTRA_OPTIONS = false
|
29
|
+
# Displayed when running `cpflow help`
|
30
|
+
# DESCRIPTION = ""
|
31
|
+
# Displayed when running `cpflow help NAME`
|
32
|
+
# LONG_DESCRIPTION = ""
|
33
|
+
# Displayed along with `LONG_DESCRIPTION` when running `cpflow help NAME`
|
34
|
+
EXAMPLES = ""
|
35
|
+
# If `true`, hides the command from `cpflow help`
|
36
|
+
HIDE = false
|
37
|
+
# Whether or not to show key information like ORG and APP name in commands
|
38
|
+
WITH_INFO_HEADER = true
|
39
|
+
# Which validations to run before the command
|
40
|
+
VALIDATIONS = %w[config].freeze
|
41
|
+
|
42
|
+
def initialize(config)
|
43
|
+
@config = config
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.all_commands
|
47
|
+
Dir["#{__dir__}/*.rb"].each_with_object({}) do |file, result|
|
48
|
+
filename = File.basename(file, ".rb")
|
49
|
+
classname = File.read(file).match(/^\s+class (\w+) < Base($| .*$)/)&.captures&.first
|
50
|
+
result[filename.to_sym] = Object.const_get("::Command::#{classname}") if classname
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.common_options
|
55
|
+
[org_option, verbose_option, trace_option]
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/MethodLength
|
59
|
+
def self.org_option(required: false)
|
60
|
+
{
|
61
|
+
name: :org,
|
62
|
+
params: {
|
63
|
+
aliases: ["-o"],
|
64
|
+
banner: "ORG_NAME",
|
65
|
+
desc: "Organization name",
|
66
|
+
type: :string,
|
67
|
+
required: required
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.app_option(required: false)
|
73
|
+
{
|
74
|
+
name: :app,
|
75
|
+
params: {
|
76
|
+
aliases: ["-a"],
|
77
|
+
banner: "APP_NAME",
|
78
|
+
desc: "Application name",
|
79
|
+
type: :string,
|
80
|
+
required: required
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.workload_option(required: false)
|
86
|
+
{
|
87
|
+
name: :workload,
|
88
|
+
params: {
|
89
|
+
aliases: ["-w"],
|
90
|
+
banner: "WORKLOAD_NAME",
|
91
|
+
desc: "Workload name",
|
92
|
+
type: :string,
|
93
|
+
required: required
|
94
|
+
}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.replica_option(required: false)
|
99
|
+
{
|
100
|
+
name: :replica,
|
101
|
+
params: {
|
102
|
+
aliases: ["-r"],
|
103
|
+
banner: "REPLICA_NAME",
|
104
|
+
desc: "Replica name",
|
105
|
+
type: :string,
|
106
|
+
required: required
|
107
|
+
}
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.image_option(required: false)
|
112
|
+
{
|
113
|
+
name: :image,
|
114
|
+
params: {
|
115
|
+
aliases: ["-i"],
|
116
|
+
banner: "IMAGE_NAME",
|
117
|
+
desc: "Image name",
|
118
|
+
type: :string,
|
119
|
+
required: required
|
120
|
+
}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.log_method_option(required: false)
|
125
|
+
{
|
126
|
+
name: :log_method,
|
127
|
+
params: {
|
128
|
+
type: :numeric,
|
129
|
+
banner: "LOG_METHOD",
|
130
|
+
desc: "Log method",
|
131
|
+
required: required,
|
132
|
+
valid_values: [1, 2, 3],
|
133
|
+
default: 3
|
134
|
+
}
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.commit_option(required: false)
|
139
|
+
{
|
140
|
+
name: :commit,
|
141
|
+
params: {
|
142
|
+
aliases: ["-c"],
|
143
|
+
banner: "COMMIT_HASH",
|
144
|
+
desc: "Commit hash",
|
145
|
+
type: :string,
|
146
|
+
required: required
|
147
|
+
}
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.location_option(required: false)
|
152
|
+
{
|
153
|
+
name: :location,
|
154
|
+
params: {
|
155
|
+
aliases: ["-l"],
|
156
|
+
banner: "LOCATION_NAME",
|
157
|
+
desc: "Location name",
|
158
|
+
type: :string,
|
159
|
+
required: required
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.domain_option(required: false)
|
165
|
+
{
|
166
|
+
name: :domain,
|
167
|
+
params: {
|
168
|
+
banner: "DOMAIN_NAME",
|
169
|
+
desc: "Domain name",
|
170
|
+
type: :string,
|
171
|
+
required: required
|
172
|
+
}
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.upstream_token_option(required: false)
|
177
|
+
{
|
178
|
+
name: :upstream_token,
|
179
|
+
params: {
|
180
|
+
aliases: ["-t"],
|
181
|
+
banner: "UPSTREAM_TOKEN",
|
182
|
+
desc: "Upstream token",
|
183
|
+
type: :string,
|
184
|
+
required: required
|
185
|
+
}
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.skip_confirm_option(required: false)
|
190
|
+
{
|
191
|
+
name: :yes,
|
192
|
+
params: {
|
193
|
+
aliases: ["-y"],
|
194
|
+
banner: "SKIP_CONFIRM",
|
195
|
+
desc: "Skip confirmation",
|
196
|
+
type: :boolean,
|
197
|
+
required: required
|
198
|
+
}
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.version_option(required: false)
|
203
|
+
{
|
204
|
+
name: :version,
|
205
|
+
params: {
|
206
|
+
aliases: ["-v"],
|
207
|
+
banner: "VERSION",
|
208
|
+
desc: "Displays the current version of the CLI",
|
209
|
+
type: :boolean,
|
210
|
+
required: required
|
211
|
+
}
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.use_local_token_option(required: false)
|
216
|
+
{
|
217
|
+
name: :use_local_token,
|
218
|
+
params: {
|
219
|
+
desc: "Override remote CPLN_TOKEN with local token",
|
220
|
+
type: :boolean,
|
221
|
+
required: required
|
222
|
+
}
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.terminal_size_option(required: false)
|
227
|
+
{
|
228
|
+
name: :terminal_size,
|
229
|
+
params: {
|
230
|
+
banner: "ROWS,COLS",
|
231
|
+
desc: "Override remote terminal size (e.g. `--terminal-size 10,20`)",
|
232
|
+
type: :string,
|
233
|
+
required: required,
|
234
|
+
valid_regex: /^\d+,\d+$/
|
235
|
+
}
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.wait_option(title = "", required: false)
|
240
|
+
{
|
241
|
+
name: :wait,
|
242
|
+
params: {
|
243
|
+
desc: "Waits for #{title}",
|
244
|
+
type: :boolean,
|
245
|
+
required: required
|
246
|
+
}
|
247
|
+
}
|
248
|
+
end
|
249
|
+
|
250
|
+
def self.verbose_option(required: false)
|
251
|
+
{
|
252
|
+
name: :verbose,
|
253
|
+
params: {
|
254
|
+
aliases: ["-d"],
|
255
|
+
desc: "Shows detailed logs",
|
256
|
+
type: :boolean,
|
257
|
+
required: required
|
258
|
+
}
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.trace_option(required: false)
|
263
|
+
{
|
264
|
+
name: :trace,
|
265
|
+
params: {
|
266
|
+
desc: "Shows trace of API calls. WARNING: may contain sensitive data",
|
267
|
+
type: :boolean,
|
268
|
+
required: required
|
269
|
+
}
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.skip_secret_access_binding_option(required: false)
|
274
|
+
{
|
275
|
+
name: :skip_secret_access_binding,
|
276
|
+
new_name: :skip_secrets_setup,
|
277
|
+
params: {
|
278
|
+
desc: "Skips secret access binding",
|
279
|
+
type: :boolean,
|
280
|
+
required: required
|
281
|
+
}
|
282
|
+
}
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.skip_secrets_setup_option(required: false)
|
286
|
+
{
|
287
|
+
name: :skip_secrets_setup,
|
288
|
+
params: {
|
289
|
+
desc: "Skips secrets setup",
|
290
|
+
type: :boolean,
|
291
|
+
required: required
|
292
|
+
}
|
293
|
+
}
|
294
|
+
end
|
295
|
+
|
296
|
+
def self.run_release_phase_option(required: false)
|
297
|
+
{
|
298
|
+
name: :run_release_phase,
|
299
|
+
params: {
|
300
|
+
desc: "Runs release phase",
|
301
|
+
type: :boolean,
|
302
|
+
required: required
|
303
|
+
}
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.logs_limit_option(required: false)
|
308
|
+
{
|
309
|
+
name: :limit,
|
310
|
+
params: {
|
311
|
+
banner: "NUMBER",
|
312
|
+
desc: "Limit on number of log entries to show",
|
313
|
+
type: :numeric,
|
314
|
+
required: required,
|
315
|
+
default: 200
|
316
|
+
}
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.logs_since_option(required: false)
|
321
|
+
{
|
322
|
+
name: :since,
|
323
|
+
params: {
|
324
|
+
banner: "DURATION",
|
325
|
+
desc: "Loopback window for showing logs " \
|
326
|
+
"(see https://www.npmjs.com/package/parse-duration for the accepted formats, e.g., '1h')",
|
327
|
+
type: :string,
|
328
|
+
required: required,
|
329
|
+
default: "1h"
|
330
|
+
}
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.interactive_option(required: false)
|
335
|
+
{
|
336
|
+
name: :interactive,
|
337
|
+
params: {
|
338
|
+
desc: "Runs interactive command",
|
339
|
+
type: :boolean,
|
340
|
+
required: required
|
341
|
+
}
|
342
|
+
}
|
343
|
+
end
|
344
|
+
|
345
|
+
def self.detached_option(required: false)
|
346
|
+
{
|
347
|
+
name: :detached,
|
348
|
+
params: {
|
349
|
+
desc: "Runs non-interactive command, detaches, and prints commands to log and stop the job",
|
350
|
+
type: :boolean,
|
351
|
+
required: required
|
352
|
+
}
|
353
|
+
}
|
354
|
+
end
|
355
|
+
|
356
|
+
def self.cpu_option(required: false)
|
357
|
+
{
|
358
|
+
name: :cpu,
|
359
|
+
params: {
|
360
|
+
banner: "CPU",
|
361
|
+
desc: "Overrides CPU millicores " \
|
362
|
+
"(e.g., '100m' for 100 millicores, '1' for 1 core)",
|
363
|
+
type: :string,
|
364
|
+
required: required,
|
365
|
+
valid_regex: /^\d+m?$/
|
366
|
+
}
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.memory_option(required: false)
|
371
|
+
{
|
372
|
+
name: :memory,
|
373
|
+
params: {
|
374
|
+
banner: "MEMORY",
|
375
|
+
desc: "Overrides memory size " \
|
376
|
+
"(e.g., '100Mi' for 100 mebibytes, '1Gi' for 1 gibibyte)",
|
377
|
+
type: :string,
|
378
|
+
required: required,
|
379
|
+
valid_regex: /^\d+[MG]i$/
|
380
|
+
}
|
381
|
+
}
|
382
|
+
end
|
383
|
+
|
384
|
+
def self.entrypoint_option(required: false)
|
385
|
+
{
|
386
|
+
name: :entrypoint,
|
387
|
+
params: {
|
388
|
+
banner: "ENTRYPOINT",
|
389
|
+
desc: "Overrides entrypoint " \
|
390
|
+
"(must be a single command or a script path that exists in the container)",
|
391
|
+
type: :string,
|
392
|
+
required: required,
|
393
|
+
valid_regex: /^\S+$/
|
394
|
+
}
|
395
|
+
}
|
396
|
+
end
|
397
|
+
|
398
|
+
def self.validations_option(required: false)
|
399
|
+
{
|
400
|
+
name: :validations,
|
401
|
+
params: {
|
402
|
+
banner: "VALIDATION_1,VALIDATION_2,...",
|
403
|
+
desc: "Which validations to run " \
|
404
|
+
"(must be separated by a comma)",
|
405
|
+
type: :string,
|
406
|
+
required: required,
|
407
|
+
default: VALIDATIONS_WITHOUT_ADDITIONAL_OPTIONS.join(","),
|
408
|
+
valid_regex: /^(#{ALL_VALIDATIONS.join("|")})(,(#{ALL_VALIDATIONS.join("|")}))*$/
|
409
|
+
}
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
def self.skip_post_creation_hook_option(required: false)
|
414
|
+
{
|
415
|
+
name: :skip_post_creation_hook,
|
416
|
+
params: {
|
417
|
+
desc: "Skips post-creation hook",
|
418
|
+
type: :boolean,
|
419
|
+
required: required
|
420
|
+
}
|
421
|
+
}
|
422
|
+
end
|
423
|
+
|
424
|
+
def self.skip_pre_deletion_hook_option(required: false)
|
425
|
+
{
|
426
|
+
name: :skip_pre_deletion_hook,
|
427
|
+
params: {
|
428
|
+
desc: "Skips pre-deletion hook",
|
429
|
+
type: :boolean,
|
430
|
+
required: required
|
431
|
+
}
|
432
|
+
}
|
433
|
+
end
|
434
|
+
|
435
|
+
def self.add_app_identity_option(required: false)
|
436
|
+
{
|
437
|
+
name: :add_app_identity,
|
438
|
+
params: {
|
439
|
+
desc: "Adds app identity template if it does not exist",
|
440
|
+
type: :boolean,
|
441
|
+
required: required
|
442
|
+
}
|
443
|
+
}
|
444
|
+
end
|
445
|
+
# rubocop:enable Metrics/MethodLength
|
446
|
+
|
447
|
+
def self.all_options
|
448
|
+
methods.grep(/_option$/).map { |method| send(method.to_s) }
|
449
|
+
end
|
450
|
+
|
451
|
+
def self.all_options_by_key_name
|
452
|
+
all_options.each_with_object({}) do |option, result|
|
453
|
+
option[:params][:aliases]&.each { |current_alias| result[current_alias.to_s] = option }
|
454
|
+
result["--#{option[:name]}"] = option
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# NOTE: use simplified variant atm, as shelljoin do different escaping
|
459
|
+
# TODO: most probably need better logic for escaping various quotes
|
460
|
+
def args_join(args)
|
461
|
+
args.join(" ")
|
462
|
+
end
|
463
|
+
|
464
|
+
def progress
|
465
|
+
$stderr
|
466
|
+
end
|
467
|
+
|
468
|
+
def step_error(error, abort_on_error: true)
|
469
|
+
message = error.message
|
470
|
+
if abort_on_error
|
471
|
+
progress.puts(" #{Shell.color('failed!', :red)}\n\n")
|
472
|
+
Shell.abort(message)
|
473
|
+
else
|
474
|
+
Shell.write_to_tmp_stderr(message)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def step_finish(success)
|
479
|
+
if success
|
480
|
+
progress.puts(" #{Shell.color('done!', :green)}")
|
481
|
+
else
|
482
|
+
progress.puts(" #{Shell.color('failed!', :red)}\n\n#{Shell.read_from_tmp_stderr}\n\n")
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def step(message, abort_on_error: true, retry_on_failure: false) # rubocop:disable Metrics/MethodLength
|
487
|
+
progress.print("#{message}...")
|
488
|
+
|
489
|
+
Shell.use_tmp_stderr do
|
490
|
+
success = false
|
491
|
+
|
492
|
+
begin
|
493
|
+
if retry_on_failure
|
494
|
+
until (success = yield)
|
495
|
+
progress.print(".")
|
496
|
+
Kernel.sleep(1)
|
497
|
+
end
|
498
|
+
else
|
499
|
+
success = yield
|
500
|
+
end
|
501
|
+
rescue RuntimeError => e
|
502
|
+
step_error(e, abort_on_error: abort_on_error)
|
503
|
+
end
|
504
|
+
|
505
|
+
step_finish(success)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def cp
|
510
|
+
@cp ||= Controlplane.new(config)
|
511
|
+
end
|
512
|
+
|
513
|
+
def ensure_docker_running!
|
514
|
+
result = Shell.cmd("docker", "version", capture_stderr: true)
|
515
|
+
return if result[:success]
|
516
|
+
|
517
|
+
raise "Can't run Docker. Please make sure that it's installed and started, then try again."
|
518
|
+
end
|
519
|
+
|
520
|
+
def run_command_in_latest_image(command, title:)
|
521
|
+
# Need to prefix the command with '.controlplane/'
|
522
|
+
# if it's a file in the '.controlplane' directory,
|
523
|
+
# for backwards compatibility
|
524
|
+
path = Pathname.new("#{config.app_cpln_dir}/#{command}").expand_path
|
525
|
+
command = ".controlplane/#{command}" if File.exist?(path)
|
526
|
+
|
527
|
+
progress.puts("Running #{title}...\n\n")
|
528
|
+
|
529
|
+
begin
|
530
|
+
Cpflow::Cli.start(["run", "-a", config.app, "--image", "latest", "--", command])
|
531
|
+
rescue SystemExit => e
|
532
|
+
progress.puts
|
533
|
+
|
534
|
+
raise "Failed to run #{title}." if e.status.nonzero?
|
535
|
+
|
536
|
+
progress.puts("Finished running #{title}.\n\n")
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class BuildImage < Base
|
5
|
+
NAME = "build-image"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
commit_option
|
9
|
+
].freeze
|
10
|
+
ACCEPTS_EXTRA_OPTIONS = true
|
11
|
+
DESCRIPTION = "Builds and pushes the image to Control Plane"
|
12
|
+
LONG_DESCRIPTION = <<~DESC
|
13
|
+
- Builds and pushes the image to Control Plane
|
14
|
+
- Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
|
15
|
+
- Uses `.controlplane/Dockerfile` or a different Dockerfile specified through `dockerfile` in the `.controlplane/controlplane.yml` file
|
16
|
+
- If a commit is provided through `--commit` or `-c`, it will be set as the runtime env var `GIT_COMMIT`
|
17
|
+
- Accepts extra options that are passed to `docker build`
|
18
|
+
DESC
|
19
|
+
|
20
|
+
def call # rubocop:disable Metrics/MethodLength
|
21
|
+
ensure_docker_running!
|
22
|
+
|
23
|
+
dockerfile = config.current[:dockerfile] || "Dockerfile"
|
24
|
+
dockerfile = "#{config.app_cpln_dir}/#{dockerfile}"
|
25
|
+
|
26
|
+
raise "Can't find Dockerfile at '#{dockerfile}'." unless File.exist?(dockerfile)
|
27
|
+
|
28
|
+
progress.puts("Building image from Dockerfile '#{dockerfile}'...\n\n")
|
29
|
+
|
30
|
+
image_name = cp.latest_image_next
|
31
|
+
image_url = "#{config.org}.registry.cpln.io/#{image_name}"
|
32
|
+
|
33
|
+
commit = config.options[:commit]
|
34
|
+
build_args = []
|
35
|
+
build_args.push("GIT_COMMIT=#{commit}") if commit
|
36
|
+
|
37
|
+
cp.image_build(image_url, dockerfile: dockerfile,
|
38
|
+
docker_args: config.args,
|
39
|
+
build_args: build_args)
|
40
|
+
|
41
|
+
progress.puts("\nPushed image to '/org/#{config.org}/image/#{image_name}'.\n\n")
|
42
|
+
|
43
|
+
step("Waiting for image to be available", retry_on_failure: true) do
|
44
|
+
images = cp.query_images["items"]
|
45
|
+
images.find { |image| image["name"] == image_name }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|