cpl 0.7.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
- ### Common Options
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
- ### Commands
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
+ ![](assets/grafana-alert.png)
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
+ ![](assets/memcached.png)
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
+ ![](assets/sidekiq-pre-stop-hook.png)
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
@@ -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: myorg
12
- APP_NAME: myapp-staging
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
- 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
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 rails db:migrate -a ${APP_NAME} --image latest
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-name
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 exist -a ${APP_NAME}; then
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 'LOG_LEVEL=warn rails db:reset' -a ${APP_NAME} --image latest
79
+ cpl run:detached -a ${APP_NAME} --image latest -- LOG_LEVEL=warn rails db:reset
81
80
  else
82
- cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a ${APP_NAME} --image latest
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
@@ -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
- # 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
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. CPLN offers the ability to use multiple locations.
9
- # TODO -- allow specfication of multiple locations
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 docker image
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 docker images
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 'maintenance')
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
- # if match_if_app_name_starts_with == true, then use this config for app names beginning,
36
- # like my-app-review-pr123 or my-app-review-anything-goes
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 org for production
41
+ # Use a different organization for production.
41
42
  cpln_org: my-org-production
42
- # Allows running command 'cpl pipeline-promote my-app-staging' to promote the staging app to production
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
- # you can specify different dockerfile relative to .controlplane folder, default is just 'Dockerfile'
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
- wait_for("workload to start") { cp.fetch_workload(workload) }
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
- wait_for("replica") do
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
- progress.puts "- Ensure workload is deleted"
211
- cp.delete_workload(workload)
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
- clone_workload
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 -a`.match(/(\d+)\s*rows;\s*(\d+)\s*columns/).captures
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 "- Connecting"
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
@@ -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
- clone_workload
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
- ensure_workload_deleted(one_off)
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 "- Scheduled, fetching logs (it is cron job, so it may take up to a minute to start)"
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 "ERROR: #{e}"
135
+ progress.puts(Shell.color("ERROR: #{e}", :red))
132
136
  retry
133
137
  end
134
- progress.puts "- Finished workload and logger"
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 !!!! replicas: $REPLICAS_QTY"
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "0.7.0"
4
+ VERSION = "1.0.1"
5
+ MIN_CPLN_VERSION = "0.0.71"
5
6
  end
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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ bundle exec rake update_command_docs
3
+ git diff --exit-code || exit 1
@@ -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
- ### Common Options
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
- ### Commands
58
+ ## Commands
59
59
 
60
60
  #{commands_str}
61
61
  DATA
@@ -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, see cron docs
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: '/org/APP_ORG/image/APP_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:
@@ -6,15 +6,16 @@ spec:
6
6
  - name: maintenance
7
7
  env:
8
8
  - name: PORT
9
- value: '3000'
9
+ value: "3000"
10
10
  - name: PAGE_URL
11
- value: ''
12
- image: 'shakacode/maintenance-mode'
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
@@ -7,15 +7,16 @@ spec:
7
7
  cpu: 3m
8
8
  memory: 10Mi
9
9
  args:
10
- - '-l'
10
+ - "-l"
11
11
  - 0.0.0.0
12
- image: 'memcached:alpine'
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: