cpl 0.7.0 → 1.0.1
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/command_docs.yml +24 -0
- data/.github/workflows/rspec.yml +8 -5
- data/.github/workflows/rubocop.yml +5 -2
- data/.gitignore +1 -0
- data/.overcommit.yml +3 -0
- data/Gemfile.lock +4 -4
- data/README.md +152 -125
- data/Rakefile +7 -2
- data/docs/assets/grafana-alert.png +0 -0
- data/docs/assets/memcached.png +0 -0
- data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
- data/docs/commands.md +2 -2
- data/docs/tips.md +177 -0
- data/examples/circleci.yml +8 -9
- data/examples/controlplane.yml +16 -14
- data/lib/command/base.rb +8 -14
- data/lib/command/cleanup_stale_apps.rb +1 -0
- data/lib/command/run.rb +8 -6
- data/lib/command/run_detached.rb +12 -8
- data/lib/core/scripts.rb +2 -2
- data/lib/cpl/version.rb +2 -1
- data/lib/cpl.rb +42 -1
- data/script/check_command_docs +3 -0
- data/script/{generate_commands_docs → update_command_docs} +2 -2
- data/templates/daily-task.yml +5 -4
- data/templates/maintenance.yml +4 -3
- data/templates/memcached.yml +3 -2
- data/templates/postgres.yml +3 -2
- data/templates/rails.yml +3 -2
- data/templates/redis.yml +2 -1
- data/templates/sidekiq.yml +13 -4
- metadata +9 -3
Binary file
|
Binary file
|
Binary file
|
data/docs/commands.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
|
2
2
|
|
3
|
-
|
3
|
+
## Common Options
|
4
4
|
|
5
5
|
```
|
6
6
|
-a XXX, --app XXX app ref on Control Plane (GVC)
|
@@ -9,7 +9,7 @@
|
|
9
9
|
This `-a` option is used in most of the commands and will pick all other app configurations from the project-specific
|
10
10
|
`.controlplane/controlplane.yml` file.
|
11
11
|
|
12
|
-
|
12
|
+
## Commands
|
13
13
|
|
14
14
|
### `apply-template`
|
15
15
|
|
data/docs/tips.md
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
# Tips
|
2
|
+
|
3
|
+
1. [GVCs vs. Orgs](#gvcs-vs-orgs)
|
4
|
+
2. [RAM](#ram)
|
5
|
+
3. [Remote IP](#remote-ip)
|
6
|
+
4. [ENV Values](#env-values)
|
7
|
+
5. [CI](#ci)
|
8
|
+
6. [Memcached](#memcached)
|
9
|
+
7. [Sidekiq](#sidekiq)
|
10
|
+
- [Quieting Non-Critical Workers During Deployments](#quieting-non-critical-workers-during-deployments)
|
11
|
+
- [Setting Up a Pre Stop Hook](#setting-up-a-pre-stop-hook)
|
12
|
+
- [Setting Up a Liveness Probe](#setting-up-a-liveness-probe)
|
13
|
+
8. [Useful Links](#useful-links)
|
14
|
+
|
15
|
+
## GVCs vs. Orgs
|
16
|
+
|
17
|
+
- A "GVC" roughly corresponds to a Heroku "app."
|
18
|
+
- Images are available at the org level.
|
19
|
+
- Multiple GVCs within an org can use the same image.
|
20
|
+
- You can have different images within a GVC and even within a workload. This flexibility is one of the key differences
|
21
|
+
compared to Heroku apps.
|
22
|
+
|
23
|
+
## RAM
|
24
|
+
|
25
|
+
Any workload replica that reaches the max memory is terminated and restarted. You can configure alerts for workload
|
26
|
+
restarts and the percentage of memory used in the Control Plane UX.
|
27
|
+
|
28
|
+
Here are the steps for configuring an alert for the percentage of memory used:
|
29
|
+
|
30
|
+
1. Navigate to the workload that you want to configure the alert for
|
31
|
+
2. Click "Metrics" on the left menu to go to Grafana
|
32
|
+
3. On Grafana, go to the alerting page by clicking on the alert icon in the sidebar
|
33
|
+
4. Click on "New alert rule"
|
34
|
+
5. In the "Set a query and alert condition" section, select "Grafana managed alert"
|
35
|
+
6. There should be a default query named `A`
|
36
|
+
7. Change the data source of the query to `metrics`
|
37
|
+
8. Click "Code" on the top right of the query and enter `mem_used{workload="workload_name"} / mem_reserved{workload="workload_name"} * 100`
|
38
|
+
(replace `workload_name` with the name of the workload)
|
39
|
+
9. There should be a default expression named `B`, with the type `Reduce`, the function `Last`, and the input `A` (this
|
40
|
+
ensures that we're getting the last data point of the query)
|
41
|
+
10. There should be a default expression named `C`, with the type `Threshold`, and the input `B` (this is where you
|
42
|
+
configure the condition for firing the alert, e.g., `IS ABOVE 95`)
|
43
|
+
11. You can then preview the alert and check if it's firing or not based on the example time range of the query
|
44
|
+
12. In the "Alert evaluation behavior" section, you can configure how often the alert should be evaluated and for how
|
45
|
+
long the condition should be true before firing (for example, you might want the alert only to be fired if the
|
46
|
+
percentage has been above `95` for more than 20 seconds)
|
47
|
+
13. In the "Add details for your alert" section, fill out the name, folder, group, and summary for your alert
|
48
|
+
14. In the "Notifications" section, you can configure a label for the alert if you're using a custom notification policy,
|
49
|
+
but there should be a default root route for all alerts
|
50
|
+
15. Once you're done, save and exit in the top right of the page
|
51
|
+
16. Click "Contact points" on the top menu
|
52
|
+
17. Edit the `grafana-default-email` contact point and add the email where you want to receive notifications
|
53
|
+
18. You should now receive notifications for the alert in your email
|
54
|
+
|
55
|
+

|
56
|
+
|
57
|
+
The steps for configuring an alert for workload restarts are almost identical, but the code for the query would be
|
58
|
+
`container_restarts`.
|
59
|
+
|
60
|
+
For more information on Grafana alerts, see: https://grafana.com/docs/grafana/latest/alerting/
|
61
|
+
|
62
|
+
## Remote IP
|
63
|
+
|
64
|
+
The actual remote IP of the workload container is in the 127.0.0.x network, so that will be the value of the
|
65
|
+
`REMOTE_ADDR` env var.
|
66
|
+
|
67
|
+
However, Control Plane additionally sets the `x-forwarded-for` and `x-envoy-external-address` headers (and others - see:
|
68
|
+
https://docs.controlplane.com/concepts/security#headers). On Rails, the `ActionDispatch::RemoteIp` middleware should
|
69
|
+
pick those up and automatically populate `request.remote_ip`.
|
70
|
+
|
71
|
+
So `REMOTE_ADDR` should not be used directly, only `request.remote_ip`.
|
72
|
+
|
73
|
+
## ENV Values
|
74
|
+
|
75
|
+
You can store ENV values used by a container (within a workload) within Control Plane at the following levels:
|
76
|
+
|
77
|
+
1. Workload Container
|
78
|
+
2. GVC
|
79
|
+
|
80
|
+
For your "review apps," it is convenient to have simple ENVs stored in plain text in your source code. You will want to
|
81
|
+
keep some ENVs, like the Rails' `SECRET_KEY_BASE`, out of your source code. For staging and production apps, you will
|
82
|
+
set these values directly at the GVC or workload levels, so none of these ENV values are committed to the source code.
|
83
|
+
|
84
|
+
For storing ENVs in the source code, we can use a level of indirection so that you can store an ENV value in your source
|
85
|
+
code like `cpln://secret/my-app-review-env-secrets.SECRET_KEY_BASE` and then have the secret value stored at the org
|
86
|
+
level, which applies to your GVCs mapped to that org.
|
87
|
+
|
88
|
+
Here is how you do this:
|
89
|
+
|
90
|
+
1. In the upper left "Manage Org" menu, click on "Secrets"
|
91
|
+
2. Create a secret with `Secret Type: Dictionary` (e.g., `my-secrets`) and add the secret env vars there
|
92
|
+
3. In the upper left "Manage GVC" menu, click on "Identities"
|
93
|
+
4. Create an identity (e.g., `my-identity`)
|
94
|
+
5. Navigate to the workload that you want to associate with the identity created
|
95
|
+
6. Click "Identity" on the left menu and select the identity created
|
96
|
+
7. In the lower left "Access Control" menu, click on "Policies"
|
97
|
+
8. Create a policy with `Target Kind: Secret` and add a binding with the `reveal` permission for the identity created
|
98
|
+
9. Use `cpln://secret/...` in the app to access the secret env vars (e.g., `cpln://secret/my-secrets.SOME_VAR`)
|
99
|
+
|
100
|
+
## CI
|
101
|
+
|
102
|
+
**Note:** Docker builds much slower on Apple Silicon, so try configuring CI to build the images when using Apple
|
103
|
+
hardware.
|
104
|
+
|
105
|
+
Make sure to create a profile on CI before running any `cpln` or `cpl` commands.
|
106
|
+
|
107
|
+
```sh
|
108
|
+
CPLN_TOKEN=...
|
109
|
+
cpln profile create default --token ${CPLN_TOKEN}
|
110
|
+
```
|
111
|
+
|
112
|
+
Also, log in to the Control Plane Docker repository if building and pushing an image.
|
113
|
+
|
114
|
+
```sh
|
115
|
+
cpln image docker-login
|
116
|
+
```
|
117
|
+
|
118
|
+
## Memcached
|
119
|
+
|
120
|
+
On the workload container for Memcached (using the `memcached:alpine` image), configure the command with the args
|
121
|
+
`-l 0.0.0.0`.
|
122
|
+
|
123
|
+
To do this:
|
124
|
+
|
125
|
+
1. Navigate to the workload container for Memcached
|
126
|
+
2. Click "Command" on the top menu
|
127
|
+
3. Add the args and save
|
128
|
+
|
129
|
+

|
130
|
+
|
131
|
+
## Sidekiq
|
132
|
+
|
133
|
+
### Quieting Non-Critical Workers During Deployments
|
134
|
+
|
135
|
+
To avoid locks in migrations, we can quiet non-critical workers during deployments. Doing this early enough in the CI
|
136
|
+
allows all workers to finish jobs gracefully before deploying the new image.
|
137
|
+
|
138
|
+
There's no need to unquiet the workers, as that will happen automatically after deploying the new image.
|
139
|
+
|
140
|
+
```sh
|
141
|
+
cpl run 'rails runner "Sidekiq::ProcessSet.new.each { |w| w.quiet! unless w[%q(hostname)].start_with?(%q(criticalworker.)) }"' -a my-app
|
142
|
+
```
|
143
|
+
|
144
|
+
### Setting Up a Pre Stop Hook
|
145
|
+
|
146
|
+
By setting up a pre stop hook in the lifecycle of the workload container for Sidekiq, which sends "QUIET" to the workers,
|
147
|
+
we can ensure that all workers will finish jobs gracefully before Control Plane stops the replica. That also works
|
148
|
+
nicely for multiple replicas.
|
149
|
+
|
150
|
+
A couple of notes:
|
151
|
+
|
152
|
+
- We can't use the process name as regex because it's Ruby, not Sidekiq.
|
153
|
+
- We need to add a space after `sidekiq`; otherwise, it sends `TSTP` to the `sidekiqswarm` process as well, and for some
|
154
|
+
reason, that doesn't work.
|
155
|
+
|
156
|
+
So with `^` and `\s`, we guarantee it's sent only to worker processes.
|
157
|
+
|
158
|
+
```sh
|
159
|
+
pkill -TSTP -f ^sidekiq\s
|
160
|
+
```
|
161
|
+
|
162
|
+
To do this:
|
163
|
+
|
164
|
+
1. Navigate to the workload container for Sidekiq
|
165
|
+
2. Click "Lifecycle" on the top menu
|
166
|
+
3. Add the command and args below "Pre Stop Hook" and save
|
167
|
+
|
168
|
+

|
169
|
+
|
170
|
+
### Setting Up a Liveness Probe
|
171
|
+
|
172
|
+
To set up a liveness probe on port 7433, see: https://github.com/arturictus/sidekiq_alive
|
173
|
+
|
174
|
+
## Useful Links
|
175
|
+
|
176
|
+
- For best practices for the app's Dockerfile, see: https://lipanski.com/posts/dockerfile-ruby-best-practices
|
177
|
+
- For migrating from Heroku Postgres to RDS, see: https://pelle.io/posts/hetzner-rds-postgres
|
data/examples/circleci.yml
CHANGED
@@ -8,8 +8,8 @@ build-staging:
|
|
8
8
|
- image: cimg/ruby:3.1-node
|
9
9
|
resource_class: large
|
10
10
|
environment:
|
11
|
-
CPLN_ORG:
|
12
|
-
APP_NAME:
|
11
|
+
CPLN_ORG: my-org
|
12
|
+
APP_NAME: my-app-staging
|
13
13
|
steps:
|
14
14
|
- checkout
|
15
15
|
- setup_remote_docker:
|
@@ -21,14 +21,13 @@ build-staging:
|
|
21
21
|
cpln profile create default --token ${CPLN_TOKEN} --org ${CPLN_ORG} --gvc ${APP_NAME}
|
22
22
|
cpln image docker-login
|
23
23
|
|
24
|
-
|
25
|
-
sudo ln -s ~/heroku-to-control-plane/cpl /usr/local/bin/cpl
|
24
|
+
gem install cpl
|
26
25
|
- run:
|
27
26
|
name: Containerize and push image
|
28
27
|
command: cpl build-image -a ${APP_NAME}
|
29
28
|
- run:
|
30
29
|
name: Database tasks
|
31
|
-
command: cpl run:detached
|
30
|
+
command: cpl run:detached -a ${APP_NAME} --image latest -- rails db:migrate
|
32
31
|
- run:
|
33
32
|
name: Deploy image
|
34
33
|
command: cpl deploy-image -a ${APP_NAME}
|
@@ -43,7 +42,7 @@ build-review-app:
|
|
43
42
|
- image: cimg/ruby:3.1-node
|
44
43
|
resource_class: large
|
45
44
|
environment:
|
46
|
-
CPLN_ORG: my-org
|
45
|
+
CPLN_ORG: my-org
|
47
46
|
steps:
|
48
47
|
- checkout
|
49
48
|
- setup_remote_docker:
|
@@ -65,7 +64,7 @@ build-review-app:
|
|
65
64
|
- run:
|
66
65
|
name: Provision review app if needed
|
67
66
|
command: |
|
68
|
-
if ! cpl
|
67
|
+
if ! cpl exists -a ${APP_NAME}; then
|
69
68
|
cpl setup-app -a ${APP_NAME}
|
70
69
|
echo "export NEW_APP=true" >> $BASH_ENV
|
71
70
|
fi
|
@@ -77,9 +76,9 @@ build-review-app:
|
|
77
76
|
name: Database tasks
|
78
77
|
command: |
|
79
78
|
if [ -n "${NEW_APP}" ]; then
|
80
|
-
cpl run:detached
|
79
|
+
cpl run:detached -a ${APP_NAME} --image latest -- LOG_LEVEL=warn rails db:reset
|
81
80
|
else
|
82
|
-
cpl run:detached
|
81
|
+
cpl run:detached -a ${APP_NAME} --image latest -- LOG_LEVEL=warn rails db:migrate
|
83
82
|
fi
|
84
83
|
- run:
|
85
84
|
name: Deploy image
|
data/examples/controlplane.yml
CHANGED
@@ -1,47 +1,49 @@
|
|
1
|
+
# Keys beginning with "cpln_" correspond to your settings in Control Plane.
|
2
|
+
|
1
3
|
aliases:
|
2
4
|
common: &common
|
3
|
-
#
|
4
|
-
# Production apps will use a different
|
5
|
-
# keys beginning with CPLN correspond to your settings in Control Plane
|
5
|
+
# Organization name for staging (customize to your needs).
|
6
|
+
# Production apps will use a different organization, specified below, for security.
|
6
7
|
cpln_org: my-org-staging
|
7
8
|
|
8
|
-
# Example apps use only location.
|
9
|
-
# TODO
|
9
|
+
# Example apps use only one location. Control Plane offers the ability to use multiple locations.
|
10
|
+
# TODO: Allow specification of multiple locations.
|
10
11
|
default_location: aws-us-east-2
|
11
12
|
|
12
13
|
# Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
|
13
14
|
one_off_workload: rails
|
14
15
|
|
15
|
-
# Workloads that are application itself and are using application
|
16
|
+
# Workloads that are for the application itself and are using application Docker images.
|
16
17
|
app_workloads:
|
17
18
|
- rails
|
18
19
|
- sidekiq
|
19
20
|
|
20
|
-
# Additional "service type" workloads, using non-application
|
21
|
+
# Additional "service type" workloads, using non-application Docker images.
|
21
22
|
additional_workloads:
|
22
23
|
- redis
|
23
24
|
- postgres
|
24
25
|
- memcached
|
25
26
|
|
26
|
-
# Configure the workload name used when maintenance mode is on (defaults to
|
27
|
+
# Configure the workload name used when maintenance mode is on (defaults to "maintenance")
|
27
28
|
maintenance_workload: maintenance
|
28
29
|
|
29
30
|
apps:
|
30
31
|
my-app-staging:
|
31
|
-
# Use the values from the common section above
|
32
|
+
# Use the values from the common section above.
|
32
33
|
<<: *common
|
33
34
|
my-app-review:
|
34
35
|
<<: *common
|
35
|
-
#
|
36
|
-
#
|
36
|
+
# If `match_if_app_name_starts_with` is `true`, then use this config for app names starting with this name,
|
37
|
+
# e.g., "my-app-review-pr123", "my-app-review-anything-goes", etc.
|
37
38
|
match_if_app_name_starts_with: true
|
38
39
|
my-app-production:
|
39
40
|
<<: *common
|
40
|
-
# Use a different
|
41
|
+
# Use a different organization for production.
|
41
42
|
cpln_org: my-org-production
|
42
|
-
# Allows running command
|
43
|
+
# Allows running the command `cpl promote-app-from-upstream -a my-app-production`
|
44
|
+
# to promote the staging app to production.
|
43
45
|
upstream: my-app-staging
|
44
46
|
my-app-other:
|
45
47
|
<<: *common
|
46
|
-
#
|
48
|
+
# You can specify a different `Dockerfile` relative to the `.controlplane/` directory (defaults to "Dockerfile").
|
47
49
|
dockerfile: ../some_other/Dockerfile
|
data/lib/command/base.rb
CHANGED
@@ -187,28 +187,22 @@ module Command
|
|
187
187
|
end
|
188
188
|
end
|
189
189
|
|
190
|
-
def wait_for(title)
|
191
|
-
progress.print "- Waiting for #{title}"
|
192
|
-
until yield
|
193
|
-
progress.print(".")
|
194
|
-
sleep(1)
|
195
|
-
end
|
196
|
-
progress.puts
|
197
|
-
end
|
198
|
-
|
199
190
|
def wait_for_workload(workload)
|
200
|
-
|
191
|
+
step("Waiting for workload", retry_on_failure: true) do
|
192
|
+
cp.fetch_workload(workload)
|
193
|
+
end
|
201
194
|
end
|
202
195
|
|
203
196
|
def wait_for_replica(workload, location)
|
204
|
-
|
197
|
+
step("Waiting for replica", retry_on_failure: true) do
|
205
198
|
cp.workload_get_replicas_safely(workload, location: location)&.dig("items", 0)
|
206
199
|
end
|
207
200
|
end
|
208
201
|
|
209
202
|
def ensure_workload_deleted(workload)
|
210
|
-
|
211
|
-
|
203
|
+
step("Deleting workload") do
|
204
|
+
cp.delete_workload(workload)
|
205
|
+
end
|
212
206
|
end
|
213
207
|
|
214
208
|
def latest_image_from(items, app_name: config.app, name_only: true)
|
@@ -216,7 +210,7 @@ module Command
|
|
216
210
|
|
217
211
|
# Or special string to indicate no image available
|
218
212
|
if matching_items.empty?
|
219
|
-
"#{app_name}:#{NO_IMAGE_AVAILABLE}"
|
213
|
+
name_only ? "#{app_name}:#{NO_IMAGE_AVAILABLE}" : nil
|
220
214
|
else
|
221
215
|
latest_item = matching_items.max_by { |item| extract_image_number(item["name"]) }
|
222
216
|
name_only ? latest_item["name"] : latest_item
|
@@ -51,6 +51,7 @@ module Command
|
|
51
51
|
|
52
52
|
images = cp.image_query(app_name)["items"].filter { |item| item["name"].start_with?("#{app_name}:") }
|
53
53
|
image = latest_image_from(images, app_name: app_name, name_only: false)
|
54
|
+
next unless image
|
54
55
|
|
55
56
|
created_date = DateTime.parse(image["created"])
|
56
57
|
diff_in_days = (now - created_date).to_i
|
data/lib/command/run.rb
CHANGED
@@ -56,24 +56,26 @@ module Command
|
|
56
56
|
|
57
57
|
attr_reader :location, :workload, :one_off, :container
|
58
58
|
|
59
|
-
def call
|
59
|
+
def call # rubocop:disable Metrics/MethodLength
|
60
60
|
@location = config[:default_location]
|
61
61
|
@workload = config.options["workload"] || config[:one_off_workload]
|
62
62
|
@one_off = "#{workload}-run-#{rand(1000..9999)}"
|
63
63
|
|
64
|
-
|
64
|
+
step("Cloning workload '#{workload}' on app '#{config.options[:app]}' to '#{one_off}'") do
|
65
|
+
clone_workload
|
66
|
+
end
|
67
|
+
|
65
68
|
wait_for_workload(one_off)
|
66
69
|
wait_for_replica(one_off, location)
|
67
70
|
run_in_replica
|
68
71
|
ensure
|
72
|
+
progress.puts
|
69
73
|
ensure_workload_deleted(one_off)
|
70
74
|
end
|
71
75
|
|
72
76
|
private
|
73
77
|
|
74
78
|
def clone_workload # rubocop:disable Metrics/MethodLength
|
75
|
-
progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
|
76
|
-
|
77
79
|
# Create a base copy of workload props
|
78
80
|
spec = cp.fetch_workload!(workload).fetch("spec")
|
79
81
|
container_spec = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
|
@@ -127,7 +129,7 @@ module Command
|
|
127
129
|
if config.options[:terminal_size]
|
128
130
|
rows, cols = config.options[:terminal_size].split(",")
|
129
131
|
else
|
130
|
-
rows, cols = `stty
|
132
|
+
rows, cols = `stty size`.split(/\s+/)
|
131
133
|
end
|
132
134
|
script += "stty rows #{rows}\nstty cols #{cols}\n" if rows && cols
|
133
135
|
end
|
@@ -137,7 +139,7 @@ module Command
|
|
137
139
|
end
|
138
140
|
|
139
141
|
def run_in_replica
|
140
|
-
progress.puts
|
142
|
+
progress.puts("Connecting...\n\n")
|
141
143
|
command = %(bash -c 'eval "$CONTROLPLANE_RUNNER"')
|
142
144
|
cp.workload_exec(one_off, location: location, container: container, command: command)
|
143
145
|
end
|
data/lib/command/run_detached.rb
CHANGED
@@ -47,24 +47,28 @@ module Command
|
|
47
47
|
|
48
48
|
attr_reader :location, :workload, :one_off, :container
|
49
49
|
|
50
|
-
def call
|
50
|
+
def call # rubocop:disable Metrics/MethodLength
|
51
51
|
@location = config[:default_location]
|
52
52
|
@workload = config.options["workload"] || config[:one_off_workload]
|
53
53
|
@one_off = "#{workload}-runner-#{rand(1000..9999)}"
|
54
54
|
|
55
|
-
|
55
|
+
step("Cloning workload '#{workload}' on app '#{config.options[:app]}' to '#{one_off}'") do
|
56
|
+
clone_workload
|
57
|
+
end
|
58
|
+
|
56
59
|
wait_for_workload(one_off)
|
57
60
|
show_logs_waiting
|
58
61
|
ensure
|
59
|
-
|
62
|
+
if cp.fetch_workload(one_off)
|
63
|
+
progress.puts
|
64
|
+
ensure_workload_deleted(one_off)
|
65
|
+
end
|
60
66
|
exit(1) if @crashed
|
61
67
|
end
|
62
68
|
|
63
69
|
private
|
64
70
|
|
65
71
|
def clone_workload # rubocop:disable Metrics/MethodLength
|
66
|
-
progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
|
67
|
-
|
68
72
|
# Get base specs of workload
|
69
73
|
spec = cp.fetch_workload!(workload).fetch("spec")
|
70
74
|
container_spec = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
|
@@ -121,17 +125,17 @@ module Command
|
|
121
125
|
end
|
122
126
|
|
123
127
|
def show_logs_waiting # rubocop:disable Metrics/MethodLength
|
124
|
-
progress.puts
|
128
|
+
progress.puts("Scheduled, fetching logs (it's a cron job, so it may take up to a minute to start)...\n\n")
|
125
129
|
begin
|
126
130
|
while cp.fetch_workload(one_off)
|
127
131
|
sleep(WORKLOAD_SLEEP_CHECK)
|
128
132
|
print_uniq_logs
|
129
133
|
end
|
130
134
|
rescue RuntimeError => e
|
131
|
-
progress.puts
|
135
|
+
progress.puts(Shell.color("ERROR: #{e}", :red))
|
132
136
|
retry
|
133
137
|
end
|
134
|
-
progress.puts
|
138
|
+
progress.puts("\nFinished workload and logger.")
|
135
139
|
end
|
136
140
|
|
137
141
|
def print_uniq_logs
|
data/lib/core/scripts.rb
CHANGED
@@ -10,7 +10,7 @@ module Scripts
|
|
10
10
|
-H "Authorization: ${CONTROLPLANE_TOKEN}" -s | grep -o '"replicas":[0-9]*' | grep -o '[0-9]*')
|
11
11
|
|
12
12
|
if [ "$REPLICAS_QTY" -gt 0 ]; then
|
13
|
-
echo "-- MULTIPLE REPLICAS ATTEMPT
|
13
|
+
echo "-- MULTIPLE REPLICAS ATTEMPT: $REPLICAS_QTY --"
|
14
14
|
exit -1
|
15
15
|
fi
|
16
16
|
SHELL
|
@@ -24,7 +24,7 @@ module Scripts
|
|
24
24
|
|
25
25
|
# NOTE: please escape all '/' as '//' (as it is ruby interpolation here as well)
|
26
26
|
def http_dummy_server_ruby
|
27
|
-
'require "socket";s=TCPServer.new(ENV["PORT"]);' \
|
27
|
+
'require "socket";s=TCPServer.new(ENV["PORT"] || 80);' \
|
28
28
|
'loop do c=s.accept;c.puts("HTTP/1.1 200 OK\\nContent-Length: 2\\n\\nOk");c.close end'
|
29
29
|
end
|
30
30
|
|
data/lib/cpl/version.rb
CHANGED
data/lib/cpl.rb
CHANGED
@@ -40,16 +40,57 @@ end
|
|
40
40
|
module Cpl
|
41
41
|
class Error < StandardError; end
|
42
42
|
|
43
|
-
class Cli < Thor
|
43
|
+
class Cli < Thor # rubocop:disable Metrics/ClassLength
|
44
44
|
package_name "cpl"
|
45
45
|
default_task :no_command
|
46
46
|
|
47
47
|
def self.start(*args)
|
48
|
+
check_cpln_version
|
49
|
+
check_cpl_version
|
48
50
|
fix_help_option
|
49
51
|
|
50
52
|
super(*args)
|
51
53
|
end
|
52
54
|
|
55
|
+
def self.check_cpln_version # rubocop:disable Metrics/MethodLength
|
56
|
+
return if @checked_cpln_version
|
57
|
+
|
58
|
+
@checked_cpln_version = true
|
59
|
+
|
60
|
+
result = `cpln --version 2>/dev/null`
|
61
|
+
if $CHILD_STATUS.success?
|
62
|
+
data = JSON.parse(result)
|
63
|
+
|
64
|
+
version = data["npm"]
|
65
|
+
min_version = Cpl::MIN_CPLN_VERSION
|
66
|
+
if Gem::Version.new(version) < Gem::Version.new(min_version)
|
67
|
+
::Shell.abort("Current 'cpln' version: #{version}. Minimum supported version: #{min_version}. " \
|
68
|
+
"Please update it with 'npm update -g @controlplane/cli'.")
|
69
|
+
end
|
70
|
+
else
|
71
|
+
::Shell.abort("Can't find 'cpln' executable. Please install it with 'npm install -g @controlplane/cli'.")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.check_cpl_version # rubocop:disable Metrics/MethodLength
|
76
|
+
return if @checked_cpl_version
|
77
|
+
|
78
|
+
@checked_cpl_version = true
|
79
|
+
|
80
|
+
result = `gem search ^cpl$ --remote 2>/dev/null`
|
81
|
+
return unless $CHILD_STATUS.success?
|
82
|
+
|
83
|
+
matches = result.match(/cpl \((.+)\)/)
|
84
|
+
return unless matches
|
85
|
+
|
86
|
+
version = Cpl::VERSION
|
87
|
+
latest_version = matches[1]
|
88
|
+
return unless Gem::Version.new(version) < Gem::Version.new(latest_version)
|
89
|
+
|
90
|
+
::Shell.warn("You are not using the latest 'cpl' version. Please update it with 'gem update cpl'.")
|
91
|
+
$stderr.puts
|
92
|
+
end
|
93
|
+
|
53
94
|
# This is so that we're able to run `cpl COMMAND --help` to print the help
|
54
95
|
# (it basically changes it to `cpl --help COMMAND`, which Thor recognizes)
|
55
96
|
# Based on https://stackoverflow.com/questions/49042591/how-to-add-help-h-flag-to-thor-command
|
@@ -46,7 +46,7 @@ file_data =
|
|
46
46
|
<<~DATA
|
47
47
|
<!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
|
48
48
|
|
49
|
-
|
49
|
+
## Common Options
|
50
50
|
|
51
51
|
```
|
52
52
|
-a XXX, --app XXX app ref on Control Plane (GVC)
|
@@ -55,7 +55,7 @@ file_data =
|
|
55
55
|
This `-a` option is used in most of the commands and will pick all other app configurations from the project-specific
|
56
56
|
`.controlplane/controlplane.yml` file.
|
57
57
|
|
58
|
-
|
58
|
+
## Commands
|
59
59
|
|
60
60
|
#{commands_str}
|
61
61
|
DATA
|
data/templates/daily-task.yml
CHANGED
@@ -4,23 +4,24 @@ spec:
|
|
4
4
|
# https://docs.controlplane.com/reference/workload#cron-configuration
|
5
5
|
type: cron
|
6
6
|
job:
|
7
|
-
# Run daily job at 2am
|
7
|
+
# Run daily job at 2am (see cron docs)
|
8
8
|
schedule: 0 2 * * *
|
9
9
|
# Never or OnFailure
|
10
10
|
restartPolicy: Never
|
11
11
|
containers:
|
12
12
|
- name: daily-task
|
13
|
+
cpu: 50m
|
14
|
+
memory: 256Mi
|
13
15
|
args:
|
14
16
|
- bundle
|
15
17
|
- exec
|
16
18
|
- rails
|
17
19
|
- db:prepare
|
18
|
-
cpu: 50m
|
19
20
|
inheritEnv: true
|
20
|
-
image:
|
21
|
-
memory: 256Mi
|
21
|
+
image: "/org/APP_ORG/image/APP_IMAGE"
|
22
22
|
defaultOptions:
|
23
23
|
autoscaling:
|
24
|
+
minScale: 1
|
24
25
|
maxScale: 1
|
25
26
|
capacityAI: false
|
26
27
|
firewallConfig:
|
data/templates/maintenance.yml
CHANGED
@@ -6,15 +6,16 @@ spec:
|
|
6
6
|
- name: maintenance
|
7
7
|
env:
|
8
8
|
- name: PORT
|
9
|
-
value:
|
9
|
+
value: "3000"
|
10
10
|
- name: PAGE_URL
|
11
|
-
value:
|
12
|
-
image:
|
11
|
+
value: ""
|
12
|
+
image: "shakacode/maintenance-mode"
|
13
13
|
ports:
|
14
14
|
- number: 3000
|
15
15
|
protocol: http
|
16
16
|
defaultOptions:
|
17
17
|
autoscaling:
|
18
|
+
minScale: 1
|
18
19
|
maxScale: 1
|
19
20
|
capacityAI: false
|
20
21
|
timeoutSeconds: 60
|
data/templates/memcached.yml
CHANGED
@@ -7,15 +7,16 @@ spec:
|
|
7
7
|
cpu: 3m
|
8
8
|
memory: 10Mi
|
9
9
|
args:
|
10
|
-
-
|
10
|
+
- "-l"
|
11
11
|
- 0.0.0.0
|
12
|
-
image:
|
12
|
+
image: "memcached:alpine"
|
13
13
|
ports:
|
14
14
|
- number: 11211
|
15
15
|
protocol: tcp
|
16
16
|
defaultOptions:
|
17
17
|
autoscaling:
|
18
18
|
metric: latency
|
19
|
+
minScale: 1
|
19
20
|
maxScale: 1
|
20
21
|
capacityAI: false
|
21
22
|
firewallConfig:
|