kube-platform 3.3.1.gk.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 (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