cpl 0.4.1 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.overcommit.yml +10 -0
  3. data/Gemfile.lock +10 -3
  4. data/README.md +6 -0
  5. data/cpl.gemspec +1 -0
  6. data/docs/commands.md +51 -3
  7. data/googlee2da545df05d92f9.html +1 -0
  8. data/lib/command/base.rb +65 -7
  9. data/lib/command/build_image.rb +6 -5
  10. data/lib/command/cleanup_old_images.rb +8 -7
  11. data/lib/command/cleanup_stale_apps.rb +11 -9
  12. data/lib/command/config.rb +30 -15
  13. data/lib/command/copy_image_from_upstream.rb +110 -0
  14. data/lib/command/delete.rb +10 -12
  15. data/lib/command/deploy_image.rb +18 -7
  16. data/lib/command/env.rb +2 -2
  17. data/lib/command/exists.rb +4 -4
  18. data/lib/command/info.rb +233 -0
  19. data/lib/command/latest_image.rb +2 -2
  20. data/lib/command/logs.rb +4 -4
  21. data/lib/command/no_command.rb +3 -3
  22. data/lib/command/open.rb +4 -4
  23. data/lib/command/promote_app_from_upstream.rb +58 -0
  24. data/lib/command/ps.rb +10 -13
  25. data/lib/command/ps_restart.rb +9 -6
  26. data/lib/command/ps_start.rb +7 -6
  27. data/lib/command/ps_stop.rb +7 -6
  28. data/lib/command/run.rb +5 -5
  29. data/lib/command/run_detached.rb +7 -5
  30. data/lib/command/setup.rb +71 -13
  31. data/lib/command/test.rb +2 -2
  32. data/lib/command/version.rb +2 -2
  33. data/lib/core/config.rb +26 -19
  34. data/lib/core/controlplane.rb +77 -11
  35. data/lib/core/controlplane_api.rb +12 -0
  36. data/lib/core/controlplane_api_cli.rb +1 -1
  37. data/lib/core/controlplane_api_direct.rb +2 -2
  38. data/lib/core/shell.rb +25 -3
  39. data/lib/cpl/version.rb +1 -1
  40. data/lib/cpl.rb +19 -10
  41. data/lib/deprecated_commands.json +6 -0
  42. data/script/add_command +37 -0
  43. data/script/generate_commands_docs +5 -5
  44. data/script/rename_command +43 -0
  45. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa61e0b16e85c21a6a16d6fee067a5d68182644051b287ead7d62e9030436848
4
- data.tar.gz: 6b4fd76fcb7255930257315dd297024d3527ea4e686badb79d3f1ff4a8c66613
3
+ metadata.gz: c5989a3c887eb311dcfb8bffe88e8519172b21efd21e715c363e7a166b73dbc3
4
+ data.tar.gz: b9fc9896cdd99a38b89389866f0f0825cb3944099e4579f4641ee221f2302a92
5
5
  SHA512:
6
- metadata.gz: 5d3a5bd08d10284b4ff9825764a8198f9bda5eedc9164dcb6eb16423b14ca9f09c6124410d252496b5807824666adfd4d1d88a22ea5d6adbc7bbb527fa8914e6
7
- data.tar.gz: fa98e16499b0fa92e5c16761d6e7aa2584498a5aebed35dfdb2eefff66bb1840224f55680d9a863c779ea8fb965a3aa10df380c1cbdbca24f90c21c3a0fbaad6
6
+ metadata.gz: c73027a84228c2371999bdff122d3bab5a3970d5f11c54f55a4b47a0fae77f83903e8d5702ecdd6baa5ac9fe6346f8e433f395a6fc3e22ee3a7225ed661038c3
7
+ data.tar.gz: a2734ae8f9969be0d523dd547783284774ad461fa1dee36174a5c6bc6b653ec7a020c2c07301e31c787d5d4f7b04c4a6d983fe1e544d2d20410a54f19896d7ff
data/.overcommit.yml ADDED
@@ -0,0 +1,10 @@
1
+ PreCommit:
2
+ RuboCop:
3
+ enabled: true
4
+ on_warn: fail
5
+ command: ["bundle", "exec", "rubocop"]
6
+
7
+ PrePush:
8
+ RSpec:
9
+ enabled: true
10
+ command: ["bundle", "exec", "rspec"]
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpl (0.4.1)
4
+ cpl (0.5.1)
5
5
  debug (~> 1.7.1)
6
6
  dotenv (~> 2.8.1)
7
7
  psych (~> 5.1.0)
@@ -11,16 +11,22 @@ GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
13
  ast (2.4.2)
14
- debug (1.7.1)
14
+ childprocess (4.1.0)
15
+ debug (1.7.2)
15
16
  irb (>= 1.5.0)
16
17
  reline (>= 0.3.1)
17
18
  diff-lcs (1.5.0)
18
19
  docile (1.4.0)
19
20
  dotenv (2.8.1)
21
+ iniparse (1.5.0)
20
22
  io-console (0.6.0)
21
23
  irb (1.6.3)
22
24
  reline (>= 0.3.0)
23
25
  json (2.6.3)
26
+ overcommit (0.60.0)
27
+ childprocess (>= 0.6.3, < 5)
28
+ iniparse (~> 1.4)
29
+ rexml (~> 3.2)
24
30
  parallel (1.22.1)
25
31
  parser (3.2.0.0)
26
32
  ast (~> 2.4.1)
@@ -29,7 +35,7 @@ GEM
29
35
  rainbow (3.1.1)
30
36
  rake (13.0.6)
31
37
  regexp_parser (2.6.2)
32
- reline (0.3.2)
38
+ reline (0.3.3)
33
39
  io-console (~> 0.5)
34
40
  rexml (3.2.5)
35
41
  rspec (3.12.0)
@@ -81,6 +87,7 @@ PLATFORMS
81
87
 
82
88
  DEPENDENCIES
83
89
  cpl!
90
+ overcommit (~> 0.60.0)
84
91
  rake (~> 13.0)
85
92
  rspec (~> 3.12.0)
86
93
  rubocop (~> 1.45.0)
data/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Heroku to Control Plane `cpl` CLI
2
2
 
3
+ <meta name="author" content="Justin Gordon and Sergey Tarasov">
4
+ <meta name="description" content="Instructions on how to migrate from Heroku to Control Plane and a CLI called cpl to make it easier.">
5
+ <meta name="copyright" content="ShakaCode, 2023">
6
+ <meta name="keywords" content="Control Plane, Heroku, Kubernetes, K8, Infrastructure">
7
+ <meta name="google-site-verification" content="dIV4nMplcYl6YOKOaZMqgvdKXhLJ4cdYY6pS6e_YrPU" />
8
+
3
9
  _A playbook for migrating from [Heroku](https://heroku.com) to [Control Plane](https://controlplane.com)_
4
10
 
5
11
  [![RSpec](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml/badge.svg)](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml)
data/cpl.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency "psych", "~> 5.1.0"
21
21
  spec.add_dependency "thor", "~> 1.2.1"
22
22
 
23
+ spec.add_development_dependency "overcommit", "~> 0.60.0"
23
24
  spec.add_development_dependency "rspec", "~> 3.12.0"
24
25
  spec.add_development_dependency "rubocop", "~> 1.45.0"
25
26
  spec.add_development_dependency "rubocop-rake", "~> 0.6.0"
data/docs/commands.md CHANGED
@@ -46,16 +46,31 @@ cpl cleanup-stale-apps -a $APP_NAME
46
46
 
47
47
  ### `config`
48
48
 
49
- - Displays current configs (global and app-specific)
49
+ - Displays config for each app or a specific app
50
50
 
51
51
  ```sh
52
- # Shows the global config.
52
+ # Shows the config for each app.
53
53
  cpl config
54
54
 
55
- # Shows both global and app-specific configs.
55
+ # Shows the config for a specific app.
56
56
  cpl config -a $APP_NAME
57
57
  ```
58
58
 
59
+ ### `copy-image-from-upstream`
60
+
61
+ - Copies an image (by default the latest) from a source org to the current org
62
+ - The source org must be specified through `upstream` in the `.controlplane/controlplane.yml` file
63
+ - Additionally, the token for the source org must be provided through `--upstream-token` or `-t`
64
+ - A `cpln` profile will be temporarily created to pull the image from the source org
65
+
66
+ ```sh
67
+ # Copies the latest image from the source org to the current org.
68
+ cpl copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN
69
+
70
+ # Copies a specific image from the source org to the current org.
71
+ cpl copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --image appimage:123
72
+ ```
73
+
59
74
  ### `delete`
60
75
 
61
76
  - Deletes the whole app (GVC with all workloads and all images)
@@ -89,6 +104,25 @@ cpl env -a $APP_NAME
89
104
  if [ cpl exists -a $APP_NAME ]; ...
90
105
  ```
91
106
 
107
+ ### `info`
108
+
109
+ - Displays the diff between defined/available apps/workloads (apps equal GVCs)
110
+ - Apps that are defined but not available are displayed in red
111
+ - Apps that are available but not defined are displayed in green
112
+ - Apps that are both defined and available are displayed in white
113
+ - The diff is based on what's defined in the `.controlplane/controlplane.yml` file
114
+
115
+ ```sh
116
+ # Shows diff for all apps in all orgs.
117
+ cpl info
118
+
119
+ # Shows diff for all apps in a specific org.
120
+ cpl info -o $ORG_NAME
121
+
122
+ # Shows diff for a specific app.
123
+ cpl info -a $APP_NAME
124
+ ```
125
+
92
126
  ### `latest-image`
93
127
 
94
128
  - Displays the latest image name
@@ -121,6 +155,18 @@ cpl open -a $APP_NAME
121
155
  cpl open -a $APP_NAME -w $WORKLOAD_NAME
122
156
  ```
123
157
 
158
+ ### `promote-app-from-upstream`
159
+
160
+ - Copies the latest image from upstream, runs a release script (optional), and deploys the image
161
+ - It performs the following steps:
162
+ - Runs `cpl copy-image-from-upstream` to copy the latest image from upstream
163
+ - Runs a release script if specified through `release_script` in the `.controlplane/controlplane.yml` file
164
+ - Runs `cpl deploy-image` to deploy the image
165
+
166
+ ```sh
167
+ cpl promote-app-from-upstream -a $APP_NAME -t $UPSTREAM_TOKEN
168
+ ```
169
+
124
170
  ### `ps`
125
171
 
126
172
  - Shows running replicas in app
@@ -204,6 +250,8 @@ cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential i
204
250
 
205
251
  ```sh
206
252
  cpl run:detached rails db:prepare -a $APP_NAME
253
+
254
+ # Need to quote COMMAND if setting ENV value or passing args to command to run
207
255
  cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
208
256
 
209
257
  # Uses some other image.
@@ -0,0 +1 @@
1
+ google-site-verification: googlee2da545df05d92f9.html
data/lib/command/base.rb CHANGED
@@ -38,6 +38,19 @@ module Command
38
38
  end
39
39
  end
40
40
 
41
+ def self.org_option(required: false)
42
+ {
43
+ name: :org,
44
+ params: {
45
+ aliases: ["-o"],
46
+ banner: "ORG_NAME",
47
+ desc: "Organization name",
48
+ type: :string,
49
+ required: required
50
+ }
51
+ }
52
+ end
53
+
41
54
  def self.app_option(required: false)
42
55
  {
43
56
  name: :app,
@@ -90,6 +103,19 @@ module Command
90
103
  }
91
104
  end
92
105
 
106
+ def self.upstream_token_option(required: false)
107
+ {
108
+ name: :upstream_token,
109
+ params: {
110
+ aliases: ["-t"],
111
+ banner: "UPSTREAM_TOKEN",
112
+ desc: "Upstream token",
113
+ type: :string,
114
+ required: required
115
+ }
116
+ }
117
+ end
118
+
93
119
  def self.skip_confirm_option(required: false)
94
120
  {
95
121
  name: :yes,
@@ -163,17 +189,19 @@ module Command
163
189
  end
164
190
  end
165
191
 
166
- def latest_image
167
- @latest_image ||=
192
+ def latest_image(app = config.app, org = config.org)
193
+ @latest_image ||= {}
194
+ @latest_image[app] ||=
168
195
  begin
169
- items = cp.image_query["items"]
170
- latest_image_from(items)
196
+ items = cp.image_query(app, org)["items"]
197
+ latest_image_from(items, app_name: app)
171
198
  end
172
199
  end
173
200
 
174
- def latest_image_next
175
- @latest_image_next ||= begin
176
- image = latest_image.split(":").first
201
+ def latest_image_next(app = config.app, org = config.org)
202
+ @latest_image_next ||= {}
203
+ @latest_image_next[app] ||= begin
204
+ image = latest_image(app, org).split(":").first
177
205
  image += ":#{extract_image_number(latest_image) + 1}"
178
206
  image += "_#{config.options[:commit]}" if config.options[:commit]
179
207
  image
@@ -190,10 +218,40 @@ module Command
190
218
  $stderr
191
219
  end
192
220
 
221
+ def step(message, abort_on_error: true) # rubocop:disable Metrics/MethodLength
222
+ progress.print("#{message}...")
223
+
224
+ Shell.use_tmp_stderr do
225
+ success = false
226
+
227
+ begin
228
+ success = yield
229
+ rescue RuntimeError => e
230
+ message = e.message
231
+ if abort_on_error
232
+ progress.puts(" #{Shell.color('failed!', :red)}\n\n")
233
+ Shell.abort(message)
234
+ else
235
+ Shell.write_to_tmp_stderr(message)
236
+ end
237
+ end
238
+
239
+ if success
240
+ progress.puts(" #{Shell.color('done!', :green)}")
241
+ else
242
+ progress.puts(" #{Shell.color('failed!', :red)}\n\n#{Shell.read_from_tmp_stderr}\n\n")
243
+ end
244
+ end
245
+ end
246
+
193
247
  def cp
194
248
  @cp ||= Controlplane.new(config)
195
249
  end
196
250
 
251
+ def perform(cmd)
252
+ system(cmd) || exit(false)
253
+ end
254
+
197
255
  private
198
256
 
199
257
  # returns 0 if no prior image
@@ -8,18 +8,19 @@ module Command
8
8
  commit_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Builds and pushes the image to Control Plane"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Builds and pushes the image to Control Plane
13
13
  - Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
14
14
  - Uses `.controlplane/Dockerfile`
15
- HEREDOC
15
+ DESC
16
16
 
17
17
  def call
18
18
  ensure_docker_running!
19
19
 
20
20
  dockerfile = config.current[:dockerfile] || "Dockerfile"
21
21
  dockerfile = "#{config.app_cpln_dir}/#{dockerfile}"
22
- progress.puts "- Building dockerfile: #{dockerfile}"
22
+
23
+ progress.puts("Building image from Dockerfile '#{dockerfile}'...\n\n")
23
24
 
24
25
  cp.image_build(latest_image_next, dockerfile: dockerfile)
25
26
  end
@@ -28,9 +29,9 @@ module Command
28
29
 
29
30
  def ensure_docker_running!
30
31
  `docker version > /dev/null 2>&1`
31
- return if $?.success? # rubocop:disable Style/SpecialGlobalVars
32
+ return if $CHILD_STATUS.success?
32
33
 
33
- Shell.abort("Can't run Docker. Please make sure that it's installed and started, then try again.")
34
+ raise "Can't run Docker. Please make sure that it's installed and started, then try again."
34
35
  end
35
36
  end
36
37
  end
@@ -8,19 +8,19 @@ module Command
8
8
  skip_confirm_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Deletes all images for an app that are older than the specified amount of days"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Deletes all images for an app that are older than the specified amount of days
13
13
  - Specify the amount of days through `old_image_retention_days` in the `.controlplane/controlplane.yml` file
14
14
  - Will ask for explicit user confirmation
15
15
  - Does not affect the latest image, regardless of how old it is
16
- HEREDOC
16
+ DESC
17
17
 
18
18
  def call
19
- return progress.puts "No old images found" if old_images.empty?
19
+ return progress.puts("No old images found.") if old_images.empty?
20
20
 
21
- progress.puts "Old images:"
21
+ progress.puts("Old images:")
22
22
  old_images.each do |image|
23
- progress.puts " #{image[:name]} (#{Shell.color((image[:date]).to_s, :red)})"
23
+ progress.puts(" - #{image[:name]} (#{Shell.color((image[:date]).to_s, :red)})")
24
24
  end
25
25
 
26
26
  return unless confirm_delete
@@ -68,8 +68,9 @@ module Command
68
68
 
69
69
  def delete_images
70
70
  old_images.each do |image|
71
- cp.image_delete(image[:name])
72
- progress.puts "#{image[:name]} deleted"
71
+ step("Deleting image '#{image[:name]}'") do
72
+ cp.image_delete(image[:name])
73
+ end
73
74
  end
74
75
  end
75
76
  end
@@ -10,20 +10,20 @@ module Command
10
10
  skip_confirm_option
11
11
  ].freeze
12
12
  DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images) for all stale apps"
13
- LONG_DESCRIPTION = <<~HEREDOC
13
+ LONG_DESCRIPTION = <<~DESC
14
14
  - Deletes the whole app (GVC with all workloads and all images) for all stale apps
15
15
  - Stale apps are identified based on the creation date of the latest image
16
16
  - Specify the amount of days after an app should be considered stale through `stale_app_image_deployed_days` in the `.controlplane/controlplane.yml` file
17
17
  - If `match_if_app_name_starts_with` is `true` in the `.controlplane/controlplane.yml` file, it will delete all stale apps that start with the name
18
18
  - Will ask for explicit user confirmation
19
- HEREDOC
19
+ DESC
20
20
 
21
21
  def call # rubocop:disable Metrics/MethodLength
22
- return progress.puts "No stale apps found" if stale_apps.empty?
22
+ return progress.puts("No stale apps found.") if stale_apps.empty?
23
23
 
24
- progress.puts "Stale apps:"
24
+ progress.puts("Stale apps:")
25
25
  stale_apps.each do |app|
26
- progress.puts " #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})"
26
+ progress.puts(" #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})")
27
27
  end
28
28
 
29
29
  return unless confirm_delete
@@ -74,14 +74,16 @@ module Command
74
74
  end
75
75
 
76
76
  def delete_gvc(app)
77
- cp.gvc_delete(app[:name])
78
- progress.puts "#{app[:name]} deleted"
77
+ step("Deleting app '#{app[:name]}'") do
78
+ cp.gvc_delete(app[:name])
79
+ end
79
80
  end
80
81
 
81
82
  def delete_images(app)
82
83
  app[:images].each do |image|
83
- cp.image_delete(image)
84
- progress.puts "#{image} deleted"
84
+ step("Deleting image '#{image}'") do
85
+ cp.image_delete(image)
86
+ end
85
87
  end
86
88
  end
87
89
  end
@@ -6,28 +6,43 @@ module Command
6
6
  OPTIONS = [
7
7
  app_option
8
8
  ].freeze
9
- DESCRIPTION = "Displays current configs (global and app-specific)"
10
- LONG_DESCRIPTION = <<~HEREDOC
11
- - Displays current configs (global and app-specific)
12
- HEREDOC
13
- EXAMPLES = <<~HEREDOC
9
+ DESCRIPTION = "Displays config for each app or a specific app"
10
+ LONG_DESCRIPTION = <<~DESC
11
+ - Displays config for each app or a specific app
12
+ DESC
13
+ EXAMPLES = <<~EX
14
14
  ```sh
15
- # Shows the global config.
15
+ # Shows the config for each app.
16
16
  cpl config
17
17
 
18
- # Shows both global and app-specific configs.
18
+ # Shows the config for a specific app.
19
19
  cpl config -a $APP_NAME
20
20
  ```
21
- HEREDOC
21
+ EX
22
22
 
23
- def call
24
- puts "-- Options"
25
- puts config.options.to_hash.to_yaml[4..]
26
- puts
23
+ def call # rubocop:disable Metrics/MethodLength
24
+ if config.app
25
+ puts "#{Shell.color("Current config (app '#{config.app}')", :blue)}:"
26
+ puts pretty_print(config.current)
27
+ puts
28
+ else
29
+ config.apps.each do |app_name, app_options|
30
+ puts "#{Shell.color("Config for app '#{app_name}'", :blue)}:"
31
+ puts pretty_print(app_options)
32
+ puts
33
+ end
34
+ end
35
+ end
36
+
37
+ private
27
38
 
28
- puts "-- Current config (app: #{config.app})"
29
- puts config.app ? config.current.to_yaml[4..] : "Please specify app to get app config"
30
- puts
39
+ def pretty_print(hash)
40
+ hash.transform_keys(&:to_s)
41
+ .to_yaml(indentation: 2)[4..]
42
+ # Adds an indentation of 2 to the beginning of each line
43
+ .gsub(/^(\s*)/, " \\1")
44
+ # Adds an indentation of 2 before the '-' in array items
45
+ .gsub(/^(\s*)-\s/, "\\1 - ")
31
46
  end
32
47
  end
33
48
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class CopyImageFromUpstream < Base
5
+ NAME = "copy-image-from-upstream"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ upstream_token_option(required: true),
9
+ image_option
10
+ ].freeze
11
+ DESCRIPTION = "Copies an image (by default the latest) from a source org to the current org"
12
+ LONG_DESCRIPTION = <<~DESC
13
+ - Copies an image (by default the latest) from a source org to the current org
14
+ - The source org must be specified through `upstream` in the `.controlplane/controlplane.yml` file
15
+ - Additionally, the token for the source org must be provided through `--upstream-token` or `-t`
16
+ - A `cpln` profile will be temporarily created to pull the image from the source org
17
+ DESC
18
+ EXAMPLES = <<~EX
19
+ ```sh
20
+ # Copies the latest image from the source org to the current org.
21
+ cpl copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN
22
+
23
+ # Copies a specific image from the source org to the current org.
24
+ cpl copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --image appimage:123
25
+ ```
26
+ EX
27
+
28
+ def call # rubocop:disable Metrics/MethodLength
29
+ ensure_docker_running!
30
+
31
+ @upstream = config[:upstream]
32
+ @upstream_org = config.apps[@upstream.to_sym][:cpln_org] || config.apps[@upstream.to_sym][:org]
33
+ ensure_upstream_org!
34
+
35
+ create_upstream_profile
36
+ fetch_upstream_image_url
37
+ fetch_app_image_url
38
+ pull_image_from_upstream
39
+ push_image_to_app
40
+ ensure
41
+ delete_upstream_profile
42
+ end
43
+
44
+ private
45
+
46
+ def ensure_docker_running!
47
+ `docker version > /dev/null 2>&1`
48
+ return if $CHILD_STATUS.success?
49
+
50
+ raise "Can't run Docker. Please make sure that it's installed and started, then try again."
51
+ end
52
+
53
+ def ensure_upstream_org!
54
+ raise "Can't find option 'cpln_org' for app '#{@upstream}' in 'controlplane.yml'." unless @upstream_org
55
+ end
56
+
57
+ def create_upstream_profile
58
+ step("Creating upstream profile") do
59
+ loop do
60
+ @upstream_profile = "upstream-#{rand(1000..9999)}"
61
+ break unless cp.profile_exists?(@upstream_profile)
62
+ end
63
+
64
+ cp.profile_create(@upstream_profile, config.options[:upstream_token])
65
+ end
66
+ end
67
+
68
+ def fetch_upstream_image_url
69
+ step("Fetching upstream image URL") do
70
+ cp.profile_switch(@upstream_profile)
71
+ upstream_image = config.options[:image]
72
+ upstream_image = latest_image(@upstream, @upstream_org) if !upstream_image || upstream_image == "latest"
73
+ @upstream_image_url = "#{@upstream_org}.registry.cpln.io/#{upstream_image}"
74
+ end
75
+ end
76
+
77
+ def fetch_app_image_url
78
+ step("Fetching app image URL") do
79
+ cp.profile_switch("default")
80
+ app_image = latest_image_next(config.app, config.org)
81
+ @app_image_url = "#{config.org}.registry.cpln.io/#{app_image}"
82
+ end
83
+ end
84
+
85
+ def pull_image_from_upstream
86
+ step("Pulling image from '#{@upstream_image_url}'") do
87
+ cp.profile_switch(@upstream_profile)
88
+ cp.image_login(@upstream_org)
89
+ cp.image_pull(@upstream_image_url)
90
+ end
91
+ end
92
+
93
+ def push_image_to_app
94
+ step("Pushing image to '#{@app_image_url}'") do
95
+ cp.profile_switch("default")
96
+ cp.image_login(config.org)
97
+ cp.image_tag(@upstream_image_url, @app_image_url)
98
+ cp.image_push(@app_image_url)
99
+ end
100
+ end
101
+
102
+ def delete_upstream_profile
103
+ return unless @upstream_profile
104
+
105
+ step("Deleting upstream profile") do
106
+ cp.profile_delete(@upstream_profile)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -8,10 +8,10 @@ module Command
8
8
  skip_confirm_option
9
9
  ].freeze
10
10
  DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
11
- LONG_DESCRIPTION = <<~HEREDOC
11
+ LONG_DESCRIPTION = <<~DESC
12
12
  - Deletes the whole app (GVC with all workloads and all images)
13
13
  - Will ask for explicit user confirmation
14
- HEREDOC
14
+ DESC
15
15
 
16
16
  def call
17
17
  return unless confirm_delete
@@ -33,25 +33,23 @@ module Command
33
33
  end
34
34
 
35
35
  def delete_gvc
36
- progress.puts "- Deleting gvc:"
36
+ return progress.puts("App '#{config.app}' does not exist.") if cp.fetch_gvc.nil?
37
37
 
38
- return progress.puts "none" unless cp.fetch_gvc
39
-
40
- cp.gvc_delete
41
- progress.puts config.app
38
+ step("Deleting app '#{config.app}'") do
39
+ cp.gvc_delete
40
+ end
42
41
  end
43
42
 
44
43
  def delete_images
45
- progress.puts "- Deleting image(s):"
46
-
47
44
  images = cp.image_query["items"]
48
45
  .filter_map { |item| item["name"] if item["name"].start_with?("#{config.app}:") }
49
46
 
50
- return progress.puts "none" unless images
47
+ return progress.puts("No images to delete.") unless images.any?
51
48
 
52
49
  images.each do |image|
53
- cp.image_delete(image)
54
- progress.puts image
50
+ step("Deleting image '#{image}'") do
51
+ cp.image_delete(image)
52
+ end
55
53
  end
56
54
  end
57
55
  end
@@ -7,21 +7,32 @@ module Command
7
7
  app_option(required: true)
8
8
  ].freeze
9
9
  DESCRIPTION = "Deploys the latest image to app workloads"
10
- LONG_DESCRIPTION = <<~HEREDOC
10
+ LONG_DESCRIPTION = <<~DESC
11
11
  - Deploys the latest image to app workloads
12
- HEREDOC
12
+ DESC
13
+
14
+ def call # rubocop:disable Metrics/MethodLength
15
+ deployed_endpoints = {}
13
16
 
14
- def call
15
17
  image = latest_image
16
18
 
17
19
  config[:app_workloads].each do |workload|
18
- cp.fetch_workload!(workload).dig("spec", "containers").each do |container|
19
- next unless container["image"].match?(%r{^/org/#{config[:cpln_org]}/image/#{config.app}:})
20
+ workload_data = cp.fetch_workload!(workload)
21
+ workload_data.dig("spec", "containers").each do |container|
22
+ next unless container["image"].match?(%r{^/org/#{config.org}/image/#{config.app}:})
20
23
 
21
- cp.workload_set_image_ref(workload, container: container["name"], image: image)
22
- progress.puts "updated #{container['name']}"
24
+ container_name = container["name"]
25
+ step("Deploying image '#{image}' for workload '#{container_name}'") do
26
+ cp.workload_set_image_ref(workload, container: container_name, image: image)
27
+ deployed_endpoints[container_name] = workload_data.dig("status", "endpoint")
28
+ end
23
29
  end
24
30
  end
31
+
32
+ progress.puts("\nDeployed endpoints:")
33
+ deployed_endpoints.each do |workload, endpoint|
34
+ progress.puts(" - #{workload}: #{endpoint}")
35
+ end
25
36
  end
26
37
  end
27
38
  end