metatron 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5e862a2cda63cf2dba7bf6016c6d03bd2f1e21bae464da3425285c0741b6ddbb
4
+ data.tar.gz: e77e818e65755a7db7d4c72e096fe849bd3a099f7534c531a89f1f106d6c89a1
5
+ SHA512:
6
+ metadata.gz: 9d89f4c380b31d8b957ee25ab6859704dfaf113c158fcfe69d794dcd4b2d300be498494befed03cd4fce8c23459ad2fcebb9d31ab826519c86969d2e9a4e16ba
7
+ data.tar.gz: 3958c107c80854470f790741242caa846c9109a138347f6793a037bcabd16a6e7dfac8bf52f91b957d16b74828a629e30f402ca5e9ffadb616aa904c012f6463
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.roxanne.yml ADDED
@@ -0,0 +1,14 @@
1
+ version: 1.0
2
+ stages:
3
+ build:
4
+ image: docker:latest
5
+ scripts:
6
+ - ./scripts/build.sh
7
+ test:
8
+ image: ruby:3.1
9
+ scripts:
10
+ - ./scripts/test.sh
11
+ release:
12
+ image: ruby:3.1
13
+ only:
14
+ - main
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,55 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
5
+ Layout/LineLength:
6
+ Max: 100
7
+
8
+ AllCops:
9
+ Exclude:
10
+ - 'db/schema.rb'
11
+ - 'vendor/**/*'
12
+ TargetRubyVersion: 3.1
13
+ NewCops: enable
14
+
15
+ Metrics/AbcSize:
16
+ Max: 21
17
+
18
+ Metrics/BlockLength:
19
+ Max: 35
20
+ Exclude:
21
+ - 'spec/**/*_spec.rb'
22
+ - 'Rakefile'
23
+ - '*.gemspec'
24
+
25
+ Metrics/MethodLength:
26
+ Max: 25
27
+
28
+ Metrics/ModuleLength:
29
+ Max: 160
30
+ Exclude:
31
+ - 'spec/**/*_spec.rb'
32
+
33
+ Metrics/ClassLength:
34
+ Max: 300
35
+ Exclude:
36
+ - 'spec/**/*_spec.rb'
37
+
38
+ Gemspec/RequireMFA:
39
+ Enabled: false
40
+
41
+ Style/MixinUsage:
42
+ Exclude:
43
+ - "bin/console"
44
+
45
+ Style/StringLiterals:
46
+ Enabled: true
47
+ EnforcedStyle: double_quotes
48
+
49
+ Style/StringLiteralsInInterpolation:
50
+ Enabled: true
51
+ EnforcedStyle: double_quotes
52
+
53
+ Style/StringConcatenation:
54
+ Exclude:
55
+ - 'Rakefile'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in metatron.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,144 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ metatron (0.1.0)
5
+ json (~> 2.6)
6
+ puma (~> 5.6)
7
+ sinatra (~> 2.2)
8
+ sinatra-contrib (~> 2.2)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ast (2.4.2)
14
+ backport (1.2.0)
15
+ benchmark (0.2.0)
16
+ byebug (11.1.3)
17
+ diff-lcs (1.5.0)
18
+ docile (1.4.0)
19
+ e2mmap (0.1.0)
20
+ jaro_winkler (1.5.4)
21
+ json (2.6.2)
22
+ kramdown (2.4.0)
23
+ rexml
24
+ kramdown-parser-gfm (1.1.0)
25
+ kramdown (~> 2.0)
26
+ multi_json (1.15.0)
27
+ mustermann (1.1.2)
28
+ ruby2_keywords (~> 0.0.1)
29
+ nio4r (2.5.8)
30
+ nokogiri (1.13.8-arm64-darwin)
31
+ racc (~> 1.4)
32
+ nokogiri (1.13.8-x86_64-linux)
33
+ racc (~> 1.4)
34
+ parallel (1.22.1)
35
+ parser (3.1.2.1)
36
+ ast (~> 2.4.1)
37
+ puma (5.6.5)
38
+ nio4r (~> 2.0)
39
+ racc (1.6.0)
40
+ rack (2.2.4)
41
+ rack-protection (2.2.1)
42
+ rack
43
+ rack-test (2.0.2)
44
+ rack (>= 1.3)
45
+ rainbow (3.1.1)
46
+ rake (12.3.3)
47
+ regexp_parser (2.5.0)
48
+ reverse_markdown (2.1.1)
49
+ nokogiri
50
+ rexml (3.2.5)
51
+ rspec (3.11.0)
52
+ rspec-core (~> 3.11.0)
53
+ rspec-expectations (~> 3.11.0)
54
+ rspec-mocks (~> 3.11.0)
55
+ rspec-core (3.11.0)
56
+ rspec-support (~> 3.11.0)
57
+ rspec-expectations (3.11.0)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.11.0)
60
+ rspec-mocks (3.11.1)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.11.0)
63
+ rspec-support (3.11.0)
64
+ rubocop (1.36.0)
65
+ json (~> 2.3)
66
+ parallel (~> 1.10)
67
+ parser (>= 3.1.2.1)
68
+ rainbow (>= 2.2.2, < 4.0)
69
+ regexp_parser (>= 1.8, < 3.0)
70
+ rexml (>= 3.2.5, < 4.0)
71
+ rubocop-ast (>= 1.20.1, < 2.0)
72
+ ruby-progressbar (~> 1.7)
73
+ unicode-display_width (>= 1.4.0, < 3.0)
74
+ rubocop-ast (1.21.0)
75
+ parser (>= 3.1.1.0)
76
+ rubocop-rake (0.6.0)
77
+ rubocop (~> 1.0)
78
+ rubocop-rspec (2.12.1)
79
+ rubocop (~> 1.31)
80
+ ruby-progressbar (1.11.0)
81
+ ruby2_keywords (0.0.5)
82
+ simplecov (0.21.2)
83
+ docile (~> 1.1)
84
+ simplecov-html (~> 0.11)
85
+ simplecov_json_formatter (~> 0.1)
86
+ simplecov-cobertura (2.1.0)
87
+ rexml
88
+ simplecov (~> 0.19)
89
+ simplecov-html (0.12.3)
90
+ simplecov_json_formatter (0.1.4)
91
+ sinatra (2.2.1)
92
+ mustermann (~> 1.0)
93
+ rack (~> 2.2)
94
+ rack-protection (= 2.2.1)
95
+ tilt (~> 2.0)
96
+ sinatra-contrib (2.2.1)
97
+ multi_json
98
+ mustermann (~> 1.0)
99
+ rack-protection (= 2.2.1)
100
+ sinatra (= 2.2.1)
101
+ tilt (~> 2.0)
102
+ solargraph (0.46.0)
103
+ backport (~> 1.2)
104
+ benchmark
105
+ bundler (>= 1.17.2)
106
+ diff-lcs (~> 1.4)
107
+ e2mmap
108
+ jaro_winkler (~> 1.5)
109
+ kramdown (~> 2.3)
110
+ kramdown-parser-gfm (~> 1.1)
111
+ parser (~> 3.0)
112
+ reverse_markdown (>= 1.0.5, < 3)
113
+ rubocop (>= 0.52)
114
+ thor (~> 1.0)
115
+ tilt (~> 2.0)
116
+ yard (~> 0.9, >= 0.9.24)
117
+ thor (1.2.1)
118
+ tilt (2.0.11)
119
+ unicode-display_width (2.2.0)
120
+ webrick (1.7.0)
121
+ yard (0.9.28)
122
+ webrick (~> 1.7.0)
123
+
124
+ PLATFORMS
125
+ arm64-darwin-21
126
+ x86_64-linux
127
+
128
+ DEPENDENCIES
129
+ bundler (~> 2.3)
130
+ byebug (~> 11)
131
+ metatron!
132
+ rack-test (~> 2.0)
133
+ rake (~> 12.3)
134
+ rspec (~> 3.10)
135
+ rubocop (~> 1.31)
136
+ rubocop-rake (~> 0.6)
137
+ rubocop-rspec (~> 2.11)
138
+ simplecov (~> 0.21)
139
+ simplecov-cobertura (~> 2.1)
140
+ solargraph (~> 0.45)
141
+ yard (~> 0.9)
142
+
143
+ BUNDLED WITH
144
+ 2.3.18
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Jonathan Gnagy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Metatron
2
+
3
+ Metatron in a Ruby library for creating [Metacontroller](https://metacontroller.github.io/metacontroller/)-based custom Kubernetes controllers.
4
+
5
+ The intention is to make it as easy as possible to use Ruby to manage [custom resources](https://kubernetes.io/docs/concepts/api-extension/custom-resources/) within your Kubernetes infrastructure. No Golang required to listen for and respond to resources based on your own [CustomResourceDefinition](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/) or to modify existing kubernetes resources via a [DecoratorController](https://metacontroller.github.io/metacontroller/api/decoratorcontroller.html).
6
+
7
+ Your Ruby code doesn't have to have any _real_ knowledge of the Kubernetes environment in which it operates; Metacontroller takes care of all the Kubernetes interactions and Metatron handles providing the JSON interface. Just write a `sync` method can receive and respond with the appropriate Hashes and you're on your way!
8
+
9
+ ## Usage
10
+
11
+ TODO (still a very early draft)
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["RACK_ENV"] ||= "development"
4
+
5
+ require "bundler/gem_tasks"
6
+ require "rspec/core/rake_task"
7
+ require "rubocop/rake_task"
8
+ require "yard"
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
11
+ RuboCop::RakeTask.new(:rubocop)
12
+ YARD::Rake::YardocTask.new
13
+
14
+ desc "allows running a demo controller"
15
+ task :demo do
16
+ system("rackup --host 0.0.0.0 -P #{File.expand_path(".")}/tmp/daemon.pid")
17
+ end
18
+
19
+ task default: %i[spec rubocop yard]
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ # Base class for API services
5
+ class Controller < Sinatra::Base
6
+ helpers Sinatra::CustomLogger
7
+
8
+ configure do
9
+ set :protection, except: :http_origin
10
+ set :logging, true
11
+ set :logger, Metatron::LOGGER
12
+ set :show_exceptions, false
13
+ end
14
+
15
+ before do
16
+ # Sets up a useful variable (@json_body) for accessing a parsed request body
17
+ if request.content_type&.include?("json") && !request.body.read.empty?
18
+ request.body.rewind
19
+ @json_body = JSON.parse(request.body.read)
20
+ end
21
+ rescue StandardError => e
22
+ halt(400, { error: "Request must be JSON: #{e.message}}" }.to_json)
23
+ end
24
+
25
+ error do
26
+ content_type :json
27
+
28
+ e = env["sinatra.error"]
29
+ resp = { result: "error", message: e.message }
30
+ resp[:trace] = e.full_message if settings.environment.to_s != "production"
31
+ resp.to_json
32
+ end
33
+
34
+ def request_body
35
+ @json_body
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ module Controllers
5
+ # Healthcheck service
6
+ class Ping < Sinatra::Application
7
+ configure do
8
+ set :logging, true
9
+ set :logger, Metatron::LOGGER
10
+ end
11
+
12
+ before do
13
+ content_type "application/json"
14
+
15
+ halt 403 unless request.get? || request.options?
16
+
17
+ if request.get?
18
+ headers "X-Frame-Options" => "SAMEORIGIN"
19
+ headers "X-XSS-Protection" => "1; mode=block"
20
+ end
21
+ end
22
+
23
+ after do
24
+ headers "Access-Control-Allow-Methods" => %w[GET] if request.options?
25
+ end
26
+
27
+ get "/" do
28
+ '{ "status": "up" }'
29
+ end
30
+
31
+ options "/" do
32
+ halt 200
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ # Used for "normal" sync requests
5
+ class SyncController < Controller
6
+ options "/" do
7
+ headers "Access-Control-Allow-Methods" => ["POST"]
8
+ halt 200
9
+ end
10
+
11
+ post "/" do
12
+ halt(sync.to_json)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ # Base class for templating Kubernetes resources
5
+ class Template
6
+ attr_accessor :api_version, :label_namespace, :name
7
+ attr_reader :kind
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @label_namespace = "metatron.therubyist.org"
12
+ @api_version = "v1"
13
+ run_initializers
14
+ end
15
+
16
+ alias apiVersion api_version
17
+
18
+ def self.initializer(*args)
19
+ @initializers ||= []
20
+ @initializers += args
21
+ end
22
+
23
+ def self.initializers
24
+ @initializers ||= []
25
+ end
26
+
27
+ private
28
+
29
+ def run_initializers
30
+ self.class.initializers.each { |initializer| send(initializer.to_sym) }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ module Templates
5
+ module Concerns
6
+ # Makes supporting annotated resources easier
7
+ module Annotated
8
+ def self.included(base)
9
+ # base.extend ClassMethods
10
+ base.class_eval do
11
+ attr_accessor :annotations
12
+
13
+ initializer :annotated_initialize
14
+ end
15
+ end
16
+
17
+ def annotated_initialize
18
+ @annotations = {}
19
+ end
20
+
21
+ def formatted_annotations
22
+ annotations && !annotations.empty? ? { annotations: } : {}
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ module Templates
5
+ module Concerns
6
+ # A mixin to assist with templating Kubernetes resources that create Pods
7
+ module PodProducer
8
+ def self.included(base)
9
+ # base.extend ClassMethods
10
+ base.class_eval do
11
+ attr_accessor :image, :image_pull_policy, :additional_labels, :env, :envfrom,
12
+ :resource_limits, :resource_requests, :probes, :ports, :security_context,
13
+ :volume_mounts, :volumes, :additional_containers,
14
+ :container_security_context, :affinity
15
+
16
+ initializer :pod_producer_initialize
17
+
18
+ alias_method :imagePullPolicy, :image_pull_policy
19
+ alias_method :volumeMounts, :volume_mounts
20
+ alias_method :securityContext, :security_context
21
+ alias_method :environment, :env
22
+ end
23
+ end
24
+
25
+ def pod_producer_initialize
26
+ @image = "gcr.io/google_containers/pause"
27
+ @image_pull_policy = "IfNotPresent"
28
+ @resource_limits = { memory: "512Mi", cpu: "500m" }
29
+ @resource_requests = { memory: "64Mi", cpu: "10m" }
30
+ @affinity = {}
31
+ @env = {}
32
+ @envfrom = []
33
+ @probes = {}
34
+ @ports = []
35
+ @volume_mounts = []
36
+ @volumes = []
37
+ @security_context = {}
38
+ @container_security_context = {}
39
+ @additional_containers = []
40
+ @additional_labels = {}
41
+ end
42
+
43
+ def formatted_affinity
44
+ affinity && !affinity.empty? ? { affinity: } : {}
45
+ end
46
+
47
+ def formatted_environment
48
+ env && !env.empty? ? { env: env.map { |k, v| { name: k, value: v } } } : {}
49
+ end
50
+
51
+ def formatted_envfrom
52
+ if envfrom && !envfrom.empty?
53
+ { envFrom: envfrom.map { |secret| { secretRef: { name: secret } } } }
54
+ else
55
+ {}
56
+ end
57
+ end
58
+
59
+ def formatted_ports
60
+ ports&.any? ? { ports: } : {}
61
+ end
62
+
63
+ def formatted_security_context
64
+ security_context && !security_context.empty? ? { securityContext: } : {}
65
+ end
66
+
67
+ def formatted_container_security_context
68
+ if container_security_context && !container_security_context.empty?
69
+ { securityContext: container_security_context }
70
+ else
71
+ {}
72
+ end
73
+ end
74
+
75
+ def formatted_volume_mounts
76
+ volume_mounts&.any? ? { volumeMounts: } : {}
77
+ end
78
+
79
+ def formatted_volumes
80
+ volumes&.any? ? { volumes: } : {}
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ module Templates
5
+ # The Deployment Kubernetes resource
6
+ class Deployment < Template
7
+ include Concerns::Annotated
8
+ include Concerns::PodProducer
9
+
10
+ attr_accessor :replicas, :pod_annotations,
11
+ :additional_labels, :additional_pod_labels
12
+
13
+ def initialize(name, replicas: 2)
14
+ super(name)
15
+ @api_version = "apps/v1"
16
+ @kind = "Deployment"
17
+ @replicas = replicas
18
+ @pod_annotations = {}
19
+ @additional_pod_labels = {}
20
+ end
21
+
22
+ def formatted_pod_annotations
23
+ pod_annotations && !pod_annotations.empty? ? { annotations: pod_annotations } : {}
24
+ end
25
+
26
+ # rubocop:disable Metrics/MethodLength
27
+ # rubocop:disable Metrics/AbcSize
28
+ def render
29
+ {
30
+ apiVersion:,
31
+ kind:,
32
+ metadata: {
33
+ name:,
34
+ labels: { "#{label_namespace}/name": name }.merge(additional_labels)
35
+ }.merge(formatted_annotations),
36
+ spec: {
37
+ replicas:,
38
+ strategy: { type: "RollingUpdate", rollingUpdate: { maxSurge: 2, maxUnavailable: 0 } },
39
+ selector: {
40
+ matchLabels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
41
+ },
42
+ template: {
43
+ metadata: {
44
+ labels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
45
+ }.merge(formatted_pod_annotations),
46
+ spec: {
47
+ containers: [
48
+ {
49
+ name: "app",
50
+ image:,
51
+ imagePullPolicy:,
52
+ stdin: true,
53
+ tty: true,
54
+ resources: { limits: resource_limits, requests: resource_requests }
55
+ }.merge(probes)
56
+ .merge(formatted_environment)
57
+ .merge(formatted_envfrom)
58
+ .merge(formatted_ports)
59
+ .merge(formatted_volume_mounts)
60
+ .merge(formatted_container_security_context)
61
+ ] + additional_containers
62
+ }.merge(formatted_volumes).merge(formatted_security_context)
63
+ }
64
+ }
65
+ }
66
+ end
67
+ # rubocop:enable Metrics/AbcSize
68
+ # rubocop:enable Metrics/MethodLength
69
+ end
70
+ end
71
+ end