karist 0.1.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3a610ac89447543fd04a2edbe3fc5263e6e4a9e63b22892a2e29a2aa48222bcd
4
+ data.tar.gz: 783771edbea05c3b273b75be8a56b46452f68cff42a76c71b102f4c03b2f1d5a
5
+ SHA512:
6
+ metadata.gz: cd07008239e7c1723b79b873d1a6645c1d39fa3b07cf0caa5396e62c9f11f949243dd3016c76a25bc7d2bc903c98b4c5d0d60acddc05b889379e14f2a37937b7
7
+ data.tar.gz: 51a4a7b3632877c1825d0aec2192a432a83597e85bec7aa11c775cbce8fe324b1a0ab2d8412b959865773e2148860c031e3049dadfce7a58608f40bf9b2831eb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mimosa + o
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # Karist
2
+
3
+ Karist est une manière plus simple de configurer et déployer ses applications sur Kubernetes. Plus concrètement :
4
+
5
+ * Aucun CRD requis dans vos clusters Kubernetes
6
+ * Support natif d'une infinité d'environnement
7
+ * Structure opiniatée de repository Git
8
+ * Implémentation de fonctions muables
9
+ * Une CLI pour initialiser, prédire et appliquer les changements
10
+
11
+ Il peut être comparé avec **Helm**, bien que plus limité dans ses fonctions.
12
+
13
+ ## Installation
14
+
15
+ Karist dépend entièrement de Ruby (> 3.0) car il est construit avec, mais aucune connaissance de Ruby n'est nécessaire pour utiliser cette application.
16
+
17
+ ```bash
18
+ gem install karist
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ### Concepts
24
+
25
+ Karist s'article autour des opinions suivants:
26
+
27
+ * Un fichier YAML ne doit contenir que du YAML
28
+ * Les environnements concernent des valeurs parfois communes, parfois différentes
29
+ * Un déploiement Kubernetes a besoin de plusieurs manifests (Deployment, Service, Ingress, ServiceAccount...), tous doivent être liés
30
+ * L'utilisation des fonctions natives de Kubernetes doit être mis en avant
31
+
32
+ Le lexique suivant s'applique à Karist:
33
+
34
+ * `template`: un template est un dossier contenant plusieurs manifests Kubernetes. Un bon manifest doit pouvoir être modifié aisément par injection de variables.
35
+ * `customization`: une customization est l'application de variables à un manifest Kubernetes appartant à un template.
36
+ * `release`: une release est l'instantiation d'un template en accord avec des customizations appliquées à ses manifests.
37
+ * `environment`: un environnement possède une ou plusieurs releases, qui sont des templates adaptés au contexte de l'environnement.
38
+
39
+ ```bash
40
+ karist --init .
41
+ ```
42
+
43
+ Crée une structure initiale dans le répertoire courant, directement utilisable.
44
+
45
+ ```
46
+ .
47
+ ├── templates
48
+ │   ├── stateless-app
49
+ │   │   ├── karist.yml
50
+ │   │   └── manifests
51
+ │   │   ├── deployment.yml
52
+ │   │   ├── service.yml
53
+ │   │   └── serviceaccount.yml
54
+ │   └── yourapp
55
+ ├── environments
56
+ │   ├── development
57
+ │   │   ├── releases.yml
58
+ │   │   └── nginx
59
+ │   │   └── custom.yml
60
+ │   ├── local
61
+ │   └── production
62
+ └── karist.yml
63
+ ```
64
+
65
+ Le fichier releases.yml indique par défaut ceci:
66
+
67
+ ```
68
+ releases:
69
+ - name: nginx
70
+ namespace: default
71
+ template: stateless-app
72
+ ```
73
+
74
+ Lors de l'exécution de la commande suivante :
75
+
76
+ ```bash
77
+ karist --render --env development
78
+ ```
79
+
80
+ Karist va automatiquement utiliser les manifests de `stateless-app` et charger les variables définies dans le fichier `custom.yml`.
81
+
82
+ ## Variables et fonctions
83
+
84
+ Helm utilise un moteur de langage qui rend selon moi la lecture de fichiers YAML complexe. En créant Karist, j'ai préféré mettre en avant la simplicité de YAML en implémentant une logique **au niveau des valeurs** des types standards.
85
+ Ainsi, des fonctions (toujours préfixées par _) sont utilisables auprès des manifests:
86
+
87
+ ```
88
+ # templates/stateless-app/manifests/deployment.yml
89
+ yaml
90
+ apiVersion: v1
91
+ metadata:
92
+ name: $release.name
93
+ labels:
94
+ _merge: release.labels
95
+ karist/template-name: stateless-app
96
+ ```
97
+
98
+ ```yaml
99
+ # environments/development/nginx/custom.yml
100
+ release:
101
+ name: nginx
102
+ labels:
103
+ key: value
104
+ ```
105
+
106
+ Lors de l'évaluation du manifest, Karist remplace les fonctions par une évaluation.
107
+
108
+ ```yaml
109
+ # karist --dry-render \
110
+ # ./templates/stateless-app/manifests/deployment.yml
111
+ # ./environments/development/nginx/custom.yml
112
+
113
+ apiVersion: v1
114
+ metadata:
115
+ name: nginx
116
+ labels:
117
+ karist/template-name: stateless-app
118
+ key: value
119
+ ```
120
+ ## Functions
121
+
122
+ WIP
123
+
124
+ ```
125
+ $variable: inserts a string from custom.yml
126
+ _merge: merges the current dictionnary with dictionnary from custom.yml
127
+ _sum: recursively sums an array of values (curry)
128
+ _concat: recursively concatenates an array of values (curry)
129
+ ```
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: %i[test rubocop]
data/bin/karist ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "thor"
6
+ require "karist"
7
+
8
+ require "optparse"
9
+
10
+ include Karist
11
+
12
+ class << Thor
13
+ def exit_on_failure?
14
+ true
15
+ end
16
+ end
17
+
18
+ class KaristCLI < Thor
19
+
20
+ desc "init PATH", "Generates a initial project at PATH"
21
+ def init(path)
22
+ Generator.copy_to_path!(path)
23
+ exit(0)
24
+ end
25
+
26
+ desc "render ENV", "Render releases from environment ENV", default: "development"
27
+ option :dry_run, type: :boolean, desc: "Shows result from mutations to STDOUT", default: false
28
+ option :root, type: :string, desc: "Path to root Karist project", default: Dir.pwd
29
+ option :output, type: :string, desc: "Indicates where final files should be saved", default: "#{Dir.pwd}/output"
30
+ def render(env)
31
+ Dir.chdir(options[:root]) do
32
+ app = Renderer.new(env)
33
+ app.render
34
+
35
+ if options[:dry_run]
36
+ puts app.display
37
+ exit(0)
38
+ end
39
+
40
+ if app.save_to_path!(options[:output])
41
+ puts "Files were generated successfully ! 🥳"
42
+ exit(0)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ KaristCLI.start(ARGV)
49
+
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: nginx
3
+
4
+ image:
5
+ repo: nginx
6
+ tag: latest
7
+
8
+ port: 80
9
+
10
+ common:
11
+ labels:
12
+ app: nginx
13
+ karist.io/image:
14
+ _concat:
15
+ items: ["$image.repo", "$image.tag"]
16
+ sep: ":"
@@ -0,0 +1,5 @@
1
+ ---
2
+ releases:
3
+ - name: nginx
4
+ namespace: default
5
+ template: stateless
@@ -0,0 +1,31 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name:
5
+ _concat:
6
+ items:
7
+ - $name
8
+ - deployment
9
+ sep: "-"
10
+ labels:
11
+ _merge: common.labels
12
+ spec:
13
+ replicas: 3
14
+ selector:
15
+ matchLabels:
16
+ _merge: common.labels
17
+ template:
18
+ metadata:
19
+ labels:
20
+ _merge: common.labels
21
+ spec:
22
+ containers:
23
+ - name: $name
24
+ image:
25
+ _concat:
26
+ items:
27
+ - $image.repo
28
+ - $image.tag
29
+ sep: ":"
30
+ ports:
31
+ - containerPort: $port
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karist
4
+ class Generator
5
+ def self.copy_to_path!(path)
6
+ examples_dir = File.dirname(__dir__) + "/examples"
7
+ FileUtils.cp_r(examples_dir, path, verbose: false)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ class Array
2
+ # [a, b, c] = [a.mutate, b.mutate, c.mutate]
3
+ def mutate(mutations)
4
+ self.map {|z| z.mutate(mutations)}
5
+ end
6
+ end
@@ -0,0 +1,76 @@
1
+ class Hash
2
+ def mutate(mutations)
3
+ _merge(mutations)
4
+
5
+ sum_result = _sum(mutations)
6
+ return sum_result[0] if sum_result[1]
7
+
8
+ concat = _concat(mutations)
9
+ if concat.is_a?(String)
10
+ return concat
11
+ end
12
+
13
+ self.each {|a, z| self[a] = z.mutate(mutations)}
14
+ end
15
+
16
+ # { _sum: [x, y, z]} = x + (y + z)
17
+ def _sum(mutations)
18
+ if self.key?(:_sum)
19
+ return [self[:_sum].map{|el| el.mutate(mutations)}.sum, true]
20
+ end
21
+
22
+ self
23
+ end
24
+
25
+ # { _merge: x, **} = {**, x}
26
+ def _merge(mutations)
27
+ if self.key?(:_merge)
28
+ self.merge!(mutations.dig_str(self[:_merge], mutations))
29
+ self.delete(:_merge)
30
+ end
31
+
32
+ self
33
+ end
34
+
35
+ # {_concat: {items: [a, b], sep: sep}} = a`sep`b
36
+ def _concat(mutations)
37
+ if self.key?(:_concat)
38
+ case self[:_concat]
39
+ in {items: items, sep: sep}
40
+ return items.mutate(mutations).join(sep)
41
+ end
42
+ end
43
+ end
44
+
45
+ def dig_str(str, mutations)
46
+ self.dig(*str.split(".").map{|k| k.to_sym}).mutate(mutations)
47
+ end
48
+
49
+ # monkey-patch from Rails code to implement
50
+ # #deep_symbolize_keys.
51
+ # https://github.com/rails/rails/blob/19eebf6d33dd15a0172e3ed2481bec57a89a2404/activesupport/lib/active_support/core_ext/hash/keys.rb#L65
52
+ def deep_symbolize_keys
53
+ deep_transform_keys { |key| key.to_sym rescue key }
54
+ end
55
+
56
+ def deep_transform_keys(&block)
57
+ _deep_transform_keys_in_object(self, &block)
58
+ end
59
+
60
+ def _deep_transform_keys_in_object(object, &block)
61
+ case object
62
+ when Hash
63
+ object.each_with_object(self.class.new) do |(key, value), result|
64
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
65
+ end
66
+ when Array
67
+ object.map { |e| _deep_transform_keys_in_object(e, &block) }
68
+ else
69
+ object
70
+ end
71
+ end
72
+
73
+ def deep_stringify_keys
74
+ deep_transform_keys(&:to_s)
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ class Integer
2
+ def mutate(mutations)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class String
2
+ # "_ -> x" = y
3
+ # "_from x" = y
4
+ def mutate(mutations)
5
+ case self.split("$")
6
+ in ["", from]
7
+ return mutations.dig_str(from, mutations)
8
+ else
9
+ end
10
+
11
+ return self
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ class TrueClass
2
+ def mutate(mutations)
3
+ self
4
+ end
5
+ end
6
+
7
+ class FalseClass
8
+ def mutate(mutations)
9
+ self
10
+ end
11
+ end
12
+
13
+ class NilClass
14
+ def mutate(mutations)
15
+ self
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Release
4
+ attr_reader :results, :name
5
+
6
+ def initialize(release, release_file_obj)
7
+ @release_file = release_file_obj
8
+ @name = release.fetch(:name)
9
+ @namespace = release.fetch(:namespace, "default")
10
+ @template = release.fetch(:template)
11
+ @results = {}
12
+ end
13
+
14
+ def render
15
+ custom_file = "#{@release_file.env_path}/#{@name}/custom.yml"
16
+ mutations = YAML.safe_load(File.read(custom_file)).deep_symbolize_keys
17
+
18
+ manifests_path = "#{@release_file.templates_path}/#{@template}/manifests/**.yml"
19
+ manifest_files = Dir.glob(manifests_path)
20
+ manifest_files.each do |manifest_file|
21
+ content = YAML.safe_load(File.read(manifest_file)).deep_symbolize_keys
22
+ @results[manifest_file] = content.mutate(mutations)
23
+ end
24
+ end
25
+
26
+ def marshal_all_yaml
27
+ @results.map do |k, _|
28
+ marshal_yaml(k)
29
+ end
30
+ end
31
+
32
+ def marshal_yaml(k)
33
+ @results[k].deep_stringify_keys.to_yaml
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ReleaseFile
4
+ attr_reader :env_path, :templates_path, :releases
5
+
6
+ def initialize(release_file, env_path, templates_path)
7
+ @env_path = env_path
8
+ @templates_path = templates_path
9
+
10
+ @releases = release_file.fetch(:releases, []).map do |release|
11
+ Release.new(release, self)
12
+ end
13
+ end
14
+
15
+ def render_all
16
+ @releases.each do |release|
17
+ release.render
18
+ end
19
+ end
20
+
21
+ def display_all
22
+ @releases.map do |release|
23
+ release.marshal_all_yaml
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Renderer
4
+ def initialize(env, root_path=".")
5
+ @env_path = "#{root_path}/environments/#{env}"
6
+ @templates_path = "#{root_path}/templates"
7
+
8
+ @release_file = File.read("#{@env_path}/releases.yml").then do |f|
9
+ YAML.safe_load(f).then do |y|
10
+ y.deep_symbolize_keys.then do |r|
11
+ ReleaseFile.new(r, @env_path, @templates_path)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def render
18
+ @release_file.render_all
19
+ end
20
+
21
+ def display
22
+ @release_file.display_all
23
+ end
24
+
25
+ def save_to_path!(path)
26
+ begin
27
+ FileUtils.mkdir_p(path)
28
+ @release_file.releases.each do |release|
29
+ FileUtils.mkdir_p("#{path}/#{release.name}")
30
+ release.results.each do |filename, result|
31
+ r_filename = filename.split("/")
32
+ case r_filename
33
+ in [*subpath, filename]
34
+ result_path = "#{path}/#{release.name}/#{subpath.join("/")}"
35
+ FileUtils.mkdir_p(result_path)
36
+ File.open("#{result_path}/#{filename}", "a+") do |f|
37
+ f.write(result.deep_stringify_keys.to_yaml)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ return true
43
+ rescue => e
44
+ raise e
45
+ return false
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,5 @@
1
+ # frozen_strint_literal: true
2
+
3
+ module Karist
4
+ VERSION = "0.1.7"
5
+ end
data/lib/karist.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require "yaml"
3
+ require "json"
4
+
5
+ require_relative "karist/version"
6
+ require_relative "karist/patch/array"
7
+ require_relative "karist/patch/hash"
8
+ require_relative "karist/patch/string"
9
+ require_relative "karist/patch/true_false"
10
+ require_relative "karist/patch/integer"
11
+
12
+ require_relative "karist/generator"
13
+ require_relative "karist/renderer"
14
+ require_relative "karist/release_file"
15
+ require_relative "karist/release"
16
+
17
+ module Karist
18
+ class GenericError < StandardError; end
19
+ class FunctionsError < GenericError; end
20
+ class SyntaxError < GenericError; end
21
+ class ParserError < GenericError; end
22
+ class NotAHashError < ParserError; end
23
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: karist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - Mimosao
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ description: Karist simplifies development of manifests and releases on Kubernetes
28
+ by automating templating operations.
29
+ email:
30
+ - mimosao@duck.com
31
+ executables:
32
+ - karist
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".ruby-version"
37
+ - LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - bin/karist
41
+ - lib/examples/environments/development/nginx/custom.yml
42
+ - lib/examples/environments/development/releases.yml
43
+ - lib/examples/templates/stateless/manifests/deployment.yml
44
+ - lib/karist.rb
45
+ - lib/karist/generator.rb
46
+ - lib/karist/patch/array.rb
47
+ - lib/karist/patch/hash.rb
48
+ - lib/karist/patch/integer.rb
49
+ - lib/karist/patch/string.rb
50
+ - lib/karist/patch/true_false.rb
51
+ - lib/karist/release.rb
52
+ - lib/karist/release_file.rb
53
+ - lib/karist/renderer.rb
54
+ - lib/karist/version.rb
55
+ homepage: https://github.com/mimosao/karist
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ allowed_push_host: https://rubygems.org
60
+ homepage_uri: https://github.com/mimosao/karist
61
+ source_code_uri: https://github.com/mimosao/karist
62
+ changelog_uri: https://github.com/mimosao/karist
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.7.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.4.10
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Karist is a templating system for Kubernetes
82
+ test_files: []