cpl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +60 -0
  3. data/.gitignore +14 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +16 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CONTRIBUTING.md +12 -0
  8. data/Gemfile +7 -0
  9. data/Gemfile.lock +104 -0
  10. data/LICENSE +21 -0
  11. data/README.md +318 -0
  12. data/Rakefile +11 -0
  13. data/bin/cpl +6 -0
  14. data/cpl +15 -0
  15. data/cpl.gemspec +42 -0
  16. data/docs/commands.md +219 -0
  17. data/docs/troubleshooting.md +6 -0
  18. data/examples/circleci.yml +106 -0
  19. data/examples/controlplane.yml +44 -0
  20. data/lib/command/base.rb +177 -0
  21. data/lib/command/build_image.rb +25 -0
  22. data/lib/command/config.rb +33 -0
  23. data/lib/command/delete.rb +50 -0
  24. data/lib/command/env.rb +21 -0
  25. data/lib/command/exists.rb +23 -0
  26. data/lib/command/latest_image.rb +18 -0
  27. data/lib/command/logs.rb +29 -0
  28. data/lib/command/open.rb +33 -0
  29. data/lib/command/promote_image.rb +27 -0
  30. data/lib/command/ps.rb +40 -0
  31. data/lib/command/ps_restart.rb +34 -0
  32. data/lib/command/ps_start.rb +34 -0
  33. data/lib/command/ps_stop.rb +34 -0
  34. data/lib/command/run.rb +106 -0
  35. data/lib/command/run_detached.rb +148 -0
  36. data/lib/command/setup.rb +59 -0
  37. data/lib/command/test.rb +26 -0
  38. data/lib/core/config.rb +81 -0
  39. data/lib/core/controlplane.rb +128 -0
  40. data/lib/core/controlplane_api.rb +51 -0
  41. data/lib/core/controlplane_api_cli.rb +10 -0
  42. data/lib/core/controlplane_api_direct.rb +42 -0
  43. data/lib/core/scripts.rb +34 -0
  44. data/lib/cpl/version.rb +5 -0
  45. data/lib/cpl.rb +139 -0
  46. data/lib/main.rb +5 -0
  47. data/postgres.md +436 -0
  48. data/redis.md +112 -0
  49. data/script/generate_commands_docs +60 -0
  50. data/templates/gvc.yml +13 -0
  51. data/templates/identity.yml +2 -0
  52. data/templates/memcached.yml +23 -0
  53. data/templates/postgres.yml +31 -0
  54. data/templates/rails.yml +25 -0
  55. data/templates/redis.yml +20 -0
  56. data/templates/sidekiq.yml +28 -0
  57. metadata +312 -0
data/docs/commands.md ADDED
@@ -0,0 +1,219 @@
1
+ <!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
2
+
3
+ ### Common Options
4
+
5
+ ```
6
+ -a XXX, --app XXX app ref on Control Plane (GVC)
7
+ ```
8
+
9
+ This `-a` option is used in most of the commands and will pick all other app configurations from the project-specific
10
+ `.controlplane/controlplane.yml` file.
11
+
12
+ ### Commands
13
+
14
+ ### `build-image`
15
+
16
+ - Builds and pushes the image to Control Plane
17
+ - Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
18
+ - Uses `.controlplane/Dockerfile`
19
+
20
+ ```sh
21
+ cpl build-image -a $APP_NAME
22
+ ```
23
+
24
+ ### `config`
25
+
26
+ - Displays current configs (global and app-specific)
27
+
28
+ ```sh
29
+ # Shows the global config.
30
+ cpl config
31
+
32
+ # Shows both global and app-specific configs.
33
+ cpl config -a $APP_NAME
34
+ ```
35
+
36
+ ### `delete`
37
+
38
+ - Deletes the whole app (GVC with all workloads and all images)
39
+ - Will ask for explicit user confirmation
40
+
41
+ ```sh
42
+ cpl delete -a $APP_NAME
43
+ ```
44
+
45
+ ### `env`
46
+
47
+ - Displays app-specific environment variables
48
+
49
+ ```sh
50
+ cpl env -a $APP_NAME
51
+ ```
52
+
53
+ ### `exists`
54
+
55
+ - Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
56
+
57
+ ```sh
58
+ if [ cpl exists -a $APP_NAME ]; ...
59
+ ```
60
+
61
+ ### `latest-image`
62
+
63
+ - Displays the latest image name
64
+
65
+ ```sh
66
+ cpl latest-image -a $APP_NAME
67
+ ```
68
+
69
+ ### `logs`
70
+
71
+ - Light wrapper to display tailed raw logs for app/workload syntax
72
+
73
+ ```sh
74
+ # Displays logs for the default workload (`one_off_workload`).
75
+ cpl logs -a $APP_NAME
76
+
77
+ # Displays logs for a specific workload.
78
+ cpl logs -a $APP_NAME -w $WORKLOAD_NAME
79
+ ```
80
+
81
+ ### `open`
82
+
83
+ - Opens the app endpoint URL in the default browser
84
+
85
+ ```sh
86
+ # Opens the endpoint of the default workload (`one_off_workload`).
87
+ cpl open -a $APP_NAME
88
+
89
+ # Opens the endpoint of a specific workload.
90
+ cpl open -a $APP_NAME -w $WORKLOAD_NAME
91
+ ```
92
+
93
+ ### `promote-image`
94
+
95
+ - Promotes the latest image to app workloads
96
+
97
+ ```sh
98
+ cpl promote-image -a $APP_NAME
99
+ ```
100
+
101
+ ### `ps`
102
+
103
+ - Shows running replicas in app
104
+
105
+ ```sh
106
+ # Shows running replicas in app, for all workloads.
107
+ cpl ps -a $APP_NAME
108
+
109
+ # Shows running replicas in app, for a specific workload.
110
+ cpl ps -a $APP_NAME -w $WORKLOAD_NAME
111
+ ```
112
+
113
+ ### `ps:restart`
114
+
115
+ - Forces redeploy of workloads in app
116
+
117
+ ```sh
118
+ # Forces redeploy of all workloads in app.
119
+ cpl ps:restart -a $APP_NAME
120
+
121
+ # Forces redeploy of a specific workload in app.
122
+ cpl ps:restart -a $APP_NAME -w $WORKLOAD_NAME
123
+ ```
124
+
125
+ ### `ps:start`
126
+
127
+ - Starts workloads in app
128
+
129
+ ```sh
130
+ # Starts all workloads in app.
131
+ cpl ps:start -a $APP_NAME
132
+
133
+ # Starts a specific workload in app.
134
+ cpl ps:start -a $APP_NAME -w $WORKLOAD_NAME
135
+ ```
136
+
137
+ ### `ps:stop`
138
+
139
+ - Stops workloads in app
140
+
141
+ ```sh
142
+ # Stops all workloads in app.
143
+ cpl ps:stop -a $APP_NAME
144
+
145
+ # Stops a specific workload in app.
146
+ cpl ps:stop -a $APP_NAME -w $WORKLOAD_NAME
147
+ ```
148
+
149
+ ### `run`
150
+
151
+ - Runs one-off **_interactive_** replicas (analog of `heroku run`)
152
+ - Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
153
+ - May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
154
+
155
+ > **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
156
+ > task crashing are tolerable. For production tasks, it's better to use `cpl run:detached`.
157
+
158
+ ```sh
159
+ # Opens shell (bash by default).
160
+ cpl run -a $APP_NAME
161
+
162
+ # Runs command, displays output, and exits shell.
163
+ cpl run ls / -a $APP_NAME
164
+ cpl run rails db:migrate:status -a $APP_NAME
165
+
166
+ # Runs command and keeps shell open.
167
+ cpl run rails c -a $APP_NAME
168
+
169
+ # Uses a different image (which may not be promoted yet).
170
+ cpl run rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
171
+ cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential image
172
+ ```
173
+
174
+ ### `run:detached`
175
+
176
+ - Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)
177
+ - Uses `Cron` workload type with log async fetching
178
+ - Implemented with only async execution methods, more suitable for production tasks
179
+ - Has alternative log fetch implementation with only JSON-polling and no WebSockets
180
+ - Less responsive but more stable, useful for CI tasks
181
+
182
+ ```sh
183
+ cpl run:detached rails db:prepare -a $APP_NAME
184
+ cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
185
+
186
+ # Uses some other image.
187
+ cpl run:detached rails db:migrate -a $APP_NAME --image /some/full/image/path
188
+
189
+ # Uses latest app image (which may not be promoted yet).
190
+ cpl run:detached rails db:migrate -a $APP_NAME --image latest
191
+
192
+ # Uses a different image (which may not be promoted yet).
193
+ cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
194
+ cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
195
+ ```
196
+
197
+ ### `setup`
198
+
199
+ - Applies application-specific configs from templates (e.g., for every review-app)
200
+ - Publishes (creates or updates) those at Control Plane infrastructure
201
+ - Picks templates from the `.controlplane/templates` directory
202
+ - Templates are ordinary Control Plane templates but with variable preprocessing
203
+
204
+ **Preprocessed template variables:**
205
+
206
+ ```
207
+ APP_GVC - basically GVC or app name
208
+ APP_LOCATION - default location
209
+ APP_ORG - organization
210
+ APP_IMAGE - will use latest app image
211
+ ```
212
+
213
+ ```sh
214
+ # Applies single template.
215
+ cpl setup redis -a $APP_NAME
216
+
217
+ # Applies several templates (practically creating full app).
218
+ cpl setup gvc postgres redis rails -a $APP_NAME
219
+ ```
@@ -0,0 +1,6 @@
1
+ # Troubleshooting
2
+
3
+
4
+ ## App Web Page Shows `upstream request timeout`
5
+
6
+ If you get a blank screen showing the message `upstream request timeout` on your app after running `cpl open -a my-app-name`, check out the application logs. Your image has been promoted and your app crashing when starting.
@@ -0,0 +1,106 @@
1
+ # Example config for staging app:
2
+ # - triggers on push to master
3
+ # - static app name
4
+ # - no resource provisioning
5
+ # - no database setup, only migration
6
+ build-staging:
7
+ docker:
8
+ - image: cimg/ruby:3.1-node
9
+ resource_class: large
10
+ environment:
11
+ CPLN_ORG: myorg
12
+ APP_NAME: myapp-staging
13
+ steps:
14
+ - checkout
15
+ - setup_remote_docker:
16
+ docker_layer_caching: true
17
+ - run:
18
+ name: Install Control Plane tools
19
+ command: |
20
+ sudo npm install -g @controlplane/cli && cpln --version
21
+ cpln profile create default --token ${CPLN_TOKEN} --org ${CPLN_ORG} --gvc ${APP_NAME}
22
+ cpln image docker-login
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
26
+ - run:
27
+ name: Containerize and push image
28
+ command: cpl build-image -a ${APP_NAME}
29
+ - run:
30
+ name: Database tasks
31
+ command: cpl run:detached rails db:migrate -a ${APP_NAME} --image latest
32
+ - run:
33
+ name: Promote image
34
+ command: cpl promote-image -a ${APP_NAME}
35
+
36
+ # Example config for review app:
37
+ # - triggers manually if needed
38
+ # - dynamic app name based on PR number
39
+ # - resources provisioning for new apps
40
+ # - initial database setup or migration
41
+ build-review-app:
42
+ docker:
43
+ - image: cimg/ruby:3.1-node
44
+ resource_class: large
45
+ environment:
46
+ CPLN_ORG: my-org-name
47
+ steps:
48
+ - checkout
49
+ - setup_remote_docker:
50
+ docker_layer_caching: true
51
+ - run:
52
+ name: Setup environment
53
+ command: |
54
+ PR_NUM=$(echo $CIRCLE_PULL_REQUEST | grep -Eo '[0-9]+$')
55
+ echo "export APP_NAME=hichee-review-$PR_NUM" >> $BASH_ENV
56
+ - run:
57
+ name: Install Control Plane tools
58
+ command: |
59
+ sudo npm install -g @controlplane/cli && cpln --version
60
+ cpln profile create default --token ${CPLN_TOKEN} --org ${CPLN_ORG} --gvc ${APP_NAME}
61
+ cpln image docker-login
62
+
63
+ git clone https://github.com/shakacode/heroku-to-control-plane ~/heroku-to-control-plane
64
+ sudo ln -s ~/heroku-to-control-plane/cpl /usr/local/bin/cpl
65
+ - run:
66
+ name: Provision review app if needed
67
+ command: |
68
+ if ! cpl exist -a ${APP_NAME}; then
69
+ cpl setup gvc postgres redis memcached rails sidekiq -a ${APP_NAME}
70
+ echo "export NEW_APP=true" >> $BASH_ENV
71
+ fi
72
+ - run:
73
+ name: Containerize and push image
74
+ command: |
75
+ cpl build-image -a ${APP_NAME} --commit ${CIRCLE_SHA1::7}
76
+ - run:
77
+ name: Database tasks
78
+ command: |
79
+ if [ -n "${NEW_APP}" ]; then
80
+ cpl run:detached 'LOG_LEVEL=warn rails db:reset' -a ${APP_NAME} --image latest
81
+ else
82
+ cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a ${APP_NAME} --image latest
83
+ fi
84
+ - run:
85
+ name: Promote image
86
+ command: cpl promote-image -a ${APP_NAME}
87
+
88
+ review-app:
89
+ jobs:
90
+ - start:
91
+ filters:
92
+ branches:
93
+ ignore: master
94
+ type: approval
95
+ - build-review-app:
96
+ filters:
97
+ branches:
98
+ ignore: master
99
+ requires:
100
+ - start
101
+ staging:
102
+ jobs:
103
+ - build-staging:
104
+ filters:
105
+ branches:
106
+ only: master
@@ -0,0 +1,44 @@
1
+ aliases:
2
+ 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
6
+ cpln_org: my-org-staging
7
+
8
+ # Example apps use only location. CPLN offers the ability to use multiple locations.
9
+ # TODO -- allow specfication of multiple locations
10
+ default_location: aws-us-east-2
11
+
12
+ # Configure the workload name used as a template for one-off scripts, like a Heroku one-off dyno.
13
+ one_off_workload: rails
14
+
15
+ # Workloads that are application itself and are using application docker image
16
+ app_workloads:
17
+ - rails
18
+ - sidekiq
19
+
20
+ # Additional "service type" workloads, using non-application docker images
21
+ additional_workloads:
22
+ - redis
23
+ - postgres
24
+ - memcached
25
+
26
+ apps:
27
+ my-app-staging:
28
+ # Use the values from the common section above
29
+ <<: *common
30
+ my-app-review:
31
+ <<: *common
32
+ # if match_if_app_name_starts_with == true, then use this config for app names beginning,
33
+ # like my-app-review-pr123 or my-app-review-anything-goes
34
+ match_if_app_name_starts_with: true
35
+ my-app-production:
36
+ <<: *common
37
+ # Use a different org for production
38
+ cpln_org: my-org-production
39
+ # Allows running command 'cpl pipeline-promote my-app-staging' to promote the staging app to production
40
+ upstream: my-app-staging
41
+ my-app-other:
42
+ <<: *common
43
+ # you can specify different dockerfile relative to .controlplane folder, default is just 'Dockerfile'
44
+ dockerfile: ../some_other/Dockerfile
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Base # rubocop:disable Metrics/ClassLength
5
+ attr_reader :config
6
+
7
+ # Used to call the command (`cpl NAME`)
8
+ # NAME = ""
9
+ # Displayed when running `cpl help` or `cpl help NAME` (defaults to `NAME`)
10
+ USAGE = ""
11
+ # Throws error if `true` and no arguments are passed to the command
12
+ # or if `false` and arguments are passed to the command
13
+ REQUIRES_ARGS = false
14
+ # Default arguments if none are passed to the command
15
+ DEFAULT_ARGS = [].freeze
16
+ # Options for the command (use option methods below)
17
+ OPTIONS = [].freeze
18
+ # Displayed when running `cpl help`
19
+ # DESCRIPTION = ""
20
+ # Displayed when running `cpl help NAME`
21
+ # LONG_DESCRIPTION = ""
22
+ # Displayed along with `LONG_DESCRIPTION` when running `cpl help NAME`
23
+ EXAMPLES = ""
24
+ # If `true`, hides the command from `cpl help`
25
+ HIDE = false
26
+
27
+ NO_IMAGE_AVAILABLE = "NO_IMAGE_AVAILABLE"
28
+
29
+ def initialize(config)
30
+ @config = config
31
+ end
32
+
33
+ def self.all_commands
34
+ Dir["#{__dir__}/*.rb"].each_with_object({}) do |file, result|
35
+ filename = File.basename(file, ".rb")
36
+ classname = File.read(file).match(/^\s+class (\w+) < Base($| .*$)/)&.captures&.first
37
+ result[filename.to_sym] = Object.const_get("::Command::#{classname}") if classname
38
+ end
39
+ end
40
+
41
+ def self.app_option(required: false)
42
+ {
43
+ name: :app,
44
+ params: {
45
+ aliases: ["-a"],
46
+ banner: "APP_NAME",
47
+ desc: "Application name",
48
+ type: :string,
49
+ required: required
50
+ }
51
+ }
52
+ end
53
+
54
+ def self.workload_option(required: false)
55
+ {
56
+ name: :workload,
57
+ params: {
58
+ aliases: ["-w"],
59
+ banner: "WORKLOAD_NAME",
60
+ desc: "Workload name",
61
+ type: :string,
62
+ required: required
63
+ }
64
+ }
65
+ end
66
+
67
+ def self.image_option(required: false)
68
+ {
69
+ name: :image,
70
+ params: {
71
+ aliases: ["-i"],
72
+ banner: "IMAGE_NAME",
73
+ desc: "Image name",
74
+ type: :string,
75
+ required: required
76
+ }
77
+ }
78
+ end
79
+
80
+ def self.commit_option(required: false)
81
+ {
82
+ name: :commit,
83
+ params: {
84
+ aliases: ["-c"],
85
+ banner: "COMMIT_HASH",
86
+ desc: "Commit hash",
87
+ type: :string,
88
+ required: required
89
+ }
90
+ }
91
+ end
92
+
93
+ def self.all_options
94
+ methods.grep(/_option$/).map { |method| send(method.to_s) }
95
+ end
96
+
97
+ def self.all_options_key_name
98
+ all_options.each_with_object({}) do |option, result|
99
+ option[:params][:aliases].each { |current_alias| result[current_alias.to_s] = option[:name] }
100
+ result["--#{option[:name]}"] = option[:name]
101
+ end
102
+ end
103
+
104
+ def wait_for(title)
105
+ progress.print "- Waiting for #{title}"
106
+ until yield
107
+ progress.print(".")
108
+ sleep(1)
109
+ end
110
+ progress.puts
111
+ end
112
+
113
+ def wait_for_workload(workload)
114
+ wait_for("workload to start") { cp.workload_get(workload) }
115
+ end
116
+
117
+ def wait_for_replica(workload, location)
118
+ wait_for("replica") do
119
+ cp.workload_get_replicas(workload, location: location)&.dig("items", 0)
120
+ end
121
+ end
122
+
123
+ def ensure_workload_deleted(workload)
124
+ progress.puts "- Ensure workload is deleted"
125
+ cp.workload_delete(workload, no_raise: true)
126
+ end
127
+
128
+ def latest_image # rubocop:disable Metrics/MethodLength
129
+ @latest_image ||=
130
+ begin
131
+ items = cp.image_query["items"]
132
+ matching_items = items.filter_map do |item|
133
+ item["name"] if item["name"].start_with?("#{config.app}:")
134
+ end
135
+
136
+ # Or special string to indicate no image available
137
+ if matching_items.empty?
138
+ "#{config.app}:#{NO_IMAGE_AVAILABLE}"
139
+ else
140
+ matching_items.max_by { |item| extract_image_number(item) }
141
+ end
142
+ end
143
+ end
144
+
145
+ def latest_image_next
146
+ @latest_image_next ||= begin
147
+ image = latest_image.split(":").first
148
+ image += ":#{extract_image_number(latest_image) + 1}"
149
+ image += "_#{config.options[:commit]}" if config.options[:commit]
150
+ image
151
+ end
152
+ end
153
+
154
+ # NOTE: use simplified variant atm, as shelljoin do different escaping
155
+ # TODO: most probably need better logic for escaping various quotes
156
+ def args_join(args)
157
+ args.join(" ")
158
+ end
159
+
160
+ def progress
161
+ $stderr
162
+ end
163
+
164
+ def cp
165
+ @cp ||= Controlplane.new(config)
166
+ end
167
+
168
+ private
169
+
170
+ # returns 0 if no prior image
171
+ def extract_image_number(image_name)
172
+ return 0 if image_name.end_with?(NO_IMAGE_AVAILABLE)
173
+
174
+ image_name.match(/:(\d+)/)&.captures&.first.to_i
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class BuildImage < Base
5
+ NAME = "build-image"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ commit_option
9
+ ].freeze
10
+ DESCRIPTION = "Builds and pushes the image to Control Plane"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Builds and pushes the image to Control Plane
13
+ - Automatically assigns image numbers, e.g., `app:1`, `app:2`, etc.
14
+ - Uses `.controlplane/Dockerfile`
15
+ HEREDOC
16
+
17
+ def call
18
+ dockerfile = config.current[:dockerfile] || "Dockerfile"
19
+ dockerfile = "#{config.app_cpln_dir}/#{dockerfile}"
20
+ progress.puts "- Building dockerfile: #{dockerfile}"
21
+
22
+ cp.image_build(latest_image_next, dockerfile: dockerfile)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Config < Base
5
+ NAME = "config"
6
+ OPTIONS = [
7
+ app_option
8
+ ].freeze
9
+ DESCRIPTION = "Displays current configs (global and app-specific)"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Displays current configs (global and app-specific)
12
+ HEREDOC
13
+ EXAMPLES = <<~HEREDOC
14
+ ```sh
15
+ # Shows the global config.
16
+ cpl config
17
+
18
+ # Shows both global and app-specific configs.
19
+ cpl config -a $APP_NAME
20
+ ```
21
+ HEREDOC
22
+
23
+ def call
24
+ puts "-- Options"
25
+ puts config.options.to_hash.to_yaml[4..]
26
+ puts
27
+
28
+ puts "-- Current config (app: #{config.app})"
29
+ puts config.app ? config.current.to_yaml[4..] : "Please specify app to get app config"
30
+ puts
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Delete < Base
5
+ NAME = "delete"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Deletes the whole app (GVC with all workloads and all images)
12
+ - Will ask for explicit user confirmation
13
+ HEREDOC
14
+
15
+ def call
16
+ progress.puts "Type 'delete' to delete #{config.app} and images"
17
+ progress.print "> "
18
+
19
+ return progress.puts "Not confirmed" unless $stdin.gets.chomp == "delete"
20
+
21
+ delete_gvc
22
+ delete_images
23
+ end
24
+
25
+ private
26
+
27
+ def delete_gvc
28
+ progress.puts "- Deleting gvc:"
29
+
30
+ return progress.puts "none" unless cp.gvc_get
31
+
32
+ cp.gvc_delete
33
+ progress.puts config.app
34
+ end
35
+
36
+ def delete_images
37
+ progress.puts "- Deleting image(s):"
38
+
39
+ images = cp.image_query["items"]
40
+ .filter_map { |item| item["name"] if item["name"].start_with?("#{config.app}:") }
41
+
42
+ return progress.puts "none" unless images
43
+
44
+ images.each do |image|
45
+ cp.image_delete(image)
46
+ progress.puts image
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Env < Base
5
+ NAME = "env"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Displays app-specific environment variables"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Displays app-specific environment variables
12
+ HEREDOC
13
+
14
+ def call
15
+ cp.gvc_get.dig("spec", "env").map do |prop|
16
+ # NOTE: atm no special chars handling, consider adding if needed
17
+ puts "#{prop['name']}=#{prop['value']}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Exists < Base
5
+ NAME = "exists"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Shell-checks if an application (GVC) exists, useful in scripts"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Shell-checks if an application (GVC) exists, useful in scripts, e.g.:
12
+ HEREDOC
13
+ EXAMPLES = <<~HEREDOC
14
+ ```sh
15
+ if [ cpl exists -a $APP_NAME ]; ...
16
+ ```
17
+ HEREDOC
18
+
19
+ def call
20
+ exit(!cp.gvc_get.nil?)
21
+ end
22
+ end
23
+ end