tobsch-krane 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/pipeline.nightly.yml +43 -0
  3. data/.github/probots.yml +2 -0
  4. data/.gitignore +20 -0
  5. data/.rubocop.yml +17 -0
  6. data/.shopify-build/VERSION +1 -0
  7. data/.shopify-build/kubernetes-deploy.yml +53 -0
  8. data/1.0-Upgrade.md +185 -0
  9. data/CHANGELOG.md +431 -0
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/CONTRIBUTING.md +164 -0
  12. data/Gemfile +16 -0
  13. data/ISSUE_TEMPLATE.md +25 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +655 -0
  16. data/Rakefile +36 -0
  17. data/bin/ci +21 -0
  18. data/bin/setup +16 -0
  19. data/bin/test +47 -0
  20. data/dev.yml +28 -0
  21. data/dev/flamegraph-from-tests +35 -0
  22. data/exe/krane +5 -0
  23. data/krane.gemspec +44 -0
  24. data/lib/krane.rb +7 -0
  25. data/lib/krane/bindings_parser.rb +88 -0
  26. data/lib/krane/cli/deploy_command.rb +75 -0
  27. data/lib/krane/cli/global_deploy_command.rb +54 -0
  28. data/lib/krane/cli/krane.rb +91 -0
  29. data/lib/krane/cli/render_command.rb +41 -0
  30. data/lib/krane/cli/restart_command.rb +34 -0
  31. data/lib/krane/cli/run_command.rb +54 -0
  32. data/lib/krane/cli/version_command.rb +13 -0
  33. data/lib/krane/cluster_resource_discovery.rb +113 -0
  34. data/lib/krane/common.rb +23 -0
  35. data/lib/krane/concerns/template_reporting.rb +29 -0
  36. data/lib/krane/concurrency.rb +18 -0
  37. data/lib/krane/container_logs.rb +106 -0
  38. data/lib/krane/deferred_summary_logging.rb +95 -0
  39. data/lib/krane/delayed_exceptions.rb +14 -0
  40. data/lib/krane/deploy_task.rb +363 -0
  41. data/lib/krane/deploy_task_config_validator.rb +29 -0
  42. data/lib/krane/duration_parser.rb +27 -0
  43. data/lib/krane/ejson_secret_provisioner.rb +154 -0
  44. data/lib/krane/errors.rb +28 -0
  45. data/lib/krane/formatted_logger.rb +57 -0
  46. data/lib/krane/global_deploy_task.rb +210 -0
  47. data/lib/krane/global_deploy_task_config_validator.rb +12 -0
  48. data/lib/krane/kubeclient_builder.rb +156 -0
  49. data/lib/krane/kubectl.rb +120 -0
  50. data/lib/krane/kubernetes_resource.rb +621 -0
  51. data/lib/krane/kubernetes_resource/cloudsql.rb +43 -0
  52. data/lib/krane/kubernetes_resource/config_map.rb +22 -0
  53. data/lib/krane/kubernetes_resource/cron_job.rb +18 -0
  54. data/lib/krane/kubernetes_resource/custom_resource.rb +87 -0
  55. data/lib/krane/kubernetes_resource/custom_resource_definition.rb +98 -0
  56. data/lib/krane/kubernetes_resource/daemon_set.rb +90 -0
  57. data/lib/krane/kubernetes_resource/deployment.rb +213 -0
  58. data/lib/krane/kubernetes_resource/horizontal_pod_autoscaler.rb +65 -0
  59. data/lib/krane/kubernetes_resource/ingress.rb +18 -0
  60. data/lib/krane/kubernetes_resource/job.rb +60 -0
  61. data/lib/krane/kubernetes_resource/network_policy.rb +22 -0
  62. data/lib/krane/kubernetes_resource/persistent_volume_claim.rb +80 -0
  63. data/lib/krane/kubernetes_resource/pod.rb +269 -0
  64. data/lib/krane/kubernetes_resource/pod_disruption_budget.rb +23 -0
  65. data/lib/krane/kubernetes_resource/pod_set_base.rb +71 -0
  66. data/lib/krane/kubernetes_resource/pod_template.rb +20 -0
  67. data/lib/krane/kubernetes_resource/replica_set.rb +92 -0
  68. data/lib/krane/kubernetes_resource/resource_quota.rb +22 -0
  69. data/lib/krane/kubernetes_resource/role.rb +22 -0
  70. data/lib/krane/kubernetes_resource/role_binding.rb +22 -0
  71. data/lib/krane/kubernetes_resource/secret.rb +24 -0
  72. data/lib/krane/kubernetes_resource/service.rb +104 -0
  73. data/lib/krane/kubernetes_resource/service_account.rb +22 -0
  74. data/lib/krane/kubernetes_resource/stateful_set.rb +70 -0
  75. data/lib/krane/label_selector.rb +42 -0
  76. data/lib/krane/oj.rb +4 -0
  77. data/lib/krane/options_helper.rb +39 -0
  78. data/lib/krane/remote_logs.rb +60 -0
  79. data/lib/krane/render_task.rb +118 -0
  80. data/lib/krane/renderer.rb +118 -0
  81. data/lib/krane/resource_cache.rb +68 -0
  82. data/lib/krane/resource_deployer.rb +265 -0
  83. data/lib/krane/resource_watcher.rb +171 -0
  84. data/lib/krane/restart_task.rb +228 -0
  85. data/lib/krane/rollout_conditions.rb +103 -0
  86. data/lib/krane/runner_task.rb +212 -0
  87. data/lib/krane/runner_task_config_validator.rb +18 -0
  88. data/lib/krane/statsd.rb +65 -0
  89. data/lib/krane/task_config.rb +22 -0
  90. data/lib/krane/task_config_validator.rb +96 -0
  91. data/lib/krane/template_sets.rb +173 -0
  92. data/lib/krane/version.rb +4 -0
  93. data/pull_request_template.md +8 -0
  94. data/screenshots/deploy-demo.gif +0 -0
  95. data/screenshots/migrate-logs.png +0 -0
  96. data/screenshots/missing-secret-fail.png +0 -0
  97. data/screenshots/success.png +0 -0
  98. data/screenshots/test-output.png +0 -0
  99. metadata +375 -0
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ module Krane
3
+ class RolloutConditionsError < StandardError
4
+ end
5
+
6
+ class RolloutConditions
7
+ VALID_FAILURE_CONDITION_KEYS = [:path, :value, :error_msg_path, :custom_error_msg]
8
+ VALID_SUCCESS_CONDITION_KEYS = [:path, :value]
9
+
10
+ class << self
11
+ def from_annotation(conditions_string)
12
+ return new(default_conditions) if conditions_string.downcase.strip == "true"
13
+
14
+ conditions = JSON.parse(conditions_string).slice('success_conditions', 'failure_conditions')
15
+ conditions.deep_symbolize_keys!
16
+
17
+ # Create JsonPath objects
18
+ conditions[:success_conditions]&.each do |query|
19
+ query.slice!(*VALID_SUCCESS_CONDITION_KEYS)
20
+ query[:path] = JsonPath.new(query[:path]) if query.key?(:path)
21
+ end
22
+ conditions[:failure_conditions]&.each do |query|
23
+ query.slice!(*VALID_FAILURE_CONDITION_KEYS)
24
+ query[:path] = JsonPath.new(query[:path]) if query.key?(:path)
25
+ query[:error_msg_path] = JsonPath.new(query[:error_msg_path]) if query.key?(:error_msg_path)
26
+ end
27
+
28
+ new(conditions)
29
+ rescue JSON::ParserError => e
30
+ raise RolloutConditionsError, "Rollout conditions are not valid JSON: #{e}"
31
+ rescue StandardError => e
32
+ raise RolloutConditionsError,
33
+ "Error parsing rollout conditions. " \
34
+ "This is most likely caused by an invalid JsonPath expression. Failed with: #{e}"
35
+ end
36
+
37
+ def default_conditions
38
+ {
39
+ success_conditions: [
40
+ {
41
+ path: JsonPath.new('$.status.conditions[?(@.type == "Ready")].status'),
42
+ value: "True",
43
+ },
44
+ ],
45
+ failure_conditions: [
46
+ {
47
+ path: JsonPath.new('$.status.conditions[?(@.type == "Failed")].status'),
48
+ value: "True",
49
+ error_msg_path: JsonPath.new('$.status.conditions[?(@.type == "Failed")].message'),
50
+ },
51
+ ],
52
+ }
53
+ end
54
+ end
55
+
56
+ def initialize(conditions)
57
+ @success_conditions = conditions.fetch(:success_conditions, [])
58
+ @failure_conditions = conditions.fetch(:failure_conditions, [])
59
+ end
60
+
61
+ def rollout_successful?(instance_data)
62
+ @success_conditions.all? do |query|
63
+ query[:path].first(instance_data) == query[:value]
64
+ end
65
+ end
66
+
67
+ def rollout_failed?(instance_data)
68
+ @failure_conditions.any? do |query|
69
+ query[:path].first(instance_data) == query[:value]
70
+ end
71
+ end
72
+
73
+ def failure_messages(instance_data)
74
+ @failure_conditions.map do |query|
75
+ next unless query[:path].first(instance_data) == query[:value]
76
+ query[:custom_error_msg].presence || query[:error_msg_path]&.first(instance_data)
77
+ end.compact
78
+ end
79
+
80
+ def validate!
81
+ errors = validate_conditions(@success_conditions, 'success_conditions')
82
+ errors += validate_conditions(@failure_conditions, 'failure_conditions', required: false)
83
+ raise RolloutConditionsError, errors.join(", ") unless errors.empty?
84
+ end
85
+
86
+ private
87
+
88
+ def validate_conditions(conditions, source_key, required: true)
89
+ return [] unless conditions.present? || required
90
+ errors = []
91
+ errors << "#{source_key} should be Array but found #{conditions.class}" unless conditions.is_a?(Array)
92
+ return errors if errors.present?
93
+ errors << "#{source_key} must contain at least one entry" if conditions.empty?
94
+ return errors if errors.present?
95
+
96
+ conditions.each do |query|
97
+ missing = [:path, :value].reject { |k| query.key?(k) }
98
+ errors << "Missing required key(s) for #{source_key.singularize}: #{missing}" if missing.present?
99
+ end
100
+ errors
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+ require 'tempfile'
3
+
4
+ require 'krane/common'
5
+ require 'krane/kubeclient_builder'
6
+ require 'krane/kubectl'
7
+ require 'krane/resource_cache'
8
+ require 'krane/resource_watcher'
9
+ require 'krane/kubernetes_resource'
10
+ require 'krane/kubernetes_resource/pod'
11
+ require 'krane/runner_task_config_validator'
12
+
13
+ module Krane
14
+ # Run a pod that exits upon completing a task
15
+ class RunnerTask
16
+ class TaskTemplateMissingError < TaskConfigurationError; end
17
+
18
+ attr_reader :pod_name
19
+
20
+ # Initializes the runner task
21
+ #
22
+ # @param namespace [String] Kubernetes namespace (*required*)
23
+ # @param context [String] Kubernetes context / cluster (*required*)
24
+ # @param logger [Object] Logger object (defaults to an instance of Krane::FormattedLogger)
25
+ # @param global_timeout [Integer] Timeout in seconds
26
+ def initialize(namespace:, context:, logger: nil, global_timeout: nil)
27
+ @logger = logger || Krane::FormattedLogger.build(namespace, context)
28
+ @task_config = Krane::TaskConfig.new(context, namespace, @logger)
29
+ @namespace = namespace
30
+ @context = context
31
+ @global_timeout = global_timeout
32
+ end
33
+
34
+ # Runs the task, returning a boolean representing success or failure
35
+ #
36
+ # @return [Boolean]
37
+ def run(*args)
38
+ run!(*args)
39
+ true
40
+ rescue DeploymentTimeoutError, FatalDeploymentError
41
+ false
42
+ end
43
+
44
+ # Runs the task, raising exceptions in case of issues
45
+ #
46
+ # @param template [String] The filename of the template you'll be rendering (*required*)
47
+ # @param command [Array<String>] Override the default command in the container image
48
+ # @param arguments [Array<String>] Override the default arguments for the command
49
+ # @param env_vars [Array<String>] List of env vars
50
+ # @param verify_result [Boolean] Wait for completion and verify pod success
51
+ #
52
+ # @return [nil]
53
+ def run!(template:, command:, arguments:, env_vars: [], verify_result: true)
54
+ start = Time.now.utc
55
+ @logger.reset
56
+
57
+ @logger.phase_heading("Initializing task")
58
+
59
+ @logger.info("Validating configuration")
60
+ verify_config!(template)
61
+ @logger.info("Using namespace '#{@namespace}' in context '#{@context}'")
62
+
63
+ pod = build_pod(template, command, arguments, env_vars, verify_result)
64
+ validate_pod(pod)
65
+
66
+ @logger.phase_heading("Running pod")
67
+ create_pod(pod)
68
+
69
+ if verify_result
70
+ @logger.phase_heading("Streaming logs")
71
+ watch_pod(pod)
72
+ else
73
+ record_status_once(pod)
74
+ end
75
+ StatsD.client.distribution('task_runner.duration', StatsD.duration(start), tags: statsd_tags('success'))
76
+ @logger.print_summary(:success)
77
+ rescue DeploymentTimeoutError
78
+ StatsD.client.distribution('task_runner.duration', StatsD.duration(start), tags: statsd_tags('timeout'))
79
+ @logger.print_summary(:timed_out)
80
+ raise
81
+ rescue FatalDeploymentError
82
+ StatsD.client.distribution('task_runner.duration', StatsD.duration(start), tags: statsd_tags('failure'))
83
+ @logger.print_summary(:failure)
84
+ raise
85
+ end
86
+
87
+ private
88
+
89
+ def create_pod(pod)
90
+ @logger.info("Creating pod '#{pod.name}'")
91
+ pod.deploy_started_at = Time.now.utc
92
+ kubeclient.create_pod(pod.to_kubeclient_resource)
93
+ @pod_name = pod.name
94
+ @logger.info("Pod creation succeeded")
95
+ rescue Kubeclient::HttpError => e
96
+ msg = "Failed to create pod: #{e.class.name}: #{e.message}"
97
+ @logger.summary.add_paragraph(msg)
98
+ raise FatalDeploymentError, msg
99
+ end
100
+
101
+ def build_pod(template_name, command, args, env_vars, verify_result)
102
+ task_template = get_template(template_name)
103
+ @logger.info("Using template '#{template_name}'")
104
+ pod_template = build_pod_definition(task_template)
105
+ set_container_overrides!(pod_template, command, args, env_vars)
106
+ ensure_valid_restart_policy!(pod_template, verify_result)
107
+ Pod.new(namespace: @namespace, context: @context, logger: @logger, stream_logs: true,
108
+ definition: pod_template.to_hash.deep_stringify_keys, statsd_tags: [])
109
+ end
110
+
111
+ def validate_pod(pod)
112
+ pod.validate_definition(kubectl)
113
+ end
114
+
115
+ def watch_pod(pod)
116
+ rw = ResourceWatcher.new(resources: [pod], timeout: @global_timeout,
117
+ operation_name: "run", task_config: @task_config)
118
+ rw.run(delay_sync: 1, reminder_interval: 30.seconds)
119
+ raise DeploymentTimeoutError if pod.deploy_timed_out?
120
+ raise FatalDeploymentError if pod.deploy_failed?
121
+ end
122
+
123
+ def record_status_once(pod)
124
+ cache = ResourceCache.new(@task_config)
125
+ pod.sync(cache)
126
+ warning = <<~STRING
127
+ #{ColorizedString.new('Result verification is disabled for this task.').yellow}
128
+ The following status was observed immediately after pod creation:
129
+ #{pod.pretty_status}
130
+ STRING
131
+ @logger.summary.add_paragraph(warning)
132
+ end
133
+
134
+ def verify_config!(task_template)
135
+ task_config_validator = RunnerTaskConfigValidator.new(task_template, @task_config, kubectl,
136
+ kubeclient_builder)
137
+ unless task_config_validator.valid?
138
+ @logger.summary.add_action("Configuration invalid")
139
+ @logger.summary.add_paragraph([task_config_validator.errors].map { |err| "- #{err}" }.join("\n"))
140
+ raise Krane::TaskConfigurationError
141
+ end
142
+ end
143
+
144
+ def get_template(template_name)
145
+ pod_template = kubeclient.get_pod_template(template_name, @namespace)
146
+ pod_template.template
147
+ rescue Kubeclient::ResourceNotFoundError
148
+ msg = "Pod template `#{template_name}` not found in namespace `#{@namespace}`, context `#{@context}`"
149
+ @logger.summary.add_paragraph(msg)
150
+ raise TaskTemplateMissingError, msg
151
+ rescue Kubeclient::HttpError => error
152
+ raise FatalKubeAPIError, "Error retrieving pod template: #{error.class.name}: #{error.message}"
153
+ end
154
+
155
+ def build_pod_definition(base_template)
156
+ pod_definition = base_template.dup
157
+ pod_definition.kind = 'Pod'
158
+ pod_definition.apiVersion = 'v1'
159
+ pod_definition.metadata.namespace = @namespace
160
+
161
+ unique_name = pod_definition.metadata.name + "-" + SecureRandom.hex(8)
162
+ @logger.warn("Name is too long, using '#{unique_name[0..62]}'") if unique_name.length > 63
163
+ pod_definition.metadata.name = unique_name[0..62]
164
+
165
+ pod_definition
166
+ end
167
+
168
+ def set_container_overrides!(pod_definition, command, args, env_vars)
169
+ container = pod_definition.spec.containers.find { |cont| cont.name == 'task-runner' }
170
+ if container.nil?
171
+ message = "Pod spec does not contain a template container called 'task-runner'"
172
+ @logger.summary.add_paragraph(message)
173
+ raise TaskConfigurationError, message
174
+ end
175
+
176
+ container.command = command if command
177
+ container.args = args if args
178
+
179
+ env_args = env_vars.map do |env|
180
+ key, value = env.split('=', 2)
181
+ { name: key, value: value }
182
+ end
183
+ container.env ||= []
184
+ container.env = container.env.map(&:to_h) + env_args
185
+ end
186
+
187
+ def ensure_valid_restart_policy!(template, verify)
188
+ restart_policy = template.spec.restartPolicy
189
+ if verify && restart_policy != "Never"
190
+ @logger.warn("Changed Pod RestartPolicy from '#{restart_policy}' to 'Never'. Disable "\
191
+ "result verification to use '#{restart_policy}'.")
192
+ template.spec.restartPolicy = "Never"
193
+ end
194
+ end
195
+
196
+ def kubectl
197
+ @kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
198
+ end
199
+
200
+ def kubeclient
201
+ @kubeclient ||= kubeclient_builder.build_v1_kubeclient(@context)
202
+ end
203
+
204
+ def kubeclient_builder
205
+ @kubeclient_builder ||= KubeclientBuilder.new
206
+ end
207
+
208
+ def statsd_tags(status)
209
+ %W(namespace:#{@namespace} context:#{@context} status:#{status})
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Krane
3
+ class RunnerTaskConfigValidator < TaskConfigValidator
4
+ def initialize(template, *arguments)
5
+ super(*arguments)
6
+ @template = template
7
+ @validations += %i(validate_template)
8
+ end
9
+
10
+ private
11
+
12
+ def validate_template
13
+ if @template.blank?
14
+ @errors << "Task template name can't be nil"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ require 'statsd-instrument'
3
+ require 'logger'
4
+
5
+ module Krane
6
+ class StatsD
7
+ PREFIX = "Krane"
8
+
9
+ def self.duration(start_time)
10
+ (Time.now.utc - start_time).round(1)
11
+ end
12
+
13
+ def self.client
14
+ @client ||= begin
15
+ sink = if ::StatsD::Instrument::Environment.current.env.fetch('STATSD_ENV', nil) == 'development'
16
+ ::StatsD::Instrument::LogSink.new(Logger.new($stderr))
17
+ elsif (addr = ::StatsD::Instrument::Environment.current.env.fetch('STATSD_ADDR', nil))
18
+ ::StatsD::Instrument::UDPSink.for_addr(addr)
19
+ else
20
+ ::StatsD::Instrument::NullSink.new
21
+ end
22
+ ::StatsD::Instrument::Client.new(prefix: PREFIX, sink: sink, default_sample_rate: 1.0)
23
+ end
24
+ end
25
+
26
+ module MeasureMethods
27
+ def measure_method(method_name, metric = nil)
28
+ unless method_defined?(method_name) || private_method_defined?(method_name)
29
+ raise NotImplementedError, "Cannot instrument undefined method #{method_name}"
30
+ end
31
+
32
+ unless const_defined?("InstrumentationProxy")
33
+ const_set("InstrumentationProxy", Module.new)
34
+ should_prepend = true
35
+ end
36
+
37
+ metric ||= "#{method_name}.duration"
38
+ self::InstrumentationProxy.send(:define_method, method_name) do |*args, &block|
39
+ begin
40
+ start_time = Time.now.utc
41
+ super(*args, &block)
42
+ rescue
43
+ error = true
44
+ raise
45
+ ensure
46
+ dynamic_tags = send(:statsd_tags) if respond_to?(:statsd_tags, true)
47
+ dynamic_tags ||= {}
48
+ if error
49
+ dynamic_tags[:error] = error if dynamic_tags.is_a?(Hash)
50
+ dynamic_tags << "error:#{error}" if dynamic_tags.is_a?(Array)
51
+ end
52
+
53
+ Krane::StatsD.client.distribution(
54
+ metric,
55
+ Krane::StatsD.duration(start_time),
56
+ tags: dynamic_tags
57
+ )
58
+ end
59
+ end
60
+
61
+ prepend(self::InstrumentationProxy) if should_prepend
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'krane/cluster_resource_discovery'
4
+
5
+ module Krane
6
+ class TaskConfig
7
+ attr_reader :context, :namespace, :logger
8
+
9
+ def initialize(context, namespace, logger = nil)
10
+ @context = context
11
+ @namespace = namespace
12
+ @logger = logger || FormattedLogger.build(@namespace, @context)
13
+ end
14
+
15
+ def global_kinds
16
+ @global_kinds ||= begin
17
+ cluster_resource_discoverer = ClusterResourceDiscovery.new(task_config: self)
18
+ cluster_resource_discoverer.fetch_resources(namespaced: false).map { |g| g["kind"] }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ module Krane
3
+ class TaskConfigValidator
4
+ DEFAULT_VALIDATIONS = %i(
5
+ validate_kubeconfig
6
+ validate_context_exists_in_kubeconfig
7
+ validate_context_reachable
8
+ validate_server_version
9
+ validate_namespace_exists
10
+ ).freeze
11
+
12
+ delegate :context, :namespace, :logger, to: :@task_config
13
+
14
+ def initialize(task_config, kubectl, kubeclient_builder, only: nil)
15
+ @task_config = task_config
16
+ @kubectl = kubectl
17
+ @kubeclient_builder = kubeclient_builder
18
+ @errors = nil
19
+ @validations = only || DEFAULT_VALIDATIONS
20
+ end
21
+
22
+ def valid?
23
+ @errors = []
24
+ @validations.each do |validator_name|
25
+ break if @errors.present?
26
+ send(validator_name)
27
+ end
28
+ @errors.empty?
29
+ end
30
+
31
+ def errors
32
+ valid?
33
+ @errors
34
+ end
35
+
36
+ private
37
+
38
+ def validate_kubeconfig
39
+ @errors += @kubeclient_builder.validate_config_files
40
+ end
41
+
42
+ def validate_context_exists_in_kubeconfig
43
+ unless context.present?
44
+ return @errors << "Context can not be blank"
45
+ end
46
+
47
+ _, err, st = @kubectl.run("config", "get-contexts", context, "-o", "name",
48
+ use_namespace: false, use_context: false, log_failure: false)
49
+
50
+ unless st.success?
51
+ @errors << if err.match("error: context #{context} not found")
52
+ "Context #{context} missing from your kubeconfig file(s)"
53
+ else
54
+ "Something went wrong. #{err} "
55
+ end
56
+ end
57
+ end
58
+
59
+ def validate_context_reachable
60
+ _, err, st = @kubectl.run("get", "namespaces", "-o", "name",
61
+ use_namespace: false, log_failure: false)
62
+
63
+ unless st.success?
64
+ @errors << "Something went wrong connecting to #{context}. #{err} "
65
+ end
66
+ end
67
+
68
+ def validate_namespace_exists
69
+ unless namespace.present?
70
+ return @errors << "Namespace can not be blank"
71
+ end
72
+
73
+ _, err, st = @kubectl.run("get", "namespace", "-o", "name", namespace,
74
+ use_namespace: false, log_failure: false)
75
+
76
+ unless st.success?
77
+ @errors << if err.match("Error from server [(]NotFound[)]: namespace")
78
+ "Could not find Namespace: #{namespace} in Context: #{context}"
79
+ else
80
+ "Could not connect to kubernetes cluster. #{err}"
81
+ end
82
+ end
83
+ end
84
+
85
+ def validate_server_version
86
+ if @kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
87
+ logger.warn(server_version_warning(@kubectl.server_version))
88
+ end
89
+ end
90
+
91
+ def server_version_warning(server_version)
92
+ "Minimum cluster version requirement of #{MIN_KUBE_VERSION} not met. "\
93
+ "Using #{server_version} could result in unexpected behavior as it is no longer tested against"
94
+ end
95
+ end
96
+ end