metatron 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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