kubernetes-deploy 0.29.0 → 1.0.0.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.buildkite/pipeline.nightly.yml +7 -0
- data/.rubocop.yml +0 -12
- data/.shopify-build/{kubernetes-deploy.yml → krane.yml} +8 -2
- data/1.0-Upgrade.md +109 -0
- data/CHANGELOG.md +60 -0
- data/CONTRIBUTING.md +2 -2
- data/Gemfile +1 -0
- data/README.md +86 -2
- data/dev.yml +3 -1
- data/dev/flamegraph-from-tests +1 -1
- data/exe/kubernetes-deploy +12 -9
- data/exe/kubernetes-render +9 -7
- data/exe/kubernetes-restart +3 -3
- data/exe/kubernetes-run +1 -1
- data/kubernetes-deploy.gemspec +5 -5
- data/lib/krane.rb +5 -3
- data/lib/{kubernetes-deploy → krane}/bindings_parser.rb +1 -1
- data/lib/krane/cli/deploy_command.rb +25 -13
- data/lib/krane/cli/global_deploy_command.rb +55 -0
- data/lib/krane/cli/krane.rb +12 -3
- data/lib/krane/cli/render_command.rb +19 -9
- data/lib/krane/cli/restart_command.rb +4 -4
- data/lib/krane/cli/run_command.rb +4 -4
- data/lib/krane/cli/version_command.rb +1 -1
- data/lib/krane/cluster_resource_discovery.rb +113 -0
- data/lib/{kubernetes-deploy → krane}/common.rb +8 -9
- data/lib/krane/concerns/template_reporting.rb +29 -0
- data/lib/{kubernetes-deploy → krane}/concurrency.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/container_logs.rb +3 -2
- data/lib/{kubernetes-deploy → krane}/deferred_summary_logging.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/delayed_exceptions.rb +0 -0
- data/lib/krane/deploy_task.rb +16 -0
- data/lib/krane/deploy_task_config_validator.rb +29 -0
- data/lib/krane/deprecated_deploy_task.rb +404 -0
- data/lib/{kubernetes-deploy → krane}/duration_parser.rb +1 -3
- data/lib/{kubernetes-deploy → krane}/ejson_secret_provisioner.rb +10 -13
- data/lib/krane/errors.rb +28 -0
- data/lib/{kubernetes-deploy → krane}/formatted_logger.rb +2 -2
- data/lib/krane/global_deploy_task.rb +210 -0
- data/lib/krane/global_deploy_task_config_validator.rb +12 -0
- data/lib/{kubernetes-deploy → krane}/kubeclient_builder.rb +13 -5
- data/lib/{kubernetes-deploy → krane}/kubectl.rb +14 -16
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource.rb +110 -27
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cloudsql.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/config_map.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/cron_job.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/custom_resource_definition.rb +1 -5
- data/lib/krane/kubernetes_resource/daemon_set.rb +90 -0
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/deployment.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/horizontal_pod_autoscaler.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/ingress.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/job.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/network_policy.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/persistent_volume_claim.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod.rb +6 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_disruption_budget.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_set_base.rb +3 -3
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/pod_template.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/replica_set.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/resource_quota.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/role_binding.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/secret.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/service_account.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/kubernetes_resource/stateful_set.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/label_selector.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/oj.rb +0 -0
- data/lib/{kubernetes-deploy → krane}/options_helper.rb +2 -2
- data/lib/{kubernetes-deploy → krane}/remote_logs.rb +2 -2
- data/lib/krane/render_task.rb +149 -0
- data/lib/{kubernetes-deploy → krane}/renderer.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/resource_cache.rb +10 -9
- data/lib/krane/resource_deployer.rb +265 -0
- data/lib/{kubernetes-deploy → krane}/resource_watcher.rb +24 -25
- data/lib/krane/restart_task.rb +228 -0
- data/lib/{kubernetes-deploy → krane}/rollout_conditions.rb +1 -1
- data/lib/krane/runner_task.rb +212 -0
- data/lib/{kubernetes-deploy → krane}/runner_task_config_validator.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/statsd.rb +13 -27
- data/lib/krane/task_config.rb +22 -0
- data/lib/{kubernetes-deploy → krane}/task_config_validator.rb +1 -1
- data/lib/{kubernetes-deploy → krane}/template_sets.rb +5 -5
- data/lib/krane/version.rb +4 -0
- data/lib/kubernetes-deploy/deploy_task.rb +6 -608
- data/lib/kubernetes-deploy/errors.rb +1 -26
- data/lib/kubernetes-deploy/render_task.rb +5 -122
- data/lib/kubernetes-deploy/rescue_krane_exceptions.rb +18 -0
- data/lib/kubernetes-deploy/restart_task.rb +6 -198
- data/lib/kubernetes-deploy/runner_task.rb +6 -184
- metadata +96 -70
- data/lib/kubernetes-deploy/cluster_resource_discovery.rb +0 -34
- data/lib/kubernetes-deploy/kubernetes_resource/daemon_set.rb +0 -54
- data/lib/kubernetes-deploy/task_config.rb +0 -16
- data/lib/kubernetes-deploy/version.rb +0 -4
@@ -2,13 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'active_support/duration'
|
4
4
|
|
5
|
-
module
|
6
|
-
##
|
5
|
+
module Krane
|
7
6
|
# This class is a less strict extension of ActiveSupport::Duration::ISO8601Parser.
|
8
7
|
# In addition to full ISO8601 durations, it can parse unprefixed ISO8601 time components (e.g. '1H').
|
9
8
|
# It is also case-insensitive.
|
10
9
|
# For example, this class considers the values "1H", "1h" and "PT1H" to be valid and equivalent.
|
11
|
-
|
12
10
|
class DurationParser
|
13
11
|
class ParsingError < ArgumentError; end
|
14
12
|
|
@@ -2,9 +2,9 @@
|
|
2
2
|
require 'json'
|
3
3
|
require 'base64'
|
4
4
|
require 'open3'
|
5
|
-
require '
|
5
|
+
require 'krane/kubectl'
|
6
6
|
|
7
|
-
module
|
7
|
+
module Krane
|
8
8
|
class EjsonSecretError < FatalDeploymentError
|
9
9
|
def initialize(msg)
|
10
10
|
super("Generation of Kubernetes secrets from ejson failed: #{msg}")
|
@@ -16,19 +16,16 @@ module KubernetesDeploy
|
|
16
16
|
EJSON_SECRET_KEY = "kubernetes_secrets"
|
17
17
|
EJSON_SECRETS_FILE = "secrets.ejson"
|
18
18
|
EJSON_KEYS_SECRET = "ejson-keys"
|
19
|
+
delegate :namespace, :context, :logger, to: :@task_config
|
19
20
|
|
20
|
-
def initialize(
|
21
|
-
@namespace = namespace
|
22
|
-
@context = context
|
21
|
+
def initialize(task_config:, ejson_keys_secret:, ejson_file:, statsd_tags:, selector: nil)
|
23
22
|
@ejson_keys_secret = ejson_keys_secret
|
24
23
|
@ejson_file = ejson_file
|
25
|
-
@logger = logger
|
26
24
|
@statsd_tags = statsd_tags
|
27
25
|
@selector = selector
|
26
|
+
@task_config = task_config
|
28
27
|
@kubectl = Kubectl.new(
|
29
|
-
|
30
|
-
context: @context,
|
31
|
-
logger: @logger,
|
28
|
+
task_config: @task_config,
|
32
29
|
log_failure_by_default: false,
|
33
30
|
output_is_sensitive_default: true # output may contain ejson secrets
|
34
31
|
)
|
@@ -48,7 +45,7 @@ module KubernetesDeploy
|
|
48
45
|
with_decrypted_ejson do |decrypted|
|
49
46
|
secrets = decrypted[EJSON_SECRET_KEY]
|
50
47
|
unless secrets.present?
|
51
|
-
|
48
|
+
logger.warn("#{EJSON_SECRETS_FILE} does not have key #{EJSON_SECRET_KEY}."\
|
52
49
|
"No secrets will be created.")
|
53
50
|
return []
|
54
51
|
end
|
@@ -108,14 +105,14 @@ module KubernetesDeploy
|
|
108
105
|
'metadata' => {
|
109
106
|
"name" => secret_name,
|
110
107
|
"labels" => labels,
|
111
|
-
"namespace" =>
|
108
|
+
"namespace" => namespace,
|
112
109
|
"annotations" => { EJSON_SECRET_ANNOTATION => "true" },
|
113
110
|
},
|
114
111
|
"data" => encoded_data,
|
115
112
|
}
|
116
113
|
|
117
|
-
|
118
|
-
namespace:
|
114
|
+
Krane::Secret.build(
|
115
|
+
namespace: namespace, context: context, logger: logger, definition: secret, statsd_tags: @statsd_tags,
|
119
116
|
)
|
120
117
|
end
|
121
118
|
|
data/lib/krane/errors.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Krane
|
4
|
+
class FatalDeploymentError < StandardError; end
|
5
|
+
class FatalKubeAPIError < FatalDeploymentError; end
|
6
|
+
class KubectlError < StandardError; end
|
7
|
+
class TaskConfigurationError < FatalDeploymentError; end
|
8
|
+
|
9
|
+
class InvalidTemplateError < FatalDeploymentError
|
10
|
+
attr_reader :content
|
11
|
+
attr_accessor :filename
|
12
|
+
def initialize(err, filename: nil, content: nil)
|
13
|
+
@filename = filename
|
14
|
+
@content = content
|
15
|
+
super(err)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class DeploymentTimeoutError < FatalDeploymentError; end
|
20
|
+
|
21
|
+
class EjsonPrunableError < FatalDeploymentError
|
22
|
+
def initialize
|
23
|
+
super("Found #{KubernetesResource::LAST_APPLIED_ANNOTATION} annotation on " \
|
24
|
+
"#{EjsonSecretProvisioner::EJSON_KEYS_SECRET} secret. " \
|
25
|
+
"krane will not continue since it is extremely unlikely that this secret should be pruned.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'logger'
|
3
3
|
require 'colorized_string'
|
4
|
-
require '
|
4
|
+
require 'krane/deferred_summary_logging'
|
5
5
|
|
6
|
-
module
|
6
|
+
module Krane
|
7
7
|
class FormattedLogger < Logger
|
8
8
|
include DeferredSummaryLogging
|
9
9
|
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
require 'krane/common'
|
5
|
+
require 'krane/concurrency'
|
6
|
+
require 'krane/resource_cache'
|
7
|
+
require 'krane/kubectl'
|
8
|
+
require 'krane/kubeclient_builder'
|
9
|
+
require 'krane/cluster_resource_discovery'
|
10
|
+
require 'krane/template_sets'
|
11
|
+
require 'krane/resource_deployer'
|
12
|
+
require 'krane/kubernetes_resource'
|
13
|
+
require 'krane/global_deploy_task_config_validator'
|
14
|
+
require 'krane/concerns/template_reporting'
|
15
|
+
|
16
|
+
%w(
|
17
|
+
custom_resource
|
18
|
+
custom_resource_definition
|
19
|
+
).each do |subresource|
|
20
|
+
require "krane/kubernetes_resource/#{subresource}"
|
21
|
+
end
|
22
|
+
|
23
|
+
module Krane
|
24
|
+
# Ship global resources to a context
|
25
|
+
class GlobalDeployTask
|
26
|
+
extend Krane::StatsD::MeasureMethods
|
27
|
+
include TemplateReporting
|
28
|
+
delegate :context, :logger, :global_kinds, to: :@task_config
|
29
|
+
|
30
|
+
# Initializes the deploy task
|
31
|
+
#
|
32
|
+
# @param context [String] Kubernetes context
|
33
|
+
# @param global_timeout [Integer] Timeout in seconds
|
34
|
+
# @param selector [Hash] Selector(s) parsed by Krane::LabelSelector
|
35
|
+
# @param template_paths [Array<String>] An array of template paths
|
36
|
+
def initialize(context:, global_timeout: nil, selector: nil, filenames: [], logger: nil)
|
37
|
+
template_paths = filenames.map { |path| File.expand_path(path) }
|
38
|
+
|
39
|
+
@task_config = TaskConfig.new(context, nil, logger)
|
40
|
+
@template_sets = TemplateSets.from_dirs_and_files(paths: template_paths,
|
41
|
+
logger: @task_config.logger)
|
42
|
+
@global_timeout = global_timeout
|
43
|
+
@selector = selector
|
44
|
+
end
|
45
|
+
|
46
|
+
# Runs the task, returning a boolean representing success or failure
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
def run(*args)
|
50
|
+
run!(*args)
|
51
|
+
true
|
52
|
+
rescue FatalDeploymentError
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Runs the task, raising exceptions in case of issues
|
57
|
+
#
|
58
|
+
# @param verify_result [Boolean] Wait for completion and verify success
|
59
|
+
# @param prune [Boolean] Enable deletion of resources that match the provided
|
60
|
+
# selector and do not appear in the template dir
|
61
|
+
#
|
62
|
+
# @return [nil]
|
63
|
+
def run!(verify_result: true, prune: true)
|
64
|
+
start = Time.now.utc
|
65
|
+
logger.reset
|
66
|
+
|
67
|
+
logger.phase_heading("Initializing deploy")
|
68
|
+
validate_configuration
|
69
|
+
resources = discover_resources
|
70
|
+
validate_resources(resources)
|
71
|
+
|
72
|
+
logger.phase_heading("Checking initial resource statuses")
|
73
|
+
check_initial_status(resources)
|
74
|
+
|
75
|
+
logger.phase_heading("Deploying all resources")
|
76
|
+
deploy!(resources, verify_result, prune)
|
77
|
+
|
78
|
+
StatsD.client.event("Deployment succeeded",
|
79
|
+
"Successfully deployed all resources to #{context}",
|
80
|
+
alert_type: "success", tags: statsd_tags + %w(status:success))
|
81
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
82
|
+
tags: statsd_tags << "status:success")
|
83
|
+
logger.print_summary(:success)
|
84
|
+
rescue Krane::DeploymentTimeoutError
|
85
|
+
logger.print_summary(:timed_out)
|
86
|
+
StatsD.client.event("Deployment timed out",
|
87
|
+
"One or more resources failed to deploy to #{context} in time",
|
88
|
+
alert_type: "error", tags: statsd_tags + %w(status:timeout))
|
89
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
90
|
+
tags: statsd_tags << "status:timeout")
|
91
|
+
raise
|
92
|
+
rescue Krane::FatalDeploymentError => error
|
93
|
+
logger.summary.add_action(error.message) if error.message != error.class.to_s
|
94
|
+
logger.print_summary(:failure)
|
95
|
+
StatsD.client.event("Deployment failed",
|
96
|
+
"One or more resources failed to deploy to #{context}",
|
97
|
+
alert_type: "error", tags: statsd_tags + %w(status:failed))
|
98
|
+
StatsD.client.distribution('all_resources.duration', StatsD.duration(start),
|
99
|
+
tags: statsd_tags << "status:failed")
|
100
|
+
raise
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def deploy!(resources, verify_result, prune)
|
106
|
+
resource_deployer = ResourceDeployer.new(task_config: @task_config,
|
107
|
+
prune_whitelist: prune_whitelist, max_watch_seconds: @global_timeout,
|
108
|
+
selector: @selector, statsd_tags: statsd_tags)
|
109
|
+
resource_deployer.deploy!(resources, verify_result, prune)
|
110
|
+
end
|
111
|
+
|
112
|
+
def validate_configuration
|
113
|
+
task_config_validator = GlobalDeployTaskConfigValidator.new(@task_config,
|
114
|
+
kubectl, kubeclient_builder)
|
115
|
+
errors = []
|
116
|
+
errors += task_config_validator.errors
|
117
|
+
errors += @template_sets.validate
|
118
|
+
errors << "Selector is required" unless @selector.to_h.present?
|
119
|
+
unless errors.empty?
|
120
|
+
add_para_from_list(logger: logger, action: "Configuration invalid", enum: errors)
|
121
|
+
raise TaskConfigurationError
|
122
|
+
end
|
123
|
+
|
124
|
+
logger.info("Using resource selector #{@selector}")
|
125
|
+
logger.info("All required parameters and files are present")
|
126
|
+
end
|
127
|
+
measure_method(:validate_configuration)
|
128
|
+
|
129
|
+
def validate_resources(resources)
|
130
|
+
validate_globals(resources)
|
131
|
+
|
132
|
+
Concurrency.split_across_threads(resources) do |r|
|
133
|
+
r.validate_definition(@kubectl, selector: @selector)
|
134
|
+
end
|
135
|
+
|
136
|
+
resources.select(&:has_warnings?).each do |resource|
|
137
|
+
record_warnings(logger: logger, warning: resource.validation_warning_msg,
|
138
|
+
filename: File.basename(resource.file_path))
|
139
|
+
end
|
140
|
+
|
141
|
+
failed_resources = resources.select(&:validation_failed?)
|
142
|
+
if failed_resources.present?
|
143
|
+
failed_resources.each do |r|
|
144
|
+
content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
|
145
|
+
record_invalid_template(logger: logger, err: r.validation_error_msg,
|
146
|
+
filename: File.basename(r.file_path), content: content)
|
147
|
+
end
|
148
|
+
raise FatalDeploymentError, "Template validation failed"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
measure_method(:validate_resources)
|
152
|
+
|
153
|
+
def validate_globals(resources)
|
154
|
+
return unless (namespaced = resources.reject(&:global?).presence)
|
155
|
+
namespaced_names = namespaced.map do |resource|
|
156
|
+
"#{resource.name} (#{resource.type}) in #{File.basename(resource.file_path)}"
|
157
|
+
end
|
158
|
+
namespaced_names = FormattedLogger.indent_four(namespaced_names.join("\n"))
|
159
|
+
|
160
|
+
logger.summary.add_paragraph(ColorizedString.new("Namespaced resources:\n#{namespaced_names}").yellow)
|
161
|
+
raise FatalDeploymentError, "This command cannot deploy namespaced resources"
|
162
|
+
end
|
163
|
+
|
164
|
+
def discover_resources
|
165
|
+
logger.info("Discovering resources:")
|
166
|
+
resources = []
|
167
|
+
crds_by_kind = cluster_resource_discoverer.crds.map { |crd| [crd.name, crd] }.to_h
|
168
|
+
@template_sets.with_resource_definitions do |r_def|
|
169
|
+
crd = crds_by_kind[r_def["kind"]]&.first
|
170
|
+
r = KubernetesResource.build(context: context, logger: logger, definition: r_def,
|
171
|
+
crd: crd, global_names: global_kinds, statsd_tags: statsd_tags)
|
172
|
+
resources << r
|
173
|
+
logger.info(" - #{r.id}")
|
174
|
+
end
|
175
|
+
|
176
|
+
resources.sort
|
177
|
+
rescue InvalidTemplateError => e
|
178
|
+
record_invalid_template(logger: logger, err: e.message, filename: e.filename, content: e.content)
|
179
|
+
raise FatalDeploymentError, "Failed to parse template"
|
180
|
+
end
|
181
|
+
measure_method(:discover_resources)
|
182
|
+
|
183
|
+
def cluster_resource_discoverer
|
184
|
+
@cluster_resource_discoverer ||= ClusterResourceDiscovery.new(task_config: @task_config)
|
185
|
+
end
|
186
|
+
|
187
|
+
def statsd_tags
|
188
|
+
%W(context:#{context})
|
189
|
+
end
|
190
|
+
|
191
|
+
def kubectl
|
192
|
+
@kubectl ||= Kubectl.new(task_config: @task_config, log_failure_by_default: true)
|
193
|
+
end
|
194
|
+
|
195
|
+
def kubeclient_builder
|
196
|
+
@kubeclient_builder ||= KubeclientBuilder.new
|
197
|
+
end
|
198
|
+
|
199
|
+
def prune_whitelist
|
200
|
+
cluster_resource_discoverer.prunable_resources(namespaced: false)
|
201
|
+
end
|
202
|
+
|
203
|
+
def check_initial_status(resources)
|
204
|
+
cache = ResourceCache.new(@task_config)
|
205
|
+
Concurrency.split_across_threads(resources) { |r| r.sync(cache) }
|
206
|
+
resources.each { |r| logger.info(r.pretty_status) }
|
207
|
+
end
|
208
|
+
measure_method(:check_initial_status, "initial_status.duration")
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'krane/task_config_validator'
|
4
|
+
|
5
|
+
module Krane
|
6
|
+
class GlobalDeployTaskConfigValidator < Krane::TaskConfigValidator
|
7
|
+
def initialize(*arguments)
|
8
|
+
super(*arguments)
|
9
|
+
@validations -= [:validate_namespace_exists]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'kubeclient'
|
3
3
|
|
4
|
-
module
|
4
|
+
module Krane
|
5
5
|
class KubeclientBuilder
|
6
6
|
class ContextMissingError < FatalDeploymentError
|
7
7
|
def initialize(context_name, kubeconfig)
|
@@ -55,9 +55,9 @@ module KubernetesDeploy
|
|
55
55
|
)
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
58
|
+
def build_apps_v1_kubeclient(context)
|
59
59
|
build_kubeclient(
|
60
|
-
api_version: "
|
60
|
+
api_version: "v1",
|
61
61
|
context: context,
|
62
62
|
endpoint_path: "/apis/apps"
|
63
63
|
)
|
@@ -103,6 +103,14 @@ module KubernetesDeploy
|
|
103
103
|
)
|
104
104
|
end
|
105
105
|
|
106
|
+
def build_scheduling_v1beta1_kubeclient(context)
|
107
|
+
build_kubeclient(
|
108
|
+
api_version: "v1beta1",
|
109
|
+
context: context,
|
110
|
+
endpoint_path: "/apis/scheduling.k8s.io"
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
106
114
|
def validate_config_files
|
107
115
|
errors = []
|
108
116
|
if @kubeconfig_files.empty?
|
@@ -137,8 +145,8 @@ module KubernetesDeploy
|
|
137
145
|
ssl_options: kube_context.ssl_options,
|
138
146
|
auth_options: kube_context.auth_options,
|
139
147
|
timeouts: {
|
140
|
-
open:
|
141
|
-
read:
|
148
|
+
open: Krane::Kubectl::DEFAULT_TIMEOUT,
|
149
|
+
read: Krane::Kubectl::DEFAULT_TIMEOUT,
|
142
150
|
}
|
143
151
|
)
|
144
152
|
client.discover
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'open3'
|
3
3
|
|
4
|
-
module
|
4
|
+
module Krane
|
5
5
|
class Kubectl
|
6
6
|
ERROR_MATCHERS = {
|
7
7
|
not_found: /NotFound/,
|
@@ -13,30 +13,28 @@ module KubernetesDeploy
|
|
13
13
|
|
14
14
|
class ResourceNotFoundError < StandardError; end
|
15
15
|
|
16
|
-
|
16
|
+
delegate :namespace, :context, :logger, to: :@task_config
|
17
|
+
|
18
|
+
def initialize(task_config:, log_failure_by_default:, default_timeout: DEFAULT_TIMEOUT,
|
17
19
|
output_is_sensitive_default: false)
|
18
|
-
@
|
19
|
-
@context = context
|
20
|
-
@logger = logger
|
20
|
+
@task_config = task_config
|
21
21
|
@log_failure_by_default = log_failure_by_default
|
22
22
|
@default_timeout = default_timeout
|
23
23
|
@output_is_sensitive_default = output_is_sensitive_default
|
24
|
-
|
25
|
-
raise ArgumentError, "namespace is required" if namespace.blank?
|
26
|
-
raise ArgumentError, "context is required" if context.blank?
|
27
24
|
end
|
28
25
|
|
29
26
|
def run(*args, log_failure: nil, use_context: true, use_namespace: true, output: nil,
|
30
27
|
raise_if_not_found: false, attempts: 1, output_is_sensitive: nil, retry_whitelist: nil)
|
28
|
+
raise ArgumentError, "namespace is required" if namespace.blank? && use_namespace
|
31
29
|
log_failure = @log_failure_by_default if log_failure.nil?
|
32
30
|
output_is_sensitive = @output_is_sensitive_default if output_is_sensitive.nil?
|
33
31
|
cmd = build_command_from_options(args, use_namespace, use_context, output)
|
34
32
|
out, err, st = nil
|
35
33
|
|
36
34
|
(1..attempts).to_a.each do |current_attempt|
|
37
|
-
|
35
|
+
logger.debug("Running command (attempt #{current_attempt}): #{cmd.join(' ')}")
|
38
36
|
out, err, st = Open3.capture3(*cmd)
|
39
|
-
|
37
|
+
logger.debug("Kubectl out: " + out.gsub(/\s+/, ' ')) unless output_is_sensitive
|
40
38
|
|
41
39
|
break if st.success?
|
42
40
|
raise(ResourceNotFoundError, err) if err.match(ERROR_MATCHERS[:not_found]) && raise_if_not_found
|
@@ -49,12 +47,12 @@ module KubernetesDeploy
|
|
49
47
|
else
|
50
48
|
"The following command failed and cannot be retried"
|
51
49
|
end
|
52
|
-
|
53
|
-
|
50
|
+
logger.warn("#{warning}: #{Shellwords.join(cmd)}")
|
51
|
+
logger.warn(err) unless output_is_sensitive
|
54
52
|
else
|
55
|
-
|
53
|
+
logger.debug("Kubectl err: #{output_is_sensitive ? '<suppressed sensitive output>' : err}")
|
56
54
|
end
|
57
|
-
StatsD.increment('kubectl.error', 1, tags: { context:
|
55
|
+
StatsD.client.increment('kubectl.error', 1, tags: { context: context, namespace: namespace, cmd: cmd[1] })
|
58
56
|
|
59
57
|
break unless retriable_err?(err, retry_whitelist) && current_attempt < attempts
|
60
58
|
sleep(retry_delay(current_attempt))
|
@@ -93,8 +91,8 @@ module KubernetesDeploy
|
|
93
91
|
|
94
92
|
def build_command_from_options(args, use_namespace, use_context, output)
|
95
93
|
cmd = ["kubectl"] + args
|
96
|
-
cmd.push("--namespace=#{
|
97
|
-
cmd.push("--context=#{
|
94
|
+
cmd.push("--namespace=#{namespace}") if use_namespace
|
95
|
+
cmd.push("--context=#{context}") if use_context
|
98
96
|
cmd.push("--output=#{output}") if output
|
99
97
|
cmd.push("--request-timeout=#{@default_timeout}") if @default_timeout
|
100
98
|
cmd
|