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.
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: