kube-platform 3.3.1.gk.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +31 -0
  3. data/README.md +192 -0
  4. data/bin/kube-platform +37 -0
  5. data/lib/kube-platform/application.rb +203 -0
  6. data/lib/kube-platform/cli.rb +114 -0
  7. data/lib/kube-platform/client.rb +217 -0
  8. data/lib/kube-platform/cluster.rb +224 -0
  9. data/lib/kube-platform/cluster_definition.rb +115 -0
  10. data/lib/kube-platform/configuration.rb +145 -0
  11. data/lib/kube-platform/exceptions.rb +9 -0
  12. data/lib/kube-platform/handlers/dockerhub_secret_copy.rb +52 -0
  13. data/lib/kube-platform/handlers/ebs_from_snapshot.rb +108 -0
  14. data/lib/kube-platform/handlers/handler.rb +36 -0
  15. data/lib/kube-platform/handlers/recreate_resource.rb +11 -0
  16. data/lib/kube-platform/handlers/secret_copy.rb +43 -0
  17. data/lib/kube-platform/handlers/wait_for_job_completion.rb +69 -0
  18. data/lib/kube-platform/handlers/wait_for_termination.rb +47 -0
  19. data/lib/kube-platform/health_check.rb +19 -0
  20. data/lib/kube-platform/health_checks/pods_ready.rb +188 -0
  21. data/lib/kube-platform/health_checks/r53_records.rb +82 -0
  22. data/lib/kube-platform/helpers/retry.rb +20 -0
  23. data/lib/kube-platform/images/descriptor.rb +49 -0
  24. data/lib/kube-platform/images/docker_hub_image.rb +49 -0
  25. data/lib/kube-platform/images/dockerhub_image_factory.rb +64 -0
  26. data/lib/kube-platform/images/kubernetes_docker_hub_secret_provider.rb +44 -0
  27. data/lib/kube-platform/images/repository.rb +77 -0
  28. data/lib/kube-platform/images/tag_associator.rb +80 -0
  29. data/lib/kube-platform/images/tagged_dockerhub_image.rb +36 -0
  30. data/lib/kube-platform/logger.rb +32 -0
  31. data/lib/kube-platform/manifest.rb +61 -0
  32. data/lib/kube-platform/pre_checks/r53_records.rb +66 -0
  33. data/lib/kube-platform/pre_checks/valid_platform_dependencies.rb +52 -0
  34. data/lib/kube-platform/pre_checks.rb +19 -0
  35. data/lib/kube-platform/resource.rb +152 -0
  36. data/lib/kube-platform/resource_repository.rb +73 -0
  37. data/lib/kube-platform/thor/descriptor_to_option_adapter.rb +33 -0
  38. data/lib/kube-platform/update_checker.rb +39 -0
  39. data/lib/kube-platform/version.rb +5 -0
  40. data/lib/kube-platform.rb +40 -0
  41. metadata +179 -0
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "logger"
4
+
5
+ module KubePlatform
6
+ class Client
7
+ include Logger
8
+
9
+ attr_reader :context
10
+
11
+ CONFIG_PATH = "#{ENV['HOME']}/.kube/config"
12
+ HTTP_ERROR_RETRIES = 5
13
+ BEARER_TOKEN_FILE_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/token'
14
+ CERT_PATH = '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
15
+
16
+ def initialize(context_name: nil, config_path: CONFIG_PATH)
17
+ unless in_cluster?
18
+ @context = load_context(context_name, config_path)
19
+ end
20
+ @client_map = {}
21
+ end
22
+
23
+ def create(resource)
24
+ lifecycle_hook(resource, :pre_create)
25
+ log_action(:creating, resource)
26
+ create_resource(resource)
27
+ lifecycle_hook(resource, :post_create)
28
+ end
29
+
30
+ def update(resource)
31
+ lifecycle_hook(resource, :pre_update)
32
+ log_action(:updating, resource)
33
+ update_resource(resource)
34
+ lifecycle_hook(resource, :post_update)
35
+ end
36
+
37
+ def delete(resource, continue_on_missing: true)
38
+ lifecycle_hook(resource, :pre_delete)
39
+ log_action(:deleting, resource)
40
+ delete_resource(resource, continue_on_missing: continue_on_missing)
41
+ lifecycle_hook(resource, :post_delete)
42
+ end
43
+
44
+ def exist?(resource)
45
+ get(resource) != nil
46
+ end
47
+
48
+ def client_for_api(api = "v1")
49
+ @client_map[api] ||= build_client(api)
50
+ end
51
+
52
+ def api_endpoint_from_context
53
+ @context&.api_endpoint
54
+ end
55
+
56
+ def get(resource)
57
+ method = method_for_resource_action(:get, resource)
58
+ client = client_for_resource(resource)
59
+
60
+ raw = resource.namespaced? ? client.send(method, resource.name, resource.namespace) : client.send(method, resource.name)
61
+ KubePlatform::Resource.from_spec(**(raw.to_h))
62
+ rescue Kubeclient::ResourceNotFoundError
63
+ nil
64
+ end
65
+
66
+ # This modifies the API response slightly to appease the Resource wrapper
67
+ def pods_matching_selector(selector:, namespace:)
68
+ client = client_for_api("v1")
69
+ client.get_pods(label_selector: selector, namespace: namespace).map do |pod|
70
+ pod.apiVersion = "v1"
71
+ pod.kind = "Pod"
72
+ KubePlatform::Resource.from_spec(**(pod.to_h))
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def log_action(action, resource)
79
+ logger.info("#{action.capitalize} #{resource.kind} #{resource.name}")
80
+ end
81
+
82
+ def create_resource(resource)
83
+ method = method_for_resource_action(:create, resource)
84
+ client = client_for_resource(resource)
85
+
86
+ with_http_error_retries do
87
+ client.send(method, resource.unwrap)
88
+ end
89
+ rescue StandardError # TODO: Fix this double log. Either do nothing here, or wrap a new exception around the rescued one.
90
+ log_exception(:create, resource)
91
+ raise
92
+ end
93
+
94
+ def update_resource(resource)
95
+ method = method_for_resource_action(:update, resource)
96
+ client = client_for_resource(resource)
97
+
98
+ with_http_error_retries do
99
+ client.send(method, resource.unwrap)
100
+ end
101
+ rescue StandardError # TODO: ditto
102
+ log_exception(:update, resource)
103
+ raise
104
+ end
105
+
106
+ def delete_resource(resource, continue_on_missing:)
107
+ execute_delete(resource)
108
+ rescue Kubeclient::ResourceNotFoundError
109
+ resource_not_found!(resource, continue_on_missing)
110
+ rescue StandardError # TODO: ditto
111
+ log_exception(:delete, resource)
112
+ raise
113
+ end
114
+
115
+ def execute_delete(resource)
116
+ method = method_for_resource_action(:delete, resource)
117
+ client = client_for_resource(resource)
118
+
119
+ with_http_error_retries do
120
+ if resource.namespaced?
121
+ client.send(method, resource.name, resource.namespace)
122
+ else
123
+ client.send(method, resource.name)
124
+ end
125
+ end
126
+ end
127
+
128
+ def with_http_error_retries
129
+ attempt_number = 0
130
+ backoff_randomization = rand(1.0...1.5)
131
+ begin
132
+ yield
133
+ rescue Kubeclient::ResourceNotFoundError
134
+ # This is not recoverable, so re-raise
135
+ raise
136
+ rescue Kubeclient::HttpError
137
+ if attempt_number >= HTTP_ERROR_RETRIES # TODO: ditto
138
+ logger.error("Giving up, retry limit of #{HTTP_ERROR_RETRIES} exceeded")
139
+ raise
140
+ end
141
+
142
+ sleep_time = backoff_randomization * 2**attempt_number
143
+ logger.warn("Encountered an HTTP error. Sleeping #{sleep_time}s before retrying (#{attempt_number + 1}/#{HTTP_ERROR_RETRIES})")
144
+ sleep(sleep_time)
145
+ attempt_number += 1
146
+ retry
147
+ end
148
+ end
149
+
150
+ def resource_not_found!(resource, continue_on_missing) # TODO: ditto
151
+ if continue_on_missing
152
+ logger.debug("Failed to delete #{resource.kind} #{resource.name}, it does not exist")
153
+ else
154
+ logger.error("Failed to delete #{resource.kind} #{resource.name}, it does not exist")
155
+ raise
156
+ end
157
+ end
158
+
159
+ def log_exception(action, resource)
160
+ logger.error("Exception encountered trying to #{action} #{resource.kind} '#{resource.name}'")
161
+ end
162
+
163
+ def method_for_resource_action(action, resource)
164
+ "#{action}_#{Kubeclient::ClientMixin.underscore_entity(resource.kind)}".to_sym
165
+ end
166
+
167
+ def load_context(context_name, config_path)
168
+ config = Kubeclient::Config.read(config_path)
169
+ config.context(context_name)
170
+ end
171
+
172
+ def client_for_resource(resource)
173
+ client_for_api(resource.api_version)
174
+ end
175
+
176
+ def build_client(api)
177
+ case api
178
+ when "v1"
179
+ path = "api"
180
+ version = "v1"
181
+ else
182
+ api_group, _, version = api.rpartition("/")
183
+ path = "apis/#{api_group}"
184
+ end
185
+
186
+ if in_cluster?
187
+ build_client_in_cluster(path, version)
188
+ else
189
+ Kubeclient::Client.new("#{context.api_endpoint}/#{path}", version, ssl_options: context.ssl_options, auth_options: context.auth_options)
190
+ end
191
+ end
192
+
193
+ def build_client_in_cluster(path, version)
194
+ auth_options = {
195
+ bearer_token_file: BEARER_TOKEN_FILE_PATH
196
+ }
197
+ ssl_options = {}
198
+ if File.exist?(CERT_PATH)
199
+ ssl_options[:ca_file] = CERT_PATH
200
+ end
201
+ Kubeclient::Client.new(
202
+ "https://#{ENV['KUBERNETES_SERVICE_HOST']}:#{ENV['KUBERNETES_SERVICE_PORT']}/#{path}",
203
+ version,
204
+ auth_options: auth_options,
205
+ ssl_options: ssl_options
206
+ )
207
+ end
208
+
209
+ def lifecycle_hook(resource, hook)
210
+ resource.send(hook, self) if resource.respond_to?(hook)
211
+ end
212
+
213
+ def in_cluster?
214
+ ENV['KUBERNETES_SERVICE_HOST'] && ENV['KUBERNETES_SERVICE_PORT']
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "logger"
4
+ require "active_support/time"
5
+
6
+ module KubePlatform
7
+ class Cluster
8
+ ClusterDeprecation = ActiveSupport::Deprecation.new('2.0', 'KubePlatform')
9
+
10
+ class PlatformDefinitionMismatchException < KubePlatformException
11
+ attr_reader :received_definition, :expected_definition
12
+
13
+ def initialize(message, expected_definition:, received_definition:)
14
+ @expected_definition = expected_definition
15
+ @received_definition = received_definition
16
+ super(message)
17
+ end
18
+ end
19
+
20
+ PlatformExistsException = Class.new(KubePlatformException)
21
+ PlatformNotFoundException = Class.new(KubePlatformException)
22
+ HealthCheckFailedException = Class.new(KubePlatformException)
23
+ NamespaceCreationException = Class.new(KubePlatformException)
24
+ PreCheckFailedException = Class.new(KubePlatformException)
25
+
26
+ include Logger
27
+
28
+ attr_reader :name, :cluster_definition, :client, :config
29
+
30
+ # Resources that exist outside of a namespace
31
+ GLOBAL_RESOURCES = ["Namespace", "PersistentVolume"].freeze
32
+ DEFINITION_ANNOTATION = "invoca.com/cluster_definition"
33
+ IMAGE_TAG_ANNOTATION = "invoca.com/image_tags"
34
+
35
+ NAMESPACE_CREATION_TIMEOUT = 5.minutes
36
+
37
+ def initialize(name:, cluster_definition:, client:, config:)
38
+ @name = name
39
+ @cluster_definition = cluster_definition
40
+ @client = client
41
+ @config = config
42
+ end
43
+
44
+ def create
45
+ exist? and raise PlatformExistsException, "Platform #{name} already exists"
46
+
47
+ run_pre_checks
48
+ annotate_namespace
49
+ create_namespace
50
+ create_cluster_resources
51
+ run_health_checks
52
+ logger.info "Successfully created platform #{name}"
53
+ end
54
+
55
+ def update
56
+ exist? or raise PlatformNotFoundException, "Platform #{name} does not exist"
57
+
58
+ raise_if_definition_mismatch!
59
+ copy_namespace_annotations
60
+ annotate_namespace
61
+ update_namespace
62
+ actioned_pre_update_resources? ? pre_update_actions : pre_updates
63
+ update_pods
64
+ run_health_checks
65
+ logger.info "Successfully updated platform #{name}"
66
+ end
67
+
68
+ def delete
69
+ if exist?
70
+ raise_if_definition_mismatch!
71
+ delete_cluster_resources
72
+ delete_namespace
73
+ logger.info "Successfully deleted platform #{name}"
74
+ elsif @config.force_api_calls
75
+ logger.info "Namespace #{name} does not exist. Returning."
76
+ else
77
+ raise PlatformNotFoundException, "Platform #{name} does not exist"
78
+ end
79
+ end
80
+
81
+ def image_key_tag_pairs
82
+ exist? or raise PlatformNotFoundException, "Platform #{name} does not exist"
83
+
84
+ JSON.parse(existing_namespace.annotation(IMAGE_TAG_ANNOTATION)).symbolize_keys
85
+ end
86
+
87
+ private
88
+
89
+ def exist?
90
+ !existing_namespace.nil?
91
+ end
92
+
93
+ def actioned_pre_update_resources?
94
+ cluster_definition.actioned_pre_update_resources?
95
+ end
96
+
97
+ def raise_if_definition_mismatch!
98
+ cluster_launch_definition.nil? and raise KubePlatformException, "Platform #{name} is missing the #{DEFINITION_ANNOTATION} annotation."
99
+ if cluster_launch_definition != definition_name
100
+ raise PlatformDefinitionMismatchException.new(
101
+ "Platform was launched with definition #{cluster_launch_definition} but current operation is for #{definition_name}",
102
+ expected_definition: cluster_launch_definition,
103
+ received_definition: definition_name
104
+ )
105
+ end
106
+ end
107
+
108
+ def cluster_launch_definition
109
+ @cluster_launch_definition ||= existing_namespace.annotation(DEFINITION_ANNOTATION)
110
+ end
111
+
112
+ def existing_namespace
113
+ @existing_namespace ||= client.get(namespace)
114
+ end
115
+
116
+ def definition_name
117
+ cluster_definition.name
118
+ end
119
+
120
+ def copy_namespace_annotations
121
+ existing_namespace&.annotations&.each_pair { |name, value| namespace.annotate(name, value) }
122
+ end
123
+
124
+ def annotate_namespace
125
+ namespace.annotate(DEFINITION_ANNOTATION, definition_name)
126
+ namespace.annotate(IMAGE_TAG_ANNOTATION, config.image_key_tag_pairs_without_defaults.to_json)
127
+ end
128
+
129
+ def namespace_resources(resources)
130
+ resources.each { |r| r.namespace = namespace.name unless GLOBAL_RESOURCES.include?(r.kind) }
131
+ end
132
+
133
+ def namespace
134
+ cluster_definition.namespace
135
+ end
136
+
137
+ def create_namespace
138
+ client.create(namespace)
139
+ create_time = Time.now
140
+ until client.exist?(namespace) # TODO: Use MonotonicTickCount?
141
+ if (Time.now - create_time) > NAMESPACE_CREATION_TIMEOUT
142
+ raise NamespaceCreationException, "Platform namespace #{name} failed to create in under #{NAMESPACE_CREATION_TIMEOUT} seconds"
143
+ end
144
+
145
+ logger.info("Waiting for namespace to exist... sleeping 10 seconds")
146
+ sleep(10.seconds)
147
+ end
148
+ end
149
+
150
+ def delete_namespace
151
+ client.delete(namespace)
152
+ end
153
+
154
+ def resources
155
+ @resources ||= namespace_resources(cluster_definition.resources)
156
+ end
157
+
158
+ def create_cluster_resources
159
+ resources.each { |r| client.create(r) }
160
+ end
161
+
162
+ def update_namespace
163
+ client.update(namespace)
164
+ end
165
+
166
+ def pre_update_actioned_resources
167
+ @pre_update_actioned_resources ||= cluster_definition.pre_update_actioned_resources.each { |_a, r| namespace_resources(r) }
168
+ end
169
+
170
+ def pre_update_actions
171
+ pre_update_actioned_resources.each do |action, resources|
172
+ resources.each { |r| client.send(action, r) }
173
+ end
174
+ end
175
+
176
+ def pre_update_resources
177
+ @pre_update_resources ||= namespace_resources(cluster_definition.pre_update_resources)
178
+ end
179
+
180
+ def pre_updates
181
+ pre_update_resources.each { |r| client.create(r) }
182
+ end
183
+
184
+ def update_pods
185
+ pods_to_update.each { |r| client.update(r) }
186
+ end
187
+
188
+ def pods_to_update
189
+ client.pods_matching_selector(selector: cluster_definition.update_pod_selector, namespace: namespace.name).map do |pod|
190
+ resources.find do |resource|
191
+ resource.pod_annotation(Manifest::FILENAME_ANNOTATION) == pod.annotation(Manifest::FILENAME_ANNOTATION)
192
+ end
193
+ end
194
+ end
195
+
196
+ def delete_cluster_resources
197
+ resources.reverse_each { |r| client.delete(r) }
198
+ end
199
+
200
+ # Stops at first failing health check, raising HealthCheckFailedException
201
+ # Or raises nothing if all pass.
202
+ def run_health_checks
203
+ cluster_definition.health_checks.each do |check|
204
+ logger.info("Running #{check.name} health check")
205
+ check.run(client, cluster_definition) or raise HealthCheckFailedException, "Health check for #{check.name} failed"
206
+ logger.info("Health check for #{check.name} was successful")
207
+ end
208
+ end
209
+
210
+ # Dont create cluster if failing pre check, raising HealthCheckFailedException
211
+ # Or raises nothing if all pass.
212
+ def run_pre_checks
213
+ cluster_definition.pre_checks.each do |check|
214
+ logger.info("Running #{check.name} pre check")
215
+ check.run(client, cluster_definition) or raise PreCheckFailedException, "Pre check for #{check.name} failed"
216
+ logger.info("Pre check for #{check.name} was successful")
217
+ end
218
+ end
219
+
220
+ deprecate :pre_updates,
221
+ :pre_update_resources,
222
+ deprecator: ClusterDeprecation
223
+ end
224
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KubePlatform
4
+ class ClusterDefinition
5
+ ClusterDefinitionDeprecation = ActiveSupport::Deprecation.new('2.0', 'KubePlatform')
6
+
7
+ attr_reader :name, :config, :resource_dir
8
+
9
+ def initialize(name:, definition:, resource_dir:, config:)
10
+ @name = name
11
+ @definition = definition
12
+ @resource_dir = resource_dir
13
+ @config = config
14
+ end
15
+
16
+ def namespace
17
+ @namespace ||= load_namespace
18
+ end
19
+
20
+ def resources
21
+ @resources ||= @definition[:resources].map { |r| load_resource(r) }
22
+ end
23
+
24
+ def actioned_pre_update_resources?
25
+ @definition.dig(:update, :resources).is_a?(Hash)
26
+ end
27
+
28
+ def pre_update_actioned_resources
29
+ @pre_update_actioned_resources ||= (@definition.dig(:update, :resources) || {}).reduce({}) do |hash, (action, resources)|
30
+ hash.merge(action => resources.map { |r| load_resource(r) })
31
+ end
32
+ end
33
+
34
+ def pre_update_resources
35
+ @pre_update_resources ||= (@definition.dig(:update, :resources) || []).map { |r| load_resource(r) }
36
+ end
37
+
38
+ def health_checks
39
+ @health_checks ||= (@definition[:health_checks] || []).map { |hc| load_health_check(hc) }
40
+ end
41
+
42
+ def update_pod_selector
43
+ @definition.dig(:update, :pod_selector)
44
+ end
45
+
46
+ def pre_checks
47
+ @pre_checks ||= (@definition[:pre_checks] || []).map { |pc| load_pre_check(pc) }
48
+ end
49
+
50
+ private
51
+
52
+ def handlers
53
+ @definition[:handlers] || {}
54
+ end
55
+
56
+ def load_namespace
57
+ resource_file = @definition[:namespace]
58
+ namespace = resource_file && load_resource(resource_file) or raise ManifestException, "Platform definitions must contain a 'namespace' key that references a namespace resource"
59
+
60
+ namespace.kind == "Namespace" or raise ManifestException, "#{resource_file} does not contain a Namespace resource"
61
+
62
+ namespace
63
+ end
64
+
65
+ def load_resource(resource_name)
66
+ Resource.from_yaml(File.join(resource_dir, resource_name), handlers: handlers_for_resource(resource_name), config: config, jsonnet_library_path: "#{Dir.pwd}/vendor-jb")
67
+ end
68
+
69
+ def load_health_check(check)
70
+ HealthCheck.load(class_name: check[:class], name: check[:name], config: config_for_health_check(check))
71
+ end
72
+
73
+ def load_pre_check(check)
74
+ PreCheck.load(class_name: check[:class], name: check[:name], config: config_for_pre_check(check))
75
+ end
76
+
77
+ def resource_has_handlers?(resource_name)
78
+ handlers[resource_name] && !handlers[resource_name].empty?
79
+ end
80
+
81
+ def handlers_for_resource(resource_name)
82
+ if resource_has_handlers?(resource_name)
83
+ handlers[resource_name].map do |handler_spec|
84
+ handler_class = handler_spec[:class]
85
+ config = config_for_handler(handler_spec)
86
+ build_handler(handler_class, config)
87
+ end
88
+ else
89
+ []
90
+ end
91
+ end
92
+
93
+ def config_for_health_check(check_spec)
94
+ check_config = check_spec[:config] || {}
95
+ config.merge(check_config)
96
+ end
97
+
98
+ def config_for_handler(handler_spec)
99
+ handler_config = handler_spec[:config] || {}
100
+ config.merge(handler_config)
101
+ end
102
+
103
+ def build_handler(handler_class, config)
104
+ klass = "KubePlatform::Handlers::#{handler_class}".constantize
105
+ klass.new(config)
106
+ end
107
+
108
+ def config_for_pre_check(check_spec)
109
+ check_config = check_spec[:config] || {}
110
+ config.merge(check_config)
111
+ end
112
+
113
+ deprecate :pre_update_resources, deprecator: ClusterDefinitionDeprecation
114
+ end
115
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hashie"
4
+
5
+ module KubePlatform
6
+ class Configuration < Hashie::Dash
7
+ include Hashie::Extensions::Dash::IndifferentAccess
8
+
9
+ property :cluster_name
10
+ property :domain_name
11
+ property :cluster_definition
12
+ property :manifest_path
13
+ property :image_repository
14
+ property :git_url
15
+ property :git_branch
16
+ property :git_resource_path
17
+ property :user
18
+ property :dockerhub_secret_name
19
+ property :dockerhub_secret_namespace
20
+ property :kubectl_context
21
+ property :dev_mode
22
+ property :ct_mode
23
+ property :faketime
24
+ property :force_api_calls
25
+ property :api_endpoint
26
+
27
+ NO_DEFAULT_TAG = Object.new
28
+ MASTER_TAG = "master"
29
+ MAIN_TAG = "main"
30
+ PRODUCTION_TAG = "production"
31
+ ALWAYS_PULL_TAGS = [MASTER_TAG, MAIN_TAG, PRODUCTION_TAG].freeze
32
+ CONTEXT_REGEX = %r{^https://api\.([^.]+)\.([^.]+)\.([^.]+)\.k8s\.dev\.invocaops\.com$}.freeze
33
+ OPS_CONTEXT_REGEX = %r{^[^.]+\.ops\.[^.]+$}
34
+
35
+ def initialize(attributes = {})
36
+ super(attributes)
37
+ yield self if block_given? # TODO: yield self is a code smell as it enables circular dependencies.
38
+ end
39
+
40
+ def cluster_fqdn
41
+ "#{cluster_name}.#{domain_name}"
42
+ end
43
+
44
+ def kubernetes_context
45
+ context_from_api_endpoint || kubectl_context
46
+ end
47
+
48
+ def service_tag_filters
49
+ filters = [cluster_name]
50
+
51
+ if kubernetes_context =~ OPS_CONTEXT_REGEX
52
+ filters << 'staging-ops'
53
+ else
54
+ filters << 'staging'
55
+ end
56
+
57
+ filters.compact.join(",")
58
+ end
59
+
60
+ # TODO: Remove the default option. All images in the image repository will be tagged.
61
+ def tag_for_image(image, default: NO_DEFAULT_TAG)
62
+ if default == NO_DEFAULT_TAG
63
+ tag_for_image_key(image) or
64
+ raise KubePlatform::ConfigException, "Could not locate an image tag for '#{image}' and no default was provided"
65
+ else
66
+ tag_for_image_key(image) || tag_for_image_key(default) or
67
+ raise KubePlatform::ConfigException, "Could not locate an image tag for '#{image}' or the requested default '#{default}'"
68
+ end
69
+ end
70
+
71
+ def image_for_key(key)
72
+ image_repository.get(key)&.name or
73
+ raise KubePlatform::ConfigException, "No image found for '#{key}' in #{image_repository.inspect}"
74
+ end
75
+
76
+ def master_tag?(tag)
77
+ [MASTER_TAG, MAIN_TAG].include?(tag)
78
+ end
79
+
80
+ def production_tag?(tag)
81
+ tag == PRODUCTION_TAG
82
+ end
83
+
84
+ def always_pull_tag?(tag)
85
+ ALWAYS_PULL_TAGS.include?(tag)
86
+ end
87
+
88
+ def image_key_tag_pairs
89
+ image_repository.key_tag_pairs
90
+ end
91
+
92
+ def image_key_tag_pairs_without_defaults
93
+ image_repository.key_tag_pairs_without_defaults
94
+ end
95
+
96
+ def apply_defaults(defaults)
97
+ obj = new_instance_with_additional_properties(defaults.keys)
98
+
99
+ defaults.each do |key, value|
100
+ if obj[key].nil?
101
+ obj[key] = value
102
+ end
103
+ end
104
+
105
+ obj
106
+ end
107
+
108
+ def binding
109
+ super
110
+ end
111
+
112
+ alias orig_merge merge
113
+
114
+ def merge(other)
115
+ new_instance_with_additional_properties(other.keys).orig_merge(other)
116
+ end
117
+
118
+ private
119
+
120
+ def context_from_api_endpoint
121
+ if (match = CONTEXT_REGEX.match(api_endpoint))
122
+ match.captures.join(".")
123
+ end
124
+ end
125
+
126
+ def tag_for_image_key(key)
127
+ image_repository.get(key)&.tag
128
+ end
129
+
130
+ def new_instance_with_additional_properties(properties)
131
+ klass = add_additional_properties(properties)
132
+ klass.new(self)
133
+ end
134
+
135
+ def add_additional_properties(properties)
136
+ dash_class = Class.new(self.class)
137
+ properties.each do |p|
138
+ dash_class.class_exec do
139
+ property p.to_sym
140
+ end
141
+ end
142
+ dash_class
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KubePlatform
4
+ KubePlatformException = Class.new(StandardError)
5
+ ConfigException = Class.new(KubePlatformException)
6
+ ManifestException = Class.new(KubePlatformException)
7
+ WaitTimeoutException = Class.new(KubePlatformException)
8
+ TokenNotFound = Class.new(KubePlatformException)
9
+ end