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