cpl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +60 -0
  3. data/.gitignore +14 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +16 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CONTRIBUTING.md +12 -0
  8. data/Gemfile +7 -0
  9. data/Gemfile.lock +104 -0
  10. data/LICENSE +21 -0
  11. data/README.md +318 -0
  12. data/Rakefile +11 -0
  13. data/bin/cpl +6 -0
  14. data/cpl +15 -0
  15. data/cpl.gemspec +42 -0
  16. data/docs/commands.md +219 -0
  17. data/docs/troubleshooting.md +6 -0
  18. data/examples/circleci.yml +106 -0
  19. data/examples/controlplane.yml +44 -0
  20. data/lib/command/base.rb +177 -0
  21. data/lib/command/build_image.rb +25 -0
  22. data/lib/command/config.rb +33 -0
  23. data/lib/command/delete.rb +50 -0
  24. data/lib/command/env.rb +21 -0
  25. data/lib/command/exists.rb +23 -0
  26. data/lib/command/latest_image.rb +18 -0
  27. data/lib/command/logs.rb +29 -0
  28. data/lib/command/open.rb +33 -0
  29. data/lib/command/promote_image.rb +27 -0
  30. data/lib/command/ps.rb +40 -0
  31. data/lib/command/ps_restart.rb +34 -0
  32. data/lib/command/ps_start.rb +34 -0
  33. data/lib/command/ps_stop.rb +34 -0
  34. data/lib/command/run.rb +106 -0
  35. data/lib/command/run_detached.rb +148 -0
  36. data/lib/command/setup.rb +59 -0
  37. data/lib/command/test.rb +26 -0
  38. data/lib/core/config.rb +81 -0
  39. data/lib/core/controlplane.rb +128 -0
  40. data/lib/core/controlplane_api.rb +51 -0
  41. data/lib/core/controlplane_api_cli.rb +10 -0
  42. data/lib/core/controlplane_api_direct.rb +42 -0
  43. data/lib/core/scripts.rb +34 -0
  44. data/lib/cpl/version.rb +5 -0
  45. data/lib/cpl.rb +139 -0
  46. data/lib/main.rb +5 -0
  47. data/postgres.md +436 -0
  48. data/redis.md +112 -0
  49. data/script/generate_commands_docs +60 -0
  50. data/templates/gvc.yml +13 -0
  51. data/templates/identity.yml +2 -0
  52. data/templates/memcached.yml +23 -0
  53. data/templates/postgres.yml +31 -0
  54. data/templates/rails.yml +25 -0
  55. data/templates/redis.yml +20 -0
  56. data/templates/sidekiq.yml +28 -0
  57. metadata +312 -0
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class LatestImage < Base
5
+ NAME = "latest-image"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Displays the latest image name"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Displays the latest image name
12
+ HEREDOC
13
+
14
+ def call
15
+ puts latest_image
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Logs < Base
5
+ NAME = "logs"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Light wrapper to display tailed raw logs for app/workload syntax"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Light wrapper to display tailed raw logs for app/workload syntax
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Displays logs for the default workload (`one_off_workload`).
17
+ cpl logs -a $APP_NAME
18
+
19
+ # Displays logs for a specific workload.
20
+ cpl logs -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call
25
+ workload = config.options[:workload] || config[:one_off_workload]
26
+ cp.logs(workload: workload)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Open < Base
5
+ NAME = "open"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Opens the app endpoint URL in the default browser"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Opens the app endpoint URL in the default browser
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Opens the endpoint of the default workload (`one_off_workload`).
17
+ cpl open -a $APP_NAME
18
+
19
+ # Opens the endpoint of a specific workload.
20
+ cpl open -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call
25
+ workload = config.options[:workload] || config[:one_off_workload]
26
+ data = cp.workload_get(workload)
27
+ url = data["status"]["endpoint"]
28
+ opener = `which xdg-open open`.split("\n").grep_v("not found").first
29
+
30
+ exec %(#{opener} "#{url}")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class PromoteImage < Base
5
+ NAME = "promote-image"
6
+ OPTIONS = [
7
+ app_option(required: true)
8
+ ].freeze
9
+ DESCRIPTION = "Promotes the latest image to app workloads"
10
+ LONG_DESCRIPTION = <<~HEREDOC
11
+ - Promotes the latest image to app workloads
12
+ HEREDOC
13
+
14
+ def call
15
+ image = latest_image
16
+
17
+ config[:app_workloads].each do |workload|
18
+ cp.workload_get(workload).dig("spec", "containers").each do |container|
19
+ next unless container["image"].match?(%r{^/org/#{config[:cpln_org]}/image/#{config.app}:})
20
+
21
+ cp.workload_set_image_ref(workload, container: container["name"], image: image)
22
+ progress.puts "updated #{container['name']}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/command/ps.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Ps < Base
5
+ NAME = "ps"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Shows running replicas in app"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Shows running replicas in app
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Shows running replicas in app, for all workloads.
17
+ cpl ps -a $APP_NAME
18
+
19
+ # Shows running replicas in app, for a specific workload.
20
+ cpl ps -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call # rubocop:disable Metrics/MethodLength
25
+ workloads = [config.options[:workload]] if config.options[:workload]
26
+ workloads ||= config[:app_workloads] + config[:additional_workloads]
27
+
28
+ workloads.each do |workload|
29
+ result = cp.workload_get_replicas(workload, location: config[:default_location])
30
+ if result.nil?
31
+ puts "#{workload}: no workload"
32
+ elsif result["items"].nil?
33
+ puts "#{workload}: no replicas"
34
+ else
35
+ result["items"].each { |replica| puts replica }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class PsRestart < Base
5
+ NAME = "ps:restart"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Forces redeploy of workloads in app"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Forces redeploy of workloads in app
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Forces redeploy of all workloads in app.
17
+ cpl ps:restart -a $APP_NAME
18
+
19
+ # Forces redeploy of a specific workload in app.
20
+ cpl ps:restart -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call
25
+ workloads = [config.options[:workload]] if config.options[:workload]
26
+ workloads ||= config[:app_workloads] + config[:additional_workloads]
27
+
28
+ workloads.each do |workload|
29
+ cp.workload_force_redeployment(workload)
30
+ progress.puts "#{workload} restarted"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class PsStart < Base
5
+ NAME = "ps:start"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Starts workloads in app"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Starts workloads in app
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Starts all workloads in app.
17
+ cpl ps:start -a $APP_NAME
18
+
19
+ # Starts a specific workload in app.
20
+ cpl ps:start -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call
25
+ workloads = [config.options[:workload]] if config.options[:workload]
26
+ workloads ||= config[:app_workloads] + config[:additional_workloads]
27
+
28
+ workloads.reverse_each do |workload|
29
+ cp.workload_set_suspend(workload, false)
30
+ progress.puts "#{workload} started"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class PsStop < Base
5
+ NAME = "ps:stop"
6
+ OPTIONS = [
7
+ app_option(required: true),
8
+ workload_option
9
+ ].freeze
10
+ DESCRIPTION = "Stops workloads in app"
11
+ LONG_DESCRIPTION = <<~HEREDOC
12
+ - Stops workloads in app
13
+ HEREDOC
14
+ EXAMPLES = <<~HEREDOC
15
+ ```sh
16
+ # Stops all workloads in app.
17
+ cpl ps:stop -a $APP_NAME
18
+
19
+ # Stops a specific workload in app.
20
+ cpl ps:stop -a $APP_NAME -w $WORKLOAD_NAME
21
+ ```
22
+ HEREDOC
23
+
24
+ def call
25
+ workloads = [config.options[:workload]] if config.options[:workload]
26
+ workloads ||= config[:app_workloads] + config[:additional_workloads]
27
+
28
+ workloads.each do |workload|
29
+ cp.workload_set_suspend(workload, true)
30
+ progress.puts "#{workload} stopped"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Run < Base
5
+ NAME = "run"
6
+ USAGE = "run COMMAND"
7
+ REQUIRES_ARGS = true
8
+ DEFAULT_ARGS = ["bash"].freeze
9
+ OPTIONS = [
10
+ app_option(required: true),
11
+ image_option
12
+ ].freeze
13
+ DESCRIPTION = "Runs one-off **_interactive_** replicas (analog of `heroku run`)"
14
+ LONG_DESCRIPTION = <<~HEREDOC
15
+ - Runs one-off **_interactive_** replicas (analog of `heroku run`)
16
+ - Uses `Standard` workload type and `cpln exec` as the execution method, with CLI streaming
17
+ - May not work correctly with tasks that last over 5 minutes (there's a Control Plane scaling bug at the moment)
18
+
19
+ > **IMPORTANT:** Useful for development where it's needed for interaction, and where network connection drops and
20
+ > task crashing are tolerable. For production tasks, it's better to use `cpl run:detached`.
21
+ HEREDOC
22
+ EXAMPLES = <<~HEREDOC
23
+ ```sh
24
+ # Opens shell (bash by default).
25
+ cpl run -a $APP_NAME
26
+
27
+ # Runs command, displays output, and exits shell.
28
+ cpl run ls / -a $APP_NAME
29
+ cpl run rails db:migrate:status -a $APP_NAME
30
+
31
+ # Runs command and keeps shell open.
32
+ cpl run rails c -a $APP_NAME
33
+
34
+ # Uses a different image (which may not be promoted yet).
35
+ cpl run rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
36
+ cpl run rails db:migrate -a $APP_NAME --image latest # Latest sequential image
37
+ ```
38
+ HEREDOC
39
+
40
+ attr_reader :location, :workload, :one_off
41
+
42
+ def call
43
+ @location = config[:default_location]
44
+ @workload = config[:one_off_workload]
45
+ @one_off = "#{workload}-run-#{rand(1000..9999)}"
46
+
47
+ clone_workload
48
+ wait_for_workload(one_off)
49
+ sleep 2 # sometimes replica query lags workload creation, despite ok by prev query
50
+ wait_for_replica(one_off, location)
51
+ run_in_replica
52
+ ensure
53
+ ensure_workload_deleted(one_off)
54
+ end
55
+
56
+ private
57
+
58
+ def clone_workload # rubocop:disable Metrics/MethodLength
59
+ progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
60
+
61
+ # Create a base copy of workload props
62
+ spec = cp.workload_get(workload).fetch("spec")
63
+ container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
64
+
65
+ # remove other containers if any
66
+ spec["containers"] = [container]
67
+
68
+ # Stub workload command with dummy server that just responds to port
69
+ # Needed to avoid execution of ENTRYPOINT and CMD of Dockerfile
70
+ container["command"] = "ruby"
71
+ container["args"] = ["-e", Scripts.http_dummy_server_ruby]
72
+
73
+ # Ensure one-off workload will be running
74
+ spec["defaultOptions"]["suspend"] = false
75
+
76
+ # Ensure no scaling
77
+ spec["defaultOptions"]["autoscaling"]["minScale"] = 1
78
+ spec["defaultOptions"]["autoscaling"]["minScale"] = 1
79
+ spec["defaultOptions"]["capacityAI"] = false
80
+
81
+ # Override image if specified
82
+ image = config.options[:image]
83
+ image = "/org/#{config[:cpln_org]}/image/#{latest_image}" if image == "latest"
84
+ container["image"] = image if image
85
+
86
+ # Set runner
87
+ container["env"] ||= []
88
+ container["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
89
+
90
+ # Create workload clone
91
+ cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
92
+ end
93
+
94
+ def runner_script
95
+ script = Scripts.helpers_cleanup
96
+ script += args_join(config.args)
97
+ script
98
+ end
99
+
100
+ def run_in_replica
101
+ progress.puts "- Connecting"
102
+ command = %(bash -c 'eval "$CONTROLPLANE_RUNNER"')
103
+ cp.workload_exec(one_off, location: location, container: workload, command: command)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class RunDetached < Base # rubocop:disable Metrics/ClassLength
5
+ NAME = "run:detached"
6
+ USAGE = "run:detached COMMAND"
7
+ REQUIRES_ARGS = true
8
+ OPTIONS = [
9
+ app_option(required: true),
10
+ image_option
11
+ ].freeze
12
+ DESCRIPTION = "Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)"
13
+ LONG_DESCRIPTION = <<~HEREDOC
14
+ - Runs one-off **_non-interactive_** replicas (close analog of `heroku run:detached`)
15
+ - Uses `Cron` workload type with log async fetching
16
+ - Implemented with only async execution methods, more suitable for production tasks
17
+ - Has alternative log fetch implementation with only JSON-polling and no WebSockets
18
+ - Less responsive but more stable, useful for CI tasks
19
+ HEREDOC
20
+ EXAMPLES = <<~HEREDOC
21
+ ```sh
22
+ cpl run:detached rails db:prepare -a $APP_NAME
23
+ cpl run:detached 'LOG_LEVEL=warn rails db:migrate' -a $APP_NAME
24
+
25
+ # Uses some other image.
26
+ cpl run:detached rails db:migrate -a $APP_NAME --image /some/full/image/path
27
+
28
+ # Uses latest app image (which may not be promoted yet).
29
+ cpl run:detached rails db:migrate -a $APP_NAME --image latest
30
+
31
+ # Uses a different image (which may not be promoted yet).
32
+ cpl run:detached rails db:migrate -a $APP_NAME --image appimage:123 # Exact image name
33
+ cpl run:detached rails db:migrate -a $APP_NAME --image latest # Latest sequential image
34
+ ```
35
+ HEREDOC
36
+
37
+ WORKLOAD_SLEEP_CHECK = 2
38
+
39
+ attr_reader :location, :workload, :one_off
40
+
41
+ def call
42
+ @location = config[:default_location]
43
+ @workload = config[:one_off_workload]
44
+ @one_off = "#{workload}-runner-#{rand(1000..9999)}"
45
+
46
+ clone_workload
47
+ wait_for_workload(one_off)
48
+ show_logs_waiting
49
+ ensure
50
+ ensure_workload_deleted(one_off)
51
+ exit(1) if @crashed
52
+ end
53
+
54
+ private
55
+
56
+ def clone_workload # rubocop:disable Metrics/MethodLength
57
+ progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
58
+
59
+ # Get base specs of workload
60
+ spec = cp.workload_get(workload).fetch("spec")
61
+ container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
62
+
63
+ # remove other containers if any
64
+ spec["containers"] = [container]
65
+
66
+ # Set runner
67
+ container["command"] = "bash"
68
+ container["args"] = ["-c", 'eval "$CONTROLPLANE_RUNNER"']
69
+
70
+ # Ensure one-off workload will be running
71
+ spec["defaultOptions"]["suspend"] = false
72
+
73
+ # Ensure no scaling
74
+ spec["defaultOptions"]["autoscaling"]["minScale"] = 1
75
+ spec["defaultOptions"]["autoscaling"]["minScale"] = 1
76
+ spec["defaultOptions"]["capacityAI"] = false
77
+
78
+ # Override image if specified
79
+ image = config.options[:image]
80
+ image = "/org/#{config[:cpln_org]}/image/#{latest_image}" if image == "latest"
81
+ container["image"] = image if image
82
+
83
+ # Set cron job props
84
+ spec["type"] = "cron"
85
+ spec["job"] = { "schedule" => "* * * * *", "restartPolicy" => "Never" }
86
+ spec["defaultOptions"]["autoscaling"] = {}
87
+ container.delete("ports")
88
+
89
+ container["env"] ||= []
90
+ container["env"] << { "name" => "CONTROLPLANE_TOKEN", "value" => ControlplaneApiDirect.new.api_token }
91
+ container["env"] << { "name" => "CONTROLPLANE_RUNNER", "value" => runner_script }
92
+
93
+ # Create workload clone
94
+ cp.apply("kind" => "workload", "name" => one_off, "spec" => spec)
95
+ end
96
+
97
+ def runner_script
98
+ script = "echo '-- STARTED RUNNER SCRIPT --'\n"
99
+ script += Scripts.helpers_cleanup
100
+
101
+ script += <<~SHELL
102
+ if ! eval "#{args_join(config.args)}"; then echo "----- CRASHED -----"; fi
103
+
104
+ echo "-- FINISHED RUNNER SCRIPT, DELETING WORKLOAD --"
105
+ sleep 10 # grace time for logs propagation
106
+ curl ${CPLN_ENDPOINT}${CPLN_WORKLOAD} -H "Authorization: ${CONTROLPLANE_TOKEN}" -X DELETE -s -o /dev/null
107
+ while true; do sleep 1; done # wait for SIGTERM
108
+ SHELL
109
+
110
+ script
111
+ end
112
+
113
+ def show_logs_waiting # rubocop:disable Metrics/MethodLength
114
+ progress.puts "- Scheduled, fetching logs (it is cron job, so it may take up to a minute to start)"
115
+ begin
116
+ while cp.workload_get(one_off)
117
+ sleep(WORKLOAD_SLEEP_CHECK)
118
+ print_uniq_logs
119
+ end
120
+ rescue RuntimeError => e
121
+ progress.puts "ERROR: #{e}"
122
+ retry
123
+ end
124
+ progress.puts "- Finished workload and logger"
125
+ end
126
+
127
+ def print_uniq_logs
128
+ @printed_log_entries ||= []
129
+ ts = Time.now.to_i
130
+ entries = normalized_log_entries(from: ts - 60, to: ts)
131
+
132
+ (entries - @printed_log_entries).sort.each do |(_ts, val)|
133
+ @crashed = true if val.match?(/^----- CRASHED -----$/)
134
+ puts val
135
+ end
136
+
137
+ @printed_log_entries = entries # as well truncate old entries if any
138
+ end
139
+
140
+ def normalized_log_entries(from:, to:)
141
+ log = cp.log_get(workload: one_off, from: from, to: to)
142
+
143
+ log["data"]["result"]
144
+ .each_with_object([]) { |obj, result| result.concat(obj["values"]) }
145
+ .select { |ts, _val| ts[..-10].to_i > from }
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Setup < Base
5
+ NAME = "setup"
6
+ USAGE = "setup TEMPLATE [TEMPLATE] ... [TEMPLATE]"
7
+ REQUIRES_ARGS = true
8
+ OPTIONS = [
9
+ app_option(required: true)
10
+ ].freeze
11
+ DESCRIPTION = "Applies application-specific configs from templates"
12
+ LONG_DESCRIPTION = <<~HEREDOC
13
+ - Applies application-specific configs from templates (e.g., for every review-app)
14
+ - Publishes (creates or updates) those at Control Plane infrastructure
15
+ - Picks templates from the `.controlplane/templates` directory
16
+ - Templates are ordinary Control Plane templates but with variable preprocessing
17
+
18
+ **Preprocessed template variables:**
19
+
20
+ ```
21
+ APP_GVC - basically GVC or app name
22
+ APP_LOCATION - default location
23
+ APP_ORG - organization
24
+ APP_IMAGE - will use latest app image
25
+ ```
26
+ HEREDOC
27
+ EXAMPLES = <<~HEREDOC
28
+ ```sh
29
+ # Applies single template.
30
+ cpl setup redis -a $APP_NAME
31
+
32
+ # Applies several templates (practically creating full app).
33
+ cpl setup gvc postgres redis rails -a $APP_NAME
34
+ ```
35
+ HEREDOC
36
+
37
+ def call
38
+ config.args.each do |template|
39
+ filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
40
+ abort("ERROR: Can't find template for '#{template}' at #{filename}") unless File.exist?(filename)
41
+
42
+ apply_template(filename)
43
+ progress.puts(template)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def apply_template(filename)
50
+ data = File.read(filename)
51
+ .gsub("APP_GVC", config.app)
52
+ .gsub("APP_LOCATION", config[:default_location])
53
+ .gsub("APP_ORG", config[:cpln_org])
54
+ .gsub("APP_IMAGE", latest_image)
55
+
56
+ cp.apply(YAML.safe_load(data))
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Be sure to have run: gem install debug
4
+ require "debug"
5
+
6
+ # rubocop:disable Lint/Debugger
7
+ module Command
8
+ class Test < Base
9
+ NAME = "test"
10
+ OPTIONS = all_options
11
+ DESCRIPTION = "For debugging purposes"
12
+ LONG_DESCRIPTION = <<~HEREDOC
13
+ - For debugging purposes
14
+ HEREDOC
15
+ HIDE = true
16
+
17
+ def call
18
+ # Change code here to test.
19
+ # You can use `debugger` to debug.
20
+ # debugger
21
+ # Or print values
22
+ pp latest_image_next
23
+ end
24
+ end
25
+ end
26
+ # rubocop:enable Lint/Debugger
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Config
4
+ attr_reader :config, :current,
5
+ :app, :app_dir,
6
+ # command line options
7
+ :args, :options
8
+
9
+ CONFIG_FILE_LOCATIION = ".controlplane/controlplane.yml"
10
+
11
+ def initialize(args, options)
12
+ @args = args
13
+ @options = options
14
+ @app = options[:app]
15
+
16
+ load_app_config
17
+ pick_current_config if app
18
+ end
19
+
20
+ def [](key) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
21
+ abort("ERROR: should specify app") unless app
22
+
23
+ logger = $stderr
24
+
25
+ if current.key?(key)
26
+ current.fetch(key)
27
+ elsif key == :cpln_org && current.key?(:org)
28
+ logger.puts("DEPRECATED: option 'org' is deprecated, use 'cpln_org' instead\n")
29
+ current.fetch(:org)
30
+ elsif key == :default_location && current.key?(:location)
31
+ logger.puts("DEPRECATED: option 'location' is deprecated, use 'default_location' instead\n")
32
+ current.fetch(:location)
33
+ elsif key == :match_if_app_name_starts_with && current.key?(:prefix)
34
+ logger.puts("DEPRECATED: option 'prefix' is deprecated, use 'match_if_app_name_starts_with' instead\n")
35
+ current.fetch(:prefix)
36
+ else
37
+ abort("ERROR: should specify #{key} in controlplane.yml")
38
+ end
39
+ end
40
+
41
+ def script_path
42
+ Pathname.new(__dir__).parent.parent
43
+ end
44
+
45
+ def app_cpln_dir
46
+ "#{app_dir}/.controlplane"
47
+ end
48
+
49
+ private
50
+
51
+ def pick_current_config
52
+ config[:apps].each do |c_app, c_data|
53
+ if c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
54
+ @current = c_data
55
+ break
56
+ end
57
+ end
58
+ end
59
+
60
+ def load_app_config
61
+ config_file = find_app_config_file
62
+ @config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
63
+ @app_dir = Pathname.new(config_file).parent.parent.to_s
64
+ end
65
+
66
+ def find_app_config_file
67
+ path = Pathname.new(".").expand_path
68
+
69
+ loop do
70
+ config_file = path + CONFIG_FILE_LOCATIION
71
+ break config_file if File.file?(config_file)
72
+
73
+ path = path.parent
74
+
75
+ if path.root?
76
+ puts "ERROR: Can't find project config file, should be 'project_folder/#{CONFIG_FILE_LOCATIION}'"
77
+ exit(-1)
78
+ end
79
+ end
80
+ end
81
+ end