invoca-kubernetes_templates 0.1.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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_kubernetes_templates +12 -0
- data/lib/invoca/kubernetes_templates/cli.rb +79 -0
- data/lib/invoca/kubernetes_templates/cli_arguments.rb +34 -0
- data/lib/invoca/kubernetes_templates/color.rb +33 -0
- data/lib/invoca/kubernetes_templates/deploy_grouped_resource.rb +61 -0
- data/lib/invoca/kubernetes_templates/erb_template.rb +90 -0
- data/lib/invoca/kubernetes_templates/jsonnet_template.rb +83 -0
- data/lib/invoca/kubernetes_templates/resource.rb +84 -0
- data/lib/invoca/kubernetes_templates/resource_set.rb +231 -0
- data/lib/invoca/kubernetes_templates/template.rb +53 -0
- data/lib/invoca/kubernetes_templates/template_directory_renderer.rb +176 -0
- data/lib/invoca/kubernetes_templates/version.rb +7 -0
- data/lib/invoca/kubernetes_templates.rb +10 -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: 56823d5fc55ba306d3ab46f15c1f5228fa3a57f1edcb930461c66be155291b6c
|
4
|
+
data.tar.gz: 99c85338f9c8f68433bb0443677204121a58947b87621a34c419cd3f81072b89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d7c3c7d72c82e8d6360217bf552f4bee43b019072707e34832500a1ab56874b0072a8fc448d4856b0465a0a85071aabfd266732c8ca2642d712a38cb477d965
|
7
|
+
data.tar.gz: aa8dc2d7092e56247436689f1ef6572e4401a7fb07eabced192475f1c0858ef719e0483f855d071337e0ecbc3cc7d26a9b512c69c184e074d11af65c5f9f8c37
|
@@ -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
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Invoca::KubernetesTemplates
|
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 invoca-kubernetes_templates
|
12
|
+
```
|
13
|
+
|
14
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
15
|
+
```
|
16
|
+
gem install invoca-kubernetes_templates
|
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 invoca-kubernetes_templates 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 invoca-kubernetes_templates 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/invoca-kubernetes_templates.
|
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/invoca/kubernetes_templates/cli"
|
11
|
+
|
12
|
+
Invoca::KubernetesTemplates::CLI.parse_and_render(ARGV)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "template_directory_renderer"
|
4
|
+
require_relative "cli_arguments"
|
5
|
+
|
6
|
+
module Invoca
|
7
|
+
module KubernetesTemplates
|
8
|
+
class CLI
|
9
|
+
DEFINITIONS_FILENAME = "definitions.yaml"
|
10
|
+
|
11
|
+
Arguments = CLIArguments
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def parse_and_render(options)
|
15
|
+
renderer, args = parse(options)
|
16
|
+
|
17
|
+
renderer.render(args)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse(options)
|
23
|
+
args = Arguments.new
|
24
|
+
|
25
|
+
parser = OptionParser.new do |op|
|
26
|
+
op.banner = "Usage: #{$PROGRAM_NAME} --rendered-directory=<directory> <template directory>"
|
27
|
+
|
28
|
+
op.on("--rendered-directory=RENDERED_DIRECTORY", "set the directory where rendered output is written") { args.rendered_directory = _1 }
|
29
|
+
op.on("--[no-]fork", "disable/enable fork") { |fork| args.fork = fork }
|
30
|
+
op.on("--makeflags=MAKEFLAGS", "pass through makeflags so that we can infer fork preference from -j") { args.makeflags = _1 }
|
31
|
+
op.on("--jsonnet_library_path=JSONNET_LIBRARY_PATH", "set the jsonnet library path") { args.jsonnet_library_path = _1 }
|
32
|
+
op.on("--cluster_type=CLUSTER_TYPE", "set the specific cluster type to render") { args.cluster_type = _1 }
|
33
|
+
op.on("--region=REGION", "set the specific region to render") { args.region = _1 }
|
34
|
+
op.on("--color=COLOR", "set the specific color to render") { args.color = _1 }
|
35
|
+
|
36
|
+
op.on("--variable-override=KEY:VALUE", "override a variable value set within definitions.yaml") do |override|
|
37
|
+
args.variable_overrides ||= {}
|
38
|
+
args.variable_overrides.merge!(Hash[[override.split(":", 2)]])
|
39
|
+
end
|
40
|
+
|
41
|
+
op.on("-h", "--help") do
|
42
|
+
puts op
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
parser.parse!(options)
|
48
|
+
args.template_directory = options.first
|
49
|
+
|
50
|
+
unless args.valid?
|
51
|
+
STDERR.puts(parser)
|
52
|
+
exit(1)
|
53
|
+
end
|
54
|
+
|
55
|
+
[renderer_from_args(args), args]
|
56
|
+
end
|
57
|
+
|
58
|
+
def renderer_from_args(args)
|
59
|
+
directories = template_directories(args.template_directory, DEFINITIONS_FILENAME)
|
60
|
+
|
61
|
+
TemplateDirectoryRenderer.new(
|
62
|
+
directories: directories,
|
63
|
+
rendered_directory: args.rendered_directory,
|
64
|
+
cluster_type: args.cluster_type,
|
65
|
+
region: args.region,
|
66
|
+
color: args.color,
|
67
|
+
variable_overrides: args.variable_overrides
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def template_directories(template_directory, definitions_file)
|
72
|
+
File.directory?(template_directory) or raise ArgumentError, "template directory not found--make sure to include templates/ prefix: #{template_directory}"
|
73
|
+
directories = Dir["#{template_directory}/**/#{definitions_file}"]
|
74
|
+
directories.map { |directory| File.dirname(directory) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Invoca
|
4
|
+
module KubernetesTemplates
|
5
|
+
CLIArguments =
|
6
|
+
Struct.new(
|
7
|
+
:rendered_directory,
|
8
|
+
:template_directory,
|
9
|
+
:fork,
|
10
|
+
:makeflags,
|
11
|
+
:jsonnet_library_path,
|
12
|
+
:cluster_type,
|
13
|
+
:region,
|
14
|
+
:color,
|
15
|
+
:variable_overrides
|
16
|
+
) do
|
17
|
+
def valid?
|
18
|
+
rendered_directory && template_directory
|
19
|
+
end
|
20
|
+
|
21
|
+
def fork?
|
22
|
+
if fork.nil?
|
23
|
+
makeflags&.include?('-j')
|
24
|
+
else
|
25
|
+
fork
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_files?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Invoca
|
4
|
+
module KubernetesTemplates
|
5
|
+
module Color
|
6
|
+
class << self
|
7
|
+
def black(str)
|
8
|
+
"\e[30m#{str}\e[0m"
|
9
|
+
end
|
10
|
+
|
11
|
+
def red(str)
|
12
|
+
"\e[31m#{str}\e[0m"
|
13
|
+
end
|
14
|
+
|
15
|
+
def green(str)
|
16
|
+
"\e[32m#{str}\e[0m"
|
17
|
+
end
|
18
|
+
|
19
|
+
def brown(str)
|
20
|
+
"\e[33m#{str}\e[0m"
|
21
|
+
end
|
22
|
+
|
23
|
+
def blue(str)
|
24
|
+
"\e[34m#{str}\e[0m"
|
25
|
+
end
|
26
|
+
|
27
|
+
def magenta(str)
|
28
|
+
"\e[35m#{str}\e[0m"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resource"
|
4
|
+
|
5
|
+
module Invoca
|
6
|
+
module KubernetesTemplates
|
7
|
+
class DeployGroupedResource
|
8
|
+
DEFAULT_GROUP_VARIABLE_NAME = "deploy_group"
|
9
|
+
|
10
|
+
attr_reader :groups_to_render, :variables, :template_path, :output_directory, :template_path_exclusions, :group_variable_name
|
11
|
+
|
12
|
+
def initialize(template_path:, definitions_path:, variables:, output_directory:, groups_to_render:, template_path_exclusions:, group_variable_name: nil)
|
13
|
+
@template_path = template_path
|
14
|
+
@definitions_path = definitions_path
|
15
|
+
@variables = variables
|
16
|
+
@output_directory = output_directory
|
17
|
+
@groups_to_render = groups_to_render
|
18
|
+
@template_path_exclusions = template_path_exclusions || {}
|
19
|
+
@group_variable_name = group_variable_name || DEFAULT_GROUP_VARIABLE_NAME
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(args)
|
23
|
+
@resources =
|
24
|
+
groups_to_render.map do |deploy_group|
|
25
|
+
if template_is_excluded?(deploy_group)
|
26
|
+
puts "Skipping #{Color.magenta(template_path_basename)} for #{deploy_group} deploy group due to it being " \
|
27
|
+
"excluded within the deploy group config\n\n"
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
vars = variables.merge(group_variable_name => deploy_group)
|
31
|
+
filename = filename_for_deploy_group(deploy_group)
|
32
|
+
Resource.new(template_path: template_path, definitions_path: @definitions_path, variables: vars, output_directory: output_directory, output_filename: filename).tap do |resource|
|
33
|
+
resource.render(args) if args.render_files?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def template_path_basename
|
42
|
+
@template_path_basename ||= File.basename(template_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def template_is_excluded?(deploy_group)
|
46
|
+
@template_path_exclusions[deploy_group]&.include?(template_path_basename)
|
47
|
+
end
|
48
|
+
|
49
|
+
def filename_for_deploy_group(group)
|
50
|
+
case @template_path
|
51
|
+
when /.erb/
|
52
|
+
File.basename(template_path, ".erb").sub(/([^-.]+)\.yaml$/, "#{group}-\\1.yaml")
|
53
|
+
when /.jsonnet/
|
54
|
+
File.basename(template_path).sub(/([^-.]+)\.jsonnet$/, "#{group}-\\1.yaml")
|
55
|
+
else
|
56
|
+
raise "unexpected template_path #{template_path.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "template"
|
4
|
+
|
5
|
+
module Invoca
|
6
|
+
module KubernetesTemplates
|
7
|
+
class ErbTemplate < Template
|
8
|
+
module Snippet
|
9
|
+
def snippet(file)
|
10
|
+
snippet = "#{File.dirname(@template_path)}/#{file}"
|
11
|
+
content = File.read(snippet)
|
12
|
+
erb = ERB.new(content, trim_mode: "-")
|
13
|
+
erb.filename = snippet
|
14
|
+
erb.result(variables_object._binding)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
include Snippet
|
19
|
+
|
20
|
+
class VariablesClass < BasicObject
|
21
|
+
include Snippet
|
22
|
+
|
23
|
+
def initialize(template_path, variables)
|
24
|
+
@template_path = template_path
|
25
|
+
@variables = variables
|
26
|
+
end
|
27
|
+
|
28
|
+
def _binding
|
29
|
+
::Kernel.binding
|
30
|
+
end
|
31
|
+
|
32
|
+
def keys
|
33
|
+
@variables.keys
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(sym, *args, &block)
|
37
|
+
@variables.fetch(sym.to_s) # will raise KeyError if not in @variables hash
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def variables_object
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def render(erb_binding: nil, jsonnet_library_path: nil)
|
48
|
+
rendered_erb = render_erb(erb_binding)
|
49
|
+
if template_path.end_with?("yaml.erb")
|
50
|
+
with_auto_generated_yaml_comment(sort_yaml(rendered_erb))
|
51
|
+
else
|
52
|
+
rendered_erb
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def variables_object
|
59
|
+
VariablesClass.new(template_path, variables)
|
60
|
+
end
|
61
|
+
|
62
|
+
def render_erb(erb_binding)
|
63
|
+
content = File.read(template_path)
|
64
|
+
erb = ERB.new(content, trim_mode: "-")
|
65
|
+
erb.filename = template_path
|
66
|
+
erb_binding.nil? ? erb.result(variables_object._binding) : erb.result(erb_binding) # here is where we eval the template
|
67
|
+
end
|
68
|
+
|
69
|
+
def sort_yaml(erb_yaml)
|
70
|
+
if (yaml_docs = YAML.load_stream(erb_yaml)).any?
|
71
|
+
yaml_docs.map { |yaml_doc| sort_keys(yaml_doc).to_yaml }.join("\n")
|
72
|
+
else
|
73
|
+
erb_yaml
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def sort_keys(json_doc)
|
78
|
+
case json_doc
|
79
|
+
when Array
|
80
|
+
json_doc.map { |v| sort_keys(v) }
|
81
|
+
when Hash
|
82
|
+
with_sorted_values = json_doc.transform_values { |v| sort_keys(v) }
|
83
|
+
with_sorted_values.sort.to_h
|
84
|
+
else
|
85
|
+
json_doc
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jsonnet"
|
4
|
+
require_relative "template"
|
5
|
+
|
6
|
+
module Invoca
|
7
|
+
module KubernetesTemplates
|
8
|
+
class JsonnetTemplate < Template
|
9
|
+
MULTI_FILE_RENDER_KEY = "MULTI_FILE_RENDER"
|
10
|
+
MULTI_FILE_RENDER_FILE_NAME_KEY = "MULTI_FILE_RENDER_NAME"
|
11
|
+
|
12
|
+
class MultiFileJsonnetRenderError < StandardError; end
|
13
|
+
|
14
|
+
def render(erb_binding: nil, jsonnet_library_path: nil)
|
15
|
+
json_doc = render_json_doc_from_template(jsonnet_library_path)
|
16
|
+
if multi_file_jsonnet_doc?(json_doc)
|
17
|
+
render_multi_file_jsonnet!(json_doc)
|
18
|
+
else
|
19
|
+
with_auto_generated_yaml_comment(json_doc.to_yaml)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def render_json_doc_from_template(jsonnet_library_path)
|
26
|
+
vm = Jsonnet::VM.new
|
27
|
+
vm.tla_code("vars", variables.to_json)
|
28
|
+
if jsonnet_library_path.nil?
|
29
|
+
vm.jpath_add(File.expand_path('../../../../../vendor-jb', __dir__))
|
30
|
+
else
|
31
|
+
vm.jpath_add(jsonnet_library_path)
|
32
|
+
end
|
33
|
+
JSON.parse(vm.evaluate_file(template_path))
|
34
|
+
end
|
35
|
+
|
36
|
+
def multi_file_jsonnet_doc?(json_doc)
|
37
|
+
json_doc[MULTI_FILE_RENDER_KEY]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Multi-File JSONNET Template Structuring:
|
41
|
+
# Top Level - Multi File Hash
|
42
|
+
# Keys map to Either Hash or Array
|
43
|
+
# If Array, the values of the array must be Hashs.
|
44
|
+
# Hash can be either a normal Hash or another Multi-File Hash
|
45
|
+
|
46
|
+
def render_multi_file_jsonnet!(json_doc, file_name_to_yaml_hash = {})
|
47
|
+
json_doc.delete(MULTI_FILE_RENDER_KEY)
|
48
|
+
json_doc.each do |key, value|
|
49
|
+
case value
|
50
|
+
when Hash
|
51
|
+
render_json_doc(value, key, file_name_to_yaml_hash)
|
52
|
+
when NilClass
|
53
|
+
next
|
54
|
+
else
|
55
|
+
raise ArgumentError, "must be a Hash or NilClass, was #{value.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
file_name_to_yaml_hash
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_json_doc(json_doc, default_file_name, file_name_to_yaml_hash)
|
62
|
+
if multi_file_jsonnet_doc?(json_doc)
|
63
|
+
render_multi_file_jsonnet!(json_doc, file_name_to_yaml_hash)
|
64
|
+
else
|
65
|
+
file_name = file_name_from_object(json_doc, default: default_file_name)
|
66
|
+
file_name_to_yaml_hash["#{file_name}.yaml"] = with_auto_generated_yaml_comment(json_doc.to_yaml)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_json_doc!(json_doc, context)
|
71
|
+
json_doc.is_a?(Hash) or raise MultiFileJsonnetRenderError, context
|
72
|
+
end
|
73
|
+
|
74
|
+
def file_name_from_object(object, default: nil)
|
75
|
+
if (base_name = object.delete(MULTI_FILE_RENDER_FILE_NAME_KEY))
|
76
|
+
base_name
|
77
|
+
else
|
78
|
+
default
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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 Invoca
|
9
|
+
module KubernetesTemplates
|
10
|
+
class Resource
|
11
|
+
class UnexpectedFileTypeError < StandardError; end
|
12
|
+
|
13
|
+
attr_reader :variables, :template_path, :output_directory
|
14
|
+
|
15
|
+
def initialize(template_path:, definitions_path:, variables:, output_directory:, output_filename: nil)
|
16
|
+
@template_path = template_path
|
17
|
+
@definitions_path = definitions_path
|
18
|
+
@variables = variables
|
19
|
+
@output_directory = output_directory
|
20
|
+
@output_filename = output_filename || template_filename(template_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(args)
|
24
|
+
write_template(args)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def rendered_template(args)
|
30
|
+
@rendered_template ||= template_klass.render(@template_path, variables, jsonnet_library_path: args.jsonnet_library_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_template(args)
|
34
|
+
print_status
|
35
|
+
|
36
|
+
# If a Hash is returned, that means this is a multi-file template, meaning we need to iterate over the hash.
|
37
|
+
# Else a String is returned and we can write it directly to the output file.
|
38
|
+
rt = rendered_template(args)
|
39
|
+
|
40
|
+
if args.render_files?
|
41
|
+
if rt.is_a?(Hash)
|
42
|
+
rt.each do |filename, contents|
|
43
|
+
File.write(output_path(filename), contents)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
File.write(output_path(@output_filename), rt)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def template_klass
|
52
|
+
case @template_path
|
53
|
+
when /\.erb\z/
|
54
|
+
ErbTemplate
|
55
|
+
when /\.jsonnet\z/
|
56
|
+
JsonnetTemplate
|
57
|
+
else
|
58
|
+
raise UnexpectedFileTypeError, "Unexpected file type #{@template_path}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def print_status
|
63
|
+
variable_output = variables.map { |k, v| "#{Color.magenta(k)}=#{Color.blue(v)}" }.join(', ')
|
64
|
+
puts "Writing #{Color.magenta(File.basename(output_path(@output_filename)))} with variables #{variable_output}\n\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def output_path(filename)
|
68
|
+
File.join(@output_directory, filename)
|
69
|
+
end
|
70
|
+
|
71
|
+
def template_filename(template_path)
|
72
|
+
if template_path.match(/\.erb\z/)
|
73
|
+
File.basename(template_path, '.erb')
|
74
|
+
|
75
|
+
elsif template_path.match(/\.jsonnet\z/)
|
76
|
+
Pathname.new(template_path).basename.sub_ext('.yaml')
|
77
|
+
|
78
|
+
else
|
79
|
+
raise "Unexpected template_path format: #{template_path}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,231 @@
|
|
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 Invoca
|
12
|
+
module KubernetesTemplates
|
13
|
+
class ResourceSet
|
14
|
+
attr_reader :variables, :output_directory, :deploy_group_config, :omitted_resources,
|
15
|
+
:template_directory, :target_output_directory, :regions, :colors,
|
16
|
+
:definitions_path, :kubernetes_cluster_type
|
17
|
+
|
18
|
+
def initialize(config:, template_directory:, rendered_directory:, definitions_path:, kubernetes_cluster_type:)
|
19
|
+
@variables = config["variables"] || {}
|
20
|
+
@deploy_group_config = config["deploy_groups"]
|
21
|
+
@omitted_resources = config["omitted_resources"]
|
22
|
+
@template_directory = template_directory
|
23
|
+
@target_output_directory = config["directory"] or raise ArgumentError, "missing 'directory:' in #{config.inspect}"
|
24
|
+
@regions = config["regions"] || []
|
25
|
+
@colors = config["colors"] || []
|
26
|
+
@rendered_directory = rendered_directory
|
27
|
+
@definitions_path = definitions_path
|
28
|
+
@kubernetes_cluster_type = kubernetes_cluster_type
|
29
|
+
@resources = {}
|
30
|
+
|
31
|
+
if @kubernetes_cluster_type != "kube-platform"
|
32
|
+
@target_output_directory.include?("%{plain_region}") or raise "#{@template_directory}: target_output_directory #{@target_output_directory} needs %{plain_region}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def normal_render(args)
|
37
|
+
dynamic_output_directory? and raise "Directory must not be dynamic: #{target_output_directory}"
|
38
|
+
|
39
|
+
variables["kubernetes_cluster_type"] = @kubernetes_cluster_type
|
40
|
+
|
41
|
+
if (plain_region = variables["plain_region"])
|
42
|
+
default_region_vars(plain_region)
|
43
|
+
end
|
44
|
+
|
45
|
+
output_directory = File.join(@rendered_directory, target_output_directory)
|
46
|
+
render_create_directory(args, output_directory)
|
47
|
+
end
|
48
|
+
|
49
|
+
def render(args)
|
50
|
+
@regions.any? or raise "#{template_directory}: must have at least one region"
|
51
|
+
@colors.any? or raise "#{template_directory}: must have at least one color"
|
52
|
+
|
53
|
+
variables["kubernetes_cluster_type"] = @kubernetes_cluster_type
|
54
|
+
|
55
|
+
@regions.each do |plain_region|
|
56
|
+
default_region_vars(plain_region)
|
57
|
+
|
58
|
+
@colors.each do |c|
|
59
|
+
variables["color"] = c
|
60
|
+
output_directory = File.join(@rendered_directory, format(@target_output_directory, plain_region: plain_region, color: c, type: @kubernetes_cluster_type))
|
61
|
+
render_create_directory(args, output_directory)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def render_create_directory(args, output_directory)
|
67
|
+
create_directory(output_directory)
|
68
|
+
puts "Rendering templates to: #{Color.magenta(output_directory)}"
|
69
|
+
puts "Variable assignments:"
|
70
|
+
variables.each { |k, v| puts "\t#{Color.magenta(k)}=#{Color.blue(v)}" }
|
71
|
+
puts
|
72
|
+
if omitted_resources
|
73
|
+
puts "Omitted resources:"
|
74
|
+
omitted_resources.each { |ot| puts "\t#{ot}" }
|
75
|
+
end
|
76
|
+
puts
|
77
|
+
resources(output_directory).each do |resource|
|
78
|
+
resource.render(args)
|
79
|
+
end
|
80
|
+
puts
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
CLOUD_REGION_TO_PROVIDER_AND_DATACENTER = {
|
86
|
+
# Note: The names below should match RegionDiscovery from process_settings-production.
|
87
|
+
# https://github.com/Invoca/process_settings-production/blob/main/settings/region_discovery/production_regions.yml
|
88
|
+
"us-east-1" => ['aws', "AWS-us-east-1"],
|
89
|
+
"us-east-2" => ['aws', "AWS-us-east-2"],
|
90
|
+
"us-central1" => ['gcp', "GCE-us-central1"],
|
91
|
+
"us-west2" => ['gcp', "GCE-us-west2"],
|
92
|
+
"eu-central-1" => ['aws', "AWS-eu-central-1"],
|
93
|
+
"eu-west-1" => ['aws', "AWS-eu-west-1"],
|
94
|
+
"europe-west4" => ['gcp', "GCE-europe-west4"],
|
95
|
+
|
96
|
+
# other regions
|
97
|
+
"us-east1" => ['gcp', "GCE-us-east1"],
|
98
|
+
"us-west1" => ['gcp', "GCE-us-west1"],
|
99
|
+
"local" => ['', "local"]
|
100
|
+
}.freeze
|
101
|
+
|
102
|
+
# The zone to use for failure-domain.beta.kubernetes.io/zone or topology.kubernetes.io/zone by DeployGroup
|
103
|
+
AVAILABILITY_ZONE_FOR_DEPLOY_GROUP = {
|
104
|
+
"us-east-1" => {
|
105
|
+
"primary" => "us-east-1c",
|
106
|
+
"secondary" => "us-east-1d",
|
107
|
+
"tertiary" => "us-east-1b"
|
108
|
+
},
|
109
|
+
"us-east-2" => {
|
110
|
+
"primary" => "us-east-2a",
|
111
|
+
"secondary" => "us-east-2b",
|
112
|
+
"tertiary" => "us-east-2c"
|
113
|
+
},
|
114
|
+
"us-central1" => {
|
115
|
+
"primary" => "us-central1-a",
|
116
|
+
"secondary" => "us-central1-b",
|
117
|
+
"tertiary" => "us-central1-c"
|
118
|
+
},
|
119
|
+
"us-west2" => {
|
120
|
+
"primary" => "us-west2-a",
|
121
|
+
"secondary" => "us-west2-b",
|
122
|
+
"tertiary" => "us-west2-c"
|
123
|
+
},
|
124
|
+
"eu-central-1" => {
|
125
|
+
"primary" => "eu-central-1a",
|
126
|
+
"secondary" => "eu-central-1b",
|
127
|
+
"tertiary" => "eu-central-1c"
|
128
|
+
},
|
129
|
+
"eu-west-1" => {
|
130
|
+
"primary" => "eu-west-1a",
|
131
|
+
"secondary" => "eu-west-1b",
|
132
|
+
"tertiary" => "eu-west-1c"
|
133
|
+
},
|
134
|
+
"europe-west4" => {
|
135
|
+
"primary" => "europe-west4-a",
|
136
|
+
"secondary" => "europe-west4-b",
|
137
|
+
"tertiary" => "europe-west4-c"
|
138
|
+
},
|
139
|
+
"local" => {},
|
140
|
+
"us-west1" => {},
|
141
|
+
"us-east1" => {}
|
142
|
+
}
|
143
|
+
|
144
|
+
def default_region_vars(plain_region)
|
145
|
+
variables.has_key?("region") and raise "replace region with plain_region"
|
146
|
+
variables["plain_region"] = plain_region
|
147
|
+
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}"
|
148
|
+
variables["cloud_provider"], variables["cloud_datacenter"] = cloud_datacenter_and_provider
|
149
|
+
variables["cloud_region"] = variables["cloud_datacenter"] # for compatibility with old resource files; cloud_datacenter is preferred now
|
150
|
+
if plain_region == "local"
|
151
|
+
variables["data_silo"] ||= "local"
|
152
|
+
elsif plain_region.start_with?("us-")
|
153
|
+
variables["data_silo"] ||= "us"
|
154
|
+
end
|
155
|
+
|
156
|
+
# cannot do ||= because we want to reset when plain_region changes
|
157
|
+
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}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def create_directory(directory)
|
161
|
+
unless File.exist?(directory)
|
162
|
+
puts <<~MESSAGE
|
163
|
+
|
164
|
+
Directory #{Color.magenta(directory)} doesn't exist, #{Color.green('creating it')}
|
165
|
+
|
166
|
+
MESSAGE
|
167
|
+
FileUtils.mkdir_p(directory)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def resources(output_directory)
|
172
|
+
@resources[output_directory] ||= standard_resources(output_directory) + grouped_resources(output_directory)
|
173
|
+
end
|
174
|
+
|
175
|
+
def standard_resources(output_directory)
|
176
|
+
standard_template_paths.map do |path|
|
177
|
+
Resource.new(template_path: path, definitions_path: @definitions_path, variables: variables, output_directory: output_directory)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def grouped_resources(output_directory)
|
182
|
+
deploy_grouped_template_paths.map do |path|
|
183
|
+
DeployGroupedResource.new(
|
184
|
+
template_path: path,
|
185
|
+
definitions_path: @definitions_path,
|
186
|
+
variables: variables,
|
187
|
+
output_directory: output_directory,
|
188
|
+
groups_to_render: deploy_groups_to_render,
|
189
|
+
template_path_exclusions: deploy_group_config["exclude_files"],
|
190
|
+
group_variable_name: deploy_group_config["variable_name"]
|
191
|
+
)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def standard_template_paths
|
196
|
+
@standard_template_paths ||=
|
197
|
+
(Dir[File.join(template_directory, "*.yaml.erb")] +
|
198
|
+
Dir[File.join(template_directory, '*.jsonnet')]) -
|
199
|
+
deploy_grouped_template_paths - omitted_resource_paths
|
200
|
+
end
|
201
|
+
|
202
|
+
def deploy_grouped_template_paths
|
203
|
+
@deploy_grouped_template_paths ||=
|
204
|
+
if deploy_group_config
|
205
|
+
deploy_group_config["files"]&.map { |file| File.join(template_directory, file) } ||
|
206
|
+
(Dir[File.join(template_directory, "*-deploy.yaml.erb")] + Dir[File.join(template_directory, "*-deploy.jsonnet")]) - omitted_resource_paths
|
207
|
+
else
|
208
|
+
[]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def omitted_resource_paths
|
213
|
+
@omitted_resource_paths ||= omitted_resources&.map { |file| File.join(template_directory, file) } || []
|
214
|
+
end
|
215
|
+
|
216
|
+
def deploy_groups_to_render
|
217
|
+
group_names = deploy_group_config["group_names"]
|
218
|
+
if array_of_arrays?(group_names)
|
219
|
+
first, *rest = group_names
|
220
|
+
first.product(*rest).map { |group| group.join("-") }
|
221
|
+
else
|
222
|
+
group_names
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def array_of_arrays?(array)
|
227
|
+
array.all? { |item| item.is_a?(Array) }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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 Invoca
|
10
|
+
module KubernetesTemplates
|
11
|
+
class Template < OpenStruct
|
12
|
+
attr_reader :template_path, :variables
|
13
|
+
def initialize(template_path, variables)
|
14
|
+
@template_path = template_path
|
15
|
+
@variables = variables
|
16
|
+
end
|
17
|
+
|
18
|
+
def render(args)
|
19
|
+
raise "must be defined by subclass"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def with_auto_generated_yaml_comment(yaml_string)
|
25
|
+
comment = <<~EOS
|
26
|
+
# WARNING: DO NO EDIT THIS FILE!
|
27
|
+
# Any changes made here will be lost.
|
28
|
+
# This file is autogenerated from #{template_path}
|
29
|
+
EOS
|
30
|
+
comment + yaml_string
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
# @param [String] template_path: file path to template file that needs to be rendered.
|
35
|
+
# @param [Hash]: variables that will be used in the template file to generate distict files.
|
36
|
+
#
|
37
|
+
# @return [String] generated YAML file
|
38
|
+
# @return [Hash] Hash of file names as keys with the values being corresponding generated YAML.
|
39
|
+
#
|
40
|
+
# @raise [UnexpectedFileTypeError] if file type doesn't match [yaml.erb, erb, or jsonnet]
|
41
|
+
# @raise [MultiFileJsonnetRenderError] if Jsonnet template files don't follow proper MultiFileJsonnet templating.
|
42
|
+
|
43
|
+
# TODO
|
44
|
+
# The ErbTemplate and JsonnetTemplate classes both inherit from the Template class and implement a render method.
|
45
|
+
# However, the erb_binding parameter is used just in ErbTemplate, while the jsonnet_library_path parameter is used just in JsonnetTemplate.
|
46
|
+
# This is a little awkward. Potentially this could be refactored.
|
47
|
+
def render(template_path, variables, erb_binding: nil, jsonnet_library_path: nil)
|
48
|
+
new(template_path, variables).render(erb_binding: erb_binding, jsonnet_library_path: jsonnet_library_path)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,176 @@
|
|
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 Invoca
|
13
|
+
module KubernetesTemplates
|
14
|
+
class TemplateDirectoryRenderer
|
15
|
+
DEFINITIONS_FILENAME = "definitions.yaml"
|
16
|
+
|
17
|
+
attr_reader :directories, :omitted_names, :rendered_directory, :cluster_type, :region, :color, :variable_overrides
|
18
|
+
|
19
|
+
def initialize(directories:, rendered_directory:, omitted_names: [], cluster_type: nil, region: nil, color: nil, variable_overrides: nil)
|
20
|
+
@directories = directories_with_definitions(Array(directories))
|
21
|
+
@omitted_names = Array(omitted_names)
|
22
|
+
@rendered_directory = rendered_directory
|
23
|
+
@cluster_type = cluster_type
|
24
|
+
@region = region
|
25
|
+
@color = color
|
26
|
+
@variable_overrides = variable_overrides || {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(args)
|
30
|
+
child_pids = []
|
31
|
+
|
32
|
+
resource_sets.each do |name, resource_sets|
|
33
|
+
puts "Rendering templates for definition #{Color.red(name)}..."
|
34
|
+
resource_sets.each do |resource_set|
|
35
|
+
if args.fork?
|
36
|
+
if (pid = Process.fork)
|
37
|
+
# this is the parent
|
38
|
+
child_pids << pid
|
39
|
+
wait_if_max_forked(child_pids)
|
40
|
+
else
|
41
|
+
# this is the child
|
42
|
+
render_set(args, resource_set)
|
43
|
+
Kernel.exit! # skip at_exit handlers since parent will run those
|
44
|
+
end
|
45
|
+
else
|
46
|
+
render_set(args, resource_set)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if args.fork?
|
52
|
+
Process.waitall
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def read_definitions(path)
|
59
|
+
File.read(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
MAX_FORKED_PROCESSES = 9
|
63
|
+
|
64
|
+
def wait_if_max_forked(child_pids)
|
65
|
+
while child_pids.size >= MAX_FORKED_PROCESSES
|
66
|
+
begin
|
67
|
+
Process.waitpid # this is a race condition because 1 or more processes could exit before we get here
|
68
|
+
rescue SystemCallError # this will happen if they all exited before we called waitpid
|
69
|
+
end
|
70
|
+
child_pids.delete_if do |pid|
|
71
|
+
Process.waitpid(pid, Process::WNOHANG)
|
72
|
+
rescue Errno::ECHILD # No child processes
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def render_set(args, resource_set)
|
79
|
+
resource_set.render(args)
|
80
|
+
rescue => ex
|
81
|
+
raise "error rendering ResourceSet from #{resource_set.definitions_path}\n#{ex.class}: #{ex.message}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def directories_with_definitions(directories)
|
85
|
+
directories.select do |dir|
|
86
|
+
definitions_path = definitions_path_for_dir(dir)
|
87
|
+
File.exist?(definitions_path)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def definitions_path_for_dir(dir)
|
92
|
+
File.join(dir, DEFINITIONS_FILENAME)
|
93
|
+
end
|
94
|
+
|
95
|
+
def resource_sets
|
96
|
+
@resource_sets ||= @directories.each_with_object({}) do |dir, hash|
|
97
|
+
definitions_path = definitions_path_for_dir(dir)
|
98
|
+
config = load_config(definitions_path)
|
99
|
+
|
100
|
+
config.map do |name, config|
|
101
|
+
next if omitted_names.include?(name)
|
102
|
+
|
103
|
+
kubernetes_cluster_type = name.sub('SPP-PLACEHOLDER', 'staging').sub(/\..*/, '') # prod.gcp => prod
|
104
|
+
|
105
|
+
hash[name] ||= []
|
106
|
+
hash[name] << ResourceSet.new(config: config, template_directory: dir, rendered_directory: @rendered_directory, kubernetes_cluster_type: kubernetes_cluster_type, definitions_path: definitions_path)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_libsonnet(dir, config)
|
112
|
+
fname = File.join(dir, 'definitions.libsonnet')
|
113
|
+
existing = File.exists?(fname) ? File.read(fname) : ""
|
114
|
+
proposed = build_json(config)
|
115
|
+
|
116
|
+
if existing != proposed
|
117
|
+
puts("Generating updated #{Color.magenta(File.basename(fname))}")
|
118
|
+
File.write(fname, proposed)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def build_json(config)
|
123
|
+
hash = transform_for_jsonnet(config)
|
124
|
+
JSON.pretty_generate(hash)
|
125
|
+
end
|
126
|
+
|
127
|
+
# This method ensures that OpenStructs are
|
128
|
+
# converted to hashes to support to_json operation
|
129
|
+
# It also converts any embedded variable place holders
|
130
|
+
# into a Jsonnet friendly format:
|
131
|
+
#
|
132
|
+
# %{variable} is converted to %(variable)s which can
|
133
|
+
# then be used with the Jsonnet function std.format
|
134
|
+
def transform_for_jsonnet(hash)
|
135
|
+
hash.transform_values do |value|
|
136
|
+
case value
|
137
|
+
when OpenStruct
|
138
|
+
transform_for_jsonnet(value.to_h)
|
139
|
+
when Hash
|
140
|
+
transform_for_jsonnet(value)
|
141
|
+
else
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def load_config(definitions_path)
|
148
|
+
begin
|
149
|
+
config = YAML.safe_load(read_definitions(definitions_path), aliases: true)
|
150
|
+
rescue => ex
|
151
|
+
raise "error loading YAML from #{definitions_path}:\n#{ex.class}: #{ex.message}"
|
152
|
+
end
|
153
|
+
|
154
|
+
expand_config(config).each_with_object({}) do |(name, data), hash|
|
155
|
+
if !cluster_type || cluster_type == name.sub('SPP-PLACEHOLDER', 'staging').sub(/\..*/, '') # prod.gcp => prod
|
156
|
+
cluster_type_config = OpenStruct.new(data)
|
157
|
+
|
158
|
+
cluster_type_config.regions = cluster_type_config.regions & [region] if region
|
159
|
+
cluster_type_config.colors = cluster_type_config.colors & [color] if color
|
160
|
+
cluster_type_config.variables = cluster_type_config.variables.merge(variable_overrides)
|
161
|
+
|
162
|
+
hash[name] = cluster_type_config if (region.nil? && color.nil?) || (cluster_type_config.regions.any? && cluster_type_config.colors.any?)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# returns a copy of the given config hash with the COMMON: k-v removed and deep merged into the other config values
|
168
|
+
# (explicit config values take precedence over COMMON: ones)
|
169
|
+
def expand_config(config)
|
170
|
+
common = config.delete('COMMON') || {}
|
171
|
+
|
172
|
+
Hash[config.map { |k, v| [k, common.deep_merge(v)] }]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: invoca-kubernetes_templates
|
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-24 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_kubernetes_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_kubernetes_templates
|
71
|
+
- lib/invoca/kubernetes_templates.rb
|
72
|
+
- lib/invoca/kubernetes_templates/cli.rb
|
73
|
+
- lib/invoca/kubernetes_templates/cli_arguments.rb
|
74
|
+
- lib/invoca/kubernetes_templates/color.rb
|
75
|
+
- lib/invoca/kubernetes_templates/deploy_grouped_resource.rb
|
76
|
+
- lib/invoca/kubernetes_templates/erb_template.rb
|
77
|
+
- lib/invoca/kubernetes_templates/jsonnet_template.rb
|
78
|
+
- lib/invoca/kubernetes_templates/resource.rb
|
79
|
+
- lib/invoca/kubernetes_templates/resource_set.rb
|
80
|
+
- lib/invoca/kubernetes_templates/template.rb
|
81
|
+
- lib/invoca/kubernetes_templates/template_directory_renderer.rb
|
82
|
+
- lib/invoca/kubernetes_templates/version.rb
|
83
|
+
- sig/invoca/kubernetes_templates.rbs
|
84
|
+
homepage: https://github.com/Invoca/inovca-kubernetes_templates
|
85
|
+
licenses: []
|
86
|
+
metadata:
|
87
|
+
allowed_push_host: https://rubygems.org
|
88
|
+
homepage_uri: https://github.com/Invoca/inovca-kubernetes_templates
|
89
|
+
source_code_uri: https://github.com/Invoca/inovca-kubernetes_templates
|
90
|
+
changelog_uri: https://github.com/Invoca/inovca-kubernetes_templates/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: []
|