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.
- checksums.yaml +7 -0
- data/Gemfile +31 -0
- data/README.md +192 -0
- data/bin/kube-platform +37 -0
- data/lib/kube-platform/application.rb +203 -0
- data/lib/kube-platform/cli.rb +114 -0
- data/lib/kube-platform/client.rb +217 -0
- data/lib/kube-platform/cluster.rb +224 -0
- data/lib/kube-platform/cluster_definition.rb +115 -0
- data/lib/kube-platform/configuration.rb +145 -0
- data/lib/kube-platform/exceptions.rb +9 -0
- data/lib/kube-platform/handlers/dockerhub_secret_copy.rb +52 -0
- data/lib/kube-platform/handlers/ebs_from_snapshot.rb +108 -0
- data/lib/kube-platform/handlers/handler.rb +36 -0
- data/lib/kube-platform/handlers/recreate_resource.rb +11 -0
- data/lib/kube-platform/handlers/secret_copy.rb +43 -0
- data/lib/kube-platform/handlers/wait_for_job_completion.rb +69 -0
- data/lib/kube-platform/handlers/wait_for_termination.rb +47 -0
- data/lib/kube-platform/health_check.rb +19 -0
- data/lib/kube-platform/health_checks/pods_ready.rb +188 -0
- data/lib/kube-platform/health_checks/r53_records.rb +82 -0
- data/lib/kube-platform/helpers/retry.rb +20 -0
- data/lib/kube-platform/images/descriptor.rb +49 -0
- data/lib/kube-platform/images/docker_hub_image.rb +49 -0
- data/lib/kube-platform/images/dockerhub_image_factory.rb +64 -0
- data/lib/kube-platform/images/kubernetes_docker_hub_secret_provider.rb +44 -0
- data/lib/kube-platform/images/repository.rb +77 -0
- data/lib/kube-platform/images/tag_associator.rb +80 -0
- data/lib/kube-platform/images/tagged_dockerhub_image.rb +36 -0
- data/lib/kube-platform/logger.rb +32 -0
- data/lib/kube-platform/manifest.rb +61 -0
- data/lib/kube-platform/pre_checks/r53_records.rb +66 -0
- data/lib/kube-platform/pre_checks/valid_platform_dependencies.rb +52 -0
- data/lib/kube-platform/pre_checks.rb +19 -0
- data/lib/kube-platform/resource.rb +152 -0
- data/lib/kube-platform/resource_repository.rb +73 -0
- data/lib/kube-platform/thor/descriptor_to_option_adapter.rb +33 -0
- data/lib/kube-platform/update_checker.rb +39 -0
- data/lib/kube-platform/version.rb +5 -0
- data/lib/kube-platform.rb +40 -0
- 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
|