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,51 @@
|
|
1
|
+
require 'kube_deploy_tools/object'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
|
6
|
+
class ConfigMap
|
7
|
+
def initialize(name, from_file, namespace = 'default', labels = nil)
|
8
|
+
@name = name
|
9
|
+
@namespace = namespace
|
10
|
+
@labels = labels
|
11
|
+
@from_file = from_file
|
12
|
+
end
|
13
|
+
|
14
|
+
def base
|
15
|
+
{
|
16
|
+
'apiVersion' => 'v1',
|
17
|
+
'kind' => 'ConfigMap',
|
18
|
+
'metadata' => {},
|
19
|
+
'data' => {}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def target_hash
|
24
|
+
res = base
|
25
|
+
res['metadata']['name'] = @name
|
26
|
+
res['metadata']['namespace'] = @namespace
|
27
|
+
res['metadata']['labels'] = @labels if @labels
|
28
|
+
@from_file.each do |from_file|
|
29
|
+
if from_file.include? '='
|
30
|
+
# e.g. --from-file=config.yml=/path/to/configs/production.yml
|
31
|
+
configmap_key, filepath = from_file.split("=", 2)
|
32
|
+
res['data'][configmap_key] = File.read(filepath)
|
33
|
+
elsif File.file?(from_file)
|
34
|
+
# e.g. --from-file=/path/to/configs/production.yml
|
35
|
+
configmap_key = File.basename(from_file)
|
36
|
+
filepath = from_file
|
37
|
+
res['data'][configmap_key] = File.read(filepath)
|
38
|
+
elsif File.directory?(from_file)
|
39
|
+
# e.g. --from-file=/path/to/configs/
|
40
|
+
Dir[File.join(from_file, '*')].each do |filepath|
|
41
|
+
# NOTE(jmodes): Multiple levels of directories are not supported.
|
42
|
+
next if File.directory?(filepath)
|
43
|
+
configmap_key = File.basename(filepath)
|
44
|
+
res['data'][configmap_key] = File.read(filepath)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
res
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module KubeDeployTools
|
5
|
+
class Options
|
6
|
+
def options
|
7
|
+
$options ||= begin
|
8
|
+
res = {from_file: [], labels: {}}
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: #{opts.program_name}.rb [options]. Outputs YAML to STDOUT"
|
11
|
+
|
12
|
+
opts.on("--name [NAME]", "ConfigMap name") do |v|
|
13
|
+
res[:name] = v
|
14
|
+
end
|
15
|
+
|
16
|
+
res[:namespace] = "default"
|
17
|
+
opts.on("--namespace [NAMESPACE]", "ConfigMap namespace") do |v|
|
18
|
+
res[:namespace] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("--label [NAME=VALUE]", "ConfigMap metadata label") do |v|
|
22
|
+
res[:labels].store(*v.split('=', 2))
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("--from-file [KEYFILE]", "Key file can be specified using its file path, in which case file basename will be used as
|
26
|
+
configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory
|
27
|
+
will iterate each named file in the directory whose basename is a valid configmap key.") do |v|
|
28
|
+
res[:from_file] << v
|
29
|
+
end
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
raise "no name given" unless res[:name].strip != ''
|
33
|
+
raise "no files given" if res[:from_file].empty?
|
34
|
+
|
35
|
+
res
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require 'kube_deploy_tools/built_artifacts_file'
|
5
|
+
require 'kube_deploy_tools/deploy_config_file'
|
6
|
+
require 'kube_deploy_tools/formatted_logger'
|
7
|
+
require 'kube_deploy_tools/object'
|
8
|
+
|
9
|
+
module KubeDeployTools
|
10
|
+
class Publish
|
11
|
+
def initialize(manifest:, artifact_registry:, output_dir:)
|
12
|
+
@config = DeployConfigFile.new(manifest)
|
13
|
+
@output_dir = output_dir
|
14
|
+
|
15
|
+
@project = KubeDeployTools::PROJECT
|
16
|
+
@build_number = KubeDeployTools::BUILD_NUMBER
|
17
|
+
|
18
|
+
@artifact_registry = artifact_registry.driver
|
19
|
+
end
|
20
|
+
|
21
|
+
def publish()
|
22
|
+
@config.artifacts.each do |c|
|
23
|
+
name = c.fetch('name')
|
24
|
+
|
25
|
+
# Allow deploy.yaml to gate certain flavors to certain targets.
|
26
|
+
cluster_flavors = @config.flavors.select { |key, value| c['flavors'].nil? || c['flavors'].include?(key) }
|
27
|
+
|
28
|
+
cluster_flavors.each do |flavor, _|
|
29
|
+
@artifact_registry.upload(
|
30
|
+
local_dir: @output_dir,
|
31
|
+
name: name,
|
32
|
+
flavor: flavor,
|
33
|
+
project: @project,
|
34
|
+
build_number: @build_number,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module KubeDeployTools
|
4
|
+
class Publish::Optparser
|
5
|
+
class Options
|
6
|
+
attr_accessor :manifest_file, :output_path
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.output_path = File.join('build', 'kubernetes')
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_options(parser)
|
13
|
+
parser.on('-mMANIFEST', '--manifest MANIFEST', 'The configuration MANIFEST to render deploys with.') do |f|
|
14
|
+
self.manifest_file = f
|
15
|
+
end
|
16
|
+
|
17
|
+
parser.on('-oPATH', '--output-path PATH', 'Path where rendered manifests are written.') do |p|
|
18
|
+
self.output_path = p
|
19
|
+
end
|
20
|
+
|
21
|
+
parser.on('-')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(args)
|
26
|
+
@options = Options.new
|
27
|
+
OptionParser.new do |parser|
|
28
|
+
@options.define_options(parser)
|
29
|
+
parser.parse(args)
|
30
|
+
end
|
31
|
+
@options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'kube_deploy_tools/shellrunner'
|
4
|
+
require 'kube_deploy_tools/built_artifacts_file'
|
5
|
+
require 'kube_deploy_tools/deploy_config_file'
|
6
|
+
|
7
|
+
require 'kube_deploy_tools/image_registry/driver'
|
8
|
+
require 'kube_deploy_tools/image_registry/image'
|
9
|
+
|
10
|
+
BUILT_ARTIFACTS_FILE = 'build/kubernetes/images.yaml'.freeze
|
11
|
+
|
12
|
+
module KubeDeployTools
|
13
|
+
class Push
|
14
|
+
def initialize(config, local_prefix, registries, images, tag)
|
15
|
+
@config = config
|
16
|
+
@local_prefix = local_prefix
|
17
|
+
|
18
|
+
# If |registries| is empty, assume we will push them all,
|
19
|
+
# if not, subtract unwanted ones from |config.image_registries|
|
20
|
+
if registries.empty?
|
21
|
+
to_instantiate = config.image_registries.values
|
22
|
+
else
|
23
|
+
to_instantiate = []
|
24
|
+
config.image_registries.each do |name, registry|
|
25
|
+
if registries.member? name
|
26
|
+
to_instantiate.push registry
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@drivers = to_instantiate.map do |registry|
|
32
|
+
driver_class = ImageRegistry::Driver::MAPPINGS.fetch(registry.driver)
|
33
|
+
[registry, driver_class.new(registry: registry)]
|
34
|
+
end.to_h
|
35
|
+
@base_image_names = images
|
36
|
+
@tag = tag
|
37
|
+
end
|
38
|
+
|
39
|
+
def publish
|
40
|
+
dirname = File.dirname(BUILT_ARTIFACTS_FILE)
|
41
|
+
FileUtils.mkdir_p(dirname)
|
42
|
+
|
43
|
+
driver_images = []
|
44
|
+
|
45
|
+
@drivers.each_pair do |registry, driver|
|
46
|
+
driver_images.unshift [driver, tag_images(registry, @base_image_names)]
|
47
|
+
# Does whatever is necessary to authorize against this registry
|
48
|
+
driver.authorize
|
49
|
+
end
|
50
|
+
|
51
|
+
# Push first images to each registry in parallel
|
52
|
+
driver_images.map do |driver, all_images|
|
53
|
+
Thread.new { driver.push_image all_images[0] }
|
54
|
+
end.each(&:join)
|
55
|
+
|
56
|
+
# Push the rest of the images to each registry in parallel
|
57
|
+
driver_images.map do |driver, all_images|
|
58
|
+
_, *remaining_images = all_images
|
59
|
+
remaining_images.map do |i|
|
60
|
+
Thread.new { driver.push_image i }
|
61
|
+
end
|
62
|
+
end.flatten.each(&:join)
|
63
|
+
|
64
|
+
# Can't lock the file if it doesn't exist. Create the file as a
|
65
|
+
# placeholder until more content is loaded
|
66
|
+
File.open(BUILT_ARTIFACTS_FILE, File::CREAT|File::RDWR) do |file|
|
67
|
+
flock(file, File::LOCK_EX) do |file|
|
68
|
+
driver_images.each do |_, all_images|
|
69
|
+
update_built_artifacts(all_images, file)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Clean registry authorization in the end no matter what
|
75
|
+
ensure
|
76
|
+
Logger.info "Removing registry authorizations"
|
77
|
+
@drivers.each_pair do |registry, driver|
|
78
|
+
driver.unauthorize
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def tag_images(r, base_image_names)
|
85
|
+
base_image_names.map do |i|
|
86
|
+
local = Image.new(@local_prefix, i, 'latest')
|
87
|
+
remote = Image.new(r.prefix, i, @tag)
|
88
|
+
Shellrunner.check_call('docker', 'tag', local.full_tag, remote.full_tag)
|
89
|
+
remote
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_built_artifacts(images_to_push, file)
|
94
|
+
artifacts = KubeDeployTools::BuiltArtifactsFile.new(file)
|
95
|
+
build_id = ENV.fetch('BUILD_ID', 'LOCAL')
|
96
|
+
|
97
|
+
if !artifacts.build_id.nil? && artifacts.build_id != build_id
|
98
|
+
# Clear the images as this is a fresh build.
|
99
|
+
artifacts.images = Set.new
|
100
|
+
# Truncate the file so it will generate a new file
|
101
|
+
# and remove any old builds
|
102
|
+
file.truncate(0)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Add new images to the output list.
|
106
|
+
artifacts.build_id = build_id
|
107
|
+
images_to_push.each do |image|
|
108
|
+
artifacts.images.add image.full_tag
|
109
|
+
end
|
110
|
+
|
111
|
+
# Write the config list.
|
112
|
+
artifacts.write file
|
113
|
+
end
|
114
|
+
|
115
|
+
# Method used to protect reads and writes. From:
|
116
|
+
# https://www.safaribooksonline.com/library/view/ruby-cookbook/0596523696/ch06s13.html
|
117
|
+
def flock(file, mode)
|
118
|
+
success = file.flock(mode)
|
119
|
+
if success
|
120
|
+
begin
|
121
|
+
yield file
|
122
|
+
ensure
|
123
|
+
file.flock(File::LOCK_UN)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
return success
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'set'
|
3
|
+
require 'kube_deploy_tools/tag'
|
4
|
+
|
5
|
+
module KubeDeployTools
|
6
|
+
class Push::Optparser
|
7
|
+
|
8
|
+
class PushOptions
|
9
|
+
attr_accessor :local_prefix, :manifest_file, :registries, :images, :tag
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
self.local_prefix = 'local-registry'
|
13
|
+
self.registries = Set.new
|
14
|
+
self.tag = KubeDeployTools::tag_from_local_env
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_options(parser)
|
18
|
+
parser.on('-lPREFIX', '--local-prefix PREFIX', 'The local Docker prefix to strip to get to the base image name') do |f|
|
19
|
+
self.local_prefix = f
|
20
|
+
end
|
21
|
+
|
22
|
+
parser.on('-tTAG', '--tag TAG', 'Tag Docker images with TAG') do |t|
|
23
|
+
self.tag = t
|
24
|
+
end
|
25
|
+
|
26
|
+
parser.on('-mMANIFEST', '--manifest MANIFEST', 'The configuration MANIFEST to render deploys with.') do |f|
|
27
|
+
self.manifest_file = f
|
28
|
+
end
|
29
|
+
|
30
|
+
parser.on('-rPREFIX', '--registry REGISTRY', 'The remote Docker registry to push to (can specify multiple times). If not specified, then will push to all registries in config.') do |r|
|
31
|
+
self.registries.add r
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse(args)
|
38
|
+
@options = PushOptions.new
|
39
|
+
OptionParser.new do |parser|
|
40
|
+
@options.define_options(parser)
|
41
|
+
parser.parse!(args)
|
42
|
+
end
|
43
|
+
@options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Default rendering hook. Uses built in `templater` to render out all files
|
3
|
+
# underneath kubernetes/ directory, recursively.
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
require 'json'
|
7
|
+
require 'yaml'
|
8
|
+
require 'kube_deploy_tools/formatted_logger'
|
9
|
+
require 'kube_deploy_tools/templater'
|
10
|
+
require 'kube_deploy_tools/file_filter'
|
11
|
+
|
12
|
+
module KubeDeployTools
|
13
|
+
module RenderDeploysHook
|
14
|
+
TEMPLATING_SUFFIX = '.erb'
|
15
|
+
|
16
|
+
def self.render_deploys(config_file, input_dir, output_root, file_filters)
|
17
|
+
# Parse config into a struct.
|
18
|
+
config = YAML.load_file(config_file)
|
19
|
+
t = KubeDeployTools::Templater.new
|
20
|
+
|
21
|
+
get_valid_files(file_filters, input_dir).each do |yml|
|
22
|
+
# PREFIX/b/c/foo.yml.in -> foo.yml
|
23
|
+
output_base = File.basename(yml, TEMPLATING_SUFFIX)
|
24
|
+
|
25
|
+
# PREFIX/b/c/foo.yml.in -> b/c
|
26
|
+
subdir = File.dirname(yml[input_dir.size..-1])
|
27
|
+
|
28
|
+
# PREFIX/b/c/foo.yml.in -> output/b/c/foo.yml
|
29
|
+
if subdir == '.'
|
30
|
+
# If |subdir| is '.', joining it with output_root results in
|
31
|
+
# output_root/. , which is not concise for human readability.
|
32
|
+
# Handle this case explicitly.
|
33
|
+
output_dir = output_root
|
34
|
+
else
|
35
|
+
output_dir = File.join(output_root, subdir)
|
36
|
+
end
|
37
|
+
output_file = File.join(output_dir, output_base)
|
38
|
+
|
39
|
+
if yml.end_with? TEMPLATING_SUFFIX
|
40
|
+
# File needs to be templated with templater.
|
41
|
+
Logger.debug("Generating #{output_file} from #{yml}")
|
42
|
+
t.template_to_file(yml, config, output_file)
|
43
|
+
else
|
44
|
+
# File is not templatable, and is copied verbatim.
|
45
|
+
Logger.debug("Copying #{output_file} from #{yml}")
|
46
|
+
FileUtils.mkdir_p output_dir
|
47
|
+
FileUtils.copy(yml, output_file)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Bonus: YAML validate the output.
|
51
|
+
# * Must be valid YAML
|
52
|
+
# * If .kind is a type that takes .metadata.namespace, then require
|
53
|
+
# that .metadata.namespace is present.
|
54
|
+
begin
|
55
|
+
if File.file?(output_file)
|
56
|
+
yaml = []
|
57
|
+
YAML.load_stream(File.read(output_file)) { |doc| yaml << doc }
|
58
|
+
yaml = yaml.
|
59
|
+
compact.
|
60
|
+
map do |data|
|
61
|
+
# XXX(joshk): Non-exhaustive list.
|
62
|
+
must_have_ns = [
|
63
|
+
'ConfigMap', 'CronJob', 'DaemonSet', 'Deployment', 'Endpoints', 'HorizontalPodAutoscaler',
|
64
|
+
'Ingress', 'PersistentVolumeClaim', 'PodDisruptionBudget', 'ServiceAccount', 'Secret', 'Service'
|
65
|
+
]
|
66
|
+
if must_have_ns.member?(data.fetch('kind'))
|
67
|
+
raise "Rendered Kubernetes template missing a .metadata.namespace: #{yml}" if data.fetch('metadata', {}).fetch('namespace', '').empty?
|
68
|
+
end
|
69
|
+
# annotation added to each manifest
|
70
|
+
annotations = data['metadata']['annotations'] || {}
|
71
|
+
if config['git_commit']
|
72
|
+
annotations['git_commit'] = config['git_commit']
|
73
|
+
end
|
74
|
+
|
75
|
+
if config['git_project']
|
76
|
+
annotations['git_project'] = config['git_project']
|
77
|
+
end
|
78
|
+
|
79
|
+
data['metadata']['annotations'] = annotations
|
80
|
+
data
|
81
|
+
end
|
82
|
+
File.open(output_file, 'w') { |f| f << YAML.dump_stream(*yaml) }
|
83
|
+
end
|
84
|
+
rescue => e
|
85
|
+
raise "Failed to YAML validate #{output_file} (generated from #{yml}): #{e}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.get_valid_files(file_filters, input_dir)
|
91
|
+
filtered_files = FileFilter.filter_files(filters: file_filters, files_path: input_dir)
|
92
|
+
filtered_files.select { |f| f =~ /\.y.?ml[^\/]*$/ }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|