cpl 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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