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,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
|