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.
@@ -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