cpflow 3.0.0
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 +7 -0
- data/.github/workflows/check_cpln_links.yml +19 -0
- data/.github/workflows/command_docs.yml +24 -0
- data/.github/workflows/rspec-shared.yml +56 -0
- data/.github/workflows/rspec.yml +28 -0
- data/.github/workflows/rubocop.yml +24 -0
- data/.gitignore +18 -0
- data/.overcommit.yml +16 -0
- data/.rubocop.yml +22 -0
- data/.simplecov_spawn.rb +10 -0
- data/CHANGELOG.md +259 -0
- data/CONTRIBUTING.md +73 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +126 -0
- data/LICENSE +21 -0
- data/README.md +546 -0
- data/Rakefile +21 -0
- data/bin/cpflow +6 -0
- data/cpflow +6 -0
- data/cpflow.gemspec +41 -0
- data/docs/assets/grafana-alert.png +0 -0
- data/docs/assets/memcached.png +0 -0
- data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
- data/docs/commands.md +454 -0
- data/docs/dns.md +15 -0
- data/docs/migrating.md +262 -0
- data/docs/postgres.md +436 -0
- data/docs/redis.md +128 -0
- data/docs/secrets-and-env-values.md +42 -0
- data/docs/tips.md +150 -0
- data/docs/troubleshooting.md +6 -0
- data/examples/circleci.yml +104 -0
- data/examples/controlplane.yml +159 -0
- data/lib/command/apply_template.rb +209 -0
- data/lib/command/base.rb +540 -0
- data/lib/command/build_image.rb +49 -0
- data/lib/command/cleanup_images.rb +136 -0
- data/lib/command/cleanup_stale_apps.rb +79 -0
- data/lib/command/config.rb +48 -0
- data/lib/command/copy_image_from_upstream.rb +108 -0
- data/lib/command/delete.rb +149 -0
- data/lib/command/deploy_image.rb +56 -0
- data/lib/command/doctor.rb +47 -0
- data/lib/command/env.rb +22 -0
- data/lib/command/exists.rb +23 -0
- data/lib/command/generate.rb +45 -0
- data/lib/command/info.rb +222 -0
- data/lib/command/latest_image.rb +19 -0
- data/lib/command/logs.rb +49 -0
- data/lib/command/maintenance.rb +42 -0
- data/lib/command/maintenance_off.rb +62 -0
- data/lib/command/maintenance_on.rb +62 -0
- data/lib/command/maintenance_set_page.rb +34 -0
- data/lib/command/no_command.rb +23 -0
- data/lib/command/open.rb +33 -0
- data/lib/command/open_console.rb +26 -0
- data/lib/command/promote_app_from_upstream.rb +38 -0
- data/lib/command/ps.rb +41 -0
- data/lib/command/ps_restart.rb +37 -0
- data/lib/command/ps_start.rb +51 -0
- data/lib/command/ps_stop.rb +82 -0
- data/lib/command/ps_wait.rb +40 -0
- data/lib/command/run.rb +573 -0
- data/lib/command/setup_app.rb +113 -0
- data/lib/command/test.rb +23 -0
- data/lib/command/version.rb +18 -0
- data/lib/constants/exit_code.rb +7 -0
- data/lib/core/config.rb +316 -0
- data/lib/core/controlplane.rb +552 -0
- data/lib/core/controlplane_api.rb +170 -0
- data/lib/core/controlplane_api_direct.rb +112 -0
- data/lib/core/doctor_service.rb +104 -0
- data/lib/core/helpers.rb +26 -0
- data/lib/core/shell.rb +100 -0
- data/lib/core/template_parser.rb +76 -0
- data/lib/cpflow/version.rb +6 -0
- data/lib/cpflow.rb +288 -0
- data/lib/deprecated_commands.json +9 -0
- data/lib/generator_templates/Dockerfile +27 -0
- data/lib/generator_templates/controlplane.yml +62 -0
- data/lib/generator_templates/entrypoint.sh +8 -0
- data/lib/generator_templates/templates/app.yml +21 -0
- data/lib/generator_templates/templates/postgres.yml +176 -0
- data/lib/generator_templates/templates/rails.yml +36 -0
- data/rakelib/create_release.rake +81 -0
- data/script/add_command +37 -0
- data/script/check_command_docs +3 -0
- data/script/check_cpln_links +45 -0
- data/script/rename_command +43 -0
- data/script/update_command_docs +62 -0
- data/templates/app.yml +13 -0
- data/templates/daily-task.yml +32 -0
- data/templates/maintenance.yml +25 -0
- data/templates/memcached.yml +24 -0
- data/templates/postgres.yml +32 -0
- data/templates/rails.yml +27 -0
- data/templates/redis.yml +21 -0
- data/templates/redis2.yml +37 -0
- data/templates/sidekiq.yml +38 -0
- metadata +341 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Generator < Thor::Group
|
5
|
+
include Thor::Actions
|
6
|
+
|
7
|
+
def copy_files
|
8
|
+
directory("generator_templates", ".controlplane", verbose: ENV.fetch("HIDE_COMMAND_OUTPUT", nil) != "true")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.source_root
|
12
|
+
File.expand_path("../", __dir__)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Generate < Base
|
17
|
+
NAME = "generate"
|
18
|
+
DESCRIPTION = "Creates base Control Plane config and template files"
|
19
|
+
LONG_DESCRIPTION = <<~DESC
|
20
|
+
Creates base Control Plane config and template files
|
21
|
+
DESC
|
22
|
+
EXAMPLES = <<~EX
|
23
|
+
```sh
|
24
|
+
# Creates .controlplane directory with Control Plane config and other templates
|
25
|
+
cpflow generate
|
26
|
+
```
|
27
|
+
EX
|
28
|
+
WITH_INFO_HEADER = false
|
29
|
+
|
30
|
+
def call
|
31
|
+
if controlplane_directory_exists?
|
32
|
+
Shell.warn("The directory '.controlplane' already exists!")
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
Generator.start
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def controlplane_directory_exists?
|
42
|
+
Dir.exist? ".controlplane"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/command/info.rb
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Info < Base # rubocop:disable Metrics/ClassLength
|
5
|
+
NAME = "info"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option
|
8
|
+
].freeze
|
9
|
+
DESCRIPTION = "Displays the diff between defined/available apps/workloads (apps equal GVCs)"
|
10
|
+
LONG_DESCRIPTION = <<~DESC
|
11
|
+
- Displays the diff between defined/available apps/workloads (apps equal GVCs)
|
12
|
+
- Apps that are defined but not available are displayed in red
|
13
|
+
- Apps that are available but not defined are displayed in green
|
14
|
+
- Apps that are both defined and available are displayed in white
|
15
|
+
- The diff is based on what's defined in the `.controlplane/controlplane.yml` file
|
16
|
+
DESC
|
17
|
+
EXAMPLES = <<~EX
|
18
|
+
```sh
|
19
|
+
# Shows diff for all apps in all orgs (based on `.controlplane/controlplane.yml`).
|
20
|
+
cpflow info
|
21
|
+
|
22
|
+
# Shows diff for all apps in a specific org.
|
23
|
+
cpflow info -o $ORG_NAME
|
24
|
+
|
25
|
+
# Shows diff for a specific app.
|
26
|
+
cpflow info -a $APP_NAME
|
27
|
+
```
|
28
|
+
EX
|
29
|
+
WITH_INFO_HEADER = false
|
30
|
+
|
31
|
+
def call
|
32
|
+
@missing_apps_workloads = {}
|
33
|
+
@missing_apps_starting_with = {}
|
34
|
+
|
35
|
+
if config.app && !config.should_app_start_with?(config.app)
|
36
|
+
single_app_info
|
37
|
+
else
|
38
|
+
multiple_apps_info
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def find_workloads(app)
|
45
|
+
app_options = config.find_app_config(app)
|
46
|
+
|
47
|
+
(app_options[:app_workloads] + app_options[:additional_workloads] + [app_options[:one_off_workload]]).uniq
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_workloads(app)
|
51
|
+
cp.fetch_workloads(app)["items"].map { |workload| workload["name"] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_app_workloads(org) # rubocop:disable Metrics/MethodLength
|
55
|
+
result = {}
|
56
|
+
|
57
|
+
workloads = cp.fetch_workloads_by_org(org)["items"]
|
58
|
+
workloads.each do |workload|
|
59
|
+
app = workload["links"].find { |link| link["rel"] == "gvc" }["href"].split("/").last
|
60
|
+
|
61
|
+
result[app] ||= []
|
62
|
+
result[app].push(workload["name"])
|
63
|
+
end
|
64
|
+
|
65
|
+
if config.app
|
66
|
+
result.select { |app, _| config.app_matches?(app, config.app, config.current) }
|
67
|
+
else
|
68
|
+
result.reject { |app, _| config.find_app_config(app).nil? }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def orgs
|
73
|
+
result = []
|
74
|
+
|
75
|
+
if config.org
|
76
|
+
result.push(config.org)
|
77
|
+
else
|
78
|
+
config.apps.each do |_, app_options|
|
79
|
+
org = app_org(app_options)
|
80
|
+
result.push(org) if org && !result.include?(org)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
result.sort
|
85
|
+
end
|
86
|
+
|
87
|
+
def apps(org)
|
88
|
+
result = []
|
89
|
+
|
90
|
+
config.apps.each do |app_name, app_options|
|
91
|
+
next if config.app && !config.app_matches?(config.app, app_name, app_options)
|
92
|
+
|
93
|
+
current_app_org = app_org(app_options)
|
94
|
+
result.push(app_name.to_s) if current_app_org == org
|
95
|
+
end
|
96
|
+
|
97
|
+
result += @app_workloads.keys.map(&:to_s)
|
98
|
+
result.uniq.sort
|
99
|
+
end
|
100
|
+
|
101
|
+
def app_org(app_options)
|
102
|
+
app_options[:cpln_org]
|
103
|
+
end
|
104
|
+
|
105
|
+
def any_app_starts_with?(app)
|
106
|
+
@app_workloads.keys.find { |app_name| config.app_matches?(app_name, app, config.apps[app.to_sym]) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def check_any_app_starts_with(app)
|
110
|
+
if any_app_starts_with?(app)
|
111
|
+
false
|
112
|
+
else
|
113
|
+
@missing_apps_starting_with[app] ||= ["gvc"]
|
114
|
+
|
115
|
+
puts " - #{Shell.color("Any app starting with '#{app}'", :red)}"
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def add_to_missing_workloads(app, workload)
|
121
|
+
if config.should_app_start_with?(app)
|
122
|
+
@missing_apps_starting_with[app] ||= []
|
123
|
+
@missing_apps_starting_with[app].push(workload)
|
124
|
+
else
|
125
|
+
@missing_apps_workloads[app] ||= []
|
126
|
+
@missing_apps_workloads[app].push(workload)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def print_app(app, org)
|
131
|
+
if config.should_app_start_with?(app)
|
132
|
+
check_any_app_starts_with(app)
|
133
|
+
elsif cp.fetch_gvc(app, org).nil?
|
134
|
+
@missing_apps_workloads[app] = ["gvc"]
|
135
|
+
|
136
|
+
puts " - #{Shell.color(app, :red)}"
|
137
|
+
true
|
138
|
+
else
|
139
|
+
puts " - #{app}"
|
140
|
+
true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def print_workload(app, workload)
|
145
|
+
if @defined_workloads.include?(workload) && !@available_workloads.include?(workload)
|
146
|
+
add_to_missing_workloads(app, workload)
|
147
|
+
|
148
|
+
puts " - #{Shell.color(workload, :red)}"
|
149
|
+
elsif !@defined_workloads.include?(workload) && @available_workloads.include?(workload)
|
150
|
+
puts " - #{Shell.color(workload, :green)}"
|
151
|
+
else
|
152
|
+
puts " - #{workload}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def print_missing_apps_workloads
|
157
|
+
return if @missing_apps_workloads.empty?
|
158
|
+
|
159
|
+
puts "\nSome apps/workloads are missing. Please create them with:"
|
160
|
+
|
161
|
+
@missing_apps_workloads.each do |app, workloads|
|
162
|
+
if workloads.include?("gvc")
|
163
|
+
puts " - `cpflow setup-app -a #{app}`"
|
164
|
+
else
|
165
|
+
puts " - `cpflow apply-template #{workloads.join(' ')} -a #{app}`"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def print_missing_apps_starting_with
|
171
|
+
return if @missing_apps_starting_with.empty?
|
172
|
+
|
173
|
+
puts "\nThere are no apps starting with some names. If you wish to create any, do so with " \
|
174
|
+
"(replace 'whatever' with whatever suffix you want):"
|
175
|
+
|
176
|
+
@missing_apps_starting_with.each do |app, _workloads|
|
177
|
+
puts " - `cpflow setup-app -a #{app}-whatever`"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def single_app_info
|
182
|
+
puts "#{Shell.color(config.org, :blue)}:"
|
183
|
+
|
184
|
+
print_app(config.app, config.org)
|
185
|
+
|
186
|
+
@defined_workloads = find_workloads(config.app)
|
187
|
+
@available_workloads = fetch_workloads(config.app)
|
188
|
+
|
189
|
+
workloads = (@defined_workloads + @available_workloads).uniq
|
190
|
+
workloads.each do |workload|
|
191
|
+
print_workload(config.app, workload)
|
192
|
+
end
|
193
|
+
|
194
|
+
print_missing_apps_workloads
|
195
|
+
end
|
196
|
+
|
197
|
+
def multiple_apps_info # rubocop:disable Metrics/MethodLength
|
198
|
+
orgs.each do |org|
|
199
|
+
puts "#{Shell.color(org, :blue)}:"
|
200
|
+
|
201
|
+
@app_workloads = fetch_app_workloads(org)
|
202
|
+
|
203
|
+
apps(org).each do |app|
|
204
|
+
next unless print_app(app, org)
|
205
|
+
|
206
|
+
@defined_workloads = find_workloads(app)
|
207
|
+
@available_workloads = @app_workloads[app] || []
|
208
|
+
|
209
|
+
workloads = (@defined_workloads + @available_workloads).uniq
|
210
|
+
workloads.each do |workload|
|
211
|
+
print_workload(app, workload)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
puts
|
216
|
+
end
|
217
|
+
|
218
|
+
print_missing_apps_workloads
|
219
|
+
print_missing_apps_starting_with
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class LatestImage < Base
|
5
|
+
NAME = "latest-image"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true)
|
8
|
+
].freeze
|
9
|
+
DESCRIPTION = "Displays the latest image name"
|
10
|
+
LONG_DESCRIPTION = <<~DESC
|
11
|
+
- Displays the latest image name
|
12
|
+
DESC
|
13
|
+
WITH_INFO_HEADER = false
|
14
|
+
|
15
|
+
def call
|
16
|
+
puts cp.latest_image
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/command/logs.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Logs < Base
|
5
|
+
NAME = "logs"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
workload_option,
|
9
|
+
replica_option,
|
10
|
+
logs_limit_option,
|
11
|
+
logs_since_option
|
12
|
+
].freeze
|
13
|
+
DESCRIPTION = "Light wrapper to display tailed raw logs for app/workload syntax"
|
14
|
+
LONG_DESCRIPTION = <<~DESC
|
15
|
+
- Light wrapper to display tailed raw logs for app/workload syntax
|
16
|
+
- Defaults to showing the last 200 entries from the past 1 hour before tailing
|
17
|
+
DESC
|
18
|
+
EXAMPLES = <<~EX
|
19
|
+
```sh
|
20
|
+
# Displays logs for the default workload (`one_off_workload`).
|
21
|
+
cpflow logs -a $APP_NAME
|
22
|
+
|
23
|
+
# Displays logs for a specific workload.
|
24
|
+
cpflow logs -a $APP_NAME -w $WORKLOAD_NAME
|
25
|
+
|
26
|
+
# Displays logs for a specific replica of a workload.
|
27
|
+
cpflow logs -a $APP_NAME -w $WORKLOAD_NAME -r $REPLICA_NAME
|
28
|
+
|
29
|
+
# Uses a different limit on number of entries.
|
30
|
+
cpflow logs -a $APP_NAME --limit 100
|
31
|
+
|
32
|
+
# Uses a different loopback window.
|
33
|
+
cpflow logs -a $APP_NAME --since 30min
|
34
|
+
```
|
35
|
+
EX
|
36
|
+
|
37
|
+
def call
|
38
|
+
workload = config.options[:workload] || config[:one_off_workload]
|
39
|
+
replica = config.options[:replica]
|
40
|
+
limit = config.options[:limit]
|
41
|
+
since = config.options[:since]
|
42
|
+
|
43
|
+
message = replica ? "replica '#{replica}'" : "workload '#{workload}'"
|
44
|
+
progress.puts("Fetching logs for #{message}...\n\n")
|
45
|
+
|
46
|
+
cp.logs(workload: workload, replica: replica, limit: limit, since: since)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Maintenance < Base
|
5
|
+
NAME = "maintenance"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
domain_option
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Checks if maintenance mode is on or off for an app"
|
11
|
+
LONG_DESCRIPTION = <<~DESC
|
12
|
+
- Checks if maintenance mode is on or off for an app
|
13
|
+
- Outputs 'on' or 'off'
|
14
|
+
- Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
|
15
|
+
- Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
|
16
|
+
- Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
|
17
|
+
DESC
|
18
|
+
WITH_INFO_HEADER = false
|
19
|
+
|
20
|
+
def call # rubocop:disable Metrics/MethodLength
|
21
|
+
one_off_workload = config[:one_off_workload]
|
22
|
+
maintenance_workload = config.current[:maintenance_workload] || "maintenance"
|
23
|
+
|
24
|
+
domain_data = if config.domain
|
25
|
+
cp.fetch_domain(config.domain)
|
26
|
+
else
|
27
|
+
cp.find_domain_for([one_off_workload, maintenance_workload])
|
28
|
+
end
|
29
|
+
unless domain_data
|
30
|
+
raise "Can't find domain. " \
|
31
|
+
"Maintenance mode is only supported for domains that use path based routing mode " \
|
32
|
+
"and have a route configured for the prefix '/' on either port 80 or 443."
|
33
|
+
end
|
34
|
+
|
35
|
+
if cp.domain_workload_matches?(domain_data, maintenance_workload)
|
36
|
+
puts "on"
|
37
|
+
else
|
38
|
+
puts "off"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class MaintenanceOff < Base
|
5
|
+
NAME = "maintenance:off"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
domain_option
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Disables maintenance mode for an app"
|
11
|
+
LONG_DESCRIPTION = <<~DESC
|
12
|
+
- Disables maintenance mode for an app
|
13
|
+
- Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
|
14
|
+
- Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
|
15
|
+
- Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
|
16
|
+
DESC
|
17
|
+
|
18
|
+
def call # rubocop:disable Metrics/MethodLength
|
19
|
+
one_off_workload = config[:one_off_workload]
|
20
|
+
maintenance_workload = config.current[:maintenance_workload] || "maintenance"
|
21
|
+
|
22
|
+
domain_data = if config.domain
|
23
|
+
cp.fetch_domain(config.domain)
|
24
|
+
else
|
25
|
+
cp.find_domain_for([one_off_workload, maintenance_workload])
|
26
|
+
end
|
27
|
+
unless domain_data
|
28
|
+
raise "Can't find domain. " \
|
29
|
+
"Maintenance mode is only supported for domains that use path based routing mode " \
|
30
|
+
"and have a route configured for the prefix '/' on either port 80 or 443."
|
31
|
+
end
|
32
|
+
|
33
|
+
domain = domain_data["name"]
|
34
|
+
if cp.domain_workload_matches?(domain_data, one_off_workload)
|
35
|
+
progress.puts("Maintenance mode is already disabled for app '#{config.app}'.")
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
cp.fetch_workload!(maintenance_workload)
|
40
|
+
|
41
|
+
# Start all other workloads
|
42
|
+
Cpflow::Cli.start(["ps:start", "-a", config.app, "--wait"])
|
43
|
+
|
44
|
+
progress.puts
|
45
|
+
|
46
|
+
# Switch domain workload
|
47
|
+
step("Switching workload for domain '#{domain}' to '#{one_off_workload}'") do
|
48
|
+
cp.set_domain_workload(domain_data, one_off_workload)
|
49
|
+
|
50
|
+
# Give it a bit of time for the domain to update
|
51
|
+
Kernel.sleep(30)
|
52
|
+
end
|
53
|
+
|
54
|
+
progress.puts
|
55
|
+
|
56
|
+
# Stop maintenance workload
|
57
|
+
Cpflow::Cli.start(["ps:stop", "-a", config.app, "-w", maintenance_workload, "--wait"])
|
58
|
+
|
59
|
+
progress.puts("\nMaintenance mode disabled for app '#{config.app}'.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class MaintenanceOn < Base
|
5
|
+
NAME = "maintenance:on"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
domain_option
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Enables maintenance mode for an app"
|
11
|
+
LONG_DESCRIPTION = <<~DESC
|
12
|
+
- Enables maintenance mode for an app
|
13
|
+
- Specify the one-off workload through `one_off_workload` in the `.controlplane/controlplane.yml` file
|
14
|
+
- Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
|
15
|
+
- Maintenance mode is only supported for domains that use path based routing mode and have a route configured for the prefix '/' on either port 80 or 443
|
16
|
+
DESC
|
17
|
+
|
18
|
+
def call # rubocop:disable Metrics/MethodLength
|
19
|
+
one_off_workload = config[:one_off_workload]
|
20
|
+
maintenance_workload = config.current[:maintenance_workload] || "maintenance"
|
21
|
+
|
22
|
+
domain_data = if config.domain
|
23
|
+
cp.fetch_domain(config.domain)
|
24
|
+
else
|
25
|
+
cp.find_domain_for([one_off_workload, maintenance_workload])
|
26
|
+
end
|
27
|
+
unless domain_data
|
28
|
+
raise "Can't find domain. " \
|
29
|
+
"Maintenance mode is only supported for domains that use path based routing mode " \
|
30
|
+
"and have a route configured for the prefix '/' on either port 80 or 443."
|
31
|
+
end
|
32
|
+
|
33
|
+
domain = domain_data["name"]
|
34
|
+
if cp.domain_workload_matches?(domain_data, maintenance_workload)
|
35
|
+
progress.puts("Maintenance mode is already enabled for app '#{config.app}'.")
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
cp.fetch_workload!(maintenance_workload)
|
40
|
+
|
41
|
+
# Start maintenance workload
|
42
|
+
Cpflow::Cli.start(["ps:start", "-a", config.app, "-w", maintenance_workload, "--wait"])
|
43
|
+
|
44
|
+
progress.puts
|
45
|
+
|
46
|
+
# Switch domain workload
|
47
|
+
step("Switching workload for domain '#{domain}' to '#{maintenance_workload}'") do
|
48
|
+
cp.set_domain_workload(domain_data, maintenance_workload)
|
49
|
+
|
50
|
+
# Give it a bit of time for the domain to update
|
51
|
+
Kernel.sleep(30)
|
52
|
+
end
|
53
|
+
|
54
|
+
progress.puts
|
55
|
+
|
56
|
+
# Stop all other workloads
|
57
|
+
Cpflow::Cli.start(["ps:stop", "-a", config.app, "--wait"])
|
58
|
+
|
59
|
+
progress.puts("\nMaintenance mode enabled for app '#{config.app}'.")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class MaintenanceSetPage < Base
|
5
|
+
NAME = "maintenance:set-page"
|
6
|
+
USAGE = "maintenance:set-page PAGE_URL"
|
7
|
+
REQUIRES_ARGS = true
|
8
|
+
OPTIONS = [
|
9
|
+
app_option(required: true)
|
10
|
+
].freeze
|
11
|
+
DESCRIPTION = "Sets the page for maintenance mode"
|
12
|
+
LONG_DESCRIPTION = <<~DESC
|
13
|
+
- Sets the page for maintenance mode
|
14
|
+
- Only works if the maintenance workload uses the `shakacode/maintenance-mode` image
|
15
|
+
- Will set the URL as an env var `PAGE_URL` on the maintenance workload
|
16
|
+
- Optionally specify the maintenance workload through `maintenance_workload` in the `.controlplane/controlplane.yml` file (defaults to 'maintenance')
|
17
|
+
DESC
|
18
|
+
|
19
|
+
def call
|
20
|
+
maintenance_workload = config.current[:maintenance_workload] || "maintenance"
|
21
|
+
|
22
|
+
maintenance_workload_data = cp.fetch_workload!(maintenance_workload)
|
23
|
+
maintenance_workload_data.dig("spec", "containers").each do |container|
|
24
|
+
next unless container["image"].match?(%r{^shakacode/maintenance-mode})
|
25
|
+
|
26
|
+
container_name = container["name"]
|
27
|
+
page_url = config.args.first
|
28
|
+
step("Setting '#{page_url}' as the page for maintenance mode") do
|
29
|
+
cp.set_workload_env_var(maintenance_workload, container: container_name, name: "PAGE_URL", value: page_url)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class NoCommand < Base
|
5
|
+
NAME = "no-command"
|
6
|
+
OPTIONS = [version_option].freeze
|
7
|
+
DESCRIPTION = "Called when no command was specified"
|
8
|
+
LONG_DESCRIPTION = <<~DESC
|
9
|
+
- Called when no command was specified
|
10
|
+
DESC
|
11
|
+
HIDE = true
|
12
|
+
WITH_INFO_HEADER = false
|
13
|
+
VALIDATIONS = [].freeze
|
14
|
+
|
15
|
+
def call
|
16
|
+
if config.options[:version]
|
17
|
+
Cpflow::Cli.start(["version"])
|
18
|
+
else
|
19
|
+
Cpflow::Cli.start(["help"])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/command/open.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Open < Base
|
5
|
+
NAME = "open"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
workload_option
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Opens the app endpoint URL in the default browser"
|
11
|
+
LONG_DESCRIPTION = <<~DESC
|
12
|
+
- Opens the app endpoint URL in the default browser
|
13
|
+
DESC
|
14
|
+
EXAMPLES = <<~EX
|
15
|
+
```sh
|
16
|
+
# Opens the endpoint of the default workload (`one_off_workload`).
|
17
|
+
cpflow open -a $APP_NAME
|
18
|
+
|
19
|
+
# Opens the endpoint of a specific workload.
|
20
|
+
cpflow open -a $APP_NAME -w $WORKLOAD_NAME
|
21
|
+
```
|
22
|
+
EX
|
23
|
+
|
24
|
+
def call
|
25
|
+
workload = config.options[:workload] || config[:one_off_workload]
|
26
|
+
data = cp.fetch_workload!(workload)
|
27
|
+
url = data["status"]["endpoint"]
|
28
|
+
opener = Shell.cmd("which", "xdg-open", "open")[:output].split("\n").grep_v("not found").first
|
29
|
+
|
30
|
+
Kernel.exec(opener, url)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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 = Shell.cmd("which", "xdg-open", "open")[:output].split("\n").grep_v("not found").first
|
22
|
+
|
23
|
+
Kernel.exec(opener, url)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class PromoteAppFromUpstream < Base
|
5
|
+
NAME = "promote-app-from-upstream"
|
6
|
+
OPTIONS = [
|
7
|
+
app_option(required: true),
|
8
|
+
upstream_token_option(required: true)
|
9
|
+
].freeze
|
10
|
+
DESCRIPTION = "Copies the latest image from upstream, runs a release script (optional), and deploys the image"
|
11
|
+
LONG_DESCRIPTION = <<~DESC
|
12
|
+
- Copies the latest image from upstream, runs a release script (optional), and deploys the image
|
13
|
+
- It performs the following steps:
|
14
|
+
- Runs `cpflow copy-image-from-upstream` to copy the latest image from upstream
|
15
|
+
- Runs `cpflow deploy-image` to deploy the image
|
16
|
+
- If `.controlplane/controlplane.yml` includes the `release_script`, `cpflow deploy-image` will use the `--run-release-phase` option
|
17
|
+
- If the release script exits with a non-zero code, the command will stop executing and also exit with a non-zero code
|
18
|
+
DESC
|
19
|
+
|
20
|
+
def call
|
21
|
+
copy_image_from_upstream
|
22
|
+
deploy_image
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def copy_image_from_upstream
|
28
|
+
Cpflow::Cli.start(["copy-image-from-upstream", "-a", config.app, "-t", config.options[:upstream_token]])
|
29
|
+
progress.puts
|
30
|
+
end
|
31
|
+
|
32
|
+
def deploy_image
|
33
|
+
args = []
|
34
|
+
args.push("--run-release-phase") if config.current[:release_script]
|
35
|
+
Cpflow::Cli.start(["deploy-image", "-a", config.app, *args])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|