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