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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +19 -4
- data/README.md +67 -20
- data/Rakefile +2 -2
- data/cpl.gemspec +3 -0
- data/docs/commands.md +108 -21
- data/examples/circleci.yml +1 -1
- data/examples/controlplane.yml +3 -0
- data/lib/command/{setup.rb → apply_template.rb} +89 -15
- data/lib/command/base.rb +68 -18
- data/lib/command/build_image.rb +15 -3
- data/lib/command/copy_image_from_upstream.rb +1 -1
- data/lib/command/info.rb +9 -5
- data/lib/command/maintenance.rb +37 -0
- data/lib/command/maintenance_off.rb +58 -0
- data/lib/command/maintenance_on.rb +58 -0
- data/lib/command/maintenance_set_page.rb +34 -0
- data/lib/command/no_command.rb +1 -1
- data/lib/command/promote_app_from_upstream.rb +2 -2
- data/lib/command/ps_start.rb +22 -5
- data/lib/command/ps_stop.rb +22 -5
- data/lib/command/run.rb +53 -14
- data/lib/command/run_cleanup.rb +99 -0
- data/lib/command/run_detached.rb +22 -14
- data/lib/command/setup_app.rb +29 -0
- data/lib/core/config.rb +39 -32
- data/lib/core/controlplane.rb +78 -16
- data/lib/core/controlplane_api.rb +39 -0
- data/lib/core/controlplane_api_direct.rb +13 -3
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +6 -1
- data/lib/deprecated_commands.json +2 -1
- data/templates/daily-task.yml +30 -0
- data/templates/maintenance.yml +24 -0
- metadata +54 -4
@@ -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
|
data/lib/command/run_detached.rb
CHANGED
@@ -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
|
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
|
-
|
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"] = [
|
74
|
+
spec["containers"] = [container_spec]
|
67
75
|
|
68
76
|
# Set runner
|
69
|
-
|
70
|
-
|
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"]["
|
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
|
-
|
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
|
-
|
97
|
+
container_spec.delete("ports")
|
90
98
|
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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!(
|
53
|
-
raise "Can't find app '#{
|
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!(
|
65
|
-
raise "App '#{
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
110
|
+
def new_option_keys
|
104
111
|
{
|
105
|
-
|
106
|
-
|
107
|
-
|
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 =
|
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 |
|
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
|
data/lib/core/controlplane.rb
CHANGED
@@ -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 = "
|
40
|
-
cmd += " --
|
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
|
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
|
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
|
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
|
-
|
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
|
153
|
-
|
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 = {
|
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",
|
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
data/lib/cpl.rb
CHANGED
@@ -9,7 +9,12 @@ require "tempfile"
|
|
9
9
|
require "thor"
|
10
10
|
require "yaml"
|
11
11
|
|
12
|
-
|
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
|
@@ -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
|