cpl 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2392653f1a75e0dc398590cc5c0e9d89d4769a50af3432b2861e01ff794e8cda
4
- data.tar.gz: 13c33cc6465394b90b1fd8e94f711c98135bfdcb25756db7d3b7b0c8bfc819fa
3
+ metadata.gz: 3fb78a893e74bb1d23dad21fe62a08ce903bd87cbcc85b0cb3affb9a6b47a185
4
+ data.tar.gz: df6aec1b0be09b95ad65c61ee8c92fc4e44084985907920624868909adc78cc6
5
5
  SHA512:
6
- metadata.gz: bc2b5f2555b8f329ad8d5b2be9f5762e6f265f499baaa035e65c24257b47c26459842f2611ee8d352af65b3dcabf4f0e6570007c59f5ca543f3edb382ed04747
7
- data.tar.gz: 904028e00550311c8b9327fb3a17a8a00c75b4806e81ca8359d5b3f6ae55b19f8a89efbc3ccc087e4f62e2243930587df1f693ebbbac9470377b0e6905e0dd19
6
+ metadata.gz: 961373f6642feac2ecf0bbea2f36ae13c8d70fe64d049b21d1e998b39edd9563772da1a1f4b5038a69e54bbabf2e6c6c6149a6410f3bb5b8be1f61c592619b5d
7
+ data.tar.gz: dcb5099f234926d9b84697f5c4e58556daeee3d245d4a8f97d092ab1b95547d250ef72908ef9f81cac252f19c0a5d8ca19c4eb3c822c20346e06cb402d97c413
@@ -1,4 +1,4 @@
1
- name: CI
1
+ name: RSpec
2
2
 
3
3
  on:
4
4
  push:
@@ -7,29 +7,7 @@ on:
7
7
  pull_request:
8
8
 
9
9
  jobs:
10
- rubocop:
11
- runs-on: ubuntu-latest
12
- name: Rubocop
13
- strategy:
14
- matrix:
15
- ruby:
16
- - "2.7"
17
- - "3.0"
18
- steps:
19
- - name: Checkout code
20
- uses: actions/checkout@v3
21
- - name: Set up Ruby
22
- uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby }}
25
- rubygems: latest
26
- - name: Install dependencies
27
- run: bundle install
28
- - name: Analyze code
29
- run: bundle exec rubocop
30
-
31
10
  rspec:
32
- needs: rubocop
33
11
  runs-on: ubuntu-latest
34
12
  name: RSpec
35
13
  env:
@@ -46,8 +24,7 @@ jobs:
46
24
  uses: ruby/setup-ruby@v1
47
25
  with:
48
26
  ruby-version: ${{ matrix.ruby }}
49
- rubygems: latest
50
- bundler-cache: false
27
+ bundler-cache: true
51
28
  - name: Install dependencies
52
29
  run: bundle install
53
30
  - name: Run tests
@@ -0,0 +1,29 @@
1
+ name: Rubocop
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ rubocop:
11
+ runs-on: ubuntu-latest
12
+ name: Rubocop
13
+ strategy:
14
+ matrix:
15
+ ruby:
16
+ - "2.7"
17
+ - "3.0"
18
+ steps:
19
+ - name: Checkout code
20
+ uses: actions/checkout@v3
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true
26
+ - name: Install dependencies
27
+ run: bundle install
28
+ - name: Analyze code
29
+ run: bundle exec rubocop
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.1.0] - 2023-02-08
3
+ ## 0.1.1 - 2023-03-09
4
+
5
+ - Fixed issue with default gems for older Ruby versions (#14)
6
+
7
+ ## 0.1.0 - 2023-02-19
4
8
 
5
9
  - Initial release
data/CONTRIBUTING.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Contributing
2
2
 
3
+ ## Installation
4
+ Rather than installing `cpl` as a Ruby gem, install this repo locally and alias `cpl` command globally for easier access, e.g.:
5
+
6
+ ```sh
7
+ git clone https://github.com/shakacode/heroku-to-control-plane
8
+
9
+ # Create an alias in some local shell startup script, e.g., `.profile`, `.bashrc`, etc.
10
+ alias cpl="~/projects/heroku-to-control-plane/cpl"
11
+ ```
12
+
13
+ Or set the path of the Ruby gem in your Gemfile.
14
+
15
+ ```ruby
16
+ gem 'cpl', path: '~/projects/heroku-to-control-plane'
17
+ ```
18
+
3
19
  ## Linting
4
20
  Be sure to run `rubocop -a` before committing code.
5
21
 
data/Gemfile.lock CHANGED
@@ -1,23 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpl (0.1.0)
5
- cgi (~> 0.3.6)
4
+ cpl (0.3.0)
6
5
  debug (~> 1.7.1)
7
6
  dotenv (~> 2.8.1)
8
- json (~> 2.6.3)
9
- net-http (~> 0.3.2)
10
- pathname (~> 0.2.1)
11
7
  psych (~> 5.1.0)
12
- tempfile (~> 0.1.3)
13
8
  thor (~> 1.2.1)
14
- yaml (~> 0.2.1)
15
9
 
16
10
  GEM
17
11
  remote: https://rubygems.org/
18
12
  specs:
19
13
  ast (2.4.2)
20
- cgi (0.3.6)
21
14
  debug (1.7.1)
22
15
  irb (>= 1.5.0)
23
16
  reline (>= 0.3.1)
@@ -25,15 +18,12 @@ GEM
25
18
  docile (1.4.0)
26
19
  dotenv (2.8.1)
27
20
  io-console (0.6.0)
28
- irb (1.6.2)
21
+ irb (1.6.3)
29
22
  reline (>= 0.3.0)
30
23
  json (2.6.3)
31
- net-http (0.3.2)
32
- uri
33
24
  parallel (1.22.1)
34
25
  parser (3.2.0.0)
35
26
  ast (~> 2.4.1)
36
- pathname (0.2.1)
37
27
  psych (5.1.0)
38
28
  stringio
39
29
  rainbow (3.1.1)
@@ -82,13 +72,11 @@ GEM
82
72
  simplecov-html (0.12.3)
83
73
  simplecov_json_formatter (0.1.4)
84
74
  stringio (3.0.5)
85
- tempfile (0.1.3)
86
75
  thor (1.2.1)
87
76
  unicode-display_width (2.4.2)
88
- uri (0.12.0)
89
- yaml (0.2.1)
90
77
 
91
78
  PLATFORMS
79
+ ruby
92
80
  x86_64-linux
93
81
 
94
82
  DEPENDENCIES
@@ -101,4 +89,4 @@ DEPENDENCIES
101
89
  simplecov (~> 0.22.0)
102
90
 
103
91
  BUNDLED WITH
104
- 2.4.6
92
+ 2.3.26
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
- # Heroku to Control Plane
1
+ # Heroku to Control Plane `cpl` CLI
2
2
 
3
3
  _A playbook for migrating from [Heroku](https://heroku.com) to [Control Plane](https://controlplane.com)_
4
4
 
5
+ [![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)
6
+ [![Rubocop](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rubocop.yml/badge.svg)](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rubocop.yml)
7
+
8
+ [![Gem](https://badge.fury.io/rb/cpl.svg)](https://badge.fury.io/rb/cpl)
9
+
5
10
  This playbook shows how to move "Heroku apps" to "Control Plane workloads" via an open-source `cpl` CLI on top of Control Plane's `cpln` CLI.
6
11
 
7
12
  Heroku provides a UX and CLI that enables easy publishing of Ruby on Rails and other apps. This ease of use comes via many "Heroku" abstractions and naming conventions.
@@ -20,8 +25,8 @@ To simplify migration to and usage of Control Plane for Heroku users, this repos
20
25
  9. [CLI commands reference](#cli-commands-reference)
21
26
  10. [Mapping of Heroku Commands to `cpl` and `cpln`](#mapping-of-heroku-commands-to-cpl-and-cpln)
22
27
  11. [Examples](#examples)
23
- 12. [Migrating Postgres database from Heroku infrastructure](/postgres.md)
24
- 13. [Migrating Redis database from Heroku infrastructure](/redis.md)
28
+ 12. [Migrating Postgres database from Heroku infrastructure](/docs/postgres.md)
29
+ 13. [Migrating Redis database from Heroku infrastructure](/docs/redis.md)
25
30
 
26
31
  ## Key features
27
32
 
@@ -69,25 +74,20 @@ For the typical Rails app, this means:
69
74
 
70
75
  ## Installation
71
76
 
72
- **Note:** `cpl` CLI is configured via a local clone clone of this repo. We may publish it later as a Ruby gem or Node package.
77
+ **Note:** `cpl` CLI is configured either as a Ruby gem, [`cpl`](https://rubygems.org/gems/cpl) install or a local clone. For information on the latter, see [CONTRIBUTING.md](CONTRIBUTING.md).
73
78
 
74
79
  1. Install `node` (required for Control Plane CLI).
75
80
  2. Install `ruby` (required for these helpers).
76
- 3. Install Control Plane CLI (adds `cpln` command) and configure credentials.
81
+ 3. Install Control Plane CLI (adds `cpln` command) and configure credentials by running command `cpln login`.
77
82
 
78
83
  ```sh
79
84
  npm install -g @controlplane/cli
80
85
  cpln login
81
86
  ```
82
87
 
83
- 4. Install this repo locally and alias `cpl` command globally for easier access, e.g.:
88
+ ## Tips
84
89
 
85
- ```sh
86
- git clone https://github.com/shakacode/heroku-to-control-plane
87
-
88
- # Create an alias in some local shell startup script, e.g., `.profile`, `.bashrc`, etc.
89
- alias cpl="~/projects/heroku-to-control-plane/cpl"
90
- ```
90
+ Do not confuse the `cpl` CLI with the `cpln` CLI. The `cpl` CLI is the Heroku to Control Plane playbook CLI. The `cpln` CLI is the Control Plane CLI.
91
91
 
92
92
  - For each Git project that you want to deploy to Control Plane, copy project-specific configs to a `.controlplane` directory at the top of your project. `cpl` will pick those up depending on which project
93
93
  folder tree it runs. Thus, this automates running several projects with different configs without explicitly switching configs.
@@ -101,6 +101,8 @@ alias cpl="~/projects/heroku-to-control-plane/cpl"
101
101
  1. `myapp` is an app name defined in the `.controlplane/controlplane.yml` file, such as `ror-tutorial` in [this `controlplane.yml` file](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.controlplane/controlplane.yml).
102
102
  2. Other files in the `.controlplane/templates` directory are used by the `cpl setup` command.
103
103
 
104
+ ### Initial Setup and Deployment
105
+
104
106
  ```sh
105
107
  # Provision infrastructure (one-time-only for new apps) using templates.
106
108
  # Note how the arguments correspond to files in the `.controlplane/templates` directory.
@@ -112,13 +114,32 @@ cpl build-image -a myapp --commit 456
112
114
  # Prepare database.
113
115
  cpl run:detached rails db:prepare -a myapp --image latest
114
116
 
115
- # Promote latest image.
116
- cpl promote-image -a myapp
117
+ # Deploy latest image.
118
+ cpl deploy-image -a myapp
117
119
 
118
120
  # Open app in browser.
119
121
  cpl open -a myapp
120
122
  ```
121
123
 
124
+ ### Promoting code upgrades
125
+
126
+ ```sh
127
+ # Build and push new image with sequential image tagging, e.g. 'ror-tutorial_123'
128
+ cpl build-image -a ror-tutorial
129
+
130
+ # OR
131
+ # Build and push with sequential image tagging and commit SHA, e.g. 'ror-tutorial_123_ABCD'
132
+ cpl build-image -a ror-tutorial --commit ABCD
133
+
134
+ # Run database migrations (or other release tasks) with latest image,
135
+ # while app is still running on previous image.
136
+ # This is analogous to the release phase.
137
+ cpl run:detached rails db:migrate -a ror-tutorial --image latest
138
+
139
+ # Deploy latest image to app
140
+ cpl deploy-image -a ror-tutorial
141
+ ```
142
+
122
143
  ## Example project modifications for Control Plane
123
144
 
124
145
  _See this for a complete example._
@@ -300,17 +321,17 @@ cpl --help
300
321
 
301
322
  **`[WIP]`**
302
323
 
303
- | Heroku Command | `cpl` or `cpln` |
304
- | ---------------------------------------------------------------------------------------------------------------- | --------------- |
305
- | `[heroku ps](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-ps-type-type)` | `cpl ps` |
306
- | `[heroku config](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-config)` | ? |
307
- | `[heroku maintenance](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-maintenance)` | ? |
308
- | `[heroku logs](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-logs)` | `cpl logs` |
309
- | `[heroku pg](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pg-database)` | ? |
310
- | `[heroku pipelines:promote](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pipelines-promote)` | `cpl promote` |
311
- | `[heroku psql](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-psql-database)` | ? |
312
- | `[heroku redis](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-redis-database)` | ? |
313
- | `[heroku releases](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-releases)` | ? |
324
+ | Heroku Command | `cpl` or `cpln` |
325
+ | -------------------------------------------------------------------------------------------------------------- | --------------- |
326
+ | [heroku ps](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-ps-type-type) | `cpl ps` |
327
+ | [heroku config](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-config) | ? |
328
+ | [heroku maintenance](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-maintenance) | ? |
329
+ | [heroku logs](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-logs) | `cpl logs` |
330
+ | [heroku pg](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pg-database) | ? |
331
+ | [heroku pipelines:promote](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pipelines-promote) | `cpl promote` |
332
+ | [heroku psql](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-psql-database) | ? |
333
+ | [heroku redis](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-redis-database) | ? |
334
+ | [heroku releases](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-releases) | ? |
314
335
 
315
336
  ## Examples
316
337
 
data/Rakefile CHANGED
@@ -9,3 +9,8 @@ RSpec::Core::RakeTask.new(:spec)
9
9
  RuboCop::RakeTask.new
10
10
 
11
11
  task default: %i[spec rubocop]
12
+
13
+ desc "Updates commands.md file"
14
+ task :command_docs do
15
+ sh "./script/generate_commands_docs"
16
+ end
data/cpl.gemspec CHANGED
@@ -15,16 +15,10 @@ Gem::Specification.new do |spec|
15
15
 
16
16
  spec.required_ruby_version = ">= 2.7.0"
17
17
 
18
- spec.add_dependency "cgi", "~> 0.3.6"
19
18
  spec.add_dependency "debug", "~> 1.7.1"
20
19
  spec.add_dependency "dotenv", "~> 2.8.1"
21
- spec.add_dependency "json", "~> 2.6.3"
22
- spec.add_dependency "net-http", "~> 0.3.2"
23
- spec.add_dependency "pathname", "~> 0.2.1"
24
20
  spec.add_dependency "psych", "~> 5.1.0"
25
- spec.add_dependency "tempfile", "~> 0.1.3"
26
21
  spec.add_dependency "thor", "~> 1.2.1"
27
- spec.add_dependency "yaml", "~> 0.2.1"
28
22
 
29
23
  spec.add_development_dependency "rspec", "~> 3.12.0"
30
24
  spec.add_development_dependency "rubocop", "~> 1.45.0"
data/docs/commands.md CHANGED
@@ -21,6 +21,29 @@ This `-a` option is used in most of the commands and will pick all other app con
21
21
  cpl build-image -a $APP_NAME
22
22
  ```
23
23
 
24
+ ### `cleanup-old-images`
25
+
26
+ - Deletes all images for an app that are older than the specified amount of days
27
+ - Specify the amount of days through `old_image_retention_days` in the `.controlplane/controlplane.yml` file
28
+ - Will ask for explicit user confirmation
29
+ - Does not affect the latest image, regardless of how old it is
30
+
31
+ ```sh
32
+ cpl cleanup-old-images -a $APP_NAME
33
+ ```
34
+
35
+ ### `cleanup-stale-apps`
36
+
37
+ - Deletes the whole app (GVC with all workloads and all images) for all stale apps
38
+ - Stale apps are identified based on the creation date of the latest image
39
+ - Specify the amount of days after an app should be considered stale through `stale_app_image_deployed_days` in the `.controlplane/controlplane.yml` file
40
+ - 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
41
+ - Will ask for explicit user confirmation
42
+
43
+ ```sh
44
+ cpl cleanup-stale-apps -a $APP_NAME
45
+ ```
46
+
24
47
  ### `config`
25
48
 
26
49
  - Displays current configs (global and app-specific)
@@ -42,6 +65,14 @@ cpl config -a $APP_NAME
42
65
  cpl delete -a $APP_NAME
43
66
  ```
44
67
 
68
+ ### `deploy-image`
69
+
70
+ - Deploys the latest image to app workloads
71
+
72
+ ```sh
73
+ cpl deploy-image -a $APP_NAME
74
+ ```
75
+
45
76
  ### `env`
46
77
 
47
78
  - Displays app-specific environment variables
@@ -90,14 +121,6 @@ cpl open -a $APP_NAME
90
121
  cpl open -a $APP_NAME -w $WORKLOAD_NAME
91
122
  ```
92
123
 
93
- ### `promote-image`
94
-
95
- - Promotes the latest image to app workloads
96
-
97
- ```sh
98
- cpl promote-image -a $APP_NAME
99
- ```
100
-
101
124
  ### `ps`
102
125
 
103
126
  - Shows running replicas in app
@@ -217,3 +240,12 @@ cpl setup redis -a $APP_NAME
217
240
  # Applies several templates (practically creating full app).
218
241
  cpl setup gvc postgres redis rails -a $APP_NAME
219
242
  ```
243
+
244
+ ### `version`
245
+
246
+ - Displays the current version of the CLI
247
+ - Can also be done with `cpl --version` or `cpl -v`
248
+
249
+ ```sh
250
+ cpl version
251
+ ```
@@ -30,8 +30,8 @@ build-staging:
30
30
  name: Database tasks
31
31
  command: cpl run:detached rails db:migrate -a ${APP_NAME} --image latest
32
32
  - run:
33
- name: Promote image
34
- command: cpl promote-image -a ${APP_NAME}
33
+ name: Deploy image
34
+ command: cpl deploy-image -a ${APP_NAME}
35
35
 
36
36
  # Example config for review app:
37
37
  # - triggers manually if needed
@@ -82,8 +82,8 @@ build-review-app:
82
82
  cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a ${APP_NAME} --image latest
83
83
  fi
84
84
  - run:
85
- name: Promote image
86
- command: cpl promote-image -a ${APP_NAME}
85
+ name: Deploy image
86
+ command: cpl deploy-image -a ${APP_NAME}
87
87
 
88
88
  review-app:
89
89
  jobs:
data/lib/command/base.rb CHANGED
@@ -90,14 +90,40 @@ module Command
90
90
  }
91
91
  end
92
92
 
93
+ def self.skip_confirm_option(required: false)
94
+ {
95
+ name: :yes,
96
+ params: {
97
+ aliases: ["-y"],
98
+ banner: "SKIP_CONFIRM",
99
+ desc: "Skip confirmation",
100
+ type: :boolean,
101
+ required: required
102
+ }
103
+ }
104
+ end
105
+
106
+ def self.version_option(required: false)
107
+ {
108
+ name: :version,
109
+ params: {
110
+ aliases: ["-v"],
111
+ banner: "VERSION",
112
+ desc: "Displays the current version of the CLI",
113
+ type: :boolean,
114
+ required: required
115
+ }
116
+ }
117
+ end
118
+
93
119
  def self.all_options
94
120
  methods.grep(/_option$/).map { |method| send(method.to_s) }
95
121
  end
96
122
 
97
- def self.all_options_key_name
123
+ def self.all_options_by_key_name
98
124
  all_options.each_with_object({}) do |option, result|
99
- option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option[:name] }
100
- result["--#{option[:name]}"] = option[:name]
125
+ option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option }
126
+ result["--#{option[:name]}"] = option
101
127
  end
102
128
  end
103
129
 
@@ -125,20 +151,23 @@ module Command
125
151
  cp.workload_delete(workload, no_raise: true)
126
152
  end
127
153
 
128
- def latest_image # rubocop:disable Metrics/MethodLength
154
+ def latest_image_from(items, app_name: config.app, name_only: true)
155
+ matching_items = items.filter { |item| item["name"].start_with?("#{app_name}:") }
156
+
157
+ # Or special string to indicate no image available
158
+ if matching_items.empty?
159
+ "#{app_name}:#{NO_IMAGE_AVAILABLE}"
160
+ else
161
+ latest_item = matching_items.max_by { |item| extract_image_number(item["name"]) }
162
+ name_only ? latest_item["name"] : latest_item
163
+ end
164
+ end
165
+
166
+ def latest_image
129
167
  @latest_image ||=
130
168
  begin
131
169
  items = cp.image_query["items"]
132
- matching_items = items.filter_map do |item|
133
- item["name"] if item["name"].start_with?("#{config.app}:")
134
- end
135
-
136
- # Or special string to indicate no image available
137
- if matching_items.empty?
138
- "#{config.app}:#{NO_IMAGE_AVAILABLE}"
139
- else
140
- matching_items.max_by { |item| extract_image_number(item) }
141
- end
170
+ latest_image_from(items)
142
171
  end
143
172
  end
144
173
 
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class CleanupOldImages < Base
5
+ NAME = "cleanup-old-images"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ skip_confirm_option
9
+ ].freeze
10
+ DESCRIPTION = "Deletes all images for an app that are older than the specified amount of days"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Deletes all images for an app that are older than the specified amount of days
13
+ - Specify the amount of days through `old_image_retention_days` in the `.controlplane/controlplane.yml` file
14
+ - Will ask for explicit user confirmation
15
+ - Does not affect the latest image, regardless of how old it is
16
+ HEREDOC
17
+
18
+ def call
19
+ return progress.puts "No old images found" if old_images.empty?
20
+
21
+ progress.puts "Old images:"
22
+ old_images.each do |image|
23
+ progress.puts " #{image[:name]} (#{Shell.color((image[:date]).to_s, :red)})"
24
+ end
25
+
26
+ return unless confirm_delete
27
+
28
+ progress.puts
29
+ delete_images
30
+ end
31
+
32
+ private
33
+
34
+ def old_images # rubocop:disable Metrics/MethodLength
35
+ @old_images ||=
36
+ begin
37
+ result_images = []
38
+
39
+ now = DateTime.now
40
+ old_image_retention_days = config[:old_image_retention_days]
41
+
42
+ images = cp.image_query["items"].filter { |item| item["name"].start_with?("#{config.app}:") }
43
+
44
+ # Remove latest image, because we don't want to delete the image that is currently deployed
45
+ latest_image_name = latest_image_from(images)
46
+ images = images.filter { |item| item["name"] != latest_image_name }
47
+
48
+ images.each do |image|
49
+ created_date = DateTime.parse(image["created"])
50
+ diff_in_days = (now - created_date).to_i
51
+ next unless diff_in_days >= old_image_retention_days
52
+
53
+ result_images.push({
54
+ name: image["name"],
55
+ date: created_date
56
+ })
57
+ end
58
+
59
+ result_images
60
+ end
61
+ end
62
+
63
+ def confirm_delete
64
+ return true if config.options[:yes]
65
+
66
+ Shell.confirm("\nAre you sure you want to delete these #{old_images.length} images?")
67
+ end
68
+
69
+ def delete_images
70
+ old_images.each do |image|
71
+ cp.image_delete(image[:name])
72
+ progress.puts "#{image[:name]} deleted"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Command
6
+ class CleanupStaleApps < Base
7
+ NAME = "cleanup-stale-apps"
8
+ OPTIONS = [
9
+ app_option(required: true),
10
+ skip_confirm_option
11
+ ].freeze
12
+ DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images) for all stale apps"
13
+ LONG_DESCRIPTION = <<~HEREDOC
14
+ - Deletes the whole app (GVC with all workloads and all images) for all stale apps
15
+ - Stale apps are identified based on the creation date of the latest image
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
+ - 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
+ - Will ask for explicit user confirmation
19
+ HEREDOC
20
+
21
+ def call # rubocop:disable Metrics/MethodLength
22
+ return progress.puts "No stale apps found" if stale_apps.empty?
23
+
24
+ progress.puts "Stale apps:"
25
+ stale_apps.each do |app|
26
+ progress.puts " #{app[:name]} (#{Shell.color((app[:date]).to_s, :red)})"
27
+ end
28
+
29
+ return unless confirm_delete
30
+
31
+ progress.puts
32
+ stale_apps.each do |app|
33
+ delete_gvc(app)
34
+ delete_images(app)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def stale_apps # rubocop:disable Metrics/MethodLength
41
+ @stale_apps ||=
42
+ begin
43
+ apps = []
44
+
45
+ now = DateTime.now
46
+ stale_app_image_deployed_days = config[:stale_app_image_deployed_days]
47
+
48
+ gvcs = cp.gvc_query(config.app)["items"]
49
+ gvcs.each do |gvc|
50
+ app_name = gvc["name"]
51
+
52
+ images = cp.image_query(app_name)["items"].filter { |item| item["name"].start_with?("#{app_name}:") }
53
+ image = latest_image_from(images, app_name: app_name, name_only: false)
54
+
55
+ created_date = DateTime.parse(image["created"])
56
+ diff_in_days = (now - created_date).to_i
57
+ next unless diff_in_days >= stale_app_image_deployed_days
58
+
59
+ apps.push({
60
+ name: app_name,
61
+ date: created_date,
62
+ images: images.map { |current_image| current_image["name"] }
63
+ })
64
+ end
65
+
66
+ apps
67
+ end
68
+ end
69
+
70
+ def confirm_delete
71
+ return true if config.options[:yes]
72
+
73
+ Shell.confirm("\nAre you sure you want to delete these #{stale_apps.length} apps?")
74
+ end
75
+
76
+ def delete_gvc(app)
77
+ cp.gvc_delete(app[:name])
78
+ progress.puts "#{app[:name]} deleted"
79
+ end
80
+
81
+ def delete_images(app)
82
+ app[:images].each do |image|
83
+ cp.image_delete(image)
84
+ progress.puts "#{image} deleted"
85
+ end
86
+ end
87
+ end
88
+ end