cpl 0.5.1 → 0.7.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.
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class RunCleanup < Base
5
+ NAME = "run:cleanup"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ skip_confirm_option
9
+ ].freeze
10
+ DESCRIPTION = "Deletes stale run workloads for an app"
11
+ LONG_DESCRIPTION = <<~DESC
12
+ - Deletes stale run workloads for an app
13
+ - Workloads are considered stale based on how many days since created
14
+ - `stale_run_workload_created_days` in the `.controlplane/controlplane.yml` file specifies the number of days after created that the workload is considered stale
15
+ - Will ask for explicit user confirmation of deletion
16
+ DESC
17
+
18
+ def call # rubocop:disable Metrics/MethodLength
19
+ return progress.puts("No stale run workloads found.") if stale_run_workloads.empty?
20
+
21
+ progress.puts("Stale run workloads:")
22
+ stale_run_workloads.each do |workload|
23
+ progress.puts(" #{workload[:name]} " \
24
+ "(#{Shell.color("#{workload[:date]} - #{workload[:days]} days ago", :red)})")
25
+ end
26
+
27
+ return unless confirm_delete
28
+
29
+ progress.puts
30
+ stale_run_workloads.each do |workload|
31
+ delete_workload(workload)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def app_matches?(app, app_name, app_options)
38
+ app == app_name.to_s || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s))
39
+ end
40
+
41
+ def find_app_options(app)
42
+ @app_options ||= {}
43
+ @app_options[app] ||= config.apps.find do |app_name, app_options|
44
+ app_matches?(app, app_name, app_options)
45
+ end&.last
46
+ end
47
+
48
+ def find_workloads(app)
49
+ app_options = find_app_options(app)
50
+ return [] if app_options.nil?
51
+
52
+ (app_options[:app_workloads] + app_options[:additional_workloads] + [app_options[:one_off_workload]]).uniq
53
+ end
54
+
55
+ def stale_run_workloads # rubocop:disable Metrics/MethodLength
56
+ @stale_run_workloads ||=
57
+ begin
58
+ defined_workloads = find_workloads(config.app)
59
+
60
+ run_workloads = []
61
+
62
+ now = DateTime.now
63
+ stale_run_workload_created_days = config[:stale_run_workload_created_days]
64
+
65
+ workloads = cp.query_workloads("-run-", partial_match: true)["items"]
66
+ workloads.each do |workload|
67
+ workload_name = workload["name"]
68
+
69
+ original_workload_name, workload_number = workload_name.split("-run-")
70
+ next unless defined_workloads.include?(original_workload_name) && workload_number.match?(/^\d{4}$/)
71
+
72
+ created_date = DateTime.parse(workload["created"])
73
+ diff_in_days = (now - created_date).to_i
74
+ next unless diff_in_days >= stale_run_workload_created_days
75
+
76
+ run_workloads.push({
77
+ name: workload_name,
78
+ date: created_date,
79
+ days: diff_in_days
80
+ })
81
+ end
82
+
83
+ run_workloads
84
+ end
85
+ end
86
+
87
+ def confirm_delete
88
+ return true if config.options[:yes]
89
+
90
+ Shell.confirm("\nAre you sure you want to delete these #{stale_run_workloads.length} run workloads?")
91
+ end
92
+
93
+ def delete_workload(workload)
94
+ step("Deleting run workload '#{workload[:name]}'") do
95
+ cp.delete_workload(workload[:name])
96
+ end
97
+ end
98
+ end
99
+ end
@@ -7,7 +7,8 @@ module Command
7
7
  REQUIRES_ARGS = true
8
8
  OPTIONS = [
9
9
  app_option(required: true),
10
- image_option
10
+ image_option,
11
+ workload_option
11
12
  ].freeze
12
13
  DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
13
14
  LONG_DESCRIPTION = <<~DESC
@@ -21,9 +22,12 @@ module Command
21
22
  ```sh
22
23
  cpl run:detached rails db:prepare -a $APP_NAME
23
24
 
24
- # Need to quote COMMAND if setting ENV value or passing args to command to run
25
+ # Need to quote COMMAND if setting ENV value or passing args.
25
26
  cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
26
27
 
28
+ # COMMAND may also be passed at the end (in this case, no need to quote).
29
+ cpl run:detached -a $APP_NAME -- rails db:migrate
30
+
27
31
  # Uses some other image.
28
32
  cpl run:detached rails db:migrate -a $APP_NAME --image /some/full/image/path
29
33
 
@@ -33,16 +37,19 @@ module Command
33
37
  # Uses a different image (which may not be promoted yet).
34
38
  cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
35
39
  cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
40
+
41
+ # Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
42
+ cpl run:detached rails db:migrate:status -a $APP_NAME -w other-workload
36
43
  ```
37
44
  EX
38
45
 
39
46
  WORKLOAD_SLEEP_CHECK = 2
40
47
 
41
- attr_reader :location, :workload, :one_off
48
+ attr_reader :location, :workload, :one_off, :container
42
49
 
43
50
  def call
44
51
  @location = config[:default_location]
45
- @workload = config[:one_off_workload]
52
+ @workload = config.options["workload"] || config[:one_off_workload]
46
53
  @one_off = "#{workload}-runner-#{rand(1000..9999)}"
47
54
 
48
55
  clone_workload
@@ -60,37 +67,38 @@ module Command
60
67
 
61
68
  # Get base specs of workload
62
69
  spec = cp.fetch_workload!(workload).fetch("spec")
63
- container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
70
+ container_spec = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
71
+ @container = container_spec["name"]
64
72
 
65
73
  # remove other containers if any
66
- spec["containers"] = [container]
74
+ spec["containers"] = [container_spec]
67
75
 
68
76
  # Set runner
69
- container["command"] = "bash"
70
- container["args"] = ["-c", 'eval "$CONTROLPLANE_RUNNER"']
77
+ container_spec["command"] = "bash"
78
+ container_spec["args"] = ["-c", 'eval "$CONTROLPLANE_RUNNER"']
71
79
 
72
80
  # Ensure one-off workload will be running
73
81
  spec["defaultOptions"]["suspend"] = false
74
82
 
75
83
  # Ensure no scaling
76
84
  spec["defaultOptions"]["autoscaling"]["minScale"] = 1
77
- spec["defaultOptions"]["autoscaling"]["minScale"] = 1
85
+ spec["defaultOptions"]["autoscaling"]["maxScale"] = 1
78
86
  spec["defaultOptions"]["capacityAI"] = false
79
87
 
80
88
  # Override image if specified
81
89
  image = config.options[:image]
82
90
  image = "/org/#{config.org}/image/#{latest_image}" if image == "latest"
83
- container["image"] = image if image
91
+ container_spec["image"] = image if image
84
92
 
85
93
  # Set cron job props
86
94
  spec["type"] = "cron"
87
95
  spec["job"] = { "schedule" => "* * * * *", "restartPolicy" => "Never" }
88
96
  spec["defaultOptions"]["autoscaling"] = {}
89
- container.delete("ports")
97
+ container_spec.delete("ports")
90
98
 
91
- container["env"] ||= []
92
- container["env"] << { "name" => "CONTROLPLANE_TOKEN", "value" => ControlplaneApiDirect.new.api_token }
93
- container["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
99
+ container_spec["env"] ||= []
100
+ container_spec["env"] << { "name" => "CONTROLPLANE_TOKEN", "value" => ControlplaneApiDirect.new.api_token }
101
+ container_spec["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
94
102
 
95
103
  # Create workload clone
96
104
  cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class SetupApp < Base
5
+ NAME = "setup-app"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Creates an app and all its workloads"
10
+ LONG_DESCRIPTION = <<~DESC
11
+ - Creates an app and all its workloads
12
+ - Specify the templates for the app and workloads through `setup` in the `.controlplane/controlplane.yml` file
13
+ - This should should only be used for temporary apps like review apps, never for persistent apps like production (to update workloads for those, use 'cpl apply-template' instead)
14
+ DESC
15
+
16
+ def call
17
+ templates = config[:setup]
18
+
19
+ app = cp.fetch_gvc
20
+ if app
21
+ raise "App '#{config.app}' already exists. If you want to update this app, " \
22
+ "either run 'cpl delete -a #{config.app}' and then re-run this command, " \
23
+ "or run 'cpl apply-template #{templates.join(' ')} -a #{config.app}'."
24
+ end
25
+
26
+ Cpl::Cli.start(["apply-template", *templates, "-a", config.app])
27
+ end
28
+ end
29
+ end
data/lib/core/config.rb CHANGED
@@ -15,24 +15,15 @@ class Config
15
15
  @app = options[:app]
16
16
 
17
17
  load_app_config
18
-
19
- @apps = config[:apps]
20
-
21
- pick_current_config if app
22
- warn_deprecated_options if current
18
+ load_apps
23
19
  end
24
20
 
25
21
  def [](key)
26
22
  ensure_current_config!
27
23
 
28
- old_key = old_option_keys[key]
29
- if current.key?(key)
30
- current.fetch(key)
31
- elsif old_key && current.key?(old_key)
32
- current.fetch(old_key)
33
- else
34
- raise "Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'."
35
- end
24
+ raise "Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'." unless current.key?(key)
25
+
26
+ current.fetch(key)
36
27
  end
37
28
 
38
29
  def script_path
@@ -49,8 +40,8 @@ class Config
49
40
  raise "Can't find current config, please specify an app." unless current
50
41
  end
51
42
 
52
- def ensure_current_config_app!(app)
53
- raise "Can't find app '#{app}' in 'controlplane.yml'." unless current
43
+ def ensure_current_config_app!(app_name)
44
+ raise "Can't find app '#{app_name}' in 'controlplane.yml'." unless current
54
45
  end
55
46
 
56
47
  def ensure_config!
@@ -61,20 +52,36 @@ class Config
61
52
  raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
62
53
  end
63
54
 
64
- def ensure_config_app!(app, options)
65
- raise "App '#{app}' is empty in 'controlplane.yml'." unless options
55
+ def ensure_config_app!(app_name, app_options)
56
+ raise "App '#{app_name}' is empty in 'controlplane.yml'." unless app_options
57
+ end
58
+
59
+ def app_matches_current?(app_name, app_options)
60
+ app && (app_name.to_s == app || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s)))
66
61
  end
67
62
 
68
- def pick_current_config
69
- config[:apps].each do |c_app, c_data|
70
- ensure_config_app!(c_app, c_data)
71
- next unless c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
63
+ def pick_current_config(app_name, app_options)
64
+ @current = app_options
65
+ @org = self[:cpln_org]
66
+ ensure_current_config_app!(app_name)
67
+ end
68
+
69
+ def load_apps # rubocop:disable Metrics/MethodLength
70
+ @apps = config[:apps].to_h do |app_name, app_options|
71
+ ensure_config_app!(app_name, app_options)
72
+
73
+ app_options_with_new_keys = app_options.to_h do |key, value|
74
+ new_key = new_option_keys[key]
75
+ new_key ? [new_key, value] : [key, value]
76
+ end
77
+
78
+ if app_matches_current?(app_name, app_options_with_new_keys)
79
+ pick_current_config(app_name, app_options_with_new_keys)
80
+ warn_deprecated_options(app_options)
81
+ end
72
82
 
73
- @current = c_data
74
- @org = self[:cpln_org]
75
- break
83
+ [app_name, app_options_with_new_keys]
76
84
  end
77
- ensure_current_config_app!(app)
78
85
  end
79
86
 
80
87
  def load_app_config
@@ -100,19 +107,19 @@ class Config
100
107
  end
101
108
  end
102
109
 
103
- def old_option_keys
110
+ def new_option_keys
104
111
  {
105
- cpln_org: :org,
106
- default_location: :location,
107
- match_if_app_name_starts_with: :prefix
112
+ org: :cpln_org,
113
+ location: :default_location,
114
+ prefix: :match_if_app_name_starts_with
108
115
  }
109
116
  end
110
117
 
111
- def warn_deprecated_options
112
- deprecated_option_keys = old_option_keys.filter { |_new_key, old_key| current.key?(old_key) }
118
+ def warn_deprecated_options(app_options)
119
+ deprecated_option_keys = new_option_keys.filter { |old_key| app_options.key?(old_key) }
113
120
  return if deprecated_option_keys.empty?
114
121
 
115
- deprecated_option_keys.each do |new_key, old_key|
122
+ deprecated_option_keys.each do |old_key, new_key|
116
123
  Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
117
124
  "please use '#{new_key}' instead (in 'controlplane.yml').")
118
125
  end
@@ -35,10 +35,13 @@ class Controlplane # rubocop:disable Metrics/ClassLength
35
35
 
36
36
  # image
37
37
 
38
- def image_build(image, dockerfile:, push: true)
39
- cmd = "cpln image build --org #{org} --name #{image} --dir #{config.app_dir} --dockerfile #{dockerfile}"
40
- cmd += " --push" if push
38
+ def image_build(image, dockerfile:, build_args: [], push: true)
39
+ cmd = "docker build -t #{image} -f #{dockerfile}"
40
+ build_args.each { |build_arg| cmd += " --build-arg #{build_arg}" }
41
+ cmd += " #{config.app_dir}"
41
42
  perform!(cmd)
43
+
44
+ image_push(image) if push
42
45
  end
43
46
 
44
47
  def image_query(app_name = config.app, org_name = config.org)
@@ -97,7 +100,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength
97
100
  gvc_data = fetch_gvc(a_gvc)
98
101
  return gvc_data if gvc_data
99
102
 
100
- raise "Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'."
103
+ raise "Can't find app '#{gvc}', please create it with 'cpl setup-app -a #{config.app}'."
101
104
  end
102
105
 
103
106
  def gvc_delete(a_gvc = gvc)
@@ -122,7 +125,13 @@ class Controlplane # rubocop:disable Metrics/ClassLength
122
125
  workload_data = fetch_workload(workload)
123
126
  return workload_data if workload_data
124
127
 
125
- raise "Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'."
128
+ raise "Can't find workload '#{workload}', please create it with 'cpl apply-template #{workload} -a #{config.app}'."
129
+ end
130
+
131
+ def query_workloads(workload, partial_match: false)
132
+ op = partial_match ? "~" : "="
133
+
134
+ api.query_workloads(org: org, gvc: gvc, workload: workload, op_type: op)
126
135
  end
127
136
 
128
137
  def workload_get_replicas(workload, location:)
@@ -130,6 +139,16 @@ class Controlplane # rubocop:disable Metrics/ClassLength
130
139
  perform_yaml(cmd)
131
140
  end
132
141
 
142
+ def workload_get_replicas_safely(workload, location:)
143
+ cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml 2> /dev/null"
144
+ result = `#{cmd}`
145
+ $CHILD_STATUS.success? ? YAML.safe_load(result) : nil
146
+ end
147
+
148
+ def fetch_workload_deployments(workload)
149
+ api.workload_deployments(workload: workload, gvc: gvc, org: org)
150
+ end
151
+
133
152
  def workload_set_image_ref(workload, container:, image:)
134
153
  cmd = "cpln workload update #{workload} #{gvc_org}"
135
154
  cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
@@ -137,10 +156,26 @@ class Controlplane # rubocop:disable Metrics/ClassLength
137
156
  perform!(cmd)
138
157
  end
139
158
 
140
- def workload_set_suspend(workload, value)
159
+ def set_workload_env_var(workload, container:, name:, value:)
160
+ data = fetch_workload!(workload)
161
+ data["spec"]["containers"].each do |container_data|
162
+ next unless container_data["name"] == container
163
+
164
+ container_data["env"].each do |env_data|
165
+ next unless env_data["name"] == name
166
+
167
+ env_data["value"] = value
168
+ end
169
+ end
170
+
171
+ api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
172
+ end
173
+
174
+ def set_workload_suspend(workload, value)
141
175
  data = fetch_workload!(workload)
142
176
  data["spec"]["defaultOptions"]["suspend"] = value
143
- apply(data)
177
+
178
+ api.update_workload(org: org, gvc: gvc, workload: workload, data: data)
144
179
  end
145
180
 
146
181
  def workload_force_redeployment(workload)
@@ -149,15 +184,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength
149
184
  perform!(cmd)
150
185
  end
151
186
 
152
- def workload_delete(workload)
153
- cmd = "cpln workload delete #{workload} #{gvc_org}"
154
- cmd += " 2> /dev/null"
155
- perform(cmd)
156
- end
157
-
158
- def workload_delete!(workload)
159
- cmd = "cpln workload delete #{workload} #{gvc_org}"
160
- perform!(cmd)
187
+ def delete_workload(workload)
188
+ api.delete_workload(org: org, gvc: gvc, workload: workload)
161
189
  end
162
190
 
163
191
  def workload_connect(workload, location:, container: nil, shell: nil)
@@ -174,6 +202,40 @@ class Controlplane # rubocop:disable Metrics/ClassLength
174
202
  perform!(cmd)
175
203
  end
176
204
 
205
+ # domain
206
+
207
+ def find_domain_route(data)
208
+ port = data["spec"]["ports"].find { |current_port| current_port["number"] == 80 || current_port["number"] == 443 }
209
+ return nil if port.nil? || port["routes"].nil?
210
+
211
+ route = port["routes"].find { |current_route| current_route["prefix"] == "/" }
212
+ return nil if route.nil?
213
+
214
+ route
215
+ end
216
+
217
+ def find_domain_for(workloads)
218
+ domains = api.list_domains(org: org)["items"]
219
+ domains.find do |domain_data|
220
+ route = find_domain_route(domain_data)
221
+ next false if route.nil?
222
+
223
+ workloads.any? { |workload| route["workloadLink"].split("/").last == workload }
224
+ end
225
+ end
226
+
227
+ def get_domain_workload(data)
228
+ route = find_domain_route(data)
229
+ route["workloadLink"].split("/").last
230
+ end
231
+
232
+ def set_domain_workload(data, workload)
233
+ route = find_domain_route(data)
234
+ route["workloadLink"] = "/org/#{org}/gvc/#{gvc}/workload/#{workload}"
235
+
236
+ api.update_domain(org: org, domain: data["name"], data: data)
237
+ end
238
+
177
239
  # logs
178
240
 
179
241
  def logs(workload:)
@@ -33,6 +33,29 @@ class ControlplaneApi
33
33
  api_json_direct("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
34
34
  end
35
35
 
36
+ def query_workloads(org:, gvc:, workload:, op_type:) # rubocop:disable Metrics/MethodLength
37
+ body = {
38
+ kind: "string",
39
+ spec: {
40
+ match: "all",
41
+ terms: [
42
+ {
43
+ rel: "gvc",
44
+ op: "=",
45
+ value: gvc
46
+ },
47
+ {
48
+ property: "name",
49
+ op: op_type,
50
+ value: workload
51
+ }
52
+ ]
53
+ }
54
+ }
55
+
56
+ api_json("/org/#{org}/workload/-query", method: :post, body: body)
57
+ end
58
+
36
59
  def workload_list(org:, gvc:)
37
60
  api_json("/org/#{org}/gvc/#{gvc}/workload", method: :get)
38
61
  end
@@ -45,10 +68,26 @@ class ControlplaneApi
45
68
  api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :get)
46
69
  end
47
70
 
71
+ def update_workload(org:, gvc:, workload:, data:)
72
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :patch, body: data)
73
+ end
74
+
48
75
  def workload_deployments(org:, gvc:, workload:)
49
76
  api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}/deployment", method: :get)
50
77
  end
51
78
 
79
+ def delete_workload(org:, gvc:, workload:)
80
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :delete)
81
+ end
82
+
83
+ def list_domains(org:)
84
+ api_json("/org/#{org}/domain", method: :get)
85
+ end
86
+
87
+ def update_domain(org:, domain:, data:)
88
+ api_json("/org/#{org}/domain/#{domain}", method: :patch, body: data)
89
+ end
90
+
52
91
  private
53
92
 
54
93
  # switch between cpln rest and api
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ControlplaneApiDirect
4
- API_METHODS = { get: Net::HTTP::Get, post: Net::HTTP::Post, put: Net::HTTP::Put, delete: Net::HTTP::Delete }.freeze
4
+ API_METHODS = {
5
+ get: Net::HTTP::Get,
6
+ patch: Net::HTTP::Patch,
7
+ post: Net::HTTP::Post,
8
+ put: Net::HTTP::Put,
9
+ delete: Net::HTTP::Delete
10
+ }.freeze
5
11
  API_HOSTS = { api: "https://api.cpln.io", logs: "https://logs.cpln.io" }.freeze
6
12
 
7
13
  # API_TOKEN_REGEX = Regexp.union(
@@ -11,11 +17,12 @@ class ControlplaneApiDirect
11
17
 
12
18
  API_TOKEN_REGEX = /^[\w\-._]+$/.freeze
13
19
 
14
- def call(url, method:, host: :api) # rubocop:disable Metrics/MethodLength
20
+ def call(url, method:, host: :api, body: nil) # rubocop:disable Metrics/MethodLength
15
21
  uri = URI("#{API_HOSTS[host]}#{url}")
16
22
  request = API_METHODS[method].new(uri)
17
23
  request["Content-Type"] = "application/json"
18
24
  request["Authorization"] = api_token
25
+ request.body = body.to_json if body
19
26
 
20
27
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
21
28
 
@@ -31,13 +38,16 @@ class ControlplaneApiDirect
31
38
  end
32
39
  end
33
40
 
41
+ # rubocop:disable Style/ClassVars
34
42
  def api_token
35
43
  return @@api_token if defined?(@@api_token)
36
44
 
37
- @@api_token = ENV.fetch("CPLN_TOKEN", `cpln profile token`.chomp) # rubocop:disable Style/ClassVars
45
+ @@api_token = ENV.fetch("CPLN_TOKEN", nil)
46
+ @@api_token = `cpln profile token`.chomp if @@api_token.nil?
38
47
  return @@api_token if @@api_token.match?(API_TOKEN_REGEX)
39
48
 
40
49
  raise "Unknown API token format. " \
41
50
  "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
42
51
  end
52
+ # rubocop:enable Style/ClassVars
43
53
  end
data/lib/cpl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/cpl.rb CHANGED
@@ -9,7 +9,12 @@ require "tempfile"
9
9
  require "thor"
10
10
  require "yaml"
11
11
 
12
- modules = Dir["#{__dir__}/**/*.rb"].reject { |file| file == __FILE__ || file.end_with?("main.rb") }
12
+ # We need to require base before all commands, since the commands inherit from it
13
+ require_relative "command/base"
14
+
15
+ modules = Dir["#{__dir__}/**/*.rb"].reject do |file|
16
+ file == __FILE__ || file.end_with?("main.rb") || file.end_with?("base.rb")
17
+ end
13
18
  modules.sort.each { require(_1) }
14
19
 
15
20
  # Fix for https://github.com/erikhuda/thor/issues/398
@@ -2,5 +2,6 @@
2
2
  "build": "build-image",
3
3
  "promote": "deploy-image",
4
4
  "promote_image": "deploy-image",
5
- "runner": "run:detached"
5
+ "runner": "run:detached",
6
+ "setup": "apply-template"
6
7
  }
@@ -0,0 +1,30 @@
1
+ kind: workload
2
+ name: daily-task
3
+ spec:
4
+ # https://docs.controlplane.com/reference/workload#cron-configuration
5
+ type: cron
6
+ job:
7
+ # Run daily job at 2am, see cron docs
8
+ schedule: 0 2 * * *
9
+ # Never or OnFailure
10
+ restartPolicy: Never
11
+ containers:
12
+ - name: daily-task
13
+ args:
14
+ - bundle
15
+ - exec
16
+ - rails
17
+ - db:prepare
18
+ cpu: 50m
19
+ inheritEnv: true
20
+ image: '/org/APP_ORG/image/APP_IMAGE'
21
+ memory: 256Mi
22
+ defaultOptions:
23
+ autoscaling:
24
+ maxScale: 1
25
+ capacityAI: false
26
+ firewallConfig:
27
+ external:
28
+ outboundAllowCIDR:
29
+ - 0.0.0.0/0
30
+ identityLink: /org/APP_ORG/gvc/APP_GVC/identity/APP_GVC-identity
@@ -0,0 +1,24 @@
1
+ kind: workload
2
+ name: maintenance
3
+ spec:
4
+ type: standard
5
+ containers:
6
+ - name: maintenance
7
+ env:
8
+ - name: PORT
9
+ value: '3000'
10
+ - name: PAGE_URL
11
+ value: ''
12
+ image: 'shakacode/maintenance-mode'
13
+ ports:
14
+ - number: 3000
15
+ protocol: http
16
+ defaultOptions:
17
+ autoscaling:
18
+ maxScale: 1
19
+ capacityAI: false
20
+ timeoutSeconds: 60
21
+ firewallConfig:
22
+ external:
23
+ inboundAllowCIDR:
24
+ - 0.0.0.0/0