tobsch-krane 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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