cpl 1.1.2 → 1.3.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/.github/workflows/check_cpln_links.yml +19 -0
- data/.github/workflows/rspec.yml +1 -1
- data/.overcommit.yml +3 -0
- data/CHANGELOG.md +47 -2
- data/CONTRIBUTING.md +2 -6
- data/Gemfile.lock +8 -8
- data/README.md +57 -15
- data/docs/commands.md +29 -23
- data/docs/dns.md +9 -0
- data/docs/migrating.md +3 -3
- data/examples/controlplane.yml +67 -4
- data/lib/command/apply_template.rb +2 -1
- data/lib/command/base.rb +62 -0
- data/lib/command/build_image.rb +5 -1
- data/lib/command/config.rb +0 -5
- data/lib/command/copy_image_from_upstream.rb +5 -4
- data/lib/command/delete.rb +40 -11
- data/lib/command/env.rb +1 -0
- data/lib/command/generate.rb +45 -0
- data/lib/command/info.rb +15 -33
- data/lib/command/latest_image.rb +1 -0
- data/lib/command/maintenance.rb +9 -4
- data/lib/command/maintenance_off.rb +8 -4
- data/lib/command/maintenance_on.rb +8 -4
- data/lib/command/no_command.rb +1 -0
- data/lib/command/ps.rb +5 -1
- data/lib/command/run.rb +20 -23
- data/lib/command/run_detached.rb +38 -30
- data/lib/command/setup_app.rb +3 -3
- data/lib/command/version.rb +1 -0
- data/lib/core/config.rb +194 -66
- data/lib/core/controlplane.rb +28 -7
- data/lib/core/controlplane_api.rb +13 -1
- data/lib/core/controlplane_api_direct.rb +18 -2
- data/lib/core/helpers.rb +16 -0
- data/lib/core/shell.rb +25 -3
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +32 -3
- data/lib/generator_templates/Dockerfile +27 -0
- data/lib/generator_templates/controlplane.yml +57 -0
- data/lib/generator_templates/entrypoint.sh +8 -0
- data/lib/generator_templates/templates/gvc.yml +21 -0
- data/lib/generator_templates/templates/postgres.yml +176 -0
- data/lib/generator_templates/templates/rails.yml +36 -0
- data/script/check_cpln_links +45 -0
- metadata +14 -3
data/lib/command/run.rb
CHANGED
@@ -10,6 +10,7 @@ module Command
|
|
10
10
|
app_option(required: true),
|
11
11
|
image_option,
|
12
12
|
workload_option,
|
13
|
+
location_option,
|
13
14
|
use_local_token_option,
|
14
15
|
terminal_size_option
|
15
16
|
].freeze
|
@@ -17,7 +18,6 @@ module Command
|
|
17
18
|
LONG_DESCRIPTION = <<~DESC
|
18
19
|
- Runs one-off **_interactive_** replicas (analog of `heroku run`)
|
19
20
|
- Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
|
20
|
-
- May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
|
21
21
|
- If `fix_terminal_size` is `true` in the `.controlplane/controlplane.yml` file, the remote terminal size will be fixed to match the local terminal size (may also be overriden through `--terminal-size`)
|
22
22
|
|
23
23
|
> **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
|
@@ -29,56 +29,53 @@ module Command
|
|
29
29
|
cpl run -a $APP_NAME
|
30
30
|
|
31
31
|
# Need to quote COMMAND if setting ENV value or passing args.
|
32
|
-
cpl run 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
|
33
|
-
|
34
|
-
# COMMAND may also be passed at the end.
|
35
32
|
cpl run -a $APP_NAME -- 'LOG_LEVEL=warn rails db:migrate'
|
36
33
|
|
37
34
|
# Runs command, displays output, and exits shell.
|
38
|
-
cpl run
|
39
|
-
cpl run rails db:migrate:status
|
35
|
+
cpl run -a $APP_NAME -- ls /
|
36
|
+
cpl run -a $APP_NAME -- rails db:migrate:status
|
40
37
|
|
41
38
|
# Runs command and keeps shell open.
|
42
|
-
cpl run
|
39
|
+
cpl run -a $APP_NAME -- rails c
|
43
40
|
|
44
41
|
# Uses a different image (which may not be promoted yet).
|
45
|
-
cpl run
|
46
|
-
cpl run
|
42
|
+
cpl run -a $APP_NAME --image appimage:123 -- rails db:migrate # Exact image name
|
43
|
+
cpl run -a $APP_NAME --image latest -- rails db:migrate # Latest sequential image
|
47
44
|
|
48
45
|
# Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
|
49
|
-
cpl run
|
46
|
+
cpl run -a $APP_NAME -w other-workload -- bash
|
50
47
|
|
51
48
|
# Overrides remote CPLN_TOKEN env variable with local token.
|
52
49
|
# Useful when superuser rights are needed in remote container.
|
53
|
-
cpl run
|
50
|
+
cpl run -a $APP_NAME --use-local-token -- bash
|
54
51
|
```
|
55
52
|
EX
|
56
53
|
|
57
|
-
attr_reader :location, :
|
54
|
+
attr_reader :location, :workload_to_clone, :workload_clone, :container
|
58
55
|
|
59
56
|
def call # rubocop:disable Metrics/MethodLength
|
60
|
-
@location = config
|
61
|
-
@
|
62
|
-
@
|
57
|
+
@location = config.location
|
58
|
+
@workload_to_clone = config.options["workload"] || config[:one_off_workload]
|
59
|
+
@workload_clone = "#{workload_to_clone}-run-#{random_four_digits}"
|
63
60
|
|
64
|
-
step("Cloning workload '#{
|
61
|
+
step("Cloning workload '#{workload_to_clone}' on app '#{config.options[:app]}' to '#{workload_clone}'") do
|
65
62
|
clone_workload
|
66
63
|
end
|
67
64
|
|
68
|
-
wait_for_workload(
|
69
|
-
wait_for_replica(
|
65
|
+
wait_for_workload(workload_clone)
|
66
|
+
wait_for_replica(workload_clone, location)
|
70
67
|
run_in_replica
|
71
68
|
ensure
|
72
69
|
progress.puts
|
73
|
-
ensure_workload_deleted(
|
70
|
+
ensure_workload_deleted(workload_clone)
|
74
71
|
end
|
75
72
|
|
76
73
|
private
|
77
74
|
|
78
75
|
def clone_workload # rubocop:disable Metrics/MethodLength
|
79
76
|
# Create a base copy of workload props
|
80
|
-
spec = cp.fetch_workload!(
|
81
|
-
container_spec = spec["containers"].detect { _1["name"] ==
|
77
|
+
spec = cp.fetch_workload!(workload_to_clone).fetch("spec")
|
78
|
+
container_spec = spec["containers"].detect { _1["name"] == workload_to_clone } || spec["containers"].first
|
82
79
|
@container = container_spec["name"]
|
83
80
|
|
84
81
|
# remove other containers if any
|
@@ -111,7 +108,7 @@ module Command
|
|
111
108
|
end
|
112
109
|
|
113
110
|
# Create workload clone
|
114
|
-
cp.apply_hash("kind" => "workload", "name" =>
|
111
|
+
cp.apply_hash("kind" => "workload", "name" => workload_clone, "spec" => spec)
|
115
112
|
end
|
116
113
|
|
117
114
|
def runner_script # rubocop:disable Metrics/MethodLength
|
@@ -141,7 +138,7 @@ module Command
|
|
141
138
|
def run_in_replica
|
142
139
|
progress.puts("Connecting...\n\n")
|
143
140
|
command = %(bash -c 'eval "$CONTROLPLANE_RUNNER"')
|
144
|
-
cp.workload_exec(
|
141
|
+
cp.workload_exec(workload_clone, location: location, container: container, command: command)
|
145
142
|
end
|
146
143
|
end
|
147
144
|
end
|
data/lib/command/run_detached.rb
CHANGED
@@ -9,7 +9,9 @@ module Command
|
|
9
9
|
app_option(required: true),
|
10
10
|
image_option,
|
11
11
|
workload_option,
|
12
|
-
|
12
|
+
location_option,
|
13
|
+
use_local_token_option,
|
14
|
+
clean_on_failure_option
|
13
15
|
].freeze
|
14
16
|
DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
|
15
17
|
LONG_DESCRIPTION = <<~DESC
|
@@ -18,50 +20,46 @@ module Command
|
|
18
20
|
- Implemented with only async execution methods, more suitable for production tasks
|
19
21
|
- Has alternative log fetch implementation with only JSON-polling and no WebSockets
|
20
22
|
- Less responsive but more stable, useful for CI tasks
|
23
|
+
- Deletes the workload whenever finished with success
|
24
|
+
- Deletes the workload whenever finished with failure by default
|
25
|
+
- Use `--no-clean-on-failure` to disable cleanup to help with debugging failed runs
|
21
26
|
DESC
|
22
27
|
EXAMPLES = <<~EX
|
23
28
|
```sh
|
24
29
|
cpl run:detached rails db:prepare -a $APP_NAME
|
25
30
|
|
26
31
|
# Need to quote COMMAND if setting ENV value or passing args.
|
27
|
-
cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
|
28
|
-
|
29
|
-
# COMMAND may also be passed at the end.
|
30
32
|
cpl run:detached -a $APP_NAME -- 'LOG_LEVEL=warn rails db:migrate'
|
31
33
|
|
32
34
|
# Uses a different image (which may not be promoted yet).
|
33
|
-
cpl run:detached
|
34
|
-
cpl run:detached
|
35
|
+
cpl run:detached -a $APP_NAME --image appimage:123 -- rails db:migrate # Exact image name
|
36
|
+
cpl run:detached -a $APP_NAME --image latest -- rails db:migrate # Latest sequential image
|
35
37
|
|
36
38
|
# Uses a different workload than `one_off_workload` from `.controlplane/controlplane.yml`.
|
37
|
-
cpl run:detached
|
39
|
+
cpl run:detached -a $APP_NAME -w other-workload -- rails db:migrate:status
|
38
40
|
|
39
41
|
# Overrides remote CPLN_TOKEN env variable with local token.
|
40
42
|
# Useful when superuser rights are needed in remote container.
|
41
|
-
cpl run:detached
|
43
|
+
cpl run:detached -a $APP_NAME --use-local-token -- rails db:migrate:status
|
42
44
|
```
|
43
45
|
EX
|
44
46
|
|
45
47
|
WORKLOAD_SLEEP_CHECK = 2
|
46
48
|
|
47
|
-
attr_reader :location, :
|
49
|
+
attr_reader :location, :workload_to_clone, :workload_clone, :container
|
48
50
|
|
49
|
-
def call
|
50
|
-
@location = config
|
51
|
-
@
|
52
|
-
@
|
51
|
+
def call
|
52
|
+
@location = config.location
|
53
|
+
@workload_to_clone = config.options["workload"] || config[:one_off_workload]
|
54
|
+
@workload_clone = "#{workload_to_clone}-runner-#{random_four_digits}"
|
53
55
|
|
54
|
-
step("Cloning workload '#{
|
56
|
+
step("Cloning workload '#{workload_to_clone}' on app '#{config.options[:app]}' to '#{workload_clone}'") do
|
55
57
|
clone_workload
|
56
58
|
end
|
57
59
|
|
58
|
-
wait_for_workload(
|
60
|
+
wait_for_workload(workload_clone)
|
59
61
|
show_logs_waiting
|
60
62
|
ensure
|
61
|
-
if cp.fetch_workload(one_off)
|
62
|
-
progress.puts
|
63
|
-
ensure_workload_deleted(one_off)
|
64
|
-
end
|
65
63
|
exit(1) if @crashed
|
66
64
|
end
|
67
65
|
|
@@ -69,8 +67,8 @@ module Command
|
|
69
67
|
|
70
68
|
def clone_workload # rubocop:disable Metrics/MethodLength
|
71
69
|
# Get base specs of workload
|
72
|
-
spec = cp.fetch_workload!(
|
73
|
-
container_spec = spec["containers"].detect { _1["name"] ==
|
70
|
+
spec = cp.fetch_workload!(workload_to_clone).fetch("spec")
|
71
|
+
container_spec = spec["containers"].detect { _1["name"] == workload_to_clone } || spec["containers"].first
|
74
72
|
@container = container_spec["name"]
|
75
73
|
|
76
74
|
# remove other containers if any
|
@@ -104,7 +102,7 @@ module Command
|
|
104
102
|
container_spec["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
|
105
103
|
|
106
104
|
# Create workload clone
|
107
|
-
cp.apply_hash("kind" => "workload", "name" =>
|
105
|
+
cp.apply_hash("kind" => "workload", "name" => workload_clone, "spec" => spec)
|
108
106
|
end
|
109
107
|
|
110
108
|
def runner_script # rubocop:disable Metrics/MethodLength
|
@@ -118,12 +116,20 @@ module Command
|
|
118
116
|
end
|
119
117
|
|
120
118
|
script += <<~SHELL
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
119
|
+
crashed=0
|
120
|
+
if ! eval "#{args_join(config.args)}"; then
|
121
|
+
crashed=1
|
122
|
+
echo "----- CRASHED -----"
|
123
|
+
fi
|
124
|
+
clean_on_failure=#{config.options[:clean_on_failure] ? 1 : 0}
|
125
|
+
if [ $crashed -eq 0 ] || [ $clean_on_failure -eq 1 ]; then
|
126
|
+
echo "-- FINISHED RUNNER SCRIPT, DELETING WORKLOAD --"
|
127
|
+
sleep 30 # grace time for logs propagation
|
128
|
+
curl ${CPLN_ENDPOINT}${CPLN_WORKLOAD} -H "Authorization: ${CONTROLPLANE_TOKEN}" -X DELETE -s -o /dev/null
|
129
|
+
while true; do sleep 1; done # wait for SIGTERM
|
130
|
+
else
|
131
|
+
echo "-- FINISHED RUNNER SCRIPT --"
|
132
|
+
fi
|
127
133
|
SHELL
|
128
134
|
|
129
135
|
script
|
@@ -132,7 +138,8 @@ module Command
|
|
132
138
|
def show_logs_waiting # rubocop:disable Metrics/MethodLength
|
133
139
|
progress.puts("Scheduled, fetching logs (it's a cron job, so it may take up to a minute to start)...\n\n")
|
134
140
|
begin
|
135
|
-
|
141
|
+
@finished = false
|
142
|
+
while cp.fetch_workload(workload_clone) && !@finished
|
136
143
|
sleep(WORKLOAD_SLEEP_CHECK)
|
137
144
|
print_uniq_logs
|
138
145
|
end
|
@@ -150,6 +157,7 @@ module Command
|
|
150
157
|
|
151
158
|
(entries - @printed_log_entries).sort.each do |(_ts, val)|
|
152
159
|
@crashed = true if val.match?(/^----- CRASHED -----$/)
|
160
|
+
@finished = true if val.match?(/^-- FINISHED RUNNER SCRIPT(, DELETING WORKLOAD)? --$/)
|
153
161
|
puts val
|
154
162
|
end
|
155
163
|
|
@@ -157,7 +165,7 @@ module Command
|
|
157
165
|
end
|
158
166
|
|
159
167
|
def normalized_log_entries(from:, to:)
|
160
|
-
log = cp.log_get(workload:
|
168
|
+
log = cp.log_get(workload: workload_clone, from: from, to: to)
|
161
169
|
|
162
170
|
log["data"]["result"]
|
163
171
|
.each_with_object([]) { |obj, result| result.concat(obj["values"]) }
|
data/lib/command/setup_app.rb
CHANGED
@@ -9,12 +9,12 @@ module Command
|
|
9
9
|
DESCRIPTION = "Creates an app and all its workloads"
|
10
10
|
LONG_DESCRIPTION = <<~DESC
|
11
11
|
- Creates an app and all its workloads
|
12
|
-
- Specify the templates for the app and workloads through `
|
13
|
-
- This should
|
12
|
+
- Specify the templates for the app and workloads through `setup_app_templates` in the `.controlplane/controlplane.yml` file
|
13
|
+
- This 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
14
|
DESC
|
15
15
|
|
16
16
|
def call
|
17
|
-
templates = config[:
|
17
|
+
templates = config[:setup_app_templates]
|
18
18
|
|
19
19
|
app = cp.fetch_gvc
|
20
20
|
if app
|
data/lib/command/version.rb
CHANGED
data/lib/core/config.rb
CHANGED
@@ -1,24 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "helpers"
|
4
|
+
|
3
5
|
class Config # rubocop:disable Metrics/ClassLength
|
4
|
-
attr_reader :
|
5
|
-
:org, :org_comes_from_env, :app, :apps, :app_dir,
|
6
|
+
attr_reader :org_comes_from_env, :app_comes_from_env,
|
6
7
|
# command line options
|
7
|
-
:args, :options
|
8
|
+
:args, :options, :required_options
|
9
|
+
|
10
|
+
include Helpers
|
8
11
|
|
9
|
-
|
12
|
+
CONFIG_FILE_LOCATION = ".controlplane/controlplane.yml"
|
10
13
|
|
11
|
-
def initialize(args, options)
|
14
|
+
def initialize(args, options, required_options)
|
12
15
|
@args = args
|
13
16
|
@options = options
|
14
|
-
@
|
15
|
-
@org_comes_from_env = false
|
16
|
-
@app = options[:app]
|
17
|
+
@required_options = required_options
|
17
18
|
|
18
|
-
|
19
|
-
load_apps
|
19
|
+
ensure_required_options!
|
20
20
|
|
21
21
|
Shell.verbose_mode(options[:verbose])
|
22
|
+
trace_mode = options[:trace]
|
23
|
+
return unless trace_mode
|
24
|
+
|
25
|
+
ControlplaneApiDirect.trace = trace_mode
|
26
|
+
Shell.warn("Trace mode is enabled, this will print sensitive information to the console.")
|
27
|
+
end
|
28
|
+
|
29
|
+
def org
|
30
|
+
@org ||= load_org_from_options || load_org_from_env || load_org_from_file
|
31
|
+
end
|
32
|
+
|
33
|
+
def app
|
34
|
+
@app ||= load_app_from_options || load_app_from_env
|
35
|
+
end
|
36
|
+
|
37
|
+
def location
|
38
|
+
@location ||= load_location_from_options || load_location_from_env || load_location_from_file
|
39
|
+
end
|
40
|
+
|
41
|
+
def domain
|
42
|
+
@domain ||= load_domain_from_options || load_domain_from_file
|
22
43
|
end
|
23
44
|
|
24
45
|
def [](key)
|
@@ -41,91 +62,122 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
41
62
|
apps[app_name.to_sym]&.dig(:match_if_app_name_starts_with) || false
|
42
63
|
end
|
43
64
|
|
44
|
-
|
65
|
+
def app_dir
|
66
|
+
Pathname.new(config_file_path).parent.parent.to_s
|
67
|
+
end
|
45
68
|
|
46
|
-
def
|
47
|
-
|
69
|
+
def config
|
70
|
+
@config ||= begin
|
71
|
+
global_config = YAML.safe_load_file(config_file_path, symbolize_names: true, aliases: true)
|
72
|
+
ensure_config!(global_config)
|
73
|
+
ensure_config_apps!(global_config)
|
74
|
+
|
75
|
+
global_config
|
76
|
+
end
|
48
77
|
end
|
49
78
|
|
50
|
-
def
|
51
|
-
|
79
|
+
def apps
|
80
|
+
@apps ||= config[:apps].to_h do |app_name, app_options|
|
81
|
+
ensure_config_app!(app_name, app_options)
|
82
|
+
|
83
|
+
app_options_with_new_keys = app_options.to_h do |key, value|
|
84
|
+
new_key = new_option_keys[key]
|
85
|
+
new_key ? [new_key, value] : [key, value]
|
86
|
+
end
|
87
|
+
|
88
|
+
[app_name, app_options_with_new_keys]
|
89
|
+
end
|
52
90
|
end
|
53
91
|
|
54
|
-
def
|
55
|
-
return
|
92
|
+
def current
|
93
|
+
return unless app
|
56
94
|
|
57
|
-
|
58
|
-
|
95
|
+
@current ||= begin
|
96
|
+
app_config = find_app_config(app)
|
97
|
+
ensure_config_app!(app, app_config)
|
98
|
+
|
99
|
+
warn_deprecated_options(app_config)
|
100
|
+
|
101
|
+
app_config
|
102
|
+
end
|
59
103
|
end
|
60
104
|
|
61
|
-
def
|
62
|
-
|
105
|
+
def app_matches?(app_name1, app_name2, app_options)
|
106
|
+
app_name1 && app_name2 &&
|
107
|
+
(app_name1.to_s == app_name2.to_s ||
|
108
|
+
(app_options[:match_if_app_name_starts_with] && app_name1.to_s.start_with?(app_name2.to_s))
|
109
|
+
)
|
63
110
|
end
|
64
111
|
|
65
|
-
def
|
66
|
-
|
112
|
+
def find_app_config(app_name1)
|
113
|
+
@app_configs ||= {}
|
114
|
+
@app_configs[app_name1] ||= apps.find do |app_name2, app_config|
|
115
|
+
app_matches?(app_name1, app_name2, app_config)
|
116
|
+
end&.last
|
67
117
|
end
|
68
118
|
|
69
|
-
|
70
|
-
|
119
|
+
private
|
120
|
+
|
121
|
+
def ensure_current_config!
|
122
|
+
raise "Can't find current config, please specify an app." unless current
|
71
123
|
end
|
72
124
|
|
73
|
-
def
|
74
|
-
|
125
|
+
def ensure_config!(global_config)
|
126
|
+
raise "'controlplane.yml' is empty." unless global_config
|
75
127
|
end
|
76
128
|
|
77
|
-
def
|
78
|
-
|
79
|
-
|
129
|
+
def ensure_config_apps!(global_config)
|
130
|
+
raise "Can't find key 'apps' in 'controlplane.yml'." unless global_config[:apps]
|
131
|
+
end
|
80
132
|
|
81
|
-
|
82
|
-
|
83
|
-
else
|
84
|
-
@org = ENV.fetch("CPLN_ORG", nil)
|
85
|
-
@org_comes_from_env = true
|
86
|
-
end
|
87
|
-
ensure_current_config_org!(app_name)
|
133
|
+
def ensure_config_app!(app_name, app_options)
|
134
|
+
raise "Can't find config for app '#{app_name}' in 'controlplane.yml'." unless app_options
|
88
135
|
end
|
89
136
|
|
90
|
-
def
|
91
|
-
|
92
|
-
ensure_config_app!(app_name, app_options)
|
137
|
+
def ensure_app!
|
138
|
+
return if app
|
93
139
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
if app_matches_current?(app_name, app_options_with_new_keys)
|
100
|
-
pick_current_config(app_name, app_options_with_new_keys)
|
101
|
-
warn_deprecated_options(app_options)
|
102
|
-
end
|
140
|
+
raise "No app provided. " \
|
141
|
+
"The app can be provided either through the CPLN_APP env var " \
|
142
|
+
"('allow_app_override_by_env' must be set to true in 'controlplane.yml'), " \
|
143
|
+
"or the --app command option."
|
144
|
+
end
|
103
145
|
|
104
|
-
|
105
|
-
|
146
|
+
def ensure_org!
|
147
|
+
return if org
|
106
148
|
|
107
|
-
|
149
|
+
raise "No org provided. " \
|
150
|
+
"The org can be provided either through the CPLN_ORG env var " \
|
151
|
+
"('allow_org_override_by_env' must be set to true in 'controlplane.yml'), " \
|
152
|
+
"the --org command option, " \
|
153
|
+
"or the 'cpln_org' key in 'controlplane.yml'."
|
108
154
|
end
|
109
155
|
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
156
|
+
def ensure_required_options! # rubocop:disable Metrics/CyclomaticComplexity
|
157
|
+
ensure_app! if required_options.include?(:app)
|
158
|
+
ensure_org! if required_options.include?(:org) || app
|
159
|
+
|
160
|
+
missing_str = required_options
|
161
|
+
.reject { |option_name| %i[org app].include?(option_name) || options.key?(option_name) }
|
162
|
+
.map { |option_name| "--#{option_name}" }
|
163
|
+
.join(", ")
|
164
|
+
|
165
|
+
raise "Required options missing: #{missing_str}" unless missing_str.empty?
|
116
166
|
end
|
117
167
|
|
118
|
-
def
|
119
|
-
|
168
|
+
def config_file_path # rubocop:disable Metrics/MethodLength
|
169
|
+
@config_file_path ||= begin
|
170
|
+
path = Pathname.new(".").expand_path
|
120
171
|
|
121
|
-
|
122
|
-
|
123
|
-
|
172
|
+
loop do
|
173
|
+
config_file = path + CONFIG_FILE_LOCATION
|
174
|
+
break config_file if File.file?(config_file)
|
124
175
|
|
125
|
-
|
176
|
+
path = path.parent
|
126
177
|
|
127
|
-
|
128
|
-
|
178
|
+
if path.root?
|
179
|
+
raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATION}', please create it."
|
180
|
+
end
|
129
181
|
end
|
130
182
|
end
|
131
183
|
end
|
@@ -135,10 +187,86 @@ class Config # rubocop:disable Metrics/ClassLength
|
|
135
187
|
org: :cpln_org,
|
136
188
|
location: :default_location,
|
137
189
|
prefix: :match_if_app_name_starts_with,
|
190
|
+
setup: :setup_app_templates,
|
138
191
|
old_image_retention_days: :image_retention_days
|
139
192
|
}
|
140
193
|
end
|
141
194
|
|
195
|
+
def load_app_from_env
|
196
|
+
app_from_env = strip_str_and_validate(ENV.fetch("CPLN_APP", nil))
|
197
|
+
return unless app_from_env
|
198
|
+
|
199
|
+
app_config = find_app_config(app_from_env)
|
200
|
+
ensure_config_app!(app_from_env, app_config)
|
201
|
+
|
202
|
+
key_exists = app_config.key?(:allow_app_override_by_env)
|
203
|
+
allowed_locally = key_exists && app_config[:allow_app_override_by_env]
|
204
|
+
allowed_globally = !key_exists && config[:allow_app_override_by_env]
|
205
|
+
return unless allowed_locally || allowed_globally
|
206
|
+
|
207
|
+
@app_comes_from_env = true
|
208
|
+
|
209
|
+
app_from_env
|
210
|
+
end
|
211
|
+
|
212
|
+
def load_app_from_options
|
213
|
+
app_from_options = strip_str_and_validate(options[:app])
|
214
|
+
return unless app_from_options
|
215
|
+
|
216
|
+
app_config = find_app_config(app_from_options)
|
217
|
+
ensure_config_app!(app_from_options, app_config)
|
218
|
+
|
219
|
+
app_from_options
|
220
|
+
end
|
221
|
+
|
222
|
+
def load_org_from_env
|
223
|
+
org_from_env = strip_str_and_validate(ENV.fetch("CPLN_ORG", nil))
|
224
|
+
return unless org_from_env
|
225
|
+
|
226
|
+
key_exists = current&.key?(:allow_org_override_by_env)
|
227
|
+
allowed_locally = key_exists && current[:allow_org_override_by_env]
|
228
|
+
allowed_globally = !key_exists && config[:allow_org_override_by_env]
|
229
|
+
return unless allowed_locally || allowed_globally
|
230
|
+
|
231
|
+
@org_comes_from_env = true
|
232
|
+
|
233
|
+
org_from_env
|
234
|
+
end
|
235
|
+
|
236
|
+
def load_org_from_options
|
237
|
+
strip_str_and_validate(options[:org])
|
238
|
+
end
|
239
|
+
|
240
|
+
def load_org_from_file
|
241
|
+
return unless current&.key?(:cpln_org)
|
242
|
+
|
243
|
+
strip_str_and_validate(current[:cpln_org])
|
244
|
+
end
|
245
|
+
|
246
|
+
def load_location_from_options
|
247
|
+
strip_str_and_validate(options[:location])
|
248
|
+
end
|
249
|
+
|
250
|
+
def load_location_from_env
|
251
|
+
strip_str_and_validate(ENV.fetch("CPLN_LOCATION", nil))
|
252
|
+
end
|
253
|
+
|
254
|
+
def load_location_from_file
|
255
|
+
return unless current&.key?(:default_location)
|
256
|
+
|
257
|
+
strip_str_and_validate(current.fetch(:default_location))
|
258
|
+
end
|
259
|
+
|
260
|
+
def load_domain_from_options
|
261
|
+
strip_str_and_validate(options[:domain])
|
262
|
+
end
|
263
|
+
|
264
|
+
def load_domain_from_file
|
265
|
+
return unless current&.key?(:default_domain)
|
266
|
+
|
267
|
+
strip_str_and_validate(current.fetch(:default_domain))
|
268
|
+
end
|
269
|
+
|
142
270
|
def warn_deprecated_options(app_options)
|
143
271
|
deprecated_option_keys = new_option_keys.select { |old_key| app_options.key?(old_key) }
|
144
272
|
return if deprecated_option_keys.empty?
|