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 +4 -4
- data/CHANGELOG.md +21 -1
- data/Gemfile.lock +4 -4
- data/README.md +127 -28
- data/docs/commands.md +9 -0
- data/lib/command/apply_template.rb +30 -54
- data/lib/command/base.rb +12 -0
- data/lib/command/config.rb +5 -0
- data/lib/command/copy_image_from_upstream.rb +5 -2
- data/lib/command/info.rb +5 -2
- data/lib/command/no_command.rb +5 -3
- data/lib/command/open_console.rb +26 -0
- data/lib/command/run.rb +1 -1
- data/lib/command/run_detached.rb +1 -1
- data/lib/core/config.rb +20 -3
- data/lib/core/controlplane.rb +73 -17
- data/lib/core/controlplane_api_direct.rb +2 -0
- data/lib/core/shell.rb +13 -1
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89a038d31fb497bfc6c0d34451ace68fe554d700f4686413a1919cf6d9bceb09
|
|
4
|
+
data.tar.gz: 3c733b43e703ad7b7798cf599b71f59ea1758ce2c71babd4486670c5ed5f12a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
[](https://github.com/shakacode/heroku-to-control-plane/actions/workflows/rspec.yml)
|
|
12
12
|
[](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. [
|
|
33
|
-
6. [
|
|
34
|
-
7. [
|
|
35
|
-
8. [
|
|
36
|
-
9. [
|
|
37
|
-
10. [
|
|
38
|
-
11. [
|
|
39
|
-
12. [
|
|
40
|
-
13. [
|
|
41
|
-
14. [Migrating
|
|
42
|
-
15. [
|
|
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.
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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:
|
|
331
|
+
value: "value"
|
|
236
332
|
- name: MY_SECRET_GLOBAL_VAR
|
|
237
|
-
value:
|
|
333
|
+
value: "cpln://secret/MY_SECRET_STORE_NAME/MY_SECRET_GLOBAL_VAR"
|
|
334
|
+
```
|
|
238
335
|
|
|
239
|
-
|
|
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:
|
|
344
|
+
value: "value"
|
|
246
345
|
- name: MY_SECRET_LOCAL_VAR
|
|
247
|
-
value:
|
|
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
|
-
@
|
|
42
|
-
@
|
|
43
|
-
@
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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(
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
174
|
-
return unless @
|
|
149
|
+
def print_created_items
|
|
150
|
+
return unless @created_items.any?
|
|
175
151
|
|
|
176
|
-
|
|
177
|
-
progress.puts("\n#{Shell.color('Created
|
|
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
|
|
181
|
-
return unless @
|
|
156
|
+
def print_failed_templates
|
|
157
|
+
return unless @failed_templates.any?
|
|
182
158
|
|
|
183
|
-
|
|
184
|
-
progress.puts("\n#{Shell.color('Failed to
|
|
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
|
|
188
|
-
return unless @
|
|
163
|
+
def print_skipped_templates
|
|
164
|
+
return unless @skipped_templates.any?
|
|
189
165
|
|
|
190
|
-
|
|
191
|
-
progress.puts("\n#{Shell.color('Skipped
|
|
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
|
data/lib/command/config.rb
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
97
|
+
result.push(org) if org && !result.include?(org)
|
|
95
98
|
end
|
|
96
99
|
end
|
|
97
100
|
|
data/lib/command/no_command.rb
CHANGED
|
@@ -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.
|
|
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
|
data/lib/command/run_detached.rb
CHANGED
|
@@ -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.
|
|
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
|
data/lib/core/controlplane.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
285
|
+
f.write(data)
|
|
280
286
|
f.rewind
|
|
281
|
-
cmd = "cpln apply #{gvc_org} --file #{f.path}
|
|
287
|
+
cmd = "cpln apply #{gvc_org} --file #{f.path}"
|
|
282
288
|
if Shell.tmp_stderr
|
|
283
|
-
cmd += " 2> #{Shell.tmp_stderr.path}"
|
|
284
|
-
|
|
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
|
-
|
|
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
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.
|
|
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-
|
|
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
|