cpl 1.1.1 → 1.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea3952c523b5ee8b9815befa82df1ecaee53a323a37f081344660d27f3003371
4
- data.tar.gz: e346e1f6327ab009d0b801b651350355ad74cb9abbf9d69bb04fa46be00c4ea3
3
+ metadata.gz: 89a038d31fb497bfc6c0d34451ace68fe554d700f4686413a1919cf6d9bceb09
4
+ data.tar.gz: 3c733b43e703ad7b7798cf599b71f59ea1758ce2c71babd4486670c5ed5f12a3
5
5
  SHA512:
6
- metadata.gz: d1b0bfc22da8b9209720e8a262739dcbd738722713165370203e471f52e2aedd52087196dd5b476f05d0dc5f744cdbebe861c1840c2aa32f95339cdafa175807
7
- data.tar.gz: c067f243e3aabb72c271e02c7dd937355bf01e98645f4d163982143ad8a5e19cccdb19574dbe799e2eb7ac215a6f9e89db67f26e7e234252d227f5972b122331
6
+ metadata.gz: 325eecc73176b362c4b898632e45040544ecfdda14ecfbbd655caf92d0912aa3e431fbd8ac90c2681d4a2bcff68db0a71f80813601c1cf3b3ca32d44c9ef033f
7
+ data.tar.gz: 0c0c04c6f1d4d57114d50d603fabfbd3470da4f373f48b091991e228306080f47bd3643e5c5c706fe9e3992e661b656b4634c881ee0861b79fbfa1f63a60d8e5
data/CHANGELOG.md CHANGED
@@ -14,6 +14,24 @@ Changes since the last non-beta release.
14
14
 
15
15
  _Please add entries here for your pull requests that are not yet released._
16
16
 
17
+ ## [1.1.2] - 2023-10-17
18
+
19
+ ### Fixed
20
+
21
+ - Fixed failed build on MacOS by adding platform flag and fixed multiple files in yaml document for template. [PR 81](https://github.com/shakacode/heroku-to-control-plane/pull/81) by [justin808](https://github.com/justin808).
22
+
23
+ ### Added
24
+
25
+ - Added `open-console` command to open the app console on Control Plane. [PR 83](https://github.com/shakacode/heroku-to-control-plane/pull/83) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
26
+ - Added option to set the org with a `CPLN_ORG` env var. [PR 83](https://github.com/shakacode/heroku-to-control-plane/pull/83) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
27
+ - Added `--verbose` option to all commands for more detailed logs. [PR 83](https://github.com/shakacode/heroku-to-control-plane/pull/83) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
28
+
29
+ ### Changed
30
+
31
+ - Calling `cpl` with no command now shows the help menu. [PR 83](https://github.com/shakacode/heroku-to-control-plane/pull/83) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
32
+
33
+ ## [1.1.1] - 2023-09-23
34
+
17
35
  ### Fixed
18
36
 
19
37
  - Fixed issue where API token is not reset when switching profile. [PR 77](https://github.com/shakacode/heroku-to-control-plane/pull/77) by [Rafael Gomes](https://github.com/rafaelgomesxyz).
@@ -77,7 +95,9 @@ _Please add entries here for your pull requests that are not yet released._
77
95
 
78
96
  - Initial release
79
97
 
80
- [Unreleased]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.1.0...HEAD
98
+ [Unreleased]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.1.2...HEAD
99
+ [1.1.2]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.1.1...v1.1.2
100
+ [1.1.1]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.1.0...v1.1.1
81
101
  [1.1.0]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.0.4...v1.1.0
82
102
  [1.0.4]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.0.3...v1.0.4
83
103
  [1.0.3]: https://github.com/shakacode/heroku-to-control-plane/compare/v1.0.2...v1.0.3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cpl (1.1.1)
4
+ cpl (1.1.2)
5
5
  debug (~> 1.7.1)
6
6
  dotenv (~> 2.8.1)
7
7
  psych (~> 5.1.0)
@@ -25,7 +25,7 @@ GEM
25
25
  hashdiff (1.0.1)
26
26
  iniparse (1.5.0)
27
27
  io-console (0.6.0)
28
- irb (1.8.1)
28
+ irb (1.8.3)
29
29
  rdoc
30
30
  reline (>= 0.3.8)
31
31
  json (2.6.3)
@@ -36,7 +36,7 @@ GEM
36
36
  parallel (1.22.1)
37
37
  parser (3.2.0.0)
38
38
  ast (~> 2.4.1)
39
- psych (5.1.0)
39
+ psych (5.1.1.1)
40
40
  stringio
41
41
  public_suffix (5.0.1)
42
42
  rainbow (3.1.1)
@@ -44,7 +44,7 @@ GEM
44
44
  rdoc (6.5.0)
45
45
  psych (>= 4.0.0)
46
46
  regexp_parser (2.6.2)
47
- reline (0.3.8)
47
+ reline (0.3.9)
48
48
  io-console (~> 0.5)
49
49
  rexml (3.2.5)
50
50
  rspec (3.12.0)
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta name="keywords" content="Control Plane, Heroku, Kubernetes, K8, Infrastructure">
7
7
  <meta name="google-site-verification" content="dIV4nMplcYl6YOKOaZMqgvdKXhLJ4cdYY6pS6e_YrPU" />
8
8
 
9
- _A playbook for migrating from [Heroku](https://heroku.com) to [Control Plane](https://controlplane.com)_
9
+ _A gem that provides **Heroku Flow** functionality on Control Plane, including docs for migrating from [Heroku](https://heroku.com) to [Control Plane](https://controlplane.com)_
10
10
 
11
11
  [![RSpec](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml/badge.svg)](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml)
12
12
  [![Rubocop](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rubocop.yml/badge.svg)](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rubocop.yml)
@@ -29,17 +29,18 @@ a **helper CLI** based on templates to save lots of day-to-day typing (and human
29
29
  2. [Concept Mapping](#concept-mapping)
30
30
  3. [Installation](#installation)
31
31
  4. [Steps to Migrate](#steps-to-migrate)
32
- 5. [Config](#config)
33
- 6. [Environment](#environment)
34
- 7. [Database](#database)
35
- 8. [In-memory Databases](#in-memory-databases)
36
- 9. [Scheduled Jobs](#scheduled-jobs)
37
- 10. [CLI Commands Reference](#cli-commands-reference)
38
- 11. [Mapping of Heroku Commands to `cpl` and `cpln`](#mapping-of-heroku-commands-to-cpl-and-cpln)
39
- 12. [Examples](#examples)
40
- 13. [Migrating Postgres Database from Heroku Infrastructure](/docs/postgres.md)
41
- 14. [Migrating Redis Database from Heroku Infrastructure](/docs/redis.md)
42
- 15. [Tips](/docs/tips.md)
32
+ 5. [Configuration Files](#configuration-files)
33
+ 6. [Workflow](#workflow)
34
+ 7. [Environment](#environment)
35
+ 8. [Database](#database)
36
+ 9. [In-memory Databases](#in-memory-databases)
37
+ 10. [Scheduled Jobs](#scheduled-jobs)
38
+ 11. [CLI Commands Reference](#cli-commands-reference)
39
+ 12. [Mapping of Heroku Commands to `cpl` and `cpln`](#mapping-of-heroku-commands-to-cpl-and-cpln)
40
+ 13. [Examples](#examples)
41
+ 14. [Migrating Postgres Database from Heroku Infrastructure](/docs/postgres.md)
42
+ 15. [Migrating Redis Database from Heroku Infrastructure](/docs/redis.md)
43
+ 16. [Tips](/docs/tips.md)
43
44
 
44
45
  ## Key Features
45
46
 
@@ -89,21 +90,31 @@ For the typical Rails app, this means:
89
90
 
90
91
  ## Installation
91
92
 
92
- 1. Install [Node.js](https://nodejs.org/en) (required for Control Plane CLI).
93
- 2. Install [Ruby](https://www.ruby-lang.org/en/) (required for these helpers).
94
- 3. Install Control Plane CLI (adds `cpln` command) and configure credentials.
93
+ 1. Ensure your [Control Plane](https://controlplane.com) account is set up. Set up an `organization` <your-org> for testing in that account and modify the value for `aliases.common.cpln_org` in `.controlplane/controlplane.yml`, or you can also set it with the `CPLN_ORG` environment variable. If you need an organization, please [contact Shakacode](mailto:controlplane@shakacode.com).
94
+
95
+ 2. Install [Node.js](https://nodejs.org/en) (required for Control Plane CLI).
96
+
97
+ 3. Install [Ruby](https://www.ruby-lang.org/en/) (required for these helpers).
98
+
99
+ 4. Install Control Plane CLI, and configure access ([docs here](https://docs.controlplane.com/quickstart/quick-start-3-cli#getting-started-with-the-cli)).
95
100
 
96
101
  ```sh
102
+ # Install CLI
97
103
  npm install -g @controlplane/cli
104
+
105
+ # Configure access
98
106
  cpln login
107
+
108
+ # Update CLI
109
+ npm update -g @controlplane/cli
99
110
  ```
100
111
 
101
- 4. Install Heroku to Control Plane `cpl` CLI, either as a [Ruby gem](https://rubygems.org/gems/cpl) or a local clone.
102
- For information on the latter, see [CONTRIBUTING.md](CONTRIBUTING.md).
112
+ 5. Run `cpln image docker-login --org <your-org>` to ensure that you have access to the Control Plane Docker registry.
103
113
 
104
- ```sh
105
- gem install cpl
106
- ```
114
+ 6. Install Heroku to Control Plane `cpl` CLI, either as a [Ruby gem](https://rubygems.org/gems/cpl) or a local clone.
115
+ For information on the latter, see [CONTRIBUTING.md](CONTRIBUTING.md). You may also install `cpl` in your project's Gemfile.
116
+
117
+ 7. You can use [this Dockerfile](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.controlplane/Dockerfile) as an example for your project. Ensure that you have Docker running.
107
118
 
108
119
  **Note:** Do not confuse the `cpl` CLI with the `cpln` CLI. The `cpl` CLI is the Heroku to Control Plane playbook CLI.
109
120
  The `cpln` CLI is the Control Plane CLI.
@@ -112,9 +123,32 @@ The `cpln` CLI is the Control Plane CLI.
112
123
 
113
124
  Click [here](/docs/migrating.md) to see the steps to migrate.
114
125
 
115
- ## Config
126
+ ## Configuration Files
127
+
128
+ The `cpl` gem is based on several configuration files within a `/.controlplane` top-level directory in your project.
129
+
130
+ ```
131
+ .controlplane/
132
+ ├─ templates/
133
+ │ ├─ gvc.yml
134
+ │ ├─ postgres.yml
135
+ │ ├─ rails.yml
136
+ ├─ controlplane.yml
137
+ ├─ Dockerfile
138
+ ├─ entrypoint.sh
139
+ ```
140
+
141
+ 1. `controlplane.yml` describes the overall application. Be sure to have <your-org> as the value for `aliases.common.cpln_org`, or set it with the `CPLN_ORG` environment variable.
142
+ 2. `Dockerfile` builds the production application. `entrypoint.sh` is an _example_ entrypoint script for the production application, referenced in your Dockerfile.
143
+ 3. `templates` directory contains the templates for the various workloads, such as `rails.yml` and `postgres.yml`.
144
+ 4. `templates/gvc.yml` defines your project's GVC (like a Heroku app). More importantly, it contains ENV values for the app.
145
+ 5. `templates/rails.yml` defines your Rails workload. It may inherit ENV values from the parent GVC, which is populated from the `templates/gvc.yml`. This file also configures scaling, sizing, firewalls, and other workload-specific values.
146
+ 6. For other workloads (like lines in a Heroku `Procfile`), you create additional template files. For example, you can base a `templates/sidekiq.yml` on the `templates/rails.yml` file.
147
+ 7. You can have other files in the `templates` directory, such as `redis.yml` and `postgres.yml`, which could setup Redis and Postgres for a testing application.
148
+
149
+ Here's a complete example of all supported config keys explained for the `controlplane.yml` file:
116
150
 
117
- Here's a complete example of all supported config keys explained:
151
+ ### `controlplane.yml`
118
152
 
119
153
  ```yaml
120
154
  # Keys beginning with "cpln_" correspond to your settings in Control Plane.
@@ -208,6 +242,67 @@ apps:
208
242
  dockerfile: ../some_other/Dockerfile
209
243
  ```
210
244
 
245
+ ## Workflow
246
+
247
+ For a live example, see the [react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.controlplane/readme.md) repository.
248
+
249
+ This example should closely match the below example.
250
+
251
+ Suppose your app is called `tutorial-app`. You can run the following commands.
252
+
253
+ ### Setup Commands
254
+
255
+ ```sh
256
+ # Provision all infrastructure on Control Plane.
257
+ # `tutorial-app` will be created per definition in .controlplane/controlplane.yml.
258
+ cpl apply-template gvc postgres redis rails -a tutorial-app
259
+
260
+ # Build and push the Docker image to the Control Plane repository.
261
+ # Note, it may take many minutes. Be patient.
262
+ # Check for error messages, such as forgetting to run `cpln image docker-login --org <your-org>`.
263
+ cpl build-image -a tutorial-app
264
+
265
+ # Promote the image to the app after running the `cpl build-image` command.
266
+ # Note, the UX of the images may not show the image for up to 5 minutes. However, it's ready.
267
+ cpl deploy-image -a tutorial-app
268
+
269
+ # See how the app is starting up.
270
+ cpl logs -a tutorial-app
271
+
272
+ # Open the app in browser (once it has started up).
273
+ cpl open -a tutorial-app
274
+ ```
275
+
276
+ ### Promoting Code Updates
277
+
278
+ After committing code, you will update your deployment of `tutorial-app` with the following commands:
279
+
280
+ ```sh
281
+ # Build and push a new image with sequential image tagging, e.g. 'tutorial-app:1', then 'tutorial-app:2', etc.
282
+ cpl build-image -a tutorial-app
283
+
284
+ # Run database migrations (or other release tasks) with the latest image,
285
+ # while the app is still running on the previous image.
286
+ # This is analogous to the release phase.
287
+ cpl run:detached rails db:migrate -a tutorial-app --image latest
288
+
289
+ # Pomote the latest image to the app.
290
+ cpl deploy-image -a tutorial-app
291
+ ```
292
+
293
+ If you needed to push a new image with a specific commit SHA, you can run the following command:
294
+
295
+ ```sh
296
+ # Build and push with sequential image tagging and commit SHA, e.g. 'tutorial-app:123_ABCD', etc.
297
+ cpl build-image -a tutorial-app --commit ABCD
298
+ ```
299
+
300
+ ### Real World
301
+
302
+ Most companies will configure their CI system to handle the above steps. Please [contact Shakacode](mailto:controlplane@shakacode.com) for examples of how to do this.
303
+
304
+ You can also join our [**Slack channel**](https://reactrails.slack.com/join/shared_invite/enQtNjY3NTczMjczNzYxLTlmYjdiZmY3MTVlMzU2YWE0OWM0MzNiZDI0MzdkZGFiZTFkYTFkOGVjODBmOWEyYWQ3MzA2NGE1YWJjNmVlMGE) for ShakaCode open source projects.
305
+
211
306
  ## Environment
212
307
 
213
308
  There are two main places where we can set up environment variables in Control Plane:
@@ -227,24 +322,28 @@ It is also possible to set up a Secret store (of type `Dictionary`), which we ca
227
322
  `cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_VAR_NAME`. In such a case, we must set up an app Identity and proper
228
323
  Policy to access the secret.
229
324
 
325
+ In `templates/gvc.yml`:
326
+
230
327
  ```yaml
231
- # In `templates/gvc.yml`:
232
328
  spec:
233
329
  env:
234
330
  - name: MY_GLOBAL_VAR
235
- value: 'value'
331
+ value: "value"
236
332
  - name: MY_SECRET_GLOBAL_VAR
237
- value: 'cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_GLOBAL_VAR'
333
+ value: "cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_GLOBAL_VAR"
334
+ ```
238
335
 
239
- # In `templates/rails.yml`:
336
+ In `templates/rails.yml`:
337
+
338
+ ```yaml
240
339
  spec:
241
340
  containers:
242
341
  - name: rails
243
342
  env:
244
343
  - name: MY_LOCAL_VAR
245
- value: 'value'
344
+ value: "value"
246
345
  - name: MY_SECRET_LOCAL_VAR
247
- value: 'cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_LOCAL_VAR'
346
+ value: "cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_LOCAL_VAR"
248
347
  inheritEnv: true # To enable global env inheritance.
249
348
  ```
250
349
 
data/docs/commands.md CHANGED
@@ -227,6 +227,15 @@ cpl open -a $APP_NAME
227
227
  cpl open -a $APP_NAME -w $WORKLOAD_NAME
228
228
  ```
229
229
 
230
+ ### `open-console`
231
+
232
+ - Opens the app console on Control Plane in the default browser
233
+ - Can also go directly to a workload page if `--workload` is provided
234
+
235
+ ```sh
236
+ cpl open-console -a $APP_NAME
237
+ ```
238
+
230
239
  ### `promote-app-from-upstream`
231
240
 
232
241
  - Copies the latest image from upstream, runs a release script (optional), and deploys the image
@@ -35,13 +35,12 @@ module Command
35
35
  ```
36
36
  EX
37
37
 
38
- def call # rubocop:disable Metrics/MethodLength
38
+ def call # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
39
39
  ensure_templates!
40
40
 
41
- @app_status = :existing
42
- @created_workloads = []
43
- @failed_workloads = []
44
- @skipped_workloads = []
41
+ @created_items = []
42
+ @failed_templates = []
43
+ @skipped_templates = []
45
44
 
46
45
  @asked_for_confirmation = false
47
46
 
@@ -57,9 +56,11 @@ module Command
57
56
 
58
57
  pending_templates.each do |template, filename|
59
58
  step("Applying template '#{template}'", abort_on_error: false) do
60
- apply_template(filename)
61
- if $CHILD_STATUS.success?
62
- report_success(template)
59
+ items = apply_template(filename)
60
+ if items
61
+ items.each do |item|
62
+ report_success(item)
63
+ end
63
64
  else
64
65
  report_failure(template)
65
66
  end
@@ -68,10 +69,9 @@ module Command
68
69
  end
69
70
  end
70
71
 
71
- print_app_status
72
- print_created_workloads
73
- print_failed_workloads
74
- print_skipped_workloads
72
+ print_created_items
73
+ print_failed_templates
74
+ print_skipped_templates
75
75
  end
76
76
 
77
77
  private
@@ -130,65 +130,41 @@ module Command
130
130
  .gsub("APP_ORG", config.org)
131
131
  .gsub("APP_IMAGE", latest_image)
132
132
 
133
- cp.apply(YAML.safe_load(data))
133
+ # Don't read in YAML.safe_load as that doesn't handle multiple documents
134
+ cp.apply_template(data)
134
135
  end
135
136
 
136
- def report_success(template)
137
- if template == "gvc"
138
- @app_status = :success
139
- else
140
- @created_workloads.push(template)
141
- end
137
+ def report_success(item)
138
+ @created_items.push(item)
142
139
  end
143
140
 
144
141
  def report_failure(template)
145
- if template == "gvc"
146
- @app_status = :failure
147
- else
148
- @failed_workloads.push(template)
149
- end
142
+ @failed_templates.push(template)
150
143
  end
151
144
 
152
145
  def report_skipped(template)
153
- if template == "gvc"
154
- @app_status = :skipped
155
- else
156
- @skipped_workloads.push(template)
157
- end
158
- end
159
-
160
- def print_app_status
161
- return if @app_status == :existing
162
-
163
- case @app_status
164
- when :success
165
- progress.puts("\n#{Shell.color("Created app '#{config.app}'.", :green)}")
166
- when :failure
167
- progress.puts("\n#{Shell.color("Failed to create app '#{config.app}'.", :red)}")
168
- when :skipped
169
- progress.puts("\n#{Shell.color("Skipped app '#{config.app}' (already exists).", :blue)}")
170
- end
146
+ @skipped_templates.push(template)
171
147
  end
172
148
 
173
- def print_created_workloads
174
- return unless @created_workloads.any?
149
+ def print_created_items
150
+ return unless @created_items.any?
175
151
 
176
- workloads = @created_workloads.map { |template| " - #{template}" }.join("\n")
177
- progress.puts("\n#{Shell.color('Created workloads:', :green)}\n#{workloads}")
152
+ created = @created_items.map { |item| " - [#{item[:kind]}] #{item[:name]}" }.join("\n")
153
+ progress.puts("\n#{Shell.color('Created items:', :green)}\n#{created}")
178
154
  end
179
155
 
180
- def print_failed_workloads
181
- return unless @failed_workloads.any?
156
+ def print_failed_templates
157
+ return unless @failed_templates.any?
182
158
 
183
- workloads = @failed_workloads.map { |template| " - #{template}" }.join("\n")
184
- progress.puts("\n#{Shell.color('Failed to create workloads:', :red)}\n#{workloads}")
159
+ failed = @failed_templates.map { |template| " - #{template}" }.join("\n")
160
+ progress.puts("\n#{Shell.color('Failed to apply templates:', :red)}\n#{failed}")
185
161
  end
186
162
 
187
- def print_skipped_workloads
188
- return unless @skipped_workloads.any?
163
+ def print_skipped_templates
164
+ return unless @skipped_templates.any?
189
165
 
190
- workloads = @skipped_workloads.map { |template| " - #{template}" }.join("\n")
191
- progress.puts("\n#{Shell.color('Skipped workloads (already exist):', :blue)}\n#{workloads}")
166
+ skipped = @skipped_templates.map { |template| " - #{template}" }.join("\n")
167
+ progress.puts("\n#{Shell.color('Skipped templates (already exist):', :blue)}\n#{skipped}")
192
168
  end
193
169
  end
194
170
  end
data/lib/command/base.rb CHANGED
@@ -176,6 +176,18 @@ module Command
176
176
  }
177
177
  end
178
178
 
179
+ def self.verbose_option(required: false)
180
+ {
181
+ name: :verbose,
182
+ params: {
183
+ aliases: ["-d"],
184
+ desc: "Shows detailed logs",
185
+ type: :boolean,
186
+ required: required
187
+ }
188
+ }
189
+ end
190
+
179
191
  def self.all_options
180
192
  methods.grep(/_option$/).map { |method| send(method.to_s) }
181
193
  end
@@ -21,6 +21,11 @@ module Command
21
21
  EX
22
22
 
23
23
  def call # rubocop:disable Metrics/MethodLength
24
+ if config.org_comes_from_env
25
+ puts Shell.color("Org comes from CPLN_ORG env var", :yellow)
26
+ puts
27
+ end
28
+
24
29
  if config.app
25
30
  puts "#{Shell.color("Current config (app '#{config.app}')", :blue)}:"
26
31
  puts pretty_print(config.current)
@@ -29,7 +29,7 @@ module Command
29
29
  ensure_docker_running!
30
30
 
31
31
  @upstream = config[:upstream]
32
- @upstream_org = config.apps[@upstream.to_sym][:cpln_org]
32
+ @upstream_org = config.apps[@upstream.to_sym][:cpln_org] || ENV.fetch("CPLN_ORG_UPSTREAM", nil)
33
33
  ensure_upstream_org!
34
34
 
35
35
  create_upstream_profile
@@ -51,7 +51,10 @@ module Command
51
51
  end
52
52
 
53
53
  def ensure_upstream_org!
54
- raise "Can't find option 'cpln_org' for app '#{@upstream}' in 'controlplane.yml'." unless @upstream_org
54
+ return if @upstream_org
55
+
56
+ raise "Can't find option 'cpln_org' for app '#{@upstream}' in 'controlplane.yml', " \
57
+ "and CPLN_ORG_UPSTREAM env var is not set."
55
58
  end
56
59
 
57
60
  def create_upstream_profile
data/lib/command/info.rb CHANGED
@@ -81,17 +81,20 @@ module Command
81
81
  end
82
82
  end
83
83
 
84
- def orgs # rubocop:disable Metrics/MethodLength
84
+ def orgs # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
85
85
  result = []
86
86
 
87
87
  if config.options[:org]
88
88
  result.push(config.options[:org])
89
89
  else
90
+ org_from_env = ENV.fetch("CPLN_ORG", nil)
91
+ result.push(org_from_env) if org_from_env
92
+
90
93
  config.apps.each do |app_name, app_options|
91
94
  next if config.app && !app_matches?(config.app, app_name, app_options)
92
95
 
93
96
  org = app_options[:cpln_org]
94
- result.push(org) unless result.include?(org)
97
+ result.push(org) if org && !result.include?(org)
95
98
  end
96
99
  end
97
100
 
@@ -11,9 +11,11 @@ module Command
11
11
  HIDE = true
12
12
 
13
13
  def call
14
- return unless config.options[:version]
15
-
16
- Cpl::Cli.start(["version"])
14
+ if config.options[:version]
15
+ Cpl::Cli.start(["version"])
16
+ else
17
+ Cpl::Cli.start(["help"])
18
+ end
17
19
  end
18
20
  end
19
21
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class OpenConsole < Base
5
+ NAME = "open-console"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Opens the app console on Control Plane in the default browser"
11
+ LONG_DESCRIPTION = <<~DESC
12
+ - Opens the app console on Control Plane in the default browser
13
+ - Can also go directly to a workload page if `--workload` is provided
14
+ DESC
15
+
16
+ def call
17
+ workload = config.options[:workload]
18
+ url = "https://console.cpln.io/console/org/#{config.org}/gvc/#{config.app}"
19
+ url += "/workload/#{workload}" if workload
20
+ url += "/-info"
21
+ opener = `which xdg-open open`.split("\n").grep_v("not found").first
22
+
23
+ exec %(#{opener} "#{url}")
24
+ end
25
+ end
26
+ end
data/lib/command/run.rb CHANGED
@@ -111,7 +111,7 @@ module Command
111
111
  end
112
112
 
113
113
  # Create workload clone
114
- cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
114
+ cp.apply_hash("kind" => "workload", "name" => one_off, "spec" => spec)
115
115
  end
116
116
 
117
117
  def runner_script # rubocop:disable Metrics/MethodLength
@@ -104,7 +104,7 @@ module Command
104
104
  container_spec["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
105
105
 
106
106
  # Create workload clone
107
- cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
107
+ cp.apply_hash("kind" => "workload", "name" => one_off, "spec" => spec)
108
108
  end
109
109
 
110
110
  def runner_script # rubocop:disable Metrics/MethodLength
data/lib/core/config.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Config
3
+ class Config # rubocop:disable Metrics/ClassLength
4
4
  attr_reader :config, :current,
5
- :org, :app, :apps, :app_dir,
5
+ :org, :org_comes_from_env, :app, :apps, :app_dir,
6
6
  # command line options
7
7
  :args, :options
8
8
 
@@ -12,10 +12,13 @@ class Config
12
12
  @args = args
13
13
  @options = options
14
14
  @org = options[:org]
15
+ @org_comes_from_env = false
15
16
  @app = options[:app]
16
17
 
17
18
  load_app_config
18
19
  load_apps
20
+
21
+ Shell.verbose_mode(options[:verbose])
19
22
  end
20
23
 
21
24
  def [](key)
@@ -48,6 +51,13 @@ class Config
48
51
  raise "Can't find app '#{app_name}' in 'controlplane.yml'." unless current
49
52
  end
50
53
 
54
+ def ensure_current_config_org!(app_name)
55
+ return if @org
56
+
57
+ raise "Can't find option 'cpln_org' for app '#{app_name}' in 'controlplane.yml', " \
58
+ "and CPLN_ORG env var is not set."
59
+ end
60
+
51
61
  def ensure_config!
52
62
  raise "'controlplane.yml' is empty." unless config
53
63
  end
@@ -66,8 +76,15 @@ class Config
66
76
 
67
77
  def pick_current_config(app_name, app_options)
68
78
  @current = app_options
69
- @org = self[:cpln_org]
70
79
  ensure_current_config_app!(app_name)
80
+
81
+ if current.key?(:cpln_org)
82
+ @org = current.fetch(:cpln_org)
83
+ else
84
+ @org = ENV.fetch("CPLN_ORG", nil)
85
+ @org_comes_from_env = true
86
+ end
87
+ ensure_current_config_org!(app_name)
71
88
  end
72
89
 
73
90
  def load_apps # rubocop:disable Metrics/MethodLength
@@ -24,13 +24,13 @@ class Controlplane # rubocop:disable Metrics/ClassLength
24
24
 
25
25
  def profile_create(profile, token)
26
26
  cmd = "cpln profile create #{profile} --token #{token}"
27
- cmd += " > /dev/null" if Shell.tmp_stderr
27
+ cmd += " > /dev/null" if Shell.should_hide_output?
28
28
  perform!(cmd)
29
29
  end
30
30
 
31
31
  def profile_delete(profile)
32
32
  cmd = "cpln profile delete #{profile}"
33
- cmd += " > /dev/null" if Shell.tmp_stderr
33
+ cmd += " > /dev/null" if Shell.should_hide_output?
34
34
  perform!(cmd)
35
35
  end
36
36
 
@@ -44,7 +44,10 @@ class Controlplane # rubocop:disable Metrics/ClassLength
44
44
  end
45
45
 
46
46
  def image_build(image, dockerfile:, build_args: [], push: true)
47
- cmd = "docker build -t #{image} -f #{dockerfile}"
47
+ # https://docs.controlplane.com/guides/push-image#step-2
48
+ # Might need to use `docker buildx build` if compatiblitity issues arise
49
+ cmd = "docker build --platform=linux/amd64 -t #{image} -f #{dockerfile}"
50
+
48
51
  build_args.each { |build_arg| cmd += " --build-arg #{build_arg}" }
49
52
  cmd += " #{config.app_dir}"
50
53
  perform!(cmd)
@@ -58,25 +61,25 @@ class Controlplane # rubocop:disable Metrics/ClassLength
58
61
 
59
62
  def image_login(org_name = config.org)
60
63
  cmd = "cpln image docker-login --org #{org_name}"
61
- cmd += " > /dev/null 2>&1" if Shell.tmp_stderr
64
+ cmd += " > /dev/null 2>&1" if Shell.should_hide_output?
62
65
  perform!(cmd)
63
66
  end
64
67
 
65
68
  def image_pull(image)
66
69
  cmd = "docker pull #{image}"
67
- cmd += " > /dev/null" if Shell.tmp_stderr
70
+ cmd += " > /dev/null" if Shell.should_hide_output?
68
71
  perform!(cmd)
69
72
  end
70
73
 
71
74
  def image_tag(old_tag, new_tag)
72
75
  cmd = "docker tag #{old_tag} #{new_tag}"
73
- cmd += " > /dev/null" if Shell.tmp_stderr
76
+ cmd += " > /dev/null" if Shell.should_hide_output?
74
77
  perform!(cmd)
75
78
  end
76
79
 
77
80
  def image_push(image)
78
81
  cmd = "docker push #{image}"
79
- cmd += " > /dev/null" if Shell.tmp_stderr
82
+ cmd += " > /dev/null" if Shell.should_hide_output?
80
83
  perform!(cmd)
81
84
  end
82
85
 
@@ -145,7 +148,11 @@ class Controlplane # rubocop:disable Metrics/ClassLength
145
148
  end
146
149
 
147
150
  def workload_get_replicas_safely(workload, location:)
148
- cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml 2> /dev/null"
151
+ cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
152
+ cmd += " 2> /dev/null" if Shell.should_hide_output?
153
+
154
+ Shell.debug("CMD", cmd)
155
+
149
156
  result = `#{cmd}`
150
157
  $CHILD_STATUS.success? ? YAML.safe_load(result) : nil
151
158
  end
@@ -177,7 +184,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
177
184
  def workload_set_image_ref(workload, container:, image:)
178
185
  cmd = "cpln workload update #{workload} #{gvc_org}"
179
186
  cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
180
- cmd += " > /dev/null" if Shell.tmp_stderr
187
+ cmd += " > /dev/null" if Shell.should_hide_output?
181
188
  perform!(cmd)
182
189
  end
183
190
 
@@ -205,7 +212,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
205
212
 
206
213
  def workload_force_redeployment(workload)
207
214
  cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
208
- cmd += " > /dev/null" if Shell.tmp_stderr
215
+ cmd += " > /dev/null" if Shell.should_hide_output?
209
216
  perform!(cmd)
210
217
  end
211
218
 
@@ -273,32 +280,81 @@ class Controlplane # rubocop:disable Metrics/ClassLength
273
280
  end
274
281
 
275
282
  # apply
276
-
277
- def apply(data) # rubocop:disable Metrics/MethodLength
283
+ def apply_template(data) # rubocop:disable Metrics/MethodLength
278
284
  Tempfile.create do |f|
279
- f.write(data.to_yaml)
285
+ f.write(data)
280
286
  f.rewind
281
- cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
287
+ cmd = "cpln apply #{gvc_org} --file #{f.path}"
282
288
  if Shell.tmp_stderr
283
- cmd += " 2> #{Shell.tmp_stderr.path}"
284
- perform(cmd)
289
+ cmd += " 2> #{Shell.tmp_stderr.path}" if Shell.should_hide_output?
290
+
291
+ Shell.debug("CMD", cmd)
292
+
293
+ result = `#{cmd}`
294
+ $CHILD_STATUS.success? ? parse_apply_result(result) : false
295
+ else
296
+ Shell.debug("CMD", cmd)
297
+
298
+ result = `#{cmd}`
299
+ $CHILD_STATUS.success? ? parse_apply_result(result) : exit(false)
300
+ end
301
+ end
302
+ end
303
+
304
+ def apply_hash(data)
305
+ apply_template(data.to_yaml)
306
+ end
307
+
308
+ def parse_apply_result(result) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
309
+ items = []
310
+
311
+ lines = result.split("\n")
312
+ lines.each do |line|
313
+ # The line can be in one of these formats:
314
+ # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging"
315
+ # - "Created /org/shakacode-open-source-examples/gvc/my-app-staging/workload/redis"
316
+ # - "Updated gvc 'tutorial-app-test-1'"
317
+ # - "Updated workload 'redis'"
318
+ if line.start_with?("Created")
319
+ matches = line.match(%r{Created\s/org/[^/]+/gvc/([^/]+)($|(/([^/]+)/([^/]+)$))})&.captures
320
+ next unless matches
321
+
322
+ app, _, __, kind, name = matches
323
+ if kind
324
+ items.push({ kind: kind, name: name })
325
+ else
326
+ items.push({ kind: "app", name: app })
327
+ end
285
328
  else
286
- perform!(cmd)
329
+ matches = line.match(/Updated\s([^\s]+)\s'([^\s]+)'$/)&.captures
330
+ next unless matches
331
+
332
+ kind, name = matches
333
+ kind = "app" if kind == "gvc"
334
+ items.push({ kind: kind, name: name })
287
335
  end
288
336
  end
337
+
338
+ items
289
339
  end
290
340
 
291
341
  private
292
342
 
293
343
  def perform(cmd)
344
+ Shell.debug("CMD", cmd)
345
+
294
346
  system(cmd)
295
347
  end
296
348
 
297
349
  def perform!(cmd)
350
+ Shell.debug("CMD", cmd)
351
+
298
352
  system(cmd) || exit(false)
299
353
  end
300
354
 
301
355
  def perform_yaml(cmd)
356
+ Shell.debug("CMD", cmd)
357
+
302
358
  result = `#{cmd}`
303
359
  $CHILD_STATUS.success? ? YAML.safe_load(result) : exit(false)
304
360
  end
@@ -24,6 +24,8 @@ class ControlplaneApiDirect
24
24
  request["Authorization"] = api_token
25
25
  request.body = body.to_json if body
26
26
 
27
+ Shell.debug(method.upcase, "#{uri} #{body&.to_json}")
28
+
27
29
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) }
28
30
 
29
31
  case response
data/lib/core/shell.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Shell
4
4
  class << self
5
- attr_reader :tmp_stderr
5
+ attr_reader :tmp_stderr, :verbose
6
6
  end
7
7
 
8
8
  def self.shell
@@ -50,4 +50,16 @@ class Shell
50
50
  def self.abort(message)
51
51
  Kernel.abort(color("ERROR: #{message}", :red))
52
52
  end
53
+
54
+ def self.verbose_mode(verbose)
55
+ @verbose = verbose
56
+ end
57
+
58
+ def self.debug(prefix, message)
59
+ stderr.puts("\n[#{color(prefix, :red)}] #{message}") if verbose
60
+ end
61
+
62
+ def self.should_hide_output?
63
+ tmp_stderr && !verbose
64
+ end
53
65
  end
data/lib/cpl/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "1.1.1"
4
+ VERSION = "1.1.2"
5
5
  MIN_CPLN_VERSION = "0.0.71"
6
6
  end
data/lib/cpl.rb CHANGED
@@ -141,7 +141,7 @@ module Cpl
141
141
  usage = command_class::USAGE.empty? ? name : command_class::USAGE
142
142
  requires_args = command_class::REQUIRES_ARGS
143
143
  default_args = command_class::DEFAULT_ARGS
144
- command_options = command_class::OPTIONS
144
+ command_options = command_class::OPTIONS + [::Command::Base.verbose_option]
145
145
  description = command_class::DESCRIPTION
146
146
  long_description = command_class::LONG_DESCRIPTION
147
147
  examples = command_class::EXAMPLES
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cpl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-09-21 00:00:00.000000000 Z
12
+ date: 2023-10-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debug
@@ -251,6 +251,7 @@ files:
251
251
  - lib/command/maintenance_set_page.rb
252
252
  - lib/command/no_command.rb
253
253
  - lib/command/open.rb
254
+ - lib/command/open_console.rb
254
255
  - lib/command/promote_app_from_upstream.rb
255
256
  - lib/command/ps.rb
256
257
  - lib/command/ps_restart.rb