metatron 0.1.0
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/.gitignore +11 -0
- data/.roxanne.yml +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +55 -0
- data/.ruby-version +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +144 -0
- data/LICENSE.txt +21 -0
- data/README.md +11 -0
- data/Rakefile +19 -0
- data/lib/metatron/controller.rb +38 -0
- data/lib/metatron/controllers/ping.rb +36 -0
- data/lib/metatron/sync_controller.rb +15 -0
- data/lib/metatron/template.rb +33 -0
- data/lib/metatron/templates/concerns/annotated.rb +27 -0
- data/lib/metatron/templates/concerns/pod_producer.rb +85 -0
- data/lib/metatron/templates/deployment.rb +71 -0
- data/lib/metatron/templates/ingress.rb +148 -0
- data/lib/metatron/templates/pod.rb +46 -0
- data/lib/metatron/templates/secret.rb +33 -0
- data/lib/metatron/templates/service.rb +55 -0
- data/lib/metatron/templates/stateful_set.rb +85 -0
- data/lib/metatron/version.rb +9 -0
- data/lib/metatron.rb +39 -0
- data/metatron.gemspec +46 -0
- data/scripts/build.sh +3 -0
- data/scripts/release.sh +7 -0
- data/scripts/test.sh +6 -0
- metadata +297 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Metatron
|
4
|
+
module Templates
|
5
|
+
# Template for basic Ingress k8s resource
|
6
|
+
class Ingress < Template
|
7
|
+
attr_accessor :ingress_class, :additional_labels, :additional_annotations, :rules, :tls,
|
8
|
+
:cert_manager_cluster_issuer, :cert_manager_issuer, :cert_manager_challenge_type
|
9
|
+
|
10
|
+
def initialize(name, ingress_class = "nginx")
|
11
|
+
super(name)
|
12
|
+
@name = name
|
13
|
+
@ingress_class = ingress_class
|
14
|
+
@api_version = "networking.k8s.io/v1"
|
15
|
+
@kind = "Ingress"
|
16
|
+
@additional_labels = {}
|
17
|
+
@additional_annotations = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Supports one of (they are all equivalent):
|
21
|
+
# { host: "foo.bar", service: { name: "some_service", port: "some_port" } }
|
22
|
+
# { "foo.bar" => { "some_service" => "some_port" } }
|
23
|
+
# {
|
24
|
+
# host: "foo.bar",
|
25
|
+
# paths: [{ path: "/", service: { name: "some_service", port: "some_port" } }]
|
26
|
+
# }
|
27
|
+
def add_rule(rule)
|
28
|
+
@rules ||= []
|
29
|
+
@rules << (rule.key?(:host) ? complex_rule(rule) : simple_rule(rule))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Supports an array of hostnames to provide TLS for via some secret
|
33
|
+
# If the secret name isn't provide, its name will be derived from the first hostname
|
34
|
+
def add_tls(*tls, secret: nil)
|
35
|
+
@tls ||= []
|
36
|
+
@tls << {
|
37
|
+
hosts: tls.map(&:downcase),
|
38
|
+
secretName: secret || secret_name_from_hostname(tls.first)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def cert_manager_annotations
|
43
|
+
cert_manager = {}
|
44
|
+
if cert_manager_issuer
|
45
|
+
cert_manager[:"cert-manager.io/issuer"] = cert_manager_issuer
|
46
|
+
elsif cert_manager_cluster_issuer
|
47
|
+
cert_manager[:"cert-manager.io/cluster-issuer"] = cert_manager_cluster_issuer
|
48
|
+
end
|
49
|
+
unless cert_manager.empty?
|
50
|
+
cert_manager[:"cert-manager.io/acme-challenge-type"] = \
|
51
|
+
cert_manager_challenge_type || "http01"
|
52
|
+
end
|
53
|
+
|
54
|
+
{}.merge(cert_manager)
|
55
|
+
end
|
56
|
+
|
57
|
+
def formatted_annotations
|
58
|
+
ingress_annotations = { "kubernetes.io/ingress.class": ingress_class }
|
59
|
+
{
|
60
|
+
annotations: ingress_annotations
|
61
|
+
.merge(additional_annotations)
|
62
|
+
.merge(cert_manager_annotations)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def formatted_rules
|
67
|
+
(rules || []).empty? ? {} : { rules: }
|
68
|
+
end
|
69
|
+
|
70
|
+
def formatted_tls
|
71
|
+
(tls || []).empty? ? {} : { tls: }
|
72
|
+
end
|
73
|
+
|
74
|
+
def render
|
75
|
+
{
|
76
|
+
apiVersion:,
|
77
|
+
kind:,
|
78
|
+
metadata: {
|
79
|
+
name:,
|
80
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_labels)
|
81
|
+
}.merge(formatted_annotations),
|
82
|
+
spec: formatted_rules.merge(formatted_tls)
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def secret_name_from_hostname(hostname)
|
87
|
+
"#{hostname.downcase.gsub(".", "-")}-tls"
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# rubocop:disable Metrics/MethodLength
|
93
|
+
def complex_rule(rule)
|
94
|
+
formatted_rule = {}
|
95
|
+
formatted_rule[:host] = rule[:host]
|
96
|
+
paths = if rule.key?(:paths)
|
97
|
+
rule[:paths].map do |path|
|
98
|
+
{
|
99
|
+
pathType: "Prefix",
|
100
|
+
path: path[:path],
|
101
|
+
backend: {
|
102
|
+
service: {
|
103
|
+
name: path.dig(:service, :name),
|
104
|
+
port: { name: path.dig(:service, :port) }
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
end
|
109
|
+
else
|
110
|
+
[
|
111
|
+
{
|
112
|
+
pathType: "Prefix",
|
113
|
+
path: "/",
|
114
|
+
backend: {
|
115
|
+
service: {
|
116
|
+
name: rule.dig(:service, :name),
|
117
|
+
port: { name: rule.dig(:service, :port) }
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
]
|
122
|
+
end
|
123
|
+
formatted_rule[:http] = { paths: }
|
124
|
+
formatted_rule
|
125
|
+
end
|
126
|
+
# rubocop:enable Metrics/MethodLength
|
127
|
+
|
128
|
+
def simple_rule(rule)
|
129
|
+
formatted_rule = {}
|
130
|
+
formatted_rule[:host] = rule.keys.first.to_s
|
131
|
+
service_name, service_port = rule.values.first.to_a.first
|
132
|
+
formatted_rule[:http] = {
|
133
|
+
paths: [
|
134
|
+
pathType: "Prefix",
|
135
|
+
path: "/",
|
136
|
+
backend: {
|
137
|
+
service: {
|
138
|
+
name: service_name.to_s,
|
139
|
+
port: { name: service_port }
|
140
|
+
}
|
141
|
+
}
|
142
|
+
]
|
143
|
+
}
|
144
|
+
formatted_rule
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Metatron
|
4
|
+
module Templates
|
5
|
+
# Template for basic Pod k8s resource
|
6
|
+
class Pod < Template
|
7
|
+
include Concerns::Annotated
|
8
|
+
include Concerns::PodProducer
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
super(name)
|
12
|
+
@kind = "Pod"
|
13
|
+
end
|
14
|
+
|
15
|
+
# rubocop:disable Metrics/AbcSize
|
16
|
+
def render
|
17
|
+
{
|
18
|
+
apiVersion:,
|
19
|
+
kind:,
|
20
|
+
metadata: {
|
21
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_labels),
|
22
|
+
name:
|
23
|
+
}.merge(formatted_annotations),
|
24
|
+
spec: {
|
25
|
+
containers: [
|
26
|
+
{
|
27
|
+
name: "app",
|
28
|
+
image:,
|
29
|
+
imagePullPolicy:,
|
30
|
+
stdin: true,
|
31
|
+
tty: true,
|
32
|
+
resources: { limits: resource_limits, requests: resource_requests }
|
33
|
+
}.merge(probes)
|
34
|
+
.merge(formatted_environment)
|
35
|
+
.merge(formatted_envfrom)
|
36
|
+
.merge(formatted_ports)
|
37
|
+
.merge(formatted_volume_mounts)
|
38
|
+
.merge(formatted_container_security_context)
|
39
|
+
] + additional_containers
|
40
|
+
}.merge(formatted_volumes).merge(formatted_security_context)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/AbcSize
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Metatron
|
4
|
+
module Templates
|
5
|
+
# The Secret Kubernetes resource
|
6
|
+
class Secret < Template
|
7
|
+
include Concerns::Annotated
|
8
|
+
|
9
|
+
attr_accessor :additional_labels, :type, :data
|
10
|
+
|
11
|
+
def initialize(name, data = {})
|
12
|
+
super(name)
|
13
|
+
@data = data
|
14
|
+
@additional_labels = {}
|
15
|
+
@kind = "Secret"
|
16
|
+
@type = "Opaque"
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
{
|
21
|
+
apiVersion:,
|
22
|
+
kind:,
|
23
|
+
metadata: {
|
24
|
+
name:,
|
25
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_labels)
|
26
|
+
}.merge(formatted_annotations),
|
27
|
+
type:,
|
28
|
+
stringData: data
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Metatron
|
4
|
+
module Templates
|
5
|
+
# The Service Kubernetes resource
|
6
|
+
class Service < Template
|
7
|
+
include Concerns::Annotated
|
8
|
+
|
9
|
+
attr_accessor :type, :selector, :additional_labels, :ports,
|
10
|
+
:additional_selector_labels, :publish_not_ready_addresses
|
11
|
+
|
12
|
+
def initialize(name, port = nil)
|
13
|
+
super(name)
|
14
|
+
@kind = "Service"
|
15
|
+
@type = "ClusterIP"
|
16
|
+
@selector = { "#{label_namespace}/name": name }
|
17
|
+
@additional_labels = {}
|
18
|
+
@additional_selector_labels = {}
|
19
|
+
@publish_not_ready_addresses = false
|
20
|
+
return unless port
|
21
|
+
|
22
|
+
@ports = [
|
23
|
+
{
|
24
|
+
port: port.to_i,
|
25
|
+
targetPort: port.to_i,
|
26
|
+
protocol: "TCP",
|
27
|
+
name:
|
28
|
+
}
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
alias publishNotReadyAddresses publish_not_ready_addresses
|
33
|
+
|
34
|
+
def formatted_ports
|
35
|
+
ports&.any? ? { ports: } : {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def render
|
39
|
+
{
|
40
|
+
apiVersion:,
|
41
|
+
kind:,
|
42
|
+
metadata: {
|
43
|
+
name:,
|
44
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_labels)
|
45
|
+
}.merge(formatted_annotations),
|
46
|
+
spec: {
|
47
|
+
type:,
|
48
|
+
selector: selector.merge(additional_selector_labels),
|
49
|
+
publishNotReadyAddresses:
|
50
|
+
}.merge(formatted_ports)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Metatron
|
4
|
+
module Templates
|
5
|
+
# Template for basic StatefulSet k8s resource
|
6
|
+
class StatefulSet < Template
|
7
|
+
include Concerns::Annotated
|
8
|
+
include Concerns::PodProducer
|
9
|
+
|
10
|
+
attr_accessor :replicas, :pod_annotations, :service_name,
|
11
|
+
:pod_management_policy, :enable_service_links,
|
12
|
+
:termination_grace_period_seconds, :additional_pod_labels
|
13
|
+
|
14
|
+
def initialize(name, replicas: 2)
|
15
|
+
super(name)
|
16
|
+
@replicas = replicas
|
17
|
+
@api_version = "apps/v1"
|
18
|
+
@kind = "StatefulSet"
|
19
|
+
@pod_annotations = {}
|
20
|
+
@pod_management_policy = "OrderedReady"
|
21
|
+
@additional_pod_labels = {}
|
22
|
+
@enable_service_links = true
|
23
|
+
@service_name = name
|
24
|
+
@termination_grace_period_seconds = 60
|
25
|
+
end
|
26
|
+
|
27
|
+
alias enableServiceLinks enable_service_links
|
28
|
+
alias podManagementPolicy pod_management_policy
|
29
|
+
alias terminationGracePeriodSeconds termination_grace_period_seconds
|
30
|
+
alias serviceName service_name
|
31
|
+
|
32
|
+
def formatted_pod_annotations
|
33
|
+
pod_annotations && !pod_annotations.empty? ? { annotations: pod_annotations } : {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/MethodLength
|
37
|
+
# rubocop:disable Metrics/AbcSize
|
38
|
+
def render
|
39
|
+
{
|
40
|
+
apiVersion:,
|
41
|
+
kind:,
|
42
|
+
metadata: {
|
43
|
+
name:,
|
44
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_labels)
|
45
|
+
}.merge(formatted_annotations),
|
46
|
+
spec: {
|
47
|
+
replicas:,
|
48
|
+
serviceName:,
|
49
|
+
enableServiceLinks:,
|
50
|
+
strategy: { type: "RollingUpdate", rollingUpdate: { maxSurge: 2, maxUnavailable: 0 } },
|
51
|
+
selector: {
|
52
|
+
matchLabels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
|
53
|
+
},
|
54
|
+
template: {
|
55
|
+
metadata: {
|
56
|
+
labels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
|
57
|
+
}.merge(formatted_pod_annotations),
|
58
|
+
spec: {
|
59
|
+
terminationGracePeriodSeconds:,
|
60
|
+
containers: [
|
61
|
+
{
|
62
|
+
name: "app",
|
63
|
+
image:,
|
64
|
+
imagePullPolicy:,
|
65
|
+
stdin: true,
|
66
|
+
tty: true,
|
67
|
+
resources: { limits: resource_limits, requests: resource_requests }
|
68
|
+
}.merge(probes)
|
69
|
+
.merge(formatted_environment)
|
70
|
+
.merge(formatted_envfrom)
|
71
|
+
.merge(formatted_ports)
|
72
|
+
.merge(formatted_volume_mounts)
|
73
|
+
.merge(formatted_security_context)
|
74
|
+
] + additional_containers
|
75
|
+
}.merge(formatted_volumes)
|
76
|
+
.merge(formatted_affinity)
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
# rubocop:enable Metrics/AbcSize
|
82
|
+
# rubocop:enable Metrics/MethodLength
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/metatron.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Standard Library requirements
|
4
|
+
require "base64"
|
5
|
+
require "resolv"
|
6
|
+
require "securerandom"
|
7
|
+
require "time"
|
8
|
+
require "logger"
|
9
|
+
|
10
|
+
# External requirements
|
11
|
+
require "sinatra/base"
|
12
|
+
require "sinatra/custom_logger"
|
13
|
+
|
14
|
+
# The top-level module for Bullion
|
15
|
+
module Metatron
|
16
|
+
class Error < StandardError; end
|
17
|
+
class ConfigError < Error; end
|
18
|
+
|
19
|
+
LOGGER = Logger.new($stdout)
|
20
|
+
|
21
|
+
# Set up log level
|
22
|
+
LOGGER.level = ENV.fetch("LOG_LEVEL", :warn)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal requirements
|
26
|
+
require "metatron/version"
|
27
|
+
require "metatron/template"
|
28
|
+
require "metatron/templates/concerns/annotated"
|
29
|
+
require "metatron/templates/concerns/pod_producer"
|
30
|
+
require "metatron/templates/pod"
|
31
|
+
require "metatron/templates/deployment"
|
32
|
+
require "metatron/templates/ingress"
|
33
|
+
require "metatron/templates/secret"
|
34
|
+
require "metatron/templates/service"
|
35
|
+
require "metatron/templates/stateful_set"
|
36
|
+
require "metatron/controller"
|
37
|
+
require "metatron/sync_controller"
|
38
|
+
require "metatron/controllers/ping"
|
39
|
+
require "metatron/controllers/sync"
|
data/metatron.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/metatron/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "metatron"
|
7
|
+
spec.version = Metatron::VERSION
|
8
|
+
spec.authors = ["Jonathan Gnagy"]
|
9
|
+
spec.email = ["jonathan.gnagy@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "So meta"
|
12
|
+
spec.homepage = "https://github.com/jgnagy/metatron"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/jgnagy/metatron"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.required_ruby_version = "~> 3.1"
|
28
|
+
|
29
|
+
spec.add_runtime_dependency "json", "~> 2.6"
|
30
|
+
spec.add_runtime_dependency "puma", "~> 5.6"
|
31
|
+
spec.add_runtime_dependency "sinatra", "~> 2.2"
|
32
|
+
spec.add_runtime_dependency "sinatra-contrib", "~> 2.2"
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 2.3"
|
35
|
+
spec.add_development_dependency "byebug", "~> 11"
|
36
|
+
spec.add_development_dependency "rack-test", "~> 2.0"
|
37
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.10"
|
39
|
+
spec.add_development_dependency "rubocop", "~> 1.31"
|
40
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.6"
|
41
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.11"
|
42
|
+
spec.add_development_dependency "simplecov", "~> 0.21"
|
43
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 2.1"
|
44
|
+
spec.add_development_dependency "solargraph", "~> 0.45"
|
45
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
46
|
+
end
|
data/scripts/build.sh
ADDED
data/scripts/release.sh
ADDED