cpl 0.4.1 → 0.5.1

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.
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