cpflow 3.0.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.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/check_cpln_links.yml +19 -0
  3. data/.github/workflows/command_docs.yml +24 -0
  4. data/.github/workflows/rspec-shared.yml +56 -0
  5. data/.github/workflows/rspec.yml +28 -0
  6. data/.github/workflows/rubocop.yml +24 -0
  7. data/.gitignore +18 -0
  8. data/.overcommit.yml +16 -0
  9. data/.rubocop.yml +22 -0
  10. data/.simplecov_spawn.rb +10 -0
  11. data/CHANGELOG.md +259 -0
  12. data/CONTRIBUTING.md +73 -0
  13. data/Gemfile +7 -0
  14. data/Gemfile.lock +126 -0
  15. data/LICENSE +21 -0
  16. data/README.md +546 -0
  17. data/Rakefile +21 -0
  18. data/bin/cpflow +6 -0
  19. data/cpflow +6 -0
  20. data/cpflow.gemspec +41 -0
  21. data/docs/assets/grafana-alert.png +0 -0
  22. data/docs/assets/memcached.png +0 -0
  23. data/docs/assets/sidekiq-pre-stop-hook.png +0 -0
  24. data/docs/commands.md +454 -0
  25. data/docs/dns.md +15 -0
  26. data/docs/migrating.md +262 -0
  27. data/docs/postgres.md +436 -0
  28. data/docs/redis.md +128 -0
  29. data/docs/secrets-and-env-values.md +42 -0
  30. data/docs/tips.md +150 -0
  31. data/docs/troubleshooting.md +6 -0
  32. data/examples/circleci.yml +104 -0
  33. data/examples/controlplane.yml +159 -0
  34. data/lib/command/apply_template.rb +209 -0
  35. data/lib/command/base.rb +540 -0
  36. data/lib/command/build_image.rb +49 -0
  37. data/lib/command/cleanup_images.rb +136 -0
  38. data/lib/command/cleanup_stale_apps.rb +79 -0
  39. data/lib/command/config.rb +48 -0
  40. data/lib/command/copy_image_from_upstream.rb +108 -0
  41. data/lib/command/delete.rb +149 -0
  42. data/lib/command/deploy_image.rb +56 -0
  43. data/lib/command/doctor.rb +47 -0
  44. data/lib/command/env.rb +22 -0
  45. data/lib/command/exists.rb +23 -0
  46. data/lib/command/generate.rb +45 -0
  47. data/lib/command/info.rb +222 -0
  48. data/lib/command/latest_image.rb +19 -0
  49. data/lib/command/logs.rb +49 -0
  50. data/lib/command/maintenance.rb +42 -0
  51. data/lib/command/maintenance_off.rb +62 -0
  52. data/lib/command/maintenance_on.rb +62 -0
  53. data/lib/command/maintenance_set_page.rb +34 -0
  54. data/lib/command/no_command.rb +23 -0
  55. data/lib/command/open.rb +33 -0
  56. data/lib/command/open_console.rb +26 -0
  57. data/lib/command/promote_app_from_upstream.rb +38 -0
  58. data/lib/command/ps.rb +41 -0
  59. data/lib/command/ps_restart.rb +37 -0
  60. data/lib/command/ps_start.rb +51 -0
  61. data/lib/command/ps_stop.rb +82 -0
  62. data/lib/command/ps_wait.rb +40 -0
  63. data/lib/command/run.rb +573 -0
  64. data/lib/command/setup_app.rb +113 -0
  65. data/lib/command/test.rb +23 -0
  66. data/lib/command/version.rb +18 -0
  67. data/lib/constants/exit_code.rb +7 -0
  68. data/lib/core/config.rb +316 -0
  69. data/lib/core/controlplane.rb +552 -0
  70. data/lib/core/controlplane_api.rb +170 -0
  71. data/lib/core/controlplane_api_direct.rb +112 -0
  72. data/lib/core/doctor_service.rb +104 -0
  73. data/lib/core/helpers.rb +26 -0
  74. data/lib/core/shell.rb +100 -0
  75. data/lib/core/template_parser.rb +76 -0
  76. data/lib/cpflow/version.rb +6 -0
  77. data/lib/cpflow.rb +288 -0
  78. data/lib/deprecated_commands.json +9 -0
  79. data/lib/generator_templates/Dockerfile +27 -0
  80. data/lib/generator_templates/controlplane.yml +62 -0
  81. data/lib/generator_templates/entrypoint.sh +8 -0
  82. data/lib/generator_templates/templates/app.yml +21 -0
  83. data/lib/generator_templates/templates/postgres.yml +176 -0
  84. data/lib/generator_templates/templates/rails.yml +36 -0
  85. data/rakelib/create_release.rake +81 -0
  86. data/script/add_command +37 -0
  87. data/script/check_command_docs +3 -0
  88. data/script/check_cpln_links +45 -0
  89. data/script/rename_command +43 -0
  90. data/script/update_command_docs +62 -0
  91. data/templates/app.yml +13 -0
  92. data/templates/daily-task.yml +32 -0
  93. data/templates/maintenance.yml +25 -0
  94. data/templates/memcached.yml +24 -0
  95. data/templates/postgres.yml +32 -0
  96. data/templates/rails.yml +27 -0
  97. data/templates/redis.yml +21 -0
  98. data/templates/redis2.yml +37 -0
  99. data/templates/sidekiq.yml +38 -0
  100. metadata +341 -0
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ControlplaneApi # rubocop:disable Metrics/ClassLength
4
+ def list_orgs
5
+ api_json("/org", method: :get)
6
+ end
7
+
8
+ def gvc_list(org:)
9
+ api_json("/org/#{org}/gvc", method: :get)
10
+ end
11
+
12
+ def gvc_get(org:, gvc:)
13
+ api_json("/org/#{org}/gvc/#{gvc}", method: :get)
14
+ end
15
+
16
+ def gvc_delete(org:, gvc:)
17
+ api_json("/org/#{org}/gvc/#{gvc}", method: :delete)
18
+ end
19
+
20
+ def query_images(org:, gvc:, gvc_op_type:)
21
+ terms = [
22
+ {
23
+ property: "repository",
24
+ op: gvc_op_type,
25
+ value: gvc
26
+ }
27
+ ]
28
+
29
+ query("/org/#{org}/image", terms)
30
+ end
31
+
32
+ def fetch_image_details(org:, image:)
33
+ api_json("/org/#{org}/image/#{image}", method: :get)
34
+ end
35
+
36
+ def image_delete(org:, image:)
37
+ api_json("/org/#{org}/image/#{image}", method: :delete)
38
+ end
39
+
40
+ def log_get(org:, gvc:, workload: nil, replica: nil, from: nil, to: nil) # rubocop:disable Metrics/ParameterLists
41
+ query = { gvc: gvc }
42
+ query[:workload] = workload if workload
43
+ query[:replica] = replica if replica
44
+ query = query.map { |k, v| %(#{k}="#{v}") }.join(",").then { "{#{_1}}" }
45
+
46
+ params = { query: query }
47
+ params[:from] = "#{from}000000000" if from
48
+ params[:to] = "#{to}000000000" if to
49
+ params[:limit] = "5000"
50
+ # params << "delay_for=0"
51
+ # params << "limit=30"
52
+ # params << "direction=forward"
53
+ params = params.map { |k, v| %(#{k}=#{CGI.escape(v)}) }.join("&")
54
+
55
+ api_json("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
56
+ end
57
+
58
+ def query_workloads(org:, gvc:, workload:, gvc_op_type:, workload_op_type:) # rubocop:disable Metrics/MethodLength
59
+ terms = [
60
+ {
61
+ rel: "gvc",
62
+ op: gvc_op_type,
63
+ value: gvc
64
+ },
65
+ {
66
+ property: "name",
67
+ op: workload_op_type,
68
+ value: workload
69
+ }
70
+ ]
71
+
72
+ query("/org/#{org}/workload", terms)
73
+ end
74
+
75
+ def workload_list(org:, gvc:)
76
+ api_json("/org/#{org}/gvc/#{gvc}/workload", method: :get)
77
+ end
78
+
79
+ def workload_list_by_org(org:)
80
+ api_json("/org/#{org}/workload", method: :get)
81
+ end
82
+
83
+ def workload_get(org:, gvc:, workload:)
84
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :get)
85
+ end
86
+
87
+ def update_workload(org:, gvc:, workload:, data:)
88
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :patch, body: data)
89
+ end
90
+
91
+ def workload_deployments(org:, gvc:, workload:)
92
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}/deployment", method: :get)
93
+ end
94
+
95
+ def delete_workload(org:, gvc:, workload:)
96
+ api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :delete)
97
+ end
98
+
99
+ def list_volumesets(org:, gvc:)
100
+ api_json("/org/#{org}/gvc/#{gvc}/volumeset", method: :get)
101
+ end
102
+
103
+ def delete_volumeset(org:, gvc:, volumeset:)
104
+ api_json("/org/#{org}/gvc/#{gvc}/volumeset/#{volumeset}", method: :delete)
105
+ end
106
+
107
+ def fetch_domain(org:, domain:)
108
+ api_json("/org/#{org}/domain/#{domain}", method: :get)
109
+ end
110
+
111
+ def list_domains(org:)
112
+ api_json("/org/#{org}/domain", method: :get)
113
+ end
114
+
115
+ def update_domain(org:, domain:, data:)
116
+ api_json("/org/#{org}/domain/#{domain}", method: :patch, body: data)
117
+ end
118
+
119
+ def fetch_secret(org:, secret:)
120
+ api_json("/org/#{org}/secret/#{secret}", method: :get)
121
+ end
122
+
123
+ def delete_secret(org:, secret:)
124
+ api_json("/org/#{org}/secret/#{secret}", method: :delete)
125
+ end
126
+
127
+ def fetch_identity(org:, gvc:, identity:)
128
+ api_json("/org/#{org}/gvc/#{gvc}/identity/#{identity}", method: :get)
129
+ end
130
+
131
+ def fetch_policy(org:, policy:)
132
+ api_json("/org/#{org}/policy/#{policy}", method: :get)
133
+ end
134
+
135
+ def delete_policy(org:, policy:)
136
+ api_json("/org/#{org}/policy/#{policy}", method: :delete)
137
+ end
138
+
139
+ private
140
+
141
+ def fetch_query_pages(result)
142
+ loop do
143
+ next_page_url = result["links"].find { |link| link["rel"] == "next" }&.dig("href")
144
+ break unless next_page_url
145
+
146
+ next_page_result = api_json(next_page_url, method: :get)
147
+ result["items"] += next_page_result["items"]
148
+ result["links"] = next_page_result["links"]
149
+ end
150
+ end
151
+
152
+ def query(url, terms)
153
+ body = {
154
+ kind: "string",
155
+ spec: {
156
+ match: "all",
157
+ terms: terms
158
+ }
159
+ }
160
+
161
+ result = api_json("#{url}/-query", method: :post, body: body)
162
+ fetch_query_pages(result)
163
+
164
+ result
165
+ end
166
+
167
+ def api_json(...)
168
+ ControlplaneApiDirect.new.call(...)
169
+ end
170
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ControlplaneApiDirect
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
11
+ API_HOSTS = { api: "https://api.cpln.io", logs: "https://logs.cpln.io" }.freeze
12
+
13
+ # API_TOKEN_REGEX = Regexp.union(
14
+ # /^[\w.]{155}$/, # CPLN_TOKEN format
15
+ # /^[\w\-._]{1134}$/ # 'cpln profile token' format
16
+ # ).freeze
17
+
18
+ API_TOKEN_REGEX = /^[\w\-._]+$/.freeze
19
+ API_TOKEN_EXPIRY_SECONDS = 300
20
+
21
+ class << self
22
+ attr_accessor :trace
23
+ end
24
+
25
+ def call(url, method:, host: :api, body: nil) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
26
+ trace = ControlplaneApiDirect.trace
27
+ uri = URI("#{api_host(host)}#{url}")
28
+ request = API_METHODS[method].new(uri)
29
+ request["Content-Type"] = "application/json"
30
+
31
+ refresh_api_token if should_refresh_api_token?
32
+
33
+ request["Authorization"] = api_token[:token]
34
+ request.body = body.to_json if body
35
+
36
+ Shell.debug(method.upcase, "#{uri} #{body&.to_json}")
37
+
38
+ http = Net::HTTP.new(uri.hostname, uri.port)
39
+ http.use_ssl = uri.scheme == "https"
40
+ http.set_debug_output($stdout) if trace
41
+
42
+ response = http.start { |ht| ht.request(request) }
43
+
44
+ case response
45
+ when Net::HTTPOK
46
+ JSON.parse(response.body)
47
+ when Net::HTTPAccepted
48
+ true
49
+ when Net::HTTPNotFound
50
+ nil
51
+ when Net::HTTPForbidden
52
+ org = self.class.parse_org(url)
53
+ raise("Double check your org #{org}. #{response} #{response.body}")
54
+ else
55
+ raise("#{response} #{response.body}")
56
+ end
57
+ end
58
+
59
+ def api_host(host)
60
+ case host
61
+ when :api
62
+ ENV.fetch("CPLN_ENDPOINT", API_HOSTS[host])
63
+ else
64
+ API_HOSTS[host]
65
+ end
66
+ end
67
+
68
+ # rubocop:disable Style/ClassVars
69
+ def api_token # rubocop:disable Metrics/MethodLength
70
+ return @@api_token if defined?(@@api_token)
71
+
72
+ @@api_token = {
73
+ token: ENV.fetch("CPLN_TOKEN", nil),
74
+ comes_from_profile: false
75
+ }
76
+ if @@api_token[:token].nil?
77
+ @@api_token = {
78
+ token: Shell.cmd("cpln", "profile", "token")[:output].chomp,
79
+ comes_from_profile: true
80
+ }
81
+ end
82
+ return @@api_token if @@api_token[:token].match?(API_TOKEN_REGEX)
83
+
84
+ raise "Unknown API token format. " \
85
+ "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
86
+ end
87
+
88
+ # Returns `true` when the token is about to expire in 5 minutes
89
+ def should_refresh_api_token?
90
+ return false unless api_token[:comes_from_profile]
91
+
92
+ payload, = JWT.decode(api_token[:token], nil, false)
93
+ difference_in_seconds = payload["exp"] - Time.now.to_i
94
+
95
+ difference_in_seconds <= API_TOKEN_EXPIRY_SECONDS
96
+ rescue JWT::DecodeError
97
+ false
98
+ end
99
+
100
+ def refresh_api_token
101
+ @@api_token[:token] = Shell.cmd("cpln", "profile", "token")[:output].chomp
102
+ end
103
+
104
+ def self.reset_api_token
105
+ remove_class_variable(:@@api_token) if defined?(@@api_token)
106
+ end
107
+ # rubocop:enable Style/ClassVars
108
+
109
+ def self.parse_org(url)
110
+ url.match(%r{^/org/([^/]+)})[1]
111
+ end
112
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ValidationError < StandardError; end
4
+
5
+ class DoctorService
6
+ attr_reader :config
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def run_validations(validations, silent_if_passing: false) # rubocop:disable Metrics/MethodLength
13
+ @any_failed_validation = false
14
+
15
+ validations.each do |validation|
16
+ case validation
17
+ when "config"
18
+ validate_config
19
+ when "templates"
20
+ validate_templates
21
+ else
22
+ raise ValidationError, Shell.color("ERROR: Invalid validation '#{validation}'.", :red)
23
+ end
24
+
25
+ progress.puts("#{Shell.color('[PASS]', :green)} #{validation}") unless silent_if_passing
26
+ rescue ValidationError => e
27
+ @any_failed_validation = true
28
+
29
+ progress.puts("#{Shell.color('[FAIL]', :red)} #{validation}\n\n#{e.message}\n\n")
30
+ end
31
+
32
+ exit(ExitCode::ERROR_DEFAULT) if @any_failed_validation
33
+ end
34
+
35
+ def validate_config
36
+ check_for_app_names_contained_in_others
37
+ end
38
+
39
+ def validate_templates
40
+ @template_parser = TemplateParser.new(config)
41
+ filenames = Dir.glob("#{@template_parser.template_dir}/*.yml")
42
+ templates = @template_parser.parse(filenames)
43
+
44
+ check_for_duplicate_templates(templates)
45
+ warn_deprecated_template_variables
46
+ end
47
+
48
+ private
49
+
50
+ def check_for_app_names_contained_in_others
51
+ app_names_contained_in_others = find_app_names_contained_in_others
52
+ return if app_names_contained_in_others.empty?
53
+
54
+ message = "App names contained in others found below. Please ensure that app names are unique."
55
+ list = app_names_contained_in_others
56
+ .map { |app_prefix, app_name| " - '#{app_prefix}' is a prefix of '#{app_name}'" }
57
+ .join("\n")
58
+ raise ValidationError, "#{Shell.color("ERROR: #{message}", :red)}\n#{list}"
59
+ end
60
+
61
+ def find_app_names_contained_in_others # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
62
+ app_names = config.apps.keys.map(&:to_s).sort
63
+ app_prefixes = config.apps
64
+ .select { |_, app_options| app_options[:match_if_app_name_starts_with] }
65
+ .keys
66
+ .map(&:to_s)
67
+ .sort
68
+ app_prefixes.each_with_object([]) do |app_prefix, app_names_contained_in_others|
69
+ app_names.each do |app_name|
70
+ if app_prefix != app_name && app_name.start_with?(app_prefix)
71
+ app_names_contained_in_others.push([app_prefix, app_name])
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def check_for_duplicate_templates(templates)
78
+ grouped_templates = templates.group_by { |template| [template["kind"], template["name"]] }
79
+ duplicate_templates = grouped_templates.select { |_, group| group.size > 1 }
80
+ return if duplicate_templates.empty?
81
+
82
+ message = "Duplicate templates found with the kind/names below. Please ensure that templates are unique."
83
+ list = duplicate_templates
84
+ .map { |(kind, name), _| " - kind: #{kind}, name: #{name}" }
85
+ .join("\n")
86
+ raise ValidationError, "#{Shell.color("ERROR: #{message}", :red)}\n#{list}"
87
+ end
88
+
89
+ def warn_deprecated_template_variables
90
+ deprecated_variables = @template_parser.deprecated_variables
91
+ return if deprecated_variables.empty?
92
+
93
+ message = "Please replace these variables in the templates, " \
94
+ "as support for them will be removed in a future major version bump:"
95
+ list = deprecated_variables
96
+ .map { |old_key, new_key| " - #{old_key} -> #{new_key}" }
97
+ .join("\n")
98
+ progress.puts("\n#{Shell.color("DEPRECATED: #{message}", :yellow)}\n#{list}\n\n")
99
+ end
100
+
101
+ def progress
102
+ $stderr
103
+ end
104
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Helpers
6
+ module_function
7
+
8
+ def strip_str_and_validate(str)
9
+ return str if str.nil?
10
+
11
+ str = str.strip
12
+ str.empty? ? nil : str
13
+ end
14
+
15
+ def random_four_digits
16
+ SecureRandom.random_number(1000..9999)
17
+ end
18
+
19
+ def normalize_command_name(name)
20
+ name.to_s.tr("_", "-")
21
+ end
22
+
23
+ def normalize_option_name(name)
24
+ "--#{name.to_s.tr('_', '-')}"
25
+ end
26
+ end
data/lib/core/shell.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shell
4
+ class << self
5
+ attr_reader :tmp_stderr, :verbose
6
+ end
7
+
8
+ def self.shell
9
+ @shell ||= Thor::Shell::Color.new
10
+ end
11
+
12
+ def self.use_tmp_stderr
13
+ @tmp_stderr = Tempfile.create
14
+
15
+ yield
16
+
17
+ @tmp_stderr.close
18
+ @tmp_stderr = nil
19
+ end
20
+
21
+ def self.write_to_tmp_stderr(message)
22
+ tmp_stderr.write(message)
23
+ end
24
+
25
+ def self.read_from_tmp_stderr
26
+ tmp_stderr.rewind
27
+ tmp_stderr.read.strip
28
+ end
29
+
30
+ def self.color(message, color_key)
31
+ shell.set_color(message, color_key)
32
+ end
33
+
34
+ def self.confirm(message)
35
+ shell.yes?("#{message} (y/N)")
36
+ end
37
+
38
+ def self.warn(message)
39
+ Kernel.warn(color("WARNING: #{message}", :yellow))
40
+ end
41
+
42
+ def self.warn_deprecated(message)
43
+ Kernel.warn(color("DEPRECATED: #{message}", :yellow))
44
+ end
45
+
46
+ def self.abort(message, exit_status = ExitCode::ERROR_DEFAULT)
47
+ Kernel.warn(color("ERROR: #{message}", :red))
48
+ exit(exit_status)
49
+ end
50
+
51
+ def self.verbose_mode(verbose)
52
+ @verbose = verbose
53
+ end
54
+
55
+ def self.debug(prefix, message, sensitive_data_pattern: nil)
56
+ return unless verbose
57
+
58
+ filtered_message = hide_sensitive_data(message, sensitive_data_pattern)
59
+ Kernel.warn("\n[#{color(prefix, :red)}] #{filtered_message}")
60
+ end
61
+
62
+ def self.should_hide_output?
63
+ tmp_stderr && !verbose
64
+ end
65
+
66
+ def self.cmd(*cmd_to_run, capture_stderr: false)
67
+ output, status = capture_stderr ? Open3.capture2e(*cmd_to_run) : Open3.capture2(*cmd_to_run)
68
+
69
+ {
70
+ output: output,
71
+ success: status.success?
72
+ }
73
+ end
74
+
75
+ #
76
+ # Hide sensitive data based on the passed pattern
77
+ #
78
+ # @param [String] message
79
+ # The message to get processed.
80
+ # @param [Regexp, nil] pattern
81
+ # The regular expression to be used. If not provided, no filter gets applied.
82
+ #
83
+ # @return [String]
84
+ # Filtered message.
85
+ #
86
+ # @example
87
+ # hide_sensitive_data("--token abcd", /(?<=--token )(\S+)/)
88
+ def self.hide_sensitive_data(message, pattern = nil)
89
+ return message unless pattern.is_a?(Regexp)
90
+
91
+ message.gsub(pattern, "XXXXXXX")
92
+ end
93
+
94
+ def self.trap_interrupt
95
+ trap("SIGINT") do
96
+ puts
97
+ exit(ExitCode::INTERRUPT)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TemplateParser
4
+ attr_reader :config, :deprecated_variables
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def template_dir
11
+ "#{config.app_cpln_dir}/templates"
12
+ end
13
+
14
+ def template_filename(name)
15
+ "#{template_dir}/#{name}.yml"
16
+ end
17
+
18
+ def parse(filenames)
19
+ @deprecated_variables = {}
20
+
21
+ filenames.each_with_object([]) do |filename, templates|
22
+ yaml_file = File.read(filename)
23
+ yaml_file = replace_variables(yaml_file)
24
+
25
+ template_yamls = yaml_file.split(/^---\s*$/)
26
+ template_yamls.each do |template_yaml|
27
+ template = YAML.safe_load(template_yaml)
28
+ templates.push(template)
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def replace_variables(yaml_file) # rubocop:disable Metrics/MethodLength
36
+ yaml_file = yaml_file
37
+ .gsub("{{APP_ORG}}", config.org)
38
+ .gsub("{{APP_NAME}}", config.app)
39
+ .gsub("{{APP_LOCATION}}", config.location)
40
+ .gsub("{{APP_LOCATION_LINK}}", config.location_link)
41
+ .gsub("{{APP_IMAGE}}", cp.latest_image)
42
+ .gsub("{{APP_IMAGE_LINK}}", config.image_link(cp.latest_image))
43
+ .gsub("{{APP_IDENTITY}}", config.identity)
44
+ .gsub("{{APP_IDENTITY_LINK}}", config.identity_link)
45
+ .gsub("{{APP_SECRETS}}", config.secrets)
46
+ .gsub("{{APP_SECRETS_POLICY}}", config.secrets_policy)
47
+
48
+ find_deprecated_variables(yaml_file)
49
+
50
+ # Kept for backwards compatibility
51
+ yaml_file
52
+ .gsub("APP_ORG", config.org)
53
+ .gsub("APP_GVC", config.app)
54
+ .gsub("APP_LOCATION", config.location)
55
+ .gsub("APP_IMAGE", cp.latest_image)
56
+ end
57
+
58
+ def find_deprecated_variables(yaml_file)
59
+ new_variables.each do |old_key, new_key|
60
+ @deprecated_variables[old_key] = new_key if yaml_file.include?(old_key)
61
+ end
62
+ end
63
+
64
+ def new_variables
65
+ {
66
+ "APP_ORG" => "{{APP_ORG}}",
67
+ "APP_GVC" => "{{APP_NAME}}",
68
+ "APP_LOCATION" => "{{APP_LOCATION}}",
69
+ "APP_IMAGE" => "{{APP_IMAGE}}"
70
+ }
71
+ end
72
+
73
+ def cp
74
+ @cp ||= Controlplane.new(config)
75
+ end
76
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cpflow
4
+ VERSION = "3.0.0"
5
+ MIN_CPLN_VERSION = "2.0.1"
6
+ end