kube_deploy_tools 3.0.5
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/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
|