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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/check_cpln_links.yml +19 -0
  3. data/.github/workflows/command_docs.yml +24 -0
  4. data/.github/workflows/rspec-shared.yml +56 -0
  5. data/.github/workflows/rspec.yml +28 -0
  6. data/.github/workflows/rubocop.yml +24 -0
  7. data/.gitignore +18 -0
  8. data/.overcommit.yml +16 -0
  9. data/.rubocop.yml +22 -0
  10. data/.simplecov_spawn.rb +10 -0
  11. data/CHANGELOG.md +259 -0
  12. data/CONTRIBUTING.md +73 -0
  13. data/Gemfile +7 -0
  14. data/Gemfile.lock +126 -0
  15. data/LICENSE +21 -0
  16. data/README.md +546 -0
  17. data/Rakefile +21 -0
  18. data/bin/cpflow +6 -0
  19. data/cpflow +6 -0
  20. data/cpflow.gemspec +41 -0
  21. data/docs/assets/grafana-alert.png +0 -0
  22. data/docs/assets/memcached.png +0 -0
  23. data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
  24. data/docs/commands.md +454 -0
  25. data/docs/dns.md +15 -0
  26. data/docs/migrating.md +262 -0
  27. data/docs/postgres.md +436 -0
  28. data/docs/redis.md +128 -0
  29. data/docs/secrets-and-env-values.md +42 -0
  30. data/docs/tips.md +150 -0
  31. data/docs/troubleshooting.md +6 -0
  32. data/examples/circleci.yml +104 -0
  33. data/examples/controlplane.yml +159 -0
  34. data/lib/command/apply_template.rb +209 -0
  35. data/lib/command/base.rb +540 -0
  36. data/lib/command/build_image.rb +49 -0
  37. data/lib/command/cleanup_images.rb +136 -0
  38. data/lib/command/cleanup_stale_apps.rb +79 -0
  39. data/lib/command/config.rb +48 -0
  40. data/lib/command/copy_image_from_upstream.rb +108 -0
  41. data/lib/command/delete.rb +149 -0
  42. data/lib/command/deploy_image.rb +56 -0
  43. data/lib/command/doctor.rb +47 -0
  44. data/lib/command/env.rb +22 -0
  45. data/lib/command/exists.rb +23 -0
  46. data/lib/command/generate.rb +45 -0
  47. data/lib/command/info.rb +222 -0
  48. data/lib/command/latest_image.rb +19 -0
  49. data/lib/command/logs.rb +49 -0
  50. data/lib/command/maintenance.rb +42 -0
  51. data/lib/command/maintenance_off.rb +62 -0
  52. data/lib/command/maintenance_on.rb +62 -0
  53. data/lib/command/maintenance_set_page.rb +34 -0
  54. data/lib/command/no_command.rb +23 -0
  55. data/lib/command/open.rb +33 -0
  56. data/lib/command/open_console.rb +26 -0
  57. data/lib/command/promote_app_from_upstream.rb +38 -0
  58. data/lib/command/ps.rb +41 -0
  59. data/lib/command/ps_restart.rb +37 -0
  60. data/lib/command/ps_start.rb +51 -0
  61. data/lib/command/ps_stop.rb +82 -0
  62. data/lib/command/ps_wait.rb +40 -0
  63. data/lib/command/run.rb +573 -0
  64. data/lib/command/setup_app.rb +113 -0
  65. data/lib/command/test.rb +23 -0
  66. data/lib/command/version.rb +18 -0
  67. data/lib/constants/exit_code.rb +7 -0
  68. data/lib/core/config.rb +316 -0
  69. data/lib/core/controlplane.rb +552 -0
  70. data/lib/core/controlplane_api.rb +170 -0
  71. data/lib/core/controlplane_api_direct.rb +112 -0
  72. data/lib/core/doctor_service.rb +104 -0
  73. data/lib/core/helpers.rb +26 -0
  74. data/lib/core/shell.rb +100 -0
  75. data/lib/core/template_parser.rb +76 -0
  76. data/lib/cpflow/version.rb +6 -0
  77. data/lib/cpflow.rb +288 -0
  78. data/lib/deprecated_commands.json +9 -0
  79. data/lib/generator_templates/Dockerfile +27 -0
  80. data/lib/generator_templates/controlplane.yml +62 -0
  81. data/lib/generator_templates/entrypoint.sh +8 -0
  82. data/lib/generator_templates/templates/app.yml +21 -0
  83. data/lib/generator_templates/templates/postgres.yml +176 -0
  84. data/lib/generator_templates/templates/rails.yml +36 -0
  85. data/rakelib/create_release.rake +81 -0
  86. data/script/add_command +37 -0
  87. data/script/check_command_docs +3 -0
  88. data/script/check_cpln_links +45 -0
  89. data/script/rename_command +43 -0
  90. data/script/update_command_docs +62 -0
  91. data/templates/app.yml +13 -0
  92. data/templates/daily-task.yml +32 -0
  93. data/templates/maintenance.yml +25 -0
  94. data/templates/memcached.yml +24 -0
  95. data/templates/postgres.yml +32 -0
  96. data/templates/rails.yml +27 -0
  97. data/templates/redis.yml +21 -0
  98. data/templates/redis2.yml +37 -0
  99. data/templates/sidekiq.yml +38 -0
  100. 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
@@ -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
@@ -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
@@ -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