kube_deploy_tools 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +177 -0
  3. data/README.md +142 -0
  4. data/bin/deploy +60 -0
  5. data/bin/generate +28 -0
  6. data/bin/kdt +17 -0
  7. data/bin/make_configmap +20 -0
  8. data/bin/publish +28 -0
  9. data/bin/push +57 -0
  10. data/bin/render_deploys_hook +18 -0
  11. data/bin/templater +34 -0
  12. data/bin/upgrade +23 -0
  13. data/lib/kube_deploy_tools.rb +17 -0
  14. data/lib/kube_deploy_tools/artifact_registry.rb +30 -0
  15. data/lib/kube_deploy_tools/artifact_registry/driver.rb +13 -0
  16. data/lib/kube_deploy_tools/artifact_registry/driver_artifactory.rb +155 -0
  17. data/lib/kube_deploy_tools/artifact_registry/driver_base.rb +37 -0
  18. data/lib/kube_deploy_tools/artifact_registry/driver_gcs.rb +120 -0
  19. data/lib/kube_deploy_tools/built_artifacts_file.rb +28 -0
  20. data/lib/kube_deploy_tools/concurrency.rb +18 -0
  21. data/lib/kube_deploy_tools/deferred_summary_logging.rb +69 -0
  22. data/lib/kube_deploy_tools/deploy.rb +215 -0
  23. data/lib/kube_deploy_tools/deploy/options.rb +114 -0
  24. data/lib/kube_deploy_tools/deploy_config_file.rb +286 -0
  25. data/lib/kube_deploy_tools/deploy_config_file/deep_merge.rb +38 -0
  26. data/lib/kube_deploy_tools/deploy_config_file/util.rb +39 -0
  27. data/lib/kube_deploy_tools/errors.rb +5 -0
  28. data/lib/kube_deploy_tools/file_filter.rb +43 -0
  29. data/lib/kube_deploy_tools/formatted_logger.rb +59 -0
  30. data/lib/kube_deploy_tools/generate.rb +145 -0
  31. data/lib/kube_deploy_tools/generate/options.rb +66 -0
  32. data/lib/kube_deploy_tools/image_registry.rb +30 -0
  33. data/lib/kube_deploy_tools/image_registry/driver.rb +18 -0
  34. data/lib/kube_deploy_tools/image_registry/driver/aws.rb +121 -0
  35. data/lib/kube_deploy_tools/image_registry/driver/base.rb +50 -0
  36. data/lib/kube_deploy_tools/image_registry/driver/gcp.rb +71 -0
  37. data/lib/kube_deploy_tools/image_registry/driver/login.rb +26 -0
  38. data/lib/kube_deploy_tools/image_registry/driver/noop.rb +15 -0
  39. data/lib/kube_deploy_tools/image_registry/image.rb +17 -0
  40. data/lib/kube_deploy_tools/kdt.rb +52 -0
  41. data/lib/kube_deploy_tools/kubectl.rb +25 -0
  42. data/lib/kube_deploy_tools/kubernetes_resource.rb +57 -0
  43. data/lib/kube_deploy_tools/kubernetes_resource/deployment.rb +56 -0
  44. data/lib/kube_deploy_tools/make_configmap.rb +51 -0
  45. data/lib/kube_deploy_tools/make_configmap/options.rb +39 -0
  46. data/lib/kube_deploy_tools/object.rb +11 -0
  47. data/lib/kube_deploy_tools/publish.rb +40 -0
  48. data/lib/kube_deploy_tools/publish/options.rb +34 -0
  49. data/lib/kube_deploy_tools/push.rb +129 -0
  50. data/lib/kube_deploy_tools/push/options.rb +46 -0
  51. data/lib/kube_deploy_tools/render_deploys_hook.rb +95 -0
  52. data/lib/kube_deploy_tools/shellrunner.rb +46 -0
  53. data/lib/kube_deploy_tools/tag.rb +33 -0
  54. data/lib/kube_deploy_tools/templater.rb +63 -0
  55. data/lib/kube_deploy_tools/templater/options.rb +74 -0
  56. data/lib/kube_deploy_tools/version.rb +3 -0
  57. 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,11 @@
1
+
2
+ class Object
3
+ def present?
4
+ self && to_s.strip != ''
5
+ end
6
+
7
+ def blank?
8
+ !present?
9
+ end
10
+ end
11
+
@@ -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