metatron 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ VERSION = [
5
+ 0, # major
6
+ 1, # minor
7
+ 0 # patch
8
+ ].join(".")
9
+ 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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ docker build -t metatron:local .
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ gem install bundler -v '~> 2.3'
4
+ bundle install
5
+ rm -rf pkg/*.gem
6
+ bundle exec rake build
7
+ bundle exec gem push pkg/*.gem
data/scripts/test.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+
3
+ gem install bundler
4
+ bundle install --jobs=4
5
+ gem build ./metatron.gemspec
6
+ bundle exec rake