kubernetes_template_rendering 0.1.0.pre.1
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/.buildkite/pipeline.yml +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +9 -0
- data/README.md +48 -0
- data/Rakefile +12 -0
- data/exe/render_templates +12 -0
- data/lib/kubernetes_template_rendering/cli.rb +77 -0
- data/lib/kubernetes_template_rendering/cli_arguments.rb +32 -0
- data/lib/kubernetes_template_rendering/color.rb +31 -0
- data/lib/kubernetes_template_rendering/deploy_grouped_resource.rb +59 -0
- data/lib/kubernetes_template_rendering/erb_template.rb +88 -0
- data/lib/kubernetes_template_rendering/jsonnet_template.rb +81 -0
- data/lib/kubernetes_template_rendering/resource.rb +82 -0
- data/lib/kubernetes_template_rendering/resource_set.rb +229 -0
- data/lib/kubernetes_template_rendering/template.rb +51 -0
- data/lib/kubernetes_template_rendering/template_directory_renderer.rb +174 -0
- data/lib/kubernetes_template_rendering/version.rb +5 -0
- data/lib/kubernetes_template_rendering.rb +8 -0
- data/sig/invoca/kubernetes_templates.rbs +6 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bd6a61ce9b0278d451c6e402ffab894a703846d04704061be05b1bdd30ddfef1
|
4
|
+
data.tar.gz: '079c1784c5357f79fa1e6d6e5a15ef00ea6f2bdf72f37b1ff3949283d3d328ef'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2eee87d74893f113a7b999c312457001cc5a8df0fed38b3827b7e773b5f243f2f73625b69ceb7e330576a4438671aa7f7c6491bf80b1b3e82b2167ab31d272ed
|
7
|
+
data.tar.gz: 17698c156cc055c8d0e42c1194f88d1ba99b1152712c2ee212393222ac5a93c75ca1029f424cf01f86993f95113fec39183546f4aef04a3d46f3f02de8e6190c
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
common_config: &common_config
|
3
|
+
agents:
|
4
|
+
queue: "ruby-3-1"
|
5
|
+
timeout_in_minutes: 5
|
6
|
+
steps:
|
7
|
+
- label: ":ruby: Render Test Matrix"
|
8
|
+
timeout_in_minutes: 5
|
9
|
+
plugins:
|
10
|
+
- ssh://git@github.com/Invoca/invoca-ruby-test-matrix-buildkite-plugin.git#main:
|
11
|
+
min_ruby_version: '3.1'
|
12
|
+
slack_notification_channel: '#dev-team-release-backend-tools-octothorpe'
|
13
|
+
test_command: "bundle exec rspec"
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# CHANGELOG for `kubernetes_template_rendering`
|
2
|
+
|
3
|
+
Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
4
|
+
|
5
|
+
Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [0.1.0] - Unreleased
|
8
|
+
|
9
|
+
- Initial release
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# KubernetesTemplateRendering
|
2
|
+
|
3
|
+
The `invoca-kubernetes_template` gem is a thin wrapper around `jsonnet` and `erb` to allow for the generation of
|
4
|
+
Kubernetes manifests from a set of templates combined with a `definitions.yaml` file which stores environmental
|
5
|
+
configuration for various deployment environments.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install the gem and add to the application's Gemfile by executing:
|
10
|
+
```
|
11
|
+
bundle add kubernetes_template_rendering
|
12
|
+
```
|
13
|
+
|
14
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
15
|
+
```
|
16
|
+
gem install kubernetes_template_rendering
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
This gem is meant to be used as a command line tool for rendering templates that are either written in `jsonnet` or `erb`.
|
22
|
+
To use this gem you can either install it, and use the `render_kubernetes_template` executable directly, or you can use
|
23
|
+
`gem exec` to execute the command without first installing the gem.
|
24
|
+
|
25
|
+
### Example Usage
|
26
|
+
```bash
|
27
|
+
gem exec -g kubernetes_template_rendering render_kubernetes_templates \
|
28
|
+
--jsonnet-library-path deployment/vendor \
|
29
|
+
--rendered_directory path/to/resources \
|
30
|
+
deployment/templates
|
31
|
+
```
|
32
|
+
|
33
|
+
### Options
|
34
|
+
|
35
|
+
To see a full list of options and how to use them, run the following command:
|
36
|
+
```bash
|
37
|
+
gem exec -g kubernetes_template_rendering render_kubernetes_templates --help
|
38
|
+
```
|
39
|
+
|
40
|
+
## Development
|
41
|
+
|
42
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
43
|
+
|
44
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/invoca/kubernetes_template_rendering.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "erb"
|
5
|
+
require "fileutils"
|
6
|
+
require "optparse"
|
7
|
+
require "ostruct"
|
8
|
+
require "pathname"
|
9
|
+
require "yaml"
|
10
|
+
require_relative "../lib/kubernetes_template_rendering/cli"
|
11
|
+
|
12
|
+
KubernetesTemplateRendering::CLI.parse_and_render(ARGV)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "template_directory_renderer"
|
4
|
+
require_relative "cli_arguments"
|
5
|
+
|
6
|
+
module KubernetesTemplateRendering
|
7
|
+
class CLI
|
8
|
+
DEFINITIONS_FILENAME = "definitions.yaml"
|
9
|
+
|
10
|
+
Arguments = CLIArguments
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def parse_and_render(options)
|
14
|
+
renderer, args = parse(options)
|
15
|
+
|
16
|
+
renderer.render(args)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def parse(options)
|
22
|
+
args = Arguments.new
|
23
|
+
|
24
|
+
parser = OptionParser.new do |op|
|
25
|
+
op.banner = "Usage: #{$PROGRAM_NAME} --rendered-directory=<directory> <template directory>"
|
26
|
+
|
27
|
+
op.on("--rendered-directory=RENDERED_DIRECTORY", "set the directory where rendered output is written") { args.rendered_directory = _1 }
|
28
|
+
op.on("--[no-]fork", "disable/enable fork") { |fork| args.fork = fork }
|
29
|
+
op.on("--makeflags=MAKEFLAGS", "pass through makeflags so that we can infer fork preference from -j") { args.makeflags = _1 }
|
30
|
+
op.on("--jsonnet_library_path=JSONNET_LIBRARY_PATH", "set the jsonnet library path") { args.jsonnet_library_path = _1 }
|
31
|
+
op.on("--cluster_type=CLUSTER_TYPE", "set the specific cluster type to render") { args.cluster_type = _1 }
|
32
|
+
op.on("--region=REGION", "set the specific region to render") { args.region = _1 }
|
33
|
+
op.on("--color=COLOR", "set the specific color to render") { args.color = _1 }
|
34
|
+
|
35
|
+
op.on("--variable-override=KEY:VALUE", "override a variable value set within definitions.yaml") do |override|
|
36
|
+
args.variable_overrides ||= {}
|
37
|
+
args.variable_overrides.merge!(Hash[[override.split(":", 2)]])
|
38
|
+
end
|
39
|
+
|
40
|
+
op.on("-h", "--help") do
|
41
|
+
puts op
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
parser.parse!(options)
|
47
|
+
args.template_directory = options.first
|
48
|
+
|
49
|
+
unless args.valid?
|
50
|
+
STDERR.puts(parser)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
[renderer_from_args(args), args]
|
55
|
+
end
|
56
|
+
|
57
|
+
def renderer_from_args(args)
|
58
|
+
directories = template_directories(args.template_directory, DEFINITIONS_FILENAME)
|
59
|
+
|
60
|
+
TemplateDirectoryRenderer.new(
|
61
|
+
directories: directories,
|
62
|
+
rendered_directory: args.rendered_directory,
|
63
|
+
cluster_type: args.cluster_type,
|
64
|
+
region: args.region,
|
65
|
+
color: args.color,
|
66
|
+
variable_overrides: args.variable_overrides
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def template_directories(template_directory, definitions_file)
|
71
|
+
File.directory?(template_directory) or raise ArgumentError, "template directory not found--make sure to include templates/ prefix: #{template_directory}"
|
72
|
+
directories = Dir["#{template_directory}/**/#{definitions_file}"]
|
73
|
+
directories.map { |directory| File.dirname(directory) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KubernetesTemplateRendering
|
4
|
+
CLIArguments =
|
5
|
+
Struct.new(
|
6
|
+
:rendered_directory,
|
7
|
+
:template_directory,
|
8
|
+
:fork,
|
9
|
+
:makeflags,
|
10
|
+
:jsonnet_library_path,
|
11
|
+
:cluster_type,
|
12
|
+
:region,
|
13
|
+
:color,
|
14
|
+
:variable_overrides
|
15
|
+
) do
|
16
|
+
def valid?
|
17
|
+
rendered_directory && template_directory
|
18
|
+
end
|
19
|
+
|
20
|
+
def fork?
|
21
|
+
if fork.nil?
|
22
|
+
makeflags&.include?('-j')
|
23
|
+
else
|
24
|
+
fork
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_files?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KubernetesTemplateRendering
|
4
|
+
module Color
|
5
|
+
class << self
|
6
|
+
def black(str)
|
7
|
+
"\e[30m#{str}\e[0m"
|
8
|
+
end
|
9
|
+
|
10
|
+
def red(str)
|
11
|
+
"\e[31m#{str}\e[0m"
|
12
|
+
end
|
13
|
+
|
14
|
+
def green(str)
|
15
|
+
"\e[32m#{str}\e[0m"
|
16
|
+
end
|
17
|
+
|
18
|
+
def brown(str)
|
19
|
+
"\e[33m#{str}\e[0m"
|
20
|
+
end
|
21
|
+
|
22
|
+
def blue(str)
|
23
|
+
"\e[34m#{str}\e[0m"
|
24
|
+
end
|
25
|
+
|
26
|
+
def magenta(str)
|
27
|
+
"\e[35m#{str}\e[0m"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resource"
|
4
|
+
|
5
|
+
module KubernetesTemplateRendering
|
6
|
+
class DeployGroupedResource
|
7
|
+
DEFAULT_GROUP_VARIABLE_NAME = "deploy_group"
|
8
|
+
|
9
|
+
attr_reader :groups_to_render, :variables, :template_path, :output_directory, :template_path_exclusions, :group_variable_name
|
10
|
+
|
11
|
+
def initialize(template_path:, definitions_path:, variables:, output_directory:, groups_to_render:, template_path_exclusions:, group_variable_name: nil)
|
12
|
+
@template_path = template_path
|
13
|
+
@definitions_path = definitions_path
|
14
|
+
@variables = variables
|
15
|
+
@output_directory = output_directory
|
16
|
+
@groups_to_render = groups_to_render
|
17
|
+
@template_path_exclusions = template_path_exclusions || {}
|
18
|
+
@group_variable_name = group_variable_name || DEFAULT_GROUP_VARIABLE_NAME
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(args)
|
22
|
+
@resources =
|
23
|
+
groups_to_render.map do |deploy_group|
|
24
|
+
if template_is_excluded?(deploy_group)
|
25
|
+
puts "Skipping #{Color.magenta(template_path_basename)} for #{deploy_group} deploy group due to it being " \
|
26
|
+
"excluded within the deploy group config\n\n"
|
27
|
+
nil
|
28
|
+
else
|
29
|
+
vars = variables.merge(group_variable_name => deploy_group)
|
30
|
+
filename = filename_for_deploy_group(deploy_group)
|
31
|
+
Resource.new(template_path: template_path, definitions_path: @definitions_path, variables: vars, output_directory: output_directory, output_filename: filename).tap do |resource|
|
32
|
+
resource.render(args) if args.render_files?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def template_path_basename
|
41
|
+
@template_path_basename ||= File.basename(template_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def template_is_excluded?(deploy_group)
|
45
|
+
@template_path_exclusions[deploy_group]&.include?(template_path_basename)
|
46
|
+
end
|
47
|
+
|
48
|
+
def filename_for_deploy_group(group)
|
49
|
+
case @template_path
|
50
|
+
when /.erb/
|
51
|
+
File.basename(template_path, ".erb").sub(/([^-.]+)\.yaml$/, "#{group}-\\1.yaml")
|
52
|
+
when /.jsonnet/
|
53
|
+
File.basename(template_path).sub(/([^-.]+)\.jsonnet$/, "#{group}-\\1.yaml")
|
54
|
+
else
|
55
|
+
raise "unexpected template_path #{template_path.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "template"
|
4
|
+
|
5
|
+
module KubernetesTemplateRendering
|
6
|
+
class ErbTemplate < Template
|
7
|
+
module Snippet
|
8
|
+
def snippet(file)
|
9
|
+
snippet = "#{File.dirname(@template_path)}/#{file}"
|
10
|
+
content = File.read(snippet)
|
11
|
+
erb = ERB.new(content, trim_mode: "-")
|
12
|
+
erb.filename = snippet
|
13
|
+
erb.result(variables_object._binding)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
include Snippet
|
18
|
+
|
19
|
+
class VariablesClass < BasicObject
|
20
|
+
include Snippet
|
21
|
+
|
22
|
+
def initialize(template_path, variables)
|
23
|
+
@template_path = template_path
|
24
|
+
@variables = variables
|
25
|
+
end
|
26
|
+
|
27
|
+
def _binding
|
28
|
+
::Kernel.binding
|
29
|
+
end
|
30
|
+
|
31
|
+
def keys
|
32
|
+
@variables.keys
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(sym, *args, &block)
|
36
|
+
@variables.fetch(sym.to_s) # will raise KeyError if not in @variables hash
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def variables_object
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def render(erb_binding: nil, jsonnet_library_path: nil)
|
47
|
+
rendered_erb = render_erb(erb_binding)
|
48
|
+
if template_path.end_with?("yaml.erb")
|
49
|
+
with_auto_generated_yaml_comment(sort_yaml(rendered_erb))
|
50
|
+
else
|
51
|
+
rendered_erb
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def variables_object
|
58
|
+
VariablesClass.new(template_path, variables)
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_erb(erb_binding)
|
62
|
+
content = File.read(template_path)
|
63
|
+
erb = ERB.new(content, trim_mode: "-")
|
64
|
+
erb.filename = template_path
|
65
|
+
erb_binding.nil? ? erb.result(variables_object._binding) : erb.result(erb_binding) # here is where we eval the template
|
66
|
+
end
|
67
|
+
|
68
|
+
def sort_yaml(erb_yaml)
|
69
|
+
if (yaml_docs = YAML.load_stream(erb_yaml)).any?
|
70
|
+
yaml_docs.map { |yaml_doc| sort_keys(yaml_doc).to_yaml }.join("\n")
|
71
|
+
else
|
72
|
+
erb_yaml
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def sort_keys(json_doc)
|
77
|
+
case json_doc
|
78
|
+
when Array
|
79
|
+
json_doc.map { |v| sort_keys(v) }
|
80
|
+
when Hash
|
81
|
+
with_sorted_values = json_doc.transform_values { |v| sort_keys(v) }
|
82
|
+
with_sorted_values.sort.to_h
|
83
|
+
else
|
84
|
+
json_doc
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jsonnet"
|
4
|
+
require_relative "template"
|
5
|
+
|
6
|
+
module KubernetesTemplateRendering
|
7
|
+
class JsonnetTemplate < Template
|
8
|
+
MULTI_FILE_RENDER_KEY = "MULTI_FILE_RENDER"
|
9
|
+
MULTI_FILE_RENDER_FILE_NAME_KEY = "MULTI_FILE_RENDER_NAME"
|
10
|
+
|
11
|
+
class MultiFileJsonnetRenderError < StandardError; end
|
12
|
+
|
13
|
+
def render(erb_binding: nil, jsonnet_library_path: nil)
|
14
|
+
json_doc = render_json_doc_from_template(jsonnet_library_path)
|
15
|
+
if multi_file_jsonnet_doc?(json_doc)
|
16
|
+
render_multi_file_jsonnet!(json_doc)
|
17
|
+
else
|
18
|
+
with_auto_generated_yaml_comment(json_doc.to_yaml)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def render_json_doc_from_template(jsonnet_library_path)
|
25
|
+
vm = Jsonnet::VM.new
|
26
|
+
vm.tla_code("vars", variables.to_json)
|
27
|
+
if jsonnet_library_path.nil?
|
28
|
+
vm.jpath_add(File.expand_path('../../../vendor-jb', __dir__))
|
29
|
+
else
|
30
|
+
vm.jpath_add(jsonnet_library_path)
|
31
|
+
end
|
32
|
+
JSON.parse(vm.evaluate_file(template_path))
|
33
|
+
end
|
34
|
+
|
35
|
+
def multi_file_jsonnet_doc?(json_doc)
|
36
|
+
json_doc[MULTI_FILE_RENDER_KEY]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Multi-File JSONNET Template Structuring:
|
40
|
+
# Top Level - Multi File Hash
|
41
|
+
# Keys map to Either Hash or Array
|
42
|
+
# If Array, the values of the array must be Hashs.
|
43
|
+
# Hash can be either a normal Hash or another Multi-File Hash
|
44
|
+
|
45
|
+
def render_multi_file_jsonnet!(json_doc, file_name_to_yaml_hash = {})
|
46
|
+
json_doc.delete(MULTI_FILE_RENDER_KEY)
|
47
|
+
json_doc.each do |key, value|
|
48
|
+
case value
|
49
|
+
when Hash
|
50
|
+
render_json_doc(value, key, file_name_to_yaml_hash)
|
51
|
+
when NilClass
|
52
|
+
next
|
53
|
+
else
|
54
|
+
raise ArgumentError, "must be a Hash or NilClass, was #{value.inspect}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
file_name_to_yaml_hash
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_json_doc(json_doc, default_file_name, file_name_to_yaml_hash)
|
61
|
+
if multi_file_jsonnet_doc?(json_doc)
|
62
|
+
render_multi_file_jsonnet!(json_doc, file_name_to_yaml_hash)
|
63
|
+
else
|
64
|
+
file_name = file_name_from_object(json_doc, default: default_file_name)
|
65
|
+
file_name_to_yaml_hash["#{file_name}.yaml"] = with_auto_generated_yaml_comment(json_doc.to_yaml)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_json_doc!(json_doc, context)
|
70
|
+
json_doc.is_a?(Hash) or raise MultiFileJsonnetRenderError, context
|
71
|
+
end
|
72
|
+
|
73
|
+
def file_name_from_object(object, default: nil)
|
74
|
+
if (base_name = object.delete(MULTI_FILE_RENDER_FILE_NAME_KEY))
|
75
|
+
base_name
|
76
|
+
else
|
77
|
+
default
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "color"
|
5
|
+
require_relative "erb_template"
|
6
|
+
require_relative "jsonnet_template"
|
7
|
+
|
8
|
+
module KubernetesTemplateRendering
|
9
|
+
class Resource
|
10
|
+
class UnexpectedFileTypeError < StandardError; end
|
11
|
+
|
12
|
+
attr_reader :variables, :template_path, :output_directory
|
13
|
+
|
14
|
+
def initialize(template_path:, definitions_path:, variables:, output_directory:, output_filename: nil)
|
15
|
+
@template_path = template_path
|
16
|
+
@definitions_path = definitions_path
|
17
|
+
@variables = variables
|
18
|
+
@output_directory = output_directory
|
19
|
+
@output_filename = output_filename || template_filename(template_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(args)
|
23
|
+
write_template(args)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def rendered_template(args)
|
29
|
+
@rendered_template ||= template_klass.render(@template_path, variables, jsonnet_library_path: args.jsonnet_library_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_template(args)
|
33
|
+
print_status
|
34
|
+
|
35
|
+
# If a Hash is returned, that means this is a multi-file template, meaning we need to iterate over the hash.
|
36
|
+
# Else a String is returned and we can write it directly to the output file.
|
37
|
+
rt = rendered_template(args)
|
38
|
+
|
39
|
+
if args.render_files?
|
40
|
+
if rt.is_a?(Hash)
|
41
|
+
rt.each do |filename, contents|
|
42
|
+
File.write(output_path(filename), contents)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
File.write(output_path(@output_filename), rt)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def template_klass
|
51
|
+
case @template_path
|
52
|
+
when /\.erb\z/
|
53
|
+
ErbTemplate
|
54
|
+
when /\.jsonnet\z/
|
55
|
+
JsonnetTemplate
|
56
|
+
else
|
57
|
+
raise UnexpectedFileTypeError, "Unexpected file type #{@template_path}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def print_status
|
62
|
+
variable_output = variables.map { |k, v| "#{Color.magenta(k)}=#{Color.blue(v)}" }.join(', ')
|
63
|
+
puts "Writing #{Color.magenta(File.basename(output_path(@output_filename)))} with variables #{variable_output}\n\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
def output_path(filename)
|
67
|
+
File.join(@output_directory, filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
def template_filename(template_path)
|
71
|
+
if template_path.match(/\.erb\z/)
|
72
|
+
File.basename(template_path, '.erb')
|
73
|
+
|
74
|
+
elsif template_path.match(/\.jsonnet\z/)
|
75
|
+
Pathname.new(template_path).basename.sub_ext('.yaml')
|
76
|
+
|
77
|
+
else
|
78
|
+
raise "Unexpected template_path format: #{template_path}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resource"
|
4
|
+
require_relative "deploy_grouped_resource"
|
5
|
+
|
6
|
+
# This class points to the resources in a given template_directory for a given kubernetes_cluster_type like 'ops' or 'prod' or 'ci'.
|
7
|
+
# `config` contains the definitions found in `definitions_path`.
|
8
|
+
# The most important config is "directory" which is a pattern like "%{region}/%{type}/%{color}/staging-ops".
|
9
|
+
# The config "regions", "colors" are the sets of regions and colors to render for.
|
10
|
+
# It renders into `rendered_directory`.
|
11
|
+
module KubernetesTemplateRendering
|
12
|
+
class ResourceSet
|
13
|
+
attr_reader :variables, :output_directory, :deploy_group_config, :omitted_resources,
|
14
|
+
:template_directory, :target_output_directory, :regions, :colors,
|
15
|
+
:definitions_path, :kubernetes_cluster_type
|
16
|
+
|
17
|
+
def initialize(config:, template_directory:, rendered_directory:, definitions_path:, kubernetes_cluster_type:)
|
18
|
+
@variables = config["variables"] || {}
|
19
|
+
@deploy_group_config = config["deploy_groups"]
|
20
|
+
@omitted_resources = config["omitted_resources"]
|
21
|
+
@template_directory = template_directory
|
22
|
+
@target_output_directory = config["directory"] or raise ArgumentError, "missing 'directory:' in #{config.inspect}"
|
23
|
+
@regions = config["regions"] || []
|
24
|
+
@colors = config["colors"] || []
|
25
|
+
@rendered_directory = rendered_directory
|
26
|
+
@definitions_path = definitions_path
|
27
|
+
@kubernetes_cluster_type = kubernetes_cluster_type
|
28
|
+
@resources = {}
|
29
|
+
|
30
|
+
if @kubernetes_cluster_type != "kube-platform"
|
31
|
+
@target_output_directory.include?("%{plain_region}") or raise "#{@template_directory}: target_output_directory #{@target_output_directory} needs %{plain_region}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def normal_render(args)
|
36
|
+
dynamic_output_directory? and raise "Directory must not be dynamic: #{target_output_directory}"
|
37
|
+
|
38
|
+
variables["kubernetes_cluster_type"] = @kubernetes_cluster_type
|
39
|
+
|
40
|
+
if (plain_region = variables["plain_region"])
|
41
|
+
default_region_vars(plain_region)
|
42
|
+
end
|
43
|
+
|
44
|
+
output_directory = File.join(@rendered_directory, target_output_directory)
|
45
|
+
render_create_directory(args, output_directory)
|
46
|
+
end
|
47
|
+
|
48
|
+
def render(args)
|
49
|
+
@regions.any? or raise "#{template_directory}: must have at least one region"
|
50
|
+
@colors.any? or raise "#{template_directory}: must have at least one color"
|
51
|
+
|
52
|
+
variables["kubernetes_cluster_type"] = @kubernetes_cluster_type
|
53
|
+
|
54
|
+
@regions.each do |plain_region|
|
55
|
+
default_region_vars(plain_region)
|
56
|
+
|
57
|
+
@colors.each do |c|
|
58
|
+
variables["color"] = c
|
59
|
+
output_directory = File.join(@rendered_directory, format(@target_output_directory, plain_region: plain_region, color: c, type: @kubernetes_cluster_type))
|
60
|
+
render_create_directory(args, output_directory)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def render_create_directory(args, output_directory)
|
66
|
+
create_directory(output_directory)
|
67
|
+
puts "Rendering templates to: #{Color.magenta(output_directory)}"
|
68
|
+
puts "Variable assignments:"
|
69
|
+
variables.each { |k, v| puts "\t#{Color.magenta(k)}=#{Color.blue(v)}" }
|
70
|
+
puts
|
71
|
+
if omitted_resources
|
72
|
+
puts "Omitted resources:"
|
73
|
+
omitted_resources.each { |ot| puts "\t#{ot}" }
|
74
|
+
end
|
75
|
+
puts
|
76
|
+
resources(output_directory).each do |resource|
|
77
|
+
resource.render(args)
|
78
|
+
end
|
79
|
+
puts
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
CLOUD_REGION_TO_PROVIDER_AND_DATACENTER = {
|
85
|
+
# Note: The names below should match RegionDiscovery from process_settings-production.
|
86
|
+
# https://github.com/Invoca/process_settings-production/blob/main/settings/region_discovery/production_regions.yml
|
87
|
+
"us-east-1" => ['aws', "AWS-us-east-1"],
|
88
|
+
"us-east-2" => ['aws', "AWS-us-east-2"],
|
89
|
+
"us-central1" => ['gcp', "GCE-us-central1"],
|
90
|
+
"us-west2" => ['gcp', "GCE-us-west2"],
|
91
|
+
"eu-central-1" => ['aws', "AWS-eu-central-1"],
|
92
|
+
"eu-west-1" => ['aws', "AWS-eu-west-1"],
|
93
|
+
"europe-west4" => ['gcp', "GCE-europe-west4"],
|
94
|
+
|
95
|
+
# other regions
|
96
|
+
"us-east1" => ['gcp', "GCE-us-east1"],
|
97
|
+
"us-west1" => ['gcp', "GCE-us-west1"],
|
98
|
+
"local" => ['', "local"]
|
99
|
+
}.freeze
|
100
|
+
|
101
|
+
# The zone to use for failure-domain.beta.kubernetes.io/zone or topology.kubernetes.io/zone by DeployGroup
|
102
|
+
AVAILABILITY_ZONE_FOR_DEPLOY_GROUP = {
|
103
|
+
"us-east-1" => {
|
104
|
+
"primary" => "us-east-1c",
|
105
|
+
"secondary" => "us-east-1d",
|
106
|
+
"tertiary" => "us-east-1b"
|
107
|
+
},
|
108
|
+
"us-east-2" => {
|
109
|
+
"primary" => "us-east-2a",
|
110
|
+
"secondary" => "us-east-2b",
|
111
|
+
"tertiary" => "us-east-2c"
|
112
|
+
},
|
113
|
+
"us-central1" => {
|
114
|
+
"primary" => "us-central1-a",
|
115
|
+
"secondary" => "us-central1-b",
|
116
|
+
"tertiary" => "us-central1-c"
|
117
|
+
},
|
118
|
+
"us-west2" => {
|
119
|
+
"primary" => "us-west2-a",
|
120
|
+
"secondary" => "us-west2-b",
|
121
|
+
"tertiary" => "us-west2-c"
|
122
|
+
},
|
123
|
+
"eu-central-1" => {
|
124
|
+
"primary" => "eu-central-1a",
|
125
|
+
"secondary" => "eu-central-1b",
|
126
|
+
"tertiary" => "eu-central-1c"
|
127
|
+
},
|
128
|
+
"eu-west-1" => {
|
129
|
+
"primary" => "eu-west-1a",
|
130
|
+
"secondary" => "eu-west-1b",
|
131
|
+
"tertiary" => "eu-west-1c"
|
132
|
+
},
|
133
|
+
"europe-west4" => {
|
134
|
+
"primary" => "europe-west4-a",
|
135
|
+
"secondary" => "europe-west4-b",
|
136
|
+
"tertiary" => "europe-west4-c"
|
137
|
+
},
|
138
|
+
"local" => {},
|
139
|
+
"us-west1" => {},
|
140
|
+
"us-east1" => {}
|
141
|
+
}
|
142
|
+
|
143
|
+
def default_region_vars(plain_region)
|
144
|
+
variables.has_key?("region") and raise "replace region with plain_region"
|
145
|
+
variables["plain_region"] = plain_region
|
146
|
+
cloud_datacenter_and_provider = CLOUD_REGION_TO_PROVIDER_AND_DATACENTER[plain_region] or raise "no CLOUD_REGION_TO_PROVIDER_AND_DATACENTER entry found for #{plain_region.inspect}"
|
147
|
+
variables["cloud_provider"], variables["cloud_datacenter"] = cloud_datacenter_and_provider
|
148
|
+
variables["cloud_region"] = variables["cloud_datacenter"] # for compatibility with old resource files; cloud_datacenter is preferred now
|
149
|
+
if plain_region == "local"
|
150
|
+
variables["data_silo"] ||= "local"
|
151
|
+
elsif plain_region.start_with?("us-")
|
152
|
+
variables["data_silo"] ||= "us"
|
153
|
+
end
|
154
|
+
|
155
|
+
# cannot do ||= because we want to reset when plain_region changes
|
156
|
+
variables["availability_zone_for_deploy_group"] = AVAILABILITY_ZONE_FOR_DEPLOY_GROUP[plain_region] or raise "no AVAILABILITY_ZONE_FOR_DEPLOY_GROUP mapping found for #{plain_region.inspect}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def create_directory(directory)
|
160
|
+
unless File.exist?(directory)
|
161
|
+
puts <<~MESSAGE
|
162
|
+
|
163
|
+
Directory #{Color.magenta(directory)} doesn't exist, #{Color.green('creating it')}
|
164
|
+
|
165
|
+
MESSAGE
|
166
|
+
FileUtils.mkdir_p(directory)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def resources(output_directory)
|
171
|
+
@resources[output_directory] ||= standard_resources(output_directory) + grouped_resources(output_directory)
|
172
|
+
end
|
173
|
+
|
174
|
+
def standard_resources(output_directory)
|
175
|
+
standard_template_paths.map do |path|
|
176
|
+
Resource.new(template_path: path, definitions_path: @definitions_path, variables: variables, output_directory: output_directory)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def grouped_resources(output_directory)
|
181
|
+
deploy_grouped_template_paths.map do |path|
|
182
|
+
DeployGroupedResource.new(
|
183
|
+
template_path: path,
|
184
|
+
definitions_path: @definitions_path,
|
185
|
+
variables: variables,
|
186
|
+
output_directory: output_directory,
|
187
|
+
groups_to_render: deploy_groups_to_render,
|
188
|
+
template_path_exclusions: deploy_group_config["exclude_files"],
|
189
|
+
group_variable_name: deploy_group_config["variable_name"]
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def standard_template_paths
|
195
|
+
@standard_template_paths ||=
|
196
|
+
(Dir[File.join(template_directory, "*.yaml.erb")] +
|
197
|
+
Dir[File.join(template_directory, '*.jsonnet')]) -
|
198
|
+
deploy_grouped_template_paths - omitted_resource_paths
|
199
|
+
end
|
200
|
+
|
201
|
+
def deploy_grouped_template_paths
|
202
|
+
@deploy_grouped_template_paths ||=
|
203
|
+
if deploy_group_config
|
204
|
+
deploy_group_config["files"]&.map { |file| File.join(template_directory, file) } ||
|
205
|
+
(Dir[File.join(template_directory, "*-deploy.yaml.erb")] + Dir[File.join(template_directory, "*-deploy.jsonnet")]) - omitted_resource_paths
|
206
|
+
else
|
207
|
+
[]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def omitted_resource_paths
|
212
|
+
@omitted_resource_paths ||= omitted_resources&.map { |file| File.join(template_directory, file) } || []
|
213
|
+
end
|
214
|
+
|
215
|
+
def deploy_groups_to_render
|
216
|
+
group_names = deploy_group_config["group_names"]
|
217
|
+
if array_of_arrays?(group_names)
|
218
|
+
first, *rest = group_names
|
219
|
+
first.product(*rest).map { |group| group.join("-") }
|
220
|
+
else
|
221
|
+
group_names
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def array_of_arrays?(array)
|
226
|
+
array.all? { |item| item.is_a?(Array) }
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "open3"
|
5
|
+
require "shellwords"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
# This is a base class for all Templates. Derived classes must implement the render method.
|
9
|
+
module KubernetesTemplateRendering
|
10
|
+
class Template < OpenStruct
|
11
|
+
attr_reader :template_path, :variables
|
12
|
+
def initialize(template_path, variables)
|
13
|
+
@template_path = template_path
|
14
|
+
@variables = variables
|
15
|
+
end
|
16
|
+
|
17
|
+
def render(args)
|
18
|
+
raise "must be defined by subclass"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def with_auto_generated_yaml_comment(yaml_string)
|
24
|
+
comment = <<~EOS
|
25
|
+
# WARNING: DO NO EDIT THIS FILE!
|
26
|
+
# Any changes made here will be lost.
|
27
|
+
# This file is autogenerated from #{template_path}
|
28
|
+
EOS
|
29
|
+
comment + yaml_string
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# @param [String] template_path: file path to template file that needs to be rendered.
|
34
|
+
# @param [Hash]: variables that will be used in the template file to generate distict files.
|
35
|
+
#
|
36
|
+
# @return [String] generated YAML file
|
37
|
+
# @return [Hash] Hash of file names as keys with the values being corresponding generated YAML.
|
38
|
+
#
|
39
|
+
# @raise [UnexpectedFileTypeError] if file type doesn't match [yaml.erb, erb, or jsonnet]
|
40
|
+
# @raise [MultiFileJsonnetRenderError] if Jsonnet template files don't follow proper MultiFileJsonnet templating.
|
41
|
+
|
42
|
+
# TODO
|
43
|
+
# The ErbTemplate and JsonnetTemplate classes both inherit from the Template class and implement a render method.
|
44
|
+
# However, the erb_binding parameter is used just in ErbTemplate, while the jsonnet_library_path parameter is used just in JsonnetTemplate.
|
45
|
+
# This is a little awkward. Potentially this could be refactored.
|
46
|
+
def render(template_path, variables, erb_binding: nil, jsonnet_library_path: nil)
|
47
|
+
new(template_path, variables).render(erb_binding: erb_binding, jsonnet_library_path: jsonnet_library_path)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext' # for deep_merge
|
5
|
+
require 'invoca/utils'
|
6
|
+
require 'yaml'
|
7
|
+
require_relative 'resource_set'
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
# This class points to a collection of template directories to render, and a rendered_directory to render into.
|
11
|
+
# Optionally, some of the template directories may be omitted by including them in `omitted_names`.
|
12
|
+
module KubernetesTemplateRendering
|
13
|
+
class TemplateDirectoryRenderer
|
14
|
+
DEFINITIONS_FILENAME = "definitions.yaml"
|
15
|
+
|
16
|
+
attr_reader :directories, :omitted_names, :rendered_directory, :cluster_type, :region, :color, :variable_overrides
|
17
|
+
|
18
|
+
def initialize(directories:, rendered_directory:, omitted_names: [], cluster_type: nil, region: nil, color: nil, variable_overrides: nil)
|
19
|
+
@directories = directories_with_definitions(Array(directories))
|
20
|
+
@omitted_names = Array(omitted_names)
|
21
|
+
@rendered_directory = rendered_directory
|
22
|
+
@cluster_type = cluster_type
|
23
|
+
@region = region
|
24
|
+
@color = color
|
25
|
+
@variable_overrides = variable_overrides || {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def render(args)
|
29
|
+
child_pids = []
|
30
|
+
|
31
|
+
resource_sets.each do |name, resource_sets|
|
32
|
+
puts "Rendering templates for definition #{Color.red(name)}..."
|
33
|
+
resource_sets.each do |resource_set|
|
34
|
+
if args.fork?
|
35
|
+
if (pid = Process.fork)
|
36
|
+
# this is the parent
|
37
|
+
child_pids << pid
|
38
|
+
wait_if_max_forked(child_pids)
|
39
|
+
else
|
40
|
+
# this is the child
|
41
|
+
render_set(args, resource_set)
|
42
|
+
Kernel.exit! # skip at_exit handlers since parent will run those
|
43
|
+
end
|
44
|
+
else
|
45
|
+
render_set(args, resource_set)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if args.fork?
|
51
|
+
Process.waitall
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def read_definitions(path)
|
58
|
+
File.read(path)
|
59
|
+
end
|
60
|
+
|
61
|
+
MAX_FORKED_PROCESSES = 9
|
62
|
+
|
63
|
+
def wait_if_max_forked(child_pids)
|
64
|
+
while child_pids.size >= MAX_FORKED_PROCESSES
|
65
|
+
begin
|
66
|
+
Process.waitpid # this is a race condition because 1 or more processes could exit before we get here
|
67
|
+
rescue SystemCallError # this will happen if they all exited before we called waitpid
|
68
|
+
end
|
69
|
+
child_pids.delete_if do |pid|
|
70
|
+
Process.waitpid(pid, Process::WNOHANG)
|
71
|
+
rescue Errno::ECHILD # No child processes
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def render_set(args, resource_set)
|
78
|
+
resource_set.render(args)
|
79
|
+
rescue => ex
|
80
|
+
raise "error rendering ResourceSet from #{resource_set.definitions_path}\n#{ex.class}: #{ex.message}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def directories_with_definitions(directories)
|
84
|
+
directories.select do |dir|
|
85
|
+
definitions_path = definitions_path_for_dir(dir)
|
86
|
+
File.exist?(definitions_path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def definitions_path_for_dir(dir)
|
91
|
+
File.join(dir, DEFINITIONS_FILENAME)
|
92
|
+
end
|
93
|
+
|
94
|
+
def resource_sets
|
95
|
+
@resource_sets ||= @directories.each_with_object({}) do |dir, hash|
|
96
|
+
definitions_path = definitions_path_for_dir(dir)
|
97
|
+
config = load_config(definitions_path)
|
98
|
+
|
99
|
+
config.map do |name, config|
|
100
|
+
next if omitted_names.include?(name)
|
101
|
+
|
102
|
+
kubernetes_cluster_type = name.sub('SPP-PLACEHOLDER', 'staging').sub(/\..*/, '') # prod.gcp => prod
|
103
|
+
|
104
|
+
hash[name] ||= []
|
105
|
+
hash[name] << ResourceSet.new(config: config, template_directory: dir, rendered_directory: @rendered_directory, kubernetes_cluster_type: kubernetes_cluster_type, definitions_path: definitions_path)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_libsonnet(dir, config)
|
111
|
+
fname = File.join(dir, 'definitions.libsonnet')
|
112
|
+
existing = File.exists?(fname) ? File.read(fname) : ""
|
113
|
+
proposed = build_json(config)
|
114
|
+
|
115
|
+
if existing != proposed
|
116
|
+
puts("Generating updated #{Color.magenta(File.basename(fname))}")
|
117
|
+
File.write(fname, proposed)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_json(config)
|
122
|
+
hash = transform_for_jsonnet(config)
|
123
|
+
JSON.pretty_generate(hash)
|
124
|
+
end
|
125
|
+
|
126
|
+
# This method ensures that OpenStructs are
|
127
|
+
# converted to hashes to support to_json operation
|
128
|
+
# It also converts any embedded variable place holders
|
129
|
+
# into a Jsonnet friendly format:
|
130
|
+
#
|
131
|
+
# %{variable} is converted to %(variable)s which can
|
132
|
+
# then be used with the Jsonnet function std.format
|
133
|
+
def transform_for_jsonnet(hash)
|
134
|
+
hash.transform_values do |value|
|
135
|
+
case value
|
136
|
+
when OpenStruct
|
137
|
+
transform_for_jsonnet(value.to_h)
|
138
|
+
when Hash
|
139
|
+
transform_for_jsonnet(value)
|
140
|
+
else
|
141
|
+
value
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def load_config(definitions_path)
|
147
|
+
begin
|
148
|
+
config = YAML.safe_load(read_definitions(definitions_path), aliases: true)
|
149
|
+
rescue => ex
|
150
|
+
raise "error loading YAML from #{definitions_path}:\n#{ex.class}: #{ex.message}"
|
151
|
+
end
|
152
|
+
|
153
|
+
expand_config(config).each_with_object({}) do |(name, data), hash|
|
154
|
+
if !cluster_type || cluster_type == name.sub('SPP-PLACEHOLDER', 'staging').sub(/\..*/, '') # prod.gcp => prod
|
155
|
+
cluster_type_config = OpenStruct.new(data)
|
156
|
+
|
157
|
+
cluster_type_config.regions = cluster_type_config.regions & [region] if region
|
158
|
+
cluster_type_config.colors = cluster_type_config.colors & [color] if color
|
159
|
+
cluster_type_config.variables = (cluster_type_config.variables || {}).merge(variable_overrides)
|
160
|
+
|
161
|
+
hash[name] = cluster_type_config if (region.nil? && color.nil?) || (cluster_type_config.regions.any? && cluster_type_config.colors.any?)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# returns a copy of the given config hash with the COMMON: k-v removed and deep merged into the other config values
|
167
|
+
# (explicit config values take precedence over COMMON: ones)
|
168
|
+
def expand_config(config)
|
169
|
+
common = config.delete('COMMON') || {}
|
170
|
+
|
171
|
+
Hash[config.map { |k, v| [k, common.deep_merge(v)] }]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kubernetes_template_rendering
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Octothorpe
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-04-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jsonnet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: invoca-utils
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Tool for rendering ERB and Jsonnet templates
|
56
|
+
email:
|
57
|
+
- octothorpe@invoca.com
|
58
|
+
executables:
|
59
|
+
- render_templates
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".buildkite/pipeline.yml"
|
64
|
+
- ".rspec"
|
65
|
+
- ".rubocop.yml"
|
66
|
+
- ".ruby-version"
|
67
|
+
- CHANGELOG.md
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- exe/render_templates
|
71
|
+
- lib/kubernetes_template_rendering.rb
|
72
|
+
- lib/kubernetes_template_rendering/cli.rb
|
73
|
+
- lib/kubernetes_template_rendering/cli_arguments.rb
|
74
|
+
- lib/kubernetes_template_rendering/color.rb
|
75
|
+
- lib/kubernetes_template_rendering/deploy_grouped_resource.rb
|
76
|
+
- lib/kubernetes_template_rendering/erb_template.rb
|
77
|
+
- lib/kubernetes_template_rendering/jsonnet_template.rb
|
78
|
+
- lib/kubernetes_template_rendering/resource.rb
|
79
|
+
- lib/kubernetes_template_rendering/resource_set.rb
|
80
|
+
- lib/kubernetes_template_rendering/template.rb
|
81
|
+
- lib/kubernetes_template_rendering/template_directory_renderer.rb
|
82
|
+
- lib/kubernetes_template_rendering/version.rb
|
83
|
+
- sig/invoca/kubernetes_templates.rbs
|
84
|
+
homepage: https://github.com/Invoca/kubernetes_template_rendering
|
85
|
+
licenses: []
|
86
|
+
metadata:
|
87
|
+
allowed_push_host: https://rubygems.org
|
88
|
+
homepage_uri: https://github.com/Invoca/kubernetes_template_rendering
|
89
|
+
source_code_uri: https://github.com/Invoca/kubernetes_template_rendering
|
90
|
+
changelog_uri: https://github.com/Invoca/kubernetes_template_rendering/blob/main/CHANGELOG.md
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 3.1.0
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.3.1
|
105
|
+
requirements: []
|
106
|
+
rubygems_version: 3.4.21
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Tool for rendering ERB and Jsonnet templates
|
110
|
+
test_files: []
|