cpl 0.1.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 +7 -0
- data/.github/workflows/ci.yml +60 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +16 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +12 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +104 -0
- data/LICENSE +21 -0
- data/README.md +318 -0
- data/Rakefile +11 -0
- data/bin/cpl +6 -0
- data/cpl +15 -0
- data/cpl.gemspec +42 -0
- data/docs/commands.md +219 -0
- data/docs/troubleshooting.md +6 -0
- data/examples/circleci.yml +106 -0
- data/examples/controlplane.yml +44 -0
- data/lib/command/base.rb +177 -0
- data/lib/command/build_image.rb +25 -0
- data/lib/command/config.rb +33 -0
- data/lib/command/delete.rb +50 -0
- data/lib/command/env.rb +21 -0
- data/lib/command/exists.rb +23 -0
- data/lib/command/latest_image.rb +18 -0
- data/lib/command/logs.rb +29 -0
- data/lib/command/open.rb +33 -0
- data/lib/command/promote_image.rb +27 -0
- data/lib/command/ps.rb +40 -0
- data/lib/command/ps_restart.rb +34 -0
- data/lib/command/ps_start.rb +34 -0
- data/lib/command/ps_stop.rb +34 -0
- data/lib/command/run.rb +106 -0
- data/lib/command/run_detached.rb +148 -0
- data/lib/command/setup.rb +59 -0
- data/lib/command/test.rb +26 -0
- data/lib/core/config.rb +81 -0
- data/lib/core/controlplane.rb +128 -0
- data/lib/core/controlplane_api.rb +51 -0
- data/lib/core/controlplane_api_cli.rb +10 -0
- data/lib/core/controlplane_api_direct.rb +42 -0
- data/lib/core/scripts.rb +34 -0
- data/lib/cpl/version.rb +5 -0
- data/lib/cpl.rb +139 -0
- data/lib/main.rb +5 -0
- data/postgres.md +436 -0
- data/redis.md +112 -0
- data/script/generate_commands_docs +60 -0
- data/templates/gvc.yml +13 -0
- data/templates/identity.yml +2 -0
- data/templates/memcached.yml +23 -0
- data/templates/postgres.yml +31 -0
- data/templates/rails.yml +25 -0
- data/templates/redis.yml +20 -0
- data/templates/sidekiq.yml +28 -0
- metadata +312 -0
data/docs/commands.md
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
<!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
|
2
|
+
|
3
|
+
### Common Options
|
4
|
+
|
5
|
+
```
|
6
|
+
-a XXX, --app XXX app ref on Control Plane (GVC)
|
7
|
+
```
|
8
|
+
|
9
|
+
This `-a` option is used in most of the commands and will pick all other app configurations from the project-specific
|
10
|
+
`.controlplane/controlplane.yml` file.
|
11
|
+
|
12
|
+
### Commands
|
13
|
+
|
14
|
+
### `build-image`
|
15
|
+
|
16
|
+
- Builds and pushes the image to Control Plane
|
17
|
+
- Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
|
18
|
+
- Uses `.controlplane/Dockerfile`
|
19
|
+
|
20
|
+
```sh
|
21
|
+
cpl build-image -a $APP_NAME
|
22
|
+
```
|
23
|
+
|
24
|
+
### `config`
|
25
|
+
|
26
|
+
- Displays current configs (global and app-specific)
|
27
|
+
|
28
|
+
```sh
|
29
|
+
# Shows the global config.
|
30
|
+
cpl config
|
31
|
+
|
32
|
+
# Shows both global and app-specific configs.
|
33
|
+
cpl config -a $APP_NAME
|
34
|
+
```
|
35
|
+
|
36
|
+
### `delete`
|
37
|
+
|
38
|
+
- Deletes the whole app (GVC with all workloads and all images)
|
39
|
+
- Will ask for explicit user confirmation
|
40
|
+
|
41
|
+
```sh
|
42
|
+
cpl delete -a $APP_NAME
|
43
|
+
```
|
44
|
+
|
45
|
+
### `env`
|
46
|
+
|
47
|
+
- Displays app-specific environment variables
|
48
|
+
|
49
|
+
```sh
|
50
|
+
cpl env -a $APP_NAME
|
51
|
+
```
|
52
|
+
|
53
|
+
### `exists`
|
54
|
+
|
55
|
+
- Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
|
56
|
+
|
57
|
+
```sh
|
58
|
+
if [ cpl exists -a $APP_NAME ]; ...
|
59
|
+
```
|
60
|
+
|
61
|
+
### `latest-image`
|
62
|
+
|
63
|
+
- Displays the latest image name
|
64
|
+
|
65
|
+
```sh
|
66
|
+
cpl latest-image -a $APP_NAME
|
67
|
+
```
|
68
|
+
|
69
|
+
### `logs`
|
70
|
+
|
71
|
+
- Light wrapper to display tailed raw logs for app/workload syntax
|
72
|
+
|
73
|
+
```sh
|
74
|
+
# Displays logs for the default workload (`one_off_workload`).
|
75
|
+
cpl logs -a $APP_NAME
|
76
|
+
|
77
|
+
# Displays logs for a specific workload.
|
78
|
+
cpl logs -a $APP_NAME -w $WORKLOAD_NAME
|
79
|
+
```
|
80
|
+
|
81
|
+
### `open`
|
82
|
+
|
83
|
+
- Opens the app endpoint URL in the default browser
|
84
|
+
|
85
|
+
```sh
|
86
|
+
# Opens the endpoint of the default workload (`one_off_workload`).
|
87
|
+
cpl open -a $APP_NAME
|
88
|
+
|
89
|
+
# Opens the endpoint of a specific workload.
|
90
|
+
cpl open -a $APP_NAME -w $WORKLOAD_NAME
|
91
|
+
```
|
92
|
+
|
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
|
+
### `ps`
|
102
|
+
|
103
|
+
- Shows running replicas in app
|
104
|
+
|
105
|
+
```sh
|
106
|
+
# Shows running replicas in app, for all workloads.
|
107
|
+
cpl ps -a $APP_NAME
|
108
|
+
|
109
|
+
# Shows running replicas in app, for a specific workload.
|
110
|
+
cpl ps -a $APP_NAME -w $WORKLOAD_NAME
|
111
|
+
```
|
112
|
+
|
113
|
+
### `ps:restart`
|
114
|
+
|
115
|
+
- Forces redeploy of workloads in app
|
116
|
+
|
117
|
+
```sh
|
118
|
+
# Forces redeploy of all workloads in app.
|
119
|
+
cpl ps:restart -a $APP_NAME
|
120
|
+
|
121
|
+
# Forces redeploy of a specific workload in app.
|
122
|
+
cpl ps:restart -a $APP_NAME -w $WORKLOAD_NAME
|
123
|
+
```
|
124
|
+
|
125
|
+
### `ps:start`
|
126
|
+
|
127
|
+
- Starts workloads in app
|
128
|
+
|
129
|
+
```sh
|
130
|
+
# Starts all workloads in app.
|
131
|
+
cpl ps:start -a $APP_NAME
|
132
|
+
|
133
|
+
# Starts a specific workload in app.
|
134
|
+
cpl ps:start -a $APP_NAME -w $WORKLOAD_NAME
|
135
|
+
```
|
136
|
+
|
137
|
+
### `ps:stop`
|
138
|
+
|
139
|
+
- Stops workloads in app
|
140
|
+
|
141
|
+
```sh
|
142
|
+
# Stops all workloads in app.
|
143
|
+
cpl ps:stop -a $APP_NAME
|
144
|
+
|
145
|
+
# Stops a specific workload in app.
|
146
|
+
cpl ps:stop -a $APP_NAME -w $WORKLOAD_NAME
|
147
|
+
```
|
148
|
+
|
149
|
+
### `run`
|
150
|
+
|
151
|
+
- Runs one-off **_interactive_** replicas (analog of `heroku run`)
|
152
|
+
- Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
|
153
|
+
- May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
|
154
|
+
|
155
|
+
> **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
|
156
|
+
> task crashing are tolerable. For production tasks, it's better to use `cpl run:detached`.
|
157
|
+
|
158
|
+
```sh
|
159
|
+
# Opens shell (bash by default).
|
160
|
+
cpl run -a $APP_NAME
|
161
|
+
|
162
|
+
# Runs command, displays output, and exits shell.
|
163
|
+
cpl run ls / -a $APP_NAME
|
164
|
+
cpl run rails db:migrate:status -a $APP_NAME
|
165
|
+
|
166
|
+
# Runs command and keeps shell open.
|
167
|
+
cpl run rails c -a $APP_NAME
|
168
|
+
|
169
|
+
# Uses a different image (which may not be promoted yet).
|
170
|
+
cpl run rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
|
171
|
+
cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential image
|
172
|
+
```
|
173
|
+
|
174
|
+
### `run:detached`
|
175
|
+
|
176
|
+
- Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)
|
177
|
+
- Uses `Cron` workload type with log async fetching
|
178
|
+
- Implemented with only async execution methods, more suitable for production tasks
|
179
|
+
- Has alternative log fetch implementation with only JSON-polling and no WebSockets
|
180
|
+
- Less responsive but more stable, useful for CI tasks
|
181
|
+
|
182
|
+
```sh
|
183
|
+
cpl run:detached rails db:prepare -a $APP_NAME
|
184
|
+
cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
|
185
|
+
|
186
|
+
# Uses some other image.
|
187
|
+
cpl run:detached rails db:migrate -a $APP_NAME --image /some/full/image/path
|
188
|
+
|
189
|
+
# Uses latest app image (which may not be promoted yet).
|
190
|
+
cpl run:detached rails db:migrate -a $APP_NAME --image latest
|
191
|
+
|
192
|
+
# Uses a different image (which may not be promoted yet).
|
193
|
+
cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
|
194
|
+
cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
|
195
|
+
```
|
196
|
+
|
197
|
+
### `setup`
|
198
|
+
|
199
|
+
- Applies application-specific configs from templates (e.g., for every review-app)
|
200
|
+
- Publishes (creates or updates) those at Control Plane infrastructure
|
201
|
+
- Picks templates from the `.controlplane/templates` directory
|
202
|
+
- Templates are ordinary Control Plane templates but with variable preprocessing
|
203
|
+
|
204
|
+
**Preprocessed template variables:**
|
205
|
+
|
206
|
+
```
|
207
|
+
APP_GVC - basically GVC or app name
|
208
|
+
APP_LOCATION - default location
|
209
|
+
APP_ORG - organization
|
210
|
+
APP_IMAGE - will use latest app image
|
211
|
+
```
|
212
|
+
|
213
|
+
```sh
|
214
|
+
# Applies single template.
|
215
|
+
cpl setup redis -a $APP_NAME
|
216
|
+
|
217
|
+
# Applies several templates (practically creating full app).
|
218
|
+
cpl setup gvc postgres redis rails -a $APP_NAME
|
219
|
+
```
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# Troubleshooting
|
2
|
+
|
3
|
+
|
4
|
+
## App Web Page Shows `upstream request timeout`
|
5
|
+
|
6
|
+
If you get a blank screen showing the message `upstream request timeout` on your app after running `cpl open -a my-app-name`, check out the application logs. Your image has been promoted and your app crashing when starting.
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Example config for staging app:
|
2
|
+
# - triggers on push to master
|
3
|
+
# - static app name
|
4
|
+
# - no resource provisioning
|
5
|
+
# - no database setup, only migration
|
6
|
+
build-staging:
|
7
|
+
docker:
|
8
|
+
- image: cimg/ruby:3.1-node
|
9
|
+
resource_class: large
|
10
|
+
environment:
|
11
|
+
CPLN_ORG: myorg
|
12
|
+
APP_NAME: myapp-staging
|
13
|
+
steps:
|
14
|
+
- checkout
|
15
|
+
- setup_remote_docker:
|
16
|
+
docker_layer_caching: true
|
17
|
+
- run:
|
18
|
+
name: Install Control Plane tools
|
19
|
+
command: |
|
20
|
+
sudo npm install -g @controlplane/cli && cpln --version
|
21
|
+
cpln profile create default --token ${CPLN_TOKEN} --org ${CPLN_ORG} --gvc ${APP_NAME}
|
22
|
+
cpln image docker-login
|
23
|
+
|
24
|
+
git clone https://github.com/shakacode/heroku-to-control-plane ~/heroku-to-control-plane
|
25
|
+
sudo ln -s ~/heroku-to-control-plane/cpl /usr/local/bin/cpl
|
26
|
+
- run:
|
27
|
+
name: Containerize and push image
|
28
|
+
command: cpl build-image -a ${APP_NAME}
|
29
|
+
- run:
|
30
|
+
name: Database tasks
|
31
|
+
command: cpl run:detached rails db:migrate -a ${APP_NAME} --image latest
|
32
|
+
- run:
|
33
|
+
name: Promote image
|
34
|
+
command: cpl promote-image -a ${APP_NAME}
|
35
|
+
|
36
|
+
# Example config for review app:
|
37
|
+
# - triggers manually if needed
|
38
|
+
# - dynamic app name based on PR number
|
39
|
+
# - resources provisioning for new apps
|
40
|
+
# - initial database setup or migration
|
41
|
+
build-review-app:
|
42
|
+
docker:
|
43
|
+
- image: cimg/ruby:3.1-node
|
44
|
+
resource_class: large
|
45
|
+
environment:
|
46
|
+
CPLN_ORG: my-org-name
|
47
|
+
steps:
|
48
|
+
- checkout
|
49
|
+
- setup_remote_docker:
|
50
|
+
docker_layer_caching: true
|
51
|
+
- run:
|
52
|
+
name: Setup environment
|
53
|
+
command: |
|
54
|
+
PR_NUM=$(echo $CIRCLE_PULL_REQUEST | grep -Eo '[0-9]+$')
|
55
|
+
echo "export APP_NAME=hichee-review-$PR_NUM" >> $BASH_ENV
|
56
|
+
- run:
|
57
|
+
name: Install Control Plane tools
|
58
|
+
command: |
|
59
|
+
sudo npm install -g @controlplane/cli && cpln --version
|
60
|
+
cpln profile create default --token ${CPLN_TOKEN} --org ${CPLN_ORG} --gvc ${APP_NAME}
|
61
|
+
cpln image docker-login
|
62
|
+
|
63
|
+
git clone https://github.com/shakacode/heroku-to-control-plane ~/heroku-to-control-plane
|
64
|
+
sudo ln -s ~/heroku-to-control-plane/cpl /usr/local/bin/cpl
|
65
|
+
- run:
|
66
|
+
name: Provision review app if needed
|
67
|
+
command: |
|
68
|
+
if ! cpl exist -a ${APP_NAME}; then
|
69
|
+
cpl setup gvc postgres redis memcached rails sidekiq -a ${APP_NAME}
|
70
|
+
echo "export NEW_APP=true" >> $BASH_ENV
|
71
|
+
fi
|
72
|
+
- run:
|
73
|
+
name: Containerize and push image
|
74
|
+
command: |
|
75
|
+
cpl build-image -a ${APP_NAME} --commit ${CIRCLE_SHA1::7}
|
76
|
+
- run:
|
77
|
+
name: Database tasks
|
78
|
+
command: |
|
79
|
+
if [ -n "${NEW_APP}" ]; then
|
80
|
+
cpl run:detached 'LOG_LEVEL=warn rails db:reset' -a ${APP_NAME} --image latest
|
81
|
+
else
|
82
|
+
cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a ${APP_NAME} --image latest
|
83
|
+
fi
|
84
|
+
- run:
|
85
|
+
name: Promote image
|
86
|
+
command: cpl promote-image -a ${APP_NAME}
|
87
|
+
|
88
|
+
review-app:
|
89
|
+
jobs:
|
90
|
+
- start:
|
91
|
+
filters:
|
92
|
+
branches:
|
93
|
+
ignore: master
|
94
|
+
type: approval
|
95
|
+
- build-review-app:
|
96
|
+
filters:
|
97
|
+
branches:
|
98
|
+
ignore: master
|
99
|
+
requires:
|
100
|
+
- start
|
101
|
+
staging:
|
102
|
+
jobs:
|
103
|
+
- build-staging:
|
104
|
+
filters:
|
105
|
+
branches:
|
106
|
+
only: master
|
@@ -0,0 +1,44 @@
|
|
1
|
+
aliases:
|
2
|
+
common: &common
|
3
|
+
# Org name for staging. (customize to your needs)
|
4
|
+
# Production apps will use a different Control Plane org, specified below, for security.
|
5
|
+
# keys beginning with CPLN correspond to your settings in Control Plane
|
6
|
+
cpln_org: my-org-staging
|
7
|
+
|
8
|
+
# Example apps use only location. CPLN offers the ability to use multiple locations.
|
9
|
+
# TODO -- allow specfication of multiple locations
|
10
|
+
default_location: aws-us-east-2
|
11
|
+
|
12
|
+
# Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
|
13
|
+
one_off_workload: rails
|
14
|
+
|
15
|
+
# Workloads that are application itself and are using application docker image
|
16
|
+
app_workloads:
|
17
|
+
- rails
|
18
|
+
- sidekiq
|
19
|
+
|
20
|
+
# Additional "service type" workloads, using non-application docker images
|
21
|
+
additional_workloads:
|
22
|
+
- redis
|
23
|
+
- postgres
|
24
|
+
- memcached
|
25
|
+
|
26
|
+
apps:
|
27
|
+
my-app-staging:
|
28
|
+
# Use the values from the common section above
|
29
|
+
<<: *common
|
30
|
+
my-app-review:
|
31
|
+
<<: *common
|
32
|
+
# if match_if_app_name_starts_with == true, then use this config for app names beginning,
|
33
|
+
# like my-app-review-pr123 or my-app-review-anything-goes
|
34
|
+
match_if_app_name_starts_with: true
|
35
|
+
my-app-production:
|
36
|
+
<<: *common
|
37
|
+
# Use a different org for production
|
38
|
+
cpln_org: my-org-production
|
39
|
+
# Allows running command 'cpl pipeline-promote my-app-staging' to promote the staging app to production
|
40
|
+
upstream: my-app-staging
|
41
|
+
my-app-other:
|
42
|
+
<<: *common
|
43
|
+
# you can specify different dockerfile relative to .controlplane folder, default is just 'Dockerfile'
|
44
|
+
dockerfile: ../some_other/Dockerfile
|
data/lib/command/base.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Base # rubocop:disable Metrics/ClassLength
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
# Used to call the command (`cpl NAME`)
|
8
|
+
# NAME = ""
|
9
|
+
# Displayed when running `cpl help` or `cpl help NAME` (defaults to `NAME`)
|
10
|
+
USAGE = ""
|
11
|
+
# Throws error if `true` and no arguments are passed to the command
|
12
|
+
# or if `false` and arguments are passed to the command
|
13
|
+
REQUIRES_ARGS = false
|
14
|
+
# Default arguments if none are passed to the command
|
15
|
+
DEFAULT_ARGS = [].freeze
|
16
|
+
# Options for the command (use option methods below)
|
17
|
+
OPTIONS = [].freeze
|
18
|
+
# Displayed when running `cpl help`
|
19
|
+
# DESCRIPTION = ""
|
20
|
+
# Displayed when running `cpl help NAME`
|
21
|
+
# LONG_DESCRIPTION = ""
|
22
|
+
# Displayed along with `LONG_DESCRIPTION` when running `cpl help NAME`
|
23
|
+
EXAMPLES = ""
|
24
|
+
# If `true`, hides the command from `cpl help`
|
25
|
+
HIDE = false
|
26
|
+
|
27
|
+
NO_IMAGE_AVAILABLE = "NO_IMAGE_AVAILABLE"
|
28
|
+
|
29
|
+
def initialize(config)
|
30
|
+
@config = config
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.all_commands
|
34
|
+
Dir["#{__dir__}/*.rb"].each_with_object({}) do |file, result|
|
35
|
+
filename = File.basename(file, ".rb")
|
36
|
+
classname = File.read(file).match(/^\s+class (\w+) < Base($| .*$)/)&.captures&.first
|
37
|
+
result[filename.to_sym] = Object.const_get("::Command::#{classname}") if classname
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.app_option(required: false)
|
42
|
+
{
|
43
|
+
name: :app,
|
44
|
+
params: {
|
45
|
+
aliases: ["-a"],
|
46
|
+
banner: "APP_NAME",
|
47
|
+
desc: "Application name",
|
48
|
+
type: :string,
|
49
|
+
required: required
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.workload_option(required: false)
|
55
|
+
{
|
56
|
+
name: :workload,
|
57
|
+
params: {
|
58
|
+
aliases: ["-w"],
|
59
|
+
banner: "WORKLOAD_NAME",
|
60
|
+
desc: "Workload name",
|
61
|
+
type: :string,
|
62
|
+
required: required
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.image_option(required: false)
|
68
|
+
{
|
69
|
+
name: :image,
|
70
|
+
params: {
|
71
|
+
aliases: ["-i"],
|
72
|
+
banner: "IMAGE_NAME",
|
73
|
+
desc: "Image name",
|
74
|
+
type: :string,
|
75
|
+
required: required
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.commit_option(required: false)
|
81
|
+
{
|
82
|
+
name: :commit,
|
83
|
+
params: {
|
84
|
+
aliases: ["-c"],
|
85
|
+
banner: "COMMIT_HASH",
|
86
|
+
desc: "Commit hash",
|
87
|
+
type: :string,
|
88
|
+
required: required
|
89
|
+
}
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.all_options
|
94
|
+
methods.grep(/_option$/).map { |method| send(method.to_s) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.all_options_key_name
|
98
|
+
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]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def wait_for(title)
|
105
|
+
progress.print "- Waiting for #{title}"
|
106
|
+
until yield
|
107
|
+
progress.print(".")
|
108
|
+
sleep(1)
|
109
|
+
end
|
110
|
+
progress.puts
|
111
|
+
end
|
112
|
+
|
113
|
+
def wait_for_workload(workload)
|
114
|
+
wait_for("workload to start") { cp.workload_get(workload) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def wait_for_replica(workload, location)
|
118
|
+
wait_for("replica") do
|
119
|
+
cp.workload_get_replicas(workload, location: location)&.dig("items", 0)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def ensure_workload_deleted(workload)
|
124
|
+
progress.puts "- Ensure workload is deleted"
|
125
|
+
cp.workload_delete(workload, no_raise: true)
|
126
|
+
end
|
127
|
+
|
128
|
+
def latest_image # rubocop:disable Metrics/MethodLength
|
129
|
+
@latest_image ||=
|
130
|
+
begin
|
131
|
+
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
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def latest_image_next
|
146
|
+
@latest_image_next ||= begin
|
147
|
+
image = latest_image.split(":").first
|
148
|
+
image += ":#{extract_image_number(latest_image) + 1}"
|
149
|
+
image += "_#{config.options[:commit]}" if config.options[:commit]
|
150
|
+
image
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# NOTE: use simplified variant atm, as shelljoin do different escaping
|
155
|
+
# TODO: most probably need better logic for escaping various quotes
|
156
|
+
def args_join(args)
|
157
|
+
args.join(" ")
|
158
|
+
end
|
159
|
+
|
160
|
+
def progress
|
161
|
+
$stderr
|
162
|
+
end
|
163
|
+
|
164
|
+
def cp
|
165
|
+
@cp ||= Controlplane.new(config)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# returns 0 if no prior image
|
171
|
+
def extract_image_number(image_name)
|
172
|
+
return 0 if image_name.end_with?(NO_IMAGE_AVAILABLE)
|
173
|
+
|
174
|
+
image_name.match(/:(\d+)/)&.captures&.first.to_i
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class BuildImage < Base
|
5
|
+
NAME = "build-image"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
commit_option
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Builds and pushes the image to Control Plane"
|
11
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
12
|
+
- Builds and pushes the image to Control Plane
|
13
|
+
- Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
|
14
|
+
- Uses `.controlplane/Dockerfile`
|
15
|
+
HEREDOC
|
16
|
+
|
17
|
+
def call
|
18
|
+
dockerfile = config.current[:dockerfile] || "Dockerfile"
|
19
|
+
dockerfile = "#{config.app_cpln_dir}/#{dockerfile}"
|
20
|
+
progress.puts "- Building dockerfile: #{dockerfile}"
|
21
|
+
|
22
|
+
cp.image_build(latest_image_next, dockerfile: dockerfile)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Config < Base
|
5
|
+
NAME = "config"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option
|
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
|
14
|
+
```sh
|
15
|
+
# Shows the global config.
|
16
|
+
cpl config
|
17
|
+
|
18
|
+
# Shows both global and app-specific configs.
|
19
|
+
cpl config -a $APP_NAME
|
20
|
+
```
|
21
|
+
HEREDOC
|
22
|
+
|
23
|
+
def call
|
24
|
+
puts "-- Options"
|
25
|
+
puts config.options.to_hash.to_yaml[4..]
|
26
|
+
puts
|
27
|
+
|
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
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Delete < Base
|
5
|
+
NAME = "delete"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true)
|
8
|
+
].freeze
|
9
|
+
DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
|
10
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
11
|
+
- Deletes the whole app (GVC with all workloads and all images)
|
12
|
+
- Will ask for explicit user confirmation
|
13
|
+
HEREDOC
|
14
|
+
|
15
|
+
def call
|
16
|
+
progress.puts "Type 'delete' to delete #{config.app} and images"
|
17
|
+
progress.print "> "
|
18
|
+
|
19
|
+
return progress.puts "Not confirmed" unless $stdin.gets.chomp == "delete"
|
20
|
+
|
21
|
+
delete_gvc
|
22
|
+
delete_images
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def delete_gvc
|
28
|
+
progress.puts "- Deleting gvc:"
|
29
|
+
|
30
|
+
return progress.puts "none" unless cp.gvc_get
|
31
|
+
|
32
|
+
cp.gvc_delete
|
33
|
+
progress.puts config.app
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_images
|
37
|
+
progress.puts "- Deleting image(s):"
|
38
|
+
|
39
|
+
images = cp.image_query["items"]
|
40
|
+
.filter_map { |item| item["name"] if item["name"].start_with?("#{config.app}:") }
|
41
|
+
|
42
|
+
return progress.puts "none" unless images
|
43
|
+
|
44
|
+
images.each do |image|
|
45
|
+
cp.image_delete(image)
|
46
|
+
progress.puts image
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/command/env.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Env < Base
|
5
|
+
NAME = "env"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true)
|
8
|
+
].freeze
|
9
|
+
DESCRIPTION = "Displays app-specific environment variables"
|
10
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
11
|
+
- Displays app-specific environment variables
|
12
|
+
HEREDOC
|
13
|
+
|
14
|
+
def call
|
15
|
+
cp.gvc_get.dig("spec", "env").map do |prop|
|
16
|
+
# NOTE: atm no special chars handling, consider adding if needed
|
17
|
+
puts "#{prop['name']}=#{prop['value']}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Exists < Base
|
5
|
+
NAME = "exists"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true)
|
8
|
+
].freeze
|
9
|
+
DESCRIPTION = "Shell-checks if an application (GVC) exists, useful in scripts"
|
10
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
11
|
+
- Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
|
12
|
+
HEREDOC
|
13
|
+
EXAMPLES = <<~HEREDOC
|
14
|
+
```sh
|
15
|
+
if [ cpl exists -a $APP_NAME ]; ...
|
16
|
+
```
|
17
|
+
HEREDOC
|
18
|
+
|
19
|
+
def call
|
20
|
+
exit(!cp.gvc_get.nil?)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|