cpl 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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
|
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
|