cpl 0.1.0 → 0.2.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 +4 -4
- data/.github/workflows/{ci.yml → rspec.yml} +2 -25
- data/.github/workflows/rubocop.yml +29 -0
- data/CHANGELOG.md +6 -2
- data/CONTRIBUTING.md +16 -0
- data/Gemfile.lock +1 -14
- data/README.md +45 -23
- data/Rakefile +5 -0
- data/cpl.gemspec +0 -6
- data/docs/commands.md +23 -0
- data/lib/command/base.rb +32 -15
- data/lib/command/cleanup_old_images.rb +76 -0
- data/lib/command/cleanup_stale_apps.rb +88 -0
- data/lib/command/delete.rb +13 -5
- data/lib/core/controlplane.rb +11 -2
- data/lib/cpl/version.rb +1 -1
- metadata +8 -89
- /data/{postgres.md → docs/postgres.md} +0 -0
- /data/{redis.md → docs/redis.md} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 940fc95a21ea2771a453b12e646d182ed42ef448fd83081ac822af26d1820ad3
|
4
|
+
data.tar.gz: 4405dd3555275a1bbe135d52abaf072ed683cb183d0ee36e0060c38262ca8017
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 579a48aff74172dfe8ac8652e3538006d897ad2ce82459869227889d1e294595c0a698f846f2fe9db3970950e1f0372a6149f75ce06f6a2ab526fa451fea6649
|
7
|
+
data.tar.gz: 26581dbe132fea73f6066ba4a030738a1b08978e17d02fe6ba95ebed74d879f25a103ce5ddbeff8ba33c4508d7b302fc9f28c0d822f77c423c74c07cdf95006b
|
@@ -1,4 +1,4 @@
|
|
1
|
-
name:
|
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
|
-
|
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
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.
|
5
|
-
cgi (~> 0.3.6)
|
4
|
+
cpl (0.1.1)
|
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)
|
@@ -28,12 +21,9 @@ GEM
|
|
28
21
|
irb (1.6.2)
|
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,11 +72,8 @@ 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
|
92
79
|
x86_64-linux
|
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
|
+
[](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml)
|
6
|
+
[](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rubocop.yml)
|
7
|
+
|
8
|
+
[](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
|
77
|
+
**Note:** `cpl` CLI is configured either a Ruby gem, [`cpl`](https://rubygems.org/gems/cpl) install or a local clone 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
|
-
|
88
|
+
## Tips
|
89
|
+
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.
|
84
90
|
|
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
|
-
```
|
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.
|
@@ -119,6 +121,26 @@ cpl promote-image -a myapp
|
|
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 runner rails db:migrate -a ror-tutorial --image latest
|
138
|
+
|
139
|
+
# Pomote latest image to app
|
140
|
+
cpl promote-image -a ror-tutorial
|
141
|
+
```
|
142
|
+
|
143
|
+
|
122
144
|
## Example project modifications for Control Plane
|
123
145
|
|
124
146
|
_See this for a complete example._
|
@@ -300,17 +322,17 @@ cpl --help
|
|
300
322
|
|
301
323
|
**`[WIP]`**
|
302
324
|
|
303
|
-
| Heroku Command
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
325
|
+
| Heroku Command | `cpl` or `cpln` |
|
326
|
+
| -------------------------------------------------------------------------------------------------------------- | --------------- |
|
327
|
+
| [heroku ps](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-ps-type-type) | `cpl ps` |
|
328
|
+
| [heroku config](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-config) | ? |
|
329
|
+
| [heroku maintenance](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-maintenance) | ? |
|
330
|
+
| [heroku logs](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-logs) | `cpl logs` |
|
331
|
+
| [heroku pg](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pg-database) | ? |
|
332
|
+
| [heroku pipelines:promote](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-pipelines-promote) | `cpl promote` |
|
333
|
+
| [heroku psql](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-psql-database) | ? |
|
334
|
+
| [heroku redis](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-redis-database) | ? |
|
335
|
+
| [heroku releases](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-releases) | ? |
|
314
336
|
|
315
337
|
## Examples
|
316
338
|
|
data/Rakefile
CHANGED
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)
|
data/lib/command/base.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Command
|
4
4
|
class Base # rubocop:disable Metrics/ClassLength
|
5
|
-
attr_reader :config
|
5
|
+
attr_reader :thor_shell, :config
|
6
6
|
|
7
7
|
# Used to call the command (`cpl NAME`)
|
8
8
|
# NAME = ""
|
@@ -27,6 +27,7 @@ module Command
|
|
27
27
|
NO_IMAGE_AVAILABLE = "NO_IMAGE_AVAILABLE"
|
28
28
|
|
29
29
|
def initialize(config)
|
30
|
+
@thor_shell = Thor::Shell::Color.new
|
30
31
|
@config = config
|
31
32
|
end
|
32
33
|
|
@@ -90,14 +91,27 @@ module Command
|
|
90
91
|
}
|
91
92
|
end
|
92
93
|
|
94
|
+
def self.skip_confirm_option(required: false)
|
95
|
+
{
|
96
|
+
name: :yes,
|
97
|
+
params: {
|
98
|
+
aliases: ["-y"],
|
99
|
+
banner: "SKIP_CONFIRM",
|
100
|
+
desc: "Skip confirmation",
|
101
|
+
type: :boolean,
|
102
|
+
required: required
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
93
107
|
def self.all_options
|
94
108
|
methods.grep(/_option$/).map { |method| send(method.to_s) }
|
95
109
|
end
|
96
110
|
|
97
|
-
def self.
|
111
|
+
def self.all_options_by_key_name
|
98
112
|
all_options.each_with_object({}) do |option, result|
|
99
|
-
option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option
|
100
|
-
result["--#{option[:name]}"] = option
|
113
|
+
option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option }
|
114
|
+
result["--#{option[:name]}"] = option
|
101
115
|
end
|
102
116
|
end
|
103
117
|
|
@@ -125,20 +139,23 @@ module Command
|
|
125
139
|
cp.workload_delete(workload, no_raise: true)
|
126
140
|
end
|
127
141
|
|
128
|
-
def
|
142
|
+
def latest_image_from(items, app_name: config.app, name_only: true)
|
143
|
+
matching_items = items.filter { |item| item["name"].start_with?("#{app_name}:") }
|
144
|
+
|
145
|
+
# Or special string to indicate no image available
|
146
|
+
if matching_items.empty?
|
147
|
+
"#{app_name}:#{NO_IMAGE_AVAILABLE}"
|
148
|
+
else
|
149
|
+
latest_item = matching_items.max_by { |item| extract_image_number(item["name"]) }
|
150
|
+
name_only ? latest_item["name"] : latest_item
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def latest_image
|
129
155
|
@latest_image ||=
|
130
156
|
begin
|
131
157
|
items = cp.image_query["items"]
|
132
|
-
|
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
|
158
|
+
latest_image_from(items)
|
142
159
|
end
|
143
160
|
end
|
144
161
|
|
@@ -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]} (#{thor_shell.set_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
|
+
thor_shell.yes?("\nAre you sure you want to delete these #{old_images.length} images (y/n)?")
|
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]} (#{thor_shell.set_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
|
+
thor_shell.yes?("\nAre you sure you want to delete these #{stale_apps.length} apps (y/n)?")
|
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
|
data/lib/command/delete.rb
CHANGED
@@ -4,7 +4,8 @@ module Command
|
|
4
4
|
class Delete < Base
|
5
5
|
NAME = "delete"
|
6
6
|
OPTIONS = [
|
7
|
-
app_option(required: true)
|
7
|
+
app_option(required: true),
|
8
|
+
skip_confirm_option
|
8
9
|
].freeze
|
9
10
|
DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
|
10
11
|
LONG_DESCRIPTION = <<~HEREDOC
|
@@ -13,10 +14,7 @@ module Command
|
|
13
14
|
HEREDOC
|
14
15
|
|
15
16
|
def call
|
16
|
-
|
17
|
-
progress.print "> "
|
18
|
-
|
19
|
-
return progress.puts "Not confirmed" unless $stdin.gets.chomp == "delete"
|
17
|
+
return unless confirm_delete
|
20
18
|
|
21
19
|
delete_gvc
|
22
20
|
delete_images
|
@@ -24,6 +22,16 @@ module Command
|
|
24
22
|
|
25
23
|
private
|
26
24
|
|
25
|
+
def confirm_delete
|
26
|
+
return true if config.options[:yes]
|
27
|
+
|
28
|
+
confirmed = thor_shell.yes?("Are you sure you want to delete '#{config.app}' (y/n)?")
|
29
|
+
return false unless confirmed
|
30
|
+
|
31
|
+
progress.puts
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
27
35
|
def delete_gvc
|
28
36
|
progress.puts "- Deleting gvc:"
|
29
37
|
|
data/lib/core/controlplane.rb
CHANGED
@@ -18,8 +18,8 @@ class Controlplane
|
|
18
18
|
perform(cmd)
|
19
19
|
end
|
20
20
|
|
21
|
-
def image_query
|
22
|
-
cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{
|
21
|
+
def image_query(app_name = config.app)
|
22
|
+
cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{app_name}"
|
23
23
|
perform_yaml(cmd)
|
24
24
|
end
|
25
25
|
|
@@ -29,6 +29,15 @@ class Controlplane
|
|
29
29
|
|
30
30
|
# gvc
|
31
31
|
|
32
|
+
def gvc_query(app_name = config.app)
|
33
|
+
# When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
|
34
|
+
# otherwise we query for a gvc with the exact name.
|
35
|
+
op = config.current[:match_if_app_name_starts_with] ? "~" : "="
|
36
|
+
|
37
|
+
cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
|
38
|
+
perform_yaml(cmd)
|
39
|
+
end
|
40
|
+
|
32
41
|
def gvc_get(a_gvc = gvc)
|
33
42
|
api.gvc_get(gvc: a_gvc, org: org)
|
34
43
|
end
|
data/lib/cpl/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cpl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Gordon
|
@@ -9,22 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-03-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: cgi
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - "~>"
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: 0.3.6
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - "~>"
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: 0.3.6
|
28
14
|
- !ruby/object:Gem::Dependency
|
29
15
|
name: debug
|
30
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,48 +39,6 @@ dependencies:
|
|
53
39
|
- - "~>"
|
54
40
|
- !ruby/object:Gem::Version
|
55
41
|
version: 2.8.1
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: json
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - "~>"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 2.6.3
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - "~>"
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 2.6.3
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: net-http
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - "~>"
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: 0.3.2
|
77
|
-
type: :runtime
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - "~>"
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: 0.3.2
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: pathname
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - "~>"
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: 0.2.1
|
91
|
-
type: :runtime
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - "~>"
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: 0.2.1
|
98
42
|
- !ruby/object:Gem::Dependency
|
99
43
|
name: psych
|
100
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,20 +53,6 @@ dependencies:
|
|
109
53
|
- - "~>"
|
110
54
|
- !ruby/object:Gem::Version
|
111
55
|
version: 5.1.0
|
112
|
-
- !ruby/object:Gem::Dependency
|
113
|
-
name: tempfile
|
114
|
-
requirement: !ruby/object:Gem::Requirement
|
115
|
-
requirements:
|
116
|
-
- - "~>"
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
version: 0.1.3
|
119
|
-
type: :runtime
|
120
|
-
prerelease: false
|
121
|
-
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
requirements:
|
123
|
-
- - "~>"
|
124
|
-
- !ruby/object:Gem::Version
|
125
|
-
version: 0.1.3
|
126
56
|
- !ruby/object:Gem::Dependency
|
127
57
|
name: thor
|
128
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,20 +67,6 @@ dependencies:
|
|
137
67
|
- - "~>"
|
138
68
|
- !ruby/object:Gem::Version
|
139
69
|
version: 1.2.1
|
140
|
-
- !ruby/object:Gem::Dependency
|
141
|
-
name: yaml
|
142
|
-
requirement: !ruby/object:Gem::Requirement
|
143
|
-
requirements:
|
144
|
-
- - "~>"
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: 0.2.1
|
147
|
-
type: :runtime
|
148
|
-
prerelease: false
|
149
|
-
version_requirements: !ruby/object:Gem::Requirement
|
150
|
-
requirements:
|
151
|
-
- - "~>"
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: 0.2.1
|
154
70
|
- !ruby/object:Gem::Dependency
|
155
71
|
name: rspec
|
156
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -230,7 +146,8 @@ executables:
|
|
230
146
|
extensions: []
|
231
147
|
extra_rdoc_files: []
|
232
148
|
files:
|
233
|
-
- ".github/workflows/
|
149
|
+
- ".github/workflows/rspec.yml"
|
150
|
+
- ".github/workflows/rubocop.yml"
|
234
151
|
- ".gitignore"
|
235
152
|
- ".rspec"
|
236
153
|
- ".rubocop.yml"
|
@@ -245,11 +162,15 @@ files:
|
|
245
162
|
- cpl
|
246
163
|
- cpl.gemspec
|
247
164
|
- docs/commands.md
|
165
|
+
- docs/postgres.md
|
166
|
+
- docs/redis.md
|
248
167
|
- docs/troubleshooting.md
|
249
168
|
- examples/circleci.yml
|
250
169
|
- examples/controlplane.yml
|
251
170
|
- lib/command/base.rb
|
252
171
|
- lib/command/build_image.rb
|
172
|
+
- lib/command/cleanup_old_images.rb
|
173
|
+
- lib/command/cleanup_stale_apps.rb
|
253
174
|
- lib/command/config.rb
|
254
175
|
- lib/command/delete.rb
|
255
176
|
- lib/command/env.rb
|
@@ -275,8 +196,6 @@ files:
|
|
275
196
|
- lib/cpl.rb
|
276
197
|
- lib/cpl/version.rb
|
277
198
|
- lib/main.rb
|
278
|
-
- postgres.md
|
279
|
-
- redis.md
|
280
199
|
- script/generate_commands_docs
|
281
200
|
- templates/gvc.yml
|
282
201
|
- templates/identity.yml
|
File without changes
|
/data/{redis.md → docs/redis.md}
RENAMED
File without changes
|