kube_deploy_tools 3.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +177 -0
- data/README.md +142 -0
- data/bin/deploy +60 -0
- data/bin/generate +28 -0
- data/bin/kdt +17 -0
- data/bin/make_configmap +20 -0
- data/bin/publish +28 -0
- data/bin/push +57 -0
- data/bin/render_deploys_hook +18 -0
- data/bin/templater +34 -0
- data/bin/upgrade +23 -0
- data/lib/kube_deploy_tools.rb +17 -0
- data/lib/kube_deploy_tools/artifact_registry.rb +30 -0
- data/lib/kube_deploy_tools/artifact_registry/driver.rb +13 -0
- data/lib/kube_deploy_tools/artifact_registry/driver_artifactory.rb +155 -0
- data/lib/kube_deploy_tools/artifact_registry/driver_base.rb +37 -0
- data/lib/kube_deploy_tools/artifact_registry/driver_gcs.rb +120 -0
- data/lib/kube_deploy_tools/built_artifacts_file.rb +28 -0
- data/lib/kube_deploy_tools/concurrency.rb +18 -0
- data/lib/kube_deploy_tools/deferred_summary_logging.rb +69 -0
- data/lib/kube_deploy_tools/deploy.rb +215 -0
- data/lib/kube_deploy_tools/deploy/options.rb +114 -0
- data/lib/kube_deploy_tools/deploy_config_file.rb +286 -0
- data/lib/kube_deploy_tools/deploy_config_file/deep_merge.rb +38 -0
- data/lib/kube_deploy_tools/deploy_config_file/util.rb +39 -0
- data/lib/kube_deploy_tools/errors.rb +5 -0
- data/lib/kube_deploy_tools/file_filter.rb +43 -0
- data/lib/kube_deploy_tools/formatted_logger.rb +59 -0
- data/lib/kube_deploy_tools/generate.rb +145 -0
- data/lib/kube_deploy_tools/generate/options.rb +66 -0
- data/lib/kube_deploy_tools/image_registry.rb +30 -0
- data/lib/kube_deploy_tools/image_registry/driver.rb +18 -0
- data/lib/kube_deploy_tools/image_registry/driver/aws.rb +121 -0
- data/lib/kube_deploy_tools/image_registry/driver/base.rb +50 -0
- data/lib/kube_deploy_tools/image_registry/driver/gcp.rb +71 -0
- data/lib/kube_deploy_tools/image_registry/driver/login.rb +26 -0
- data/lib/kube_deploy_tools/image_registry/driver/noop.rb +15 -0
- data/lib/kube_deploy_tools/image_registry/image.rb +17 -0
- data/lib/kube_deploy_tools/kdt.rb +52 -0
- data/lib/kube_deploy_tools/kubectl.rb +25 -0
- data/lib/kube_deploy_tools/kubernetes_resource.rb +57 -0
- data/lib/kube_deploy_tools/kubernetes_resource/deployment.rb +56 -0
- data/lib/kube_deploy_tools/make_configmap.rb +51 -0
- data/lib/kube_deploy_tools/make_configmap/options.rb +39 -0
- data/lib/kube_deploy_tools/object.rb +11 -0
- data/lib/kube_deploy_tools/publish.rb +40 -0
- data/lib/kube_deploy_tools/publish/options.rb +34 -0
- data/lib/kube_deploy_tools/push.rb +129 -0
- data/lib/kube_deploy_tools/push/options.rb +46 -0
- data/lib/kube_deploy_tools/render_deploys_hook.rb +95 -0
- data/lib/kube_deploy_tools/shellrunner.rb +46 -0
- data/lib/kube_deploy_tools/tag.rb +33 -0
- data/lib/kube_deploy_tools/templater.rb +63 -0
- data/lib/kube_deploy_tools/templater/options.rb +74 -0
- data/lib/kube_deploy_tools/version.rb +3 -0
- metadata +191 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative '../image'
|
5
|
+
require 'kube_deploy_tools/deploy_config_file/util'
|
6
|
+
require 'kube_deploy_tools/shellrunner'
|
7
|
+
|
8
|
+
module KubeDeployTools
|
9
|
+
class ImageRegistry::Driver::Aws < ImageRegistry::Driver::Base
|
10
|
+
include DeployConfigFileUtil
|
11
|
+
|
12
|
+
def initialize(h)
|
13
|
+
super(h)
|
14
|
+
|
15
|
+
check_and_err(
|
16
|
+
@registry.config&.has_key?('region'),
|
17
|
+
"Expected .image_registries['#{@name}'] to have .config.region to be set "\
|
18
|
+
"for the AWS image registry driver, but .config.region is not set "\
|
19
|
+
"e.g. .config.region = 'us-west-2'"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def push_image(image)
|
24
|
+
create_repository(image.repository) unless repository_exists?(image.repository)
|
25
|
+
super(image)
|
26
|
+
end
|
27
|
+
|
28
|
+
def authorize_command
|
29
|
+
login_cmd = get_docker_login
|
30
|
+
raise "Unexpected login command: #{login_cmd}" if login_cmd.first(2) != ['docker', 'login']
|
31
|
+
login_cmd
|
32
|
+
end
|
33
|
+
|
34
|
+
def unauthorize
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_image(image, dryrun)
|
38
|
+
# In the AWS driver, the 'delete many' primitive is the primary one.
|
39
|
+
delete_images([image], dryrun)
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete_images(images, dryrun)
|
43
|
+
# Aggregate images by repository and call aws ecr batch-delete-image
|
44
|
+
# once per repository.
|
45
|
+
ids_by_repository = {}
|
46
|
+
images.each do |image|
|
47
|
+
repository, tag = split_full_image_id(image)
|
48
|
+
item = {'imageTag': tag}
|
49
|
+
if ids_by_repository[repository].nil?
|
50
|
+
ids_by_repository[repository] = [item]
|
51
|
+
else
|
52
|
+
ids_by_repository[repository].push(item)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# JSON format documented here:
|
57
|
+
# https://docs.aws.amazon.com/cli/latest/reference/ecr/batch-delete-image.html
|
58
|
+
ids_by_repository.each do |repository, image_ids|
|
59
|
+
# batch-delete-image has a threshold of 100 image_ids at a time
|
60
|
+
image_chunks = image_ids.each_slice(100).to_a
|
61
|
+
|
62
|
+
image_chunks.each do |images|
|
63
|
+
cmd = [
|
64
|
+
'aws', 'ecr', 'batch-delete-image',
|
65
|
+
'--repository-name', repository,
|
66
|
+
'--region', @registry.config.fetch('region'),
|
67
|
+
'--image-ids', images.to_json,
|
68
|
+
]
|
69
|
+
|
70
|
+
if dryrun
|
71
|
+
Logger.info("Would run: #{cmd}")
|
72
|
+
else
|
73
|
+
Shellrunner.check_call(*cmd)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def get_docker_login
|
81
|
+
args = Shellrunner.check_call('aws', 'ecr', 'get-login', '--region', @registry.config.fetch('region')).split
|
82
|
+
|
83
|
+
# Remove '-e' and subsequent argument
|
84
|
+
# This compensates for --no-include-email not being recognized in the Ubuntu packaged awscli
|
85
|
+
# and not usable unless you upgrade
|
86
|
+
i = args.index('-e')
|
87
|
+
if !i.nil?
|
88
|
+
# delete '-e'
|
89
|
+
args.delete_at(i)
|
90
|
+
# delete whatever value is after (usually 'none')
|
91
|
+
args.delete_at(i)
|
92
|
+
end
|
93
|
+
|
94
|
+
args
|
95
|
+
end
|
96
|
+
|
97
|
+
def repository_exists?(repository)
|
98
|
+
_, _, status = Shellrunner.run_call('aws', 'ecr', 'describe-repositories', '--repository-names', repository, '--region', @registry.config.fetch('region'))
|
99
|
+
status.success?
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_repository(repository)
|
103
|
+
Shellrunner.check_call('aws', 'ecr', 'create-repository', '--repository-name', repository, '--region', @registry.config.fetch('region'))
|
104
|
+
end
|
105
|
+
|
106
|
+
def split_full_image_id(image_id)
|
107
|
+
# Create syntax suitable for aws ecr subcommand.
|
108
|
+
# Example: 12345678.dkr.ecr.amazonaws.com/my_app:deadbeef-123
|
109
|
+
# splits into ('my_app', 'deadbeef-123') after verifying that the
|
110
|
+
# prefix is the expected one for this driver instance.
|
111
|
+
repo_with_prefix, tag = image_id.split(':', 2)
|
112
|
+
prefix, repository = repo_with_prefix.split('/', 2)
|
113
|
+
|
114
|
+
# Sanity check, as the resultant command line uses the region to specify
|
115
|
+
# the prefix implicitly.
|
116
|
+
raise "This driver can't delete images from #{prefix}, only #{@registry.prefix}" unless prefix == @registry.prefix
|
117
|
+
|
118
|
+
return repository, tag
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'kube_deploy_tools/formatted_logger'
|
2
|
+
require 'kube_deploy_tools/shellrunner'
|
3
|
+
|
4
|
+
require 'kube_deploy_tools/image_registry/image'
|
5
|
+
|
6
|
+
# Abstract Driver class that specific implementations inherit
|
7
|
+
module KubeDeployTools
|
8
|
+
class ImageRegistry
|
9
|
+
module Driver
|
10
|
+
class Base
|
11
|
+
def initialize(registry:)
|
12
|
+
@registry = registry
|
13
|
+
end
|
14
|
+
|
15
|
+
def push_image(image)
|
16
|
+
Shellrunner.check_call('docker', 'push', image.full_tag)
|
17
|
+
end
|
18
|
+
|
19
|
+
def authorize
|
20
|
+
Logger.info "performing registry login for #{@registry.prefix}"
|
21
|
+
Shellrunner.check_call(*authorize_command, print_cmd: false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def authorize_command
|
25
|
+
raise "#{self.class}#authorize_command needs explicit implementation"
|
26
|
+
end
|
27
|
+
|
28
|
+
def unauthorize
|
29
|
+
Logger.info "Performing registry unauthorization for #{@registry.prefix}"
|
30
|
+
Shellrunner.check_call(*unauthorize_command, print_cmd: false)
|
31
|
+
end
|
32
|
+
|
33
|
+
def unauthorize_command
|
34
|
+
raise "#{self.class}#unauthorize_command needs explicit implementation"
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_images(images, dryrun)
|
38
|
+
# Naive default implementation.
|
39
|
+
images.each do |image|
|
40
|
+
delete_image(image, dryrun)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_image(image, dryrun)
|
45
|
+
raise "#{self.class}#delete_image needs explicit implementation"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
class ImageRegistry::Driver::Gcp < ImageRegistry::Driver::Base
|
6
|
+
def initialize(registry:)
|
7
|
+
super
|
8
|
+
|
9
|
+
@gcloud_config_dir = Dir.mktmpdir
|
10
|
+
@activated = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def authorize
|
14
|
+
# Always prefer and activate a service account under a protected namespace if present.
|
15
|
+
if @activated
|
16
|
+
return
|
17
|
+
elsif ENV.member?('GOOGLE_APPLICATION_CREDENTIALS')
|
18
|
+
raise "Failed to activate service account" unless activate_service_account()[2].success?
|
19
|
+
@activated = true
|
20
|
+
else
|
21
|
+
user = current_user
|
22
|
+
if ! user.empty?
|
23
|
+
Logger.info "Skipping Google activation, using current user #{user}"
|
24
|
+
@activated = true
|
25
|
+
else
|
26
|
+
raise 'No usable Google authorization for pushing images; specify GOOGLE_APPLICATION_CREDENTIALS?'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Delete temporary config dir for gcloud authentication
|
32
|
+
def unauthorize
|
33
|
+
Logger.info "Cleaning up authorization for #{@registry.prefix}"
|
34
|
+
FileUtils.rm_rf(@gcloud_config_dir) unless @gcloud_config_dir.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_image(image_id, dryrun)
|
38
|
+
# Need the id path to be [HOSTNAME]/[PROJECT-ID]/[IMAGE]
|
39
|
+
if dryrun
|
40
|
+
Logger.info("DRYRUN: delete gcp image #{image_id}")
|
41
|
+
else
|
42
|
+
# --quiet removes the user-input component
|
43
|
+
_, err, status = Shellrunner.run_call('gcloud', 'container', 'images', 'delete', '--quiet', image_id, '--force-delete-tags')
|
44
|
+
if !status.success?
|
45
|
+
# gcloud gives a deceptive error msg when the image does not exist
|
46
|
+
if err.include?('is not a valid name')
|
47
|
+
Logger.warn("Image #{image_id} does not exist, skipping")
|
48
|
+
else
|
49
|
+
raise "gcloud image deletion failed!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
# activate gcloud with svc json keys on Jenkins
|
57
|
+
def activate_service_account
|
58
|
+
keypath = ENV.fetch('GOOGLE_APPLICATION_CREDENTIALS')
|
59
|
+
Logger.info("Authorizing using temp directory #{@gcloud_config_dir} and credentials #{keypath}")
|
60
|
+
|
61
|
+
ENV['XDG_CONFIG_HOME'] = @gcloud_config_dir
|
62
|
+
ENV['CLOUDSDK_CONFIG']= File.join(@gcloud_config_dir, 'gcloud')
|
63
|
+
|
64
|
+
Shellrunner.run_call('gcloud', 'auth', 'activate-service-account', '--key-file', keypath)
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_user
|
68
|
+
Shellrunner.run_call('gcloud', 'config', 'list', 'account', '--format', "value(core.account)")[0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative '../image'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
class ImageRegistry::Driver::Login < ImageRegistry::Driver::Base
|
6
|
+
# This driver expects the following to be set in the @registry hash:
|
7
|
+
# username_var: set to a string which is the env var containing the docker
|
8
|
+
# registry username
|
9
|
+
# password_var: set to a string which is the env var containing the docker
|
10
|
+
# registry password
|
11
|
+
# prefix: passed directly to docker login
|
12
|
+
def authorize_command
|
13
|
+
['docker', 'login',
|
14
|
+
'--username', ENV.fetch(@registry.config.fetch('username_var')),
|
15
|
+
'--password', ENV.fetch(@registry.config.fetch('password_var')),
|
16
|
+
@registry.prefix]
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete_image(image, dryrun)
|
20
|
+
raise 'not implemented'
|
21
|
+
end
|
22
|
+
|
23
|
+
def unauthorize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module KubeDeployTools
|
2
|
+
class Push
|
3
|
+
class Image
|
4
|
+
attr_accessor :registry, :repository, :tag
|
5
|
+
def initialize(registry, repository, tag)
|
6
|
+
registry += '/' unless registry.end_with?('/')
|
7
|
+
@registry = registry
|
8
|
+
@repository = repository
|
9
|
+
@tag = tag
|
10
|
+
end
|
11
|
+
|
12
|
+
def full_tag
|
13
|
+
"#{registry}#{repository}:#{tag}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'kube_deploy_tools/version'
|
2
|
+
require 'kube_deploy_tools/formatted_logger'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
class Kdt
|
6
|
+
DESCRIPTIONS = {
|
7
|
+
'deploy' => 'Releases all Kubernetes resources in a deploy artifact with |kubectl apply|',
|
8
|
+
'push' => 'Tags and pushes images to defined image registries',
|
9
|
+
'generate' => 'Generates artifacts based on templates in kubernetes/ and your deploy.yaml.',
|
10
|
+
'publish' => 'Publishes generated artifacts to your artifact store.',
|
11
|
+
'upgrade' => 'Upgrades a KDT 1.x deploy.yml to a KDT 2.x deploy.yaml',
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(path, args)
|
15
|
+
KubeDeployTools::Logger.logger = KubeDeployTools::FormattedLogger.build
|
16
|
+
|
17
|
+
@path = path
|
18
|
+
@args = args
|
19
|
+
end
|
20
|
+
|
21
|
+
def bins_names
|
22
|
+
@bins ||= Dir["#{@path}/*"].map { |x| File.basename(x) } - ['kdt']
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_bins
|
26
|
+
# Print full runtime version
|
27
|
+
version = Gem.loaded_specs["kube_deploy_tools"].version
|
28
|
+
puts "kube_deploy_tools #{version}"
|
29
|
+
|
30
|
+
bins_names.each do |bin|
|
31
|
+
spaces_count = 25 - bin.size
|
32
|
+
puts "-> #{bin}#{' ' * spaces_count}| #{DESCRIPTIONS[bin]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute!
|
37
|
+
bin = @args.first
|
38
|
+
|
39
|
+
raise "command '#{bin}' is not a valid command" unless valid_bin?(bin)
|
40
|
+
bin_with_path = "#{@path}/#{bin}"
|
41
|
+
bin_args = @args[1..-1]
|
42
|
+
|
43
|
+
# calling exec with multiple args will prevent shell expansion
|
44
|
+
# https://ruby-doc.org/core/Kernel.html#method-i-exec
|
45
|
+
exec bin_with_path, *bin_args
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid_bin?(bin)
|
49
|
+
bins_names.include?(bin)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'kube_deploy_tools/object'
|
2
|
+
require 'kube_deploy_tools/shellrunner'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
class Kubectl
|
6
|
+
def initialize(
|
7
|
+
context:,
|
8
|
+
kubeconfig:)
|
9
|
+
@context = context
|
10
|
+
@kubeconfig = kubeconfig
|
11
|
+
|
12
|
+
raise ArgumentError, "context is required" if context.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(*args, print_cmd: true, timeout: nil)
|
16
|
+
args = args.unshift("kubectl")
|
17
|
+
args.push("--context=#{@context}")
|
18
|
+
args.push("--kubeconfig=#{@kubeconfig}") if @kubeconfig.present?
|
19
|
+
args.push("--request-timeout=#{timeout}") if timeout.present?
|
20
|
+
|
21
|
+
Shellrunner.run_call(*args, print_cmd: print_cmd)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module KubeDeployTools
|
4
|
+
class KubernetesResource
|
5
|
+
attr_accessor :definition,
|
6
|
+
:kind,
|
7
|
+
:name,
|
8
|
+
:namespace,
|
9
|
+
:annotations
|
10
|
+
|
11
|
+
def self.build(filepath: nil, definition:, kubectl:)
|
12
|
+
opts = { filepath: filepath, definition: definition, kubectl: kubectl }
|
13
|
+
# Find the corresponding class for the Kubernetes resource, if available
|
14
|
+
if ["Deployment"].include?(definition["kind"])
|
15
|
+
klass = KubeDeployTools.const_get(definition["kind"])
|
16
|
+
klass.new(**opts)
|
17
|
+
else
|
18
|
+
# Otherwise initialize here if no class exists for this Kubernetes
|
19
|
+
# resource kind
|
20
|
+
inst = new(**opts)
|
21
|
+
inst.kind = definition["kind"]
|
22
|
+
inst
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(filepath:, definition:, kubectl:)
|
27
|
+
@filepath = filepath
|
28
|
+
@definition = definition
|
29
|
+
@kubectl = kubectl
|
30
|
+
|
31
|
+
@namespace = definition.dig('metadata', 'namespace')
|
32
|
+
@name = definition.dig('metadata', 'name')
|
33
|
+
@kind = definition['kind']
|
34
|
+
@annotations = definition.dig('metadata', 'annotations')
|
35
|
+
end
|
36
|
+
|
37
|
+
def filepath
|
38
|
+
@filepath ||= file.path
|
39
|
+
end
|
40
|
+
|
41
|
+
def file
|
42
|
+
@file ||= create_definition_tempfile
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_definition_tempfile
|
46
|
+
file = Tempfile.new(["#{@namespace}-#{@kind}-#{@name}", ".yaml"])
|
47
|
+
file.write(YAML.dump(@definition))
|
48
|
+
file
|
49
|
+
ensure
|
50
|
+
file&.close
|
51
|
+
end
|
52
|
+
|
53
|
+
def sync
|
54
|
+
# unimplemented
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'kube_deploy_tools/formatted_logger'
|
4
|
+
require 'kube_deploy_tools/object'
|
5
|
+
|
6
|
+
module KubeDeployTools
|
7
|
+
class Deployment < KubernetesResource
|
8
|
+
TIMEOUT = '60s'
|
9
|
+
|
10
|
+
attr_accessor :found,
|
11
|
+
:local_replicas,
|
12
|
+
:remote_replicas,
|
13
|
+
:recorded_replicas
|
14
|
+
|
15
|
+
def sync
|
16
|
+
@local_replicas = @definition["spec"]["replicas"]
|
17
|
+
|
18
|
+
raw_json, _err, st = @kubectl.run("get", "-f", filepath, "--output=json", print_cmd: false, timeout: TIMEOUT)
|
19
|
+
@found = st.success?
|
20
|
+
|
21
|
+
if st.success?
|
22
|
+
deployment_data = JSON.parse(raw_json)
|
23
|
+
@remote_replicas = deployment_data["spec"]["replicas"]
|
24
|
+
end
|
25
|
+
|
26
|
+
raw_json, _err, st = @kubectl.run("apply", "view-last-applied", "-f", filepath, "--output=json", print_cmd: false, timeout: TIMEOUT)
|
27
|
+
if st.success?
|
28
|
+
raw_json = fix_kubectl_apply_view_last_applied_output(raw_json)
|
29
|
+
deployment_data = JSON.parse(raw_json)
|
30
|
+
@recorded_replicas = deployment_data["spec"]["replicas"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def warn_replicas_mismatch
|
35
|
+
if @found
|
36
|
+
if @local_replicas.present? && @local_replicas.to_i != @remote_replicas.to_i
|
37
|
+
warning = "Deployment replica count mismatch! Will scale deployment/#{@name} from #{@remote_replicas} to #{@local_replicas}"
|
38
|
+
Logger.warn(warning)
|
39
|
+
elsif @local_replicas.nil? && !@recorded_replicas.nil?
|
40
|
+
# Check if we're converting to a replicaless Deployment
|
41
|
+
warning = "Deployment replica count mismatch! Will scale deployment/#{@name} from #{@remote_replicas} to 1. Run `kubectl apply set-last-applied -f #{@filepath}` first."
|
42
|
+
Logger.warn(warning)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# In kubectl version <= 1.7, `kubectl apply view-last-applied` may
|
50
|
+
# produces an invalid JSON output, which we must sanitize.
|
51
|
+
#
|
52
|
+
# The upstream fix is in kubect >= 1.8:
|
53
|
+
# https://github.com/kubernetes/kubernetes/commit/7c656ab4d2ca41e07db9f90c99ee360b0d48c651
|
54
|
+
def fix_kubectl_apply_view_last_applied_output(json)
|
55
|
+
json.sub(/\!\"\(MISSING\)/, '"')
|
56
|
+
end
|