kustomizer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5b88c7e828be9c93ec00ef81cd1fa0b583f2772c6b621cfab5ce0e422418d49c
4
+ data.tar.gz: a08c5c25f4c234ea9b698d64e6f327a5e3a17147d1cf17c613471fcf0f32a7cf
5
+ SHA512:
6
+ metadata.gz: 42ecbbe674b0b4f49c434457f99cc43084e4c6d432789d15ac4a3081e345a5dda6b459ca1db1f15057f2279f3b00454dd46e5d350a4c60fe0e391495c26d5952
7
+ data.tar.gz: dbb5dbbca1c1b1ae4e95584fb9601284b5f8c3f73a2b4ba15f02d4f1bf382634d87e86632ec7319e90761beb6de8ef8cc946909531718df4bfeff3a9793351fd
@@ -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/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in kustomizer.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ kustomizer (0.1.0)
5
+ accessory (~> 0.1.9)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ accessory (0.1.9)
11
+ diff-lcs (1.4.4)
12
+ rake (13.0.3)
13
+ rspec (3.10.0)
14
+ rspec-core (~> 3.10.0)
15
+ rspec-expectations (~> 3.10.0)
16
+ rspec-mocks (~> 3.10.0)
17
+ rspec-core (3.10.1)
18
+ rspec-support (~> 3.10.0)
19
+ rspec-expectations (3.10.1)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.10.0)
22
+ rspec-mocks (3.10.1)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.10.0)
25
+ rspec-support (3.10.1)
26
+
27
+ PLATFORMS
28
+ x86_64-darwin-20
29
+
30
+ DEPENDENCIES
31
+ kustomizer!
32
+ rake (~> 13.0)
33
+ rspec (~> 3.0)
34
+
35
+ BUNDLED WITH
36
+ 2.2.5
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Levi Aul
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.
@@ -0,0 +1,111 @@
1
+ # KustomizeR
2
+
3
+ KustomizeR is a pure-Ruby implementation of [Kustomize](https://kustomize.io/), a
4
+ Kubernetes configuration-management tool.
5
+
6
+ KustomizeR exists to be used by other Ruby tooling, as an alternative to requiring
7
+ the user to have the native Go version of Kustomize installed.
8
+
9
+ ## Roadmap
10
+
11
+ KustomizeR is **not yet feature-complete**, i.e. it does not yet do everything
12
+ that Kustomize does.
13
+
14
+ Status of `kustomization.yaml` support:
15
+
16
+ * [x] `bases`
17
+ * [ ] `commonAnnotations`
18
+ * [ ] `commonLabels`
19
+ * [ ] `configMapGenerator` and `secretGenerator`
20
+ * [ ] `crds`
21
+ * [x] `generators` (see [Extending Kustomize](https://kubectl.docs.kubernetes.io/guides/extending_kustomize/))
22
+ * [ ] `generatorOptions`
23
+ * [x] `images`
24
+ * [ ] `namePrefix` and `nameSuffix`
25
+ * [x] `namespace`
26
+ * [x] `patches`
27
+ * [x] `patchesJson6902`
28
+ * [ ] `patchesStrategicMerge`
29
+ * [ ] `replicas`
30
+ * [x] `resources`
31
+ * [ ] `transformers` (see [Extending Kustomize](https://kubectl.docs.kubernetes.io/guides/extending_kustomize/))
32
+ * [ ] `vars`
33
+
34
+ Status of support for other features:
35
+
36
+ * [ ] Resource loading
37
+ * [x] resource-config files on disk
38
+ * [x] `kustomization.yaml` files on disk
39
+ * [x] directories on disk (all resource-config files within)
40
+ * [x] directories on disk (`kustomization.yaml` file within)
41
+ * [ ] files/directories from git repo URLs
42
+
43
+ * [ ] Automatic name suffixing of generated resources
44
+ * [x] Secrets
45
+ * [ ] ConfigMaps
46
+
47
+ #### Differences from Kustomize
48
+
49
+ * KustomizeR is **not yet feature-complete**. (KustomizeR will be bumped to
50
+ version 1.0 once it reaches feature parity with Kustomize.)
51
+
52
+ * KustomizeR does not support loading Go plugins. Instead, KustomizeR supports
53
+ loading Ruby plugins. See [Plugin Development](#plugin-development) below.
54
+
55
+ * KustomizeR is modular, and is intended to be loaded and used as a library,
56
+ rather than being spawned as a subprocess. Crucially, the load path for
57
+ plugins is under the caller's control, and so higher-level frameworks can
58
+ manage project-level KustomizeR plugins.
59
+
60
+ * Some `kustomization.yaml` features have been temporarily extended in
61
+ non-compatible ways.
62
+ * `patchesJson6902` accepts an inline an `ops` array
63
+ * `patchesJson6902` accepts a `gsub` op
64
+
65
+ (Before v1.0, these extensions will be moved to become built-in plugins, to
66
+ allow for inter-compatibility with Kustomize, which could support them as
67
+ external plugins.)
68
+
69
+ ## Installation
70
+
71
+ Add this line to your application's Gemfile:
72
+
73
+ ```ruby
74
+ gem 'kustomizer'
75
+ ```
76
+
77
+ And then execute:
78
+
79
+ $ bundle install
80
+
81
+ Or install it yourself as:
82
+
83
+ $ gem install kustomizer
84
+
85
+ ## Usage
86
+
87
+ ```ruby
88
+ require 'kustomize'
89
+ k = Kustomize.load("./path/to/kustomization.yaml")
90
+
91
+ k.emit # Array of Hashes (the final resource-configs)
92
+ k.to_yaml_stream # String (merged YAML multi-document stream)
93
+ ```
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
100
+
101
+ ### Plugin Development
102
+
103
+ TODO
104
+
105
+ ## Contributing
106
+
107
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tsutsu/kustomizer.
108
+
109
+ ## License
110
+
111
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "kustomizer"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/kustomize/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "kustomizer"
7
+ spec.version = Kustomize::VERSION
8
+ spec.authors = ["Levi Aul"]
9
+ spec.email = ["levi@leviaul.com"]
10
+
11
+ spec.summary = "Pure-ruby impl of Kubernetes kustomize"
12
+ spec.description = "A pure-ruby implementation of the Kubernetes 'kustomize' command."
13
+ spec.homepage = "https://github.com/tsutsu/kustomize"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_runtime_dependency "accessory", "~> 0.1.9"
28
+ end
@@ -0,0 +1,44 @@
1
+ require 'pathname'
2
+
3
+ require 'kustomize/version'
4
+ require 'kustomize/session'
5
+
6
+ require 'kustomize/emitter/file_emitter'
7
+ require 'kustomize/emitter/directory_emitter'
8
+ require 'kustomize/emitter/document_emitter/kustomization_document_emitter'
9
+
10
+ module Kustomize
11
+ def self.load(rel_path_or_rc, session: Kustomize::Session.new, source_path: nil)
12
+ case rel_path_or_rc
13
+ when String, Pathname
14
+ load_path(rel_path_or_rc, session: session)
15
+ when Hash
16
+ load_doc(rel_path_or_rc, session: session, source_path: source_path)
17
+ else
18
+ raise ArgumentError, "must be a kustomization document or a path to one, instead got: #{rel_path_or_rc.inspect}"
19
+ end
20
+ end
21
+
22
+ def self.load_doc(rc, session: Kustomize::Session.new, source_path:)
23
+ Kustomize::Emitter::DocumentEmitter::KustomizationDocumentEmitter
24
+ .load(rc, source: source_path, session: session)
25
+ end
26
+
27
+ def self.load_path(rel_path, session: Kustomize::Session.new)
28
+ rel_path = Pathname.new(rel_path.to_s) unless rel_path.kind_of?(Pathname)
29
+
30
+ unless rel_path.exist?
31
+ raise Errno::ENOENT, rel_path.to_s
32
+ end
33
+
34
+ abs_path = rel_path.expand_path
35
+
36
+ if abs_path.file?
37
+ Kustomize::Emitter::FileEmitter.new(abs_path, session: session)
38
+ elsif abs_path.directory?
39
+ Kustomize::Emitter::DirectoryEmitter.new(abs_path, session: session)
40
+ else
41
+ raise Errno::EFTYPE, rel_path.to_s
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ module Kustomize; end
2
+
3
+ class Kustomize::Emitter
4
+ def input_emitters; []; end
5
+
6
+ def input_resources
7
+ self.input_emitters.flat_map(&:emit)
8
+ end
9
+
10
+ def emit
11
+ self.input_resources
12
+ end
13
+
14
+ def to_yaml_stream
15
+ self.emit.map(&:to_yaml).join("")
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ require 'kustomize/emitter'
2
+ require 'kustomize/emitter/file_emitter'
3
+
4
+ require 'kustomize/pathname_refinements'
5
+ using Kustomize::PathnameRefinements
6
+
7
+ class Kustomize::Emitter::DirectoryEmitter < Kustomize::Emitter
8
+ def initialize(source_path, session:)
9
+ @session = session
10
+ @source_path = source_path
11
+ end
12
+
13
+ def input_emitters
14
+ return @input_emitters if @input_emitters
15
+
16
+ maybe_ckf = @source_path.child_kustomization_file
17
+
18
+ @input_emitters =
19
+ if maybe_ckf.file?
20
+ ckf_emitter = Kustomize::Emitter::FileEmitter.new(maybe_ckf, session: @session)
21
+ [ckf_emitter]
22
+ else
23
+ @source_path.all_rc_files_within.flat_map do |rc_path|
24
+ Kustomize::Emitter::FileEmitter.new(rc_path, session: @session)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ require 'kustomize/emitter'
2
+
3
+ class Kustomize::Emitter::DocumentEmitter < Kustomize::Emitter
4
+ def self.load(doc, source:, session:)
5
+ self.new(doc, source: source, session: session)
6
+ end
7
+
8
+ def initialize(doc, source: nil, session:)
9
+ @session = session
10
+
11
+ @doc = doc
12
+ @source = source
13
+ end
14
+
15
+ def emit
16
+ [@doc]
17
+ end
18
+ end
@@ -0,0 +1,97 @@
1
+ require 'kustomize/emitter/document_emitter'
2
+ require 'kustomize/emitter/file_emitter'
3
+ require 'kustomize/emitter/directory_emitter'
4
+ require 'kustomize/emitter/plugin_emitter'
5
+
6
+ require 'kustomize/transform/json_6902_patch_transform'
7
+ require 'kustomize/transform/image_transform'
8
+ require 'kustomize/transform/namespace_transform'
9
+ require 'kustomize/transform/name_digest_autosuffix_transform'
10
+
11
+ class Kustomize::Emitter::DocumentEmitter::KustomizationDocumentEmitter < Kustomize::Emitter::DocumentEmitter
12
+ def source_directory
13
+ @source[:path].parent
14
+ end
15
+
16
+ def input_emitters
17
+ return @input_emitters if @input_emitters
18
+
19
+ rc_pathspecs =
20
+ (@doc['bases'] || []) +
21
+ (@doc['resources'] || [])
22
+
23
+ gen_pathspecs =
24
+ (@doc['generators'] || [])
25
+
26
+ input_emitters = rc_pathspecs.map do |rel_path|
27
+ build_input_emitter(rel_path)
28
+ end
29
+
30
+ input_emitters += gen_pathspecs.map do |rel_path|
31
+ Kustomize::Emitter::PluginEmitter.new(
32
+ build_input_emitter(rel_path),
33
+ session: @session
34
+ )
35
+ end
36
+
37
+ @input_emitters = input_emitters
38
+ end
39
+
40
+ def build_input_emitter(rel_path)
41
+ abs_path = self.source_directory / rel_path
42
+
43
+ unless abs_path.exist?
44
+ raise Errno::ENOENT, abs_path.to_s
45
+ end
46
+
47
+ if abs_path.file?
48
+ Kustomize::Emitter::FileEmitter.new(abs_path, session: @session)
49
+ elsif abs_path.directory?
50
+ Kustomize::Emitter::DirectoryEmitter.new(abs_path, session: @session)
51
+ else
52
+ raise Errno::EFTYPE, abs_path.to_s
53
+ end
54
+ end
55
+ private :build_input_emitter
56
+
57
+ def json_6902_patch_transforms
58
+ ((@doc['patches'] || []) + (@doc['patchesJson6902'] || [])).map do |op_spec|
59
+ Kustomize::Transform::Json6902PatchTransform.create(self, op_spec)
60
+ end
61
+ end
62
+
63
+ def image_transforms
64
+ (@doc['images'] || []).map do |op_spec|
65
+ Kustomize::Transform::ImageTransform.create(op_spec)
66
+ end
67
+ end
68
+
69
+ def namespace_transforms
70
+ if new_ns = @doc['namespace']
71
+ [Kustomize::Transform::NamespaceTransform.create(new_ns)]
72
+ else
73
+ []
74
+ end
75
+ end
76
+
77
+ def name_digest_autosuffix_transforms
78
+ [Kustomize::Transform::NameDigestAutosuffixTransform.create(self)]
79
+ end
80
+
81
+ def transforms
82
+ return @transforms if @transforms
83
+
84
+ @transforms = [
85
+ self.namespace_transforms,
86
+ self.image_transforms,
87
+ self.name_digest_autosuffix_transforms,
88
+ self.json_6902_patch_transforms
89
+ ].flatten
90
+ end
91
+
92
+ def emit
93
+ self.input_resources.map do |rc|
94
+ self.transforms.inject(rc){ |doc, xform| xform.apply(doc) }
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+
3
+ require 'kustomize/emitter'
4
+ require 'kustomize/emitter/document_emitter'
5
+
6
+ class Kustomize::Emitter::FileEmitter < Kustomize::Emitter
7
+ def initialize(source_path, session:)
8
+ @session = session
9
+ @source_path = source_path
10
+ end
11
+
12
+ def input_emitters
13
+ return @input_emitters if @input_emitters
14
+
15
+ source_docs = YAML.load_stream(@source_path.read)
16
+
17
+ @input_emitters = source_docs.map.with_index do |doc, i|
18
+ unless doc.has_key?('kind')
19
+ raise ArgumentError, "invalid Kubernetes resource-config document (missing attribute 'kind'): subdocument #{i} in #{target_path}"
20
+ end
21
+
22
+ doc_kind = doc['kind']
23
+
24
+ doc_klass =
25
+ begin
26
+ Kustomize::Emitter::DocumentEmitter.const_get(doc_kind + 'DocumentEmitter')
27
+ rescue NameError => e
28
+ Kustomize::Emitter::DocumentEmitter
29
+ end
30
+
31
+ doc_klass.load(
32
+ doc,
33
+ source: {path: @source_path, subdocument: i},
34
+ session: @session
35
+ )
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ require 'kustomize/emitter'
2
+
3
+ class Kustomize::Emitter::PluginEmitter < Kustomize::Emitter
4
+ def initialize(input_emitter, session:)
5
+ @session = session
6
+ @input_emitter = input_emitter
7
+ end
8
+
9
+ def source_directory
10
+ @source_path.parent
11
+ end
12
+
13
+ def input_emitters
14
+ [@input_emitter]
15
+ end
16
+
17
+ def plugin_instances
18
+ return @plugin_instances if @plugin_instances
19
+
20
+ @plugin_instances =
21
+ self.input_resources.map do |rc|
22
+ plugin_klass = @session.plugin_manager.get(rc['apiVersion'], rc['kind'])
23
+ plugin_klass.new(rc)
24
+ end
25
+ end
26
+
27
+ def emit
28
+ self.plugin_instances.flat_map(&:emit)
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require 'accessory'
2
+
3
+ module Kustomize; end
4
+ module Kustomize::Json6902Patch; end
5
+
6
+ class Kustomize::Json6902Patch::Op
7
+ def self.create(...)
8
+ new(...)
9
+ end
10
+
11
+ class << self
12
+ private :new
13
+ end
14
+
15
+ def parse_lens(path)
16
+ lens_parts = path[1..-1].split("/").map do |e|
17
+ e = e.gsub('~1', '/')
18
+ if e == ":all"
19
+ Accessory::Access.all
20
+ elsif e.match?(/^\d+$/)
21
+ e.to_i
22
+ else
23
+ e
24
+ end
25
+ end
26
+
27
+ Accessory::Lens[*lens_parts]
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'kustomize/json_6902_patch'
2
+
3
+ class Kustomize::Json6902Patch::AddOp < Kustomize::Json6902Patch::Op
4
+ def self.create(patch_spec)
5
+ new(
6
+ path: patch_spec['path'],
7
+ value: patch_spec['value']
8
+ )
9
+ end
10
+
11
+ def initialize(path:, value:)
12
+ @lens = parse_lens(path)
13
+ @new_value = value
14
+ end
15
+
16
+ def apply(rc)
17
+ @lens.update_in(rc) do |orig_value|
18
+ unless orig_value.nil?
19
+ raise ArgumentError, "cannot add value at #{@lens.inspect} -- value exists at target"
20
+ end
21
+
22
+ [:set, @new_value]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'kustomize/json_6902_patch'
2
+
3
+ class Kustomize::Json6902Patch::GsubOp < Kustomize::Json6902Patch::Op
4
+ def self.create(patch_spec)
5
+ self.new(
6
+ paths: patch_spec['paths'],
7
+ pattern: Regexp.new(patch_spec['pattern'], Regexp::EXTENDED),
8
+ replacement: patch_spec['replacement']
9
+ )
10
+ end
11
+
12
+ def initialize(paths:, pattern:, replacement:)
13
+ @lenses = paths.map{ |path| parse_lens(path) }
14
+ @pattern = pattern
15
+ @replacement = replacement
16
+ end
17
+
18
+ def apply(rc)
19
+ @lenses.inject(rc) do |doc, lens|
20
+ lens.update_in(doc) do |orig_value|
21
+ new_value = orig_value.gsub(@pattern, @replacement)
22
+
23
+ if new_value != orig_value
24
+ [:set, new_value]
25
+ else
26
+ :keep
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ require 'kustomize/json_6902_patch'
2
+
3
+ class Kustomize::Json6902Patch::RemoveOp < Kustomize::Json6902Patch::Op
4
+ def self.create(patch_spec)
5
+ new(
6
+ path: patch_spec['path']
7
+ )
8
+ end
9
+
10
+ def initialize(path:)
11
+ @lens = parse_lens(path)
12
+ end
13
+
14
+ def apply(rc0)
15
+ _, rc1 = @lens.pop_in(rc0)
16
+ rc1
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ require 'kustomize/json_6902_patch'
2
+
3
+ class Kustomize::Json6902Patch::ReplaceOp < Kustomize::Json6902Patch::Op
4
+ def self.create(patch_spec)
5
+ new(
6
+ path: patch_spec['path'],
7
+ value: patch_spec['value']
8
+ )
9
+ end
10
+
11
+ def initialize(path:, value:)
12
+ @lens = parse_lens(path)
13
+ @new_value = value
14
+ end
15
+
16
+ def apply(rc)
17
+ @lens.update_in(rc) do |orig_value|
18
+ if orig_value.nil?
19
+ raise ArgumentError, "cannot set value at #{@lens.inspect} -- target does not exist"
20
+ end
21
+
22
+ [:set, @new_value]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ module Kustomize; end
5
+
6
+ module Kustomize::PathnameRefinements
7
+ RC_EXT_PAT = /\.(yaml|yml)$/i
8
+ KUSTOMIZATION_FILENAME = 'kustomization.yaml'
9
+ KUSTOMIZATION_FILENAME_PAT = /^kustomization\.(yaml|yml)$/i
10
+
11
+ refine ::Pathname do
12
+ def visible?
13
+ self.basename.to_s[0] != '.'
14
+ end
15
+
16
+ def resource_config_file?
17
+ self.file? and self.visible? and !!(self.basename.to_s =~ RC_EXT_PAT)
18
+ end
19
+
20
+ def kustomization_file?
21
+ self.file? and self.visible? and !!(self.basename.to_s =~ KUSTOMIZATION_FILENAME_PAT)
22
+ end
23
+
24
+ def child_kustomization_file
25
+ self / KUSTOMIZATION_FILENAME
26
+ end
27
+
28
+ def kustomization_dir?
29
+ self.directory? and self.child_kustomization_file.file?
30
+ end
31
+
32
+ def all_rc_files_within
33
+ self.all_rc_files_within_visit.flatten
34
+ end
35
+
36
+ def all_rc_files_within_visit
37
+ if self.resource_config_file?
38
+ [self]
39
+ elsif self.directory?
40
+ self.children.map{ |ch| ch.all_rc_files_within_visit }
41
+ else
42
+ []
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,11 @@
1
+ module Kustomize; end
2
+
3
+ class Kustomize::Plugin
4
+ def initialize(rc)
5
+ @rc = rc
6
+ end
7
+
8
+ def emit
9
+ raise NotImplementedError, "Kustomize plugins must implement #emit"
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ require 'kustomize/plugin'
2
+
3
+ class Kustomize::PluginManager
4
+ def initialize(session:)
5
+ @session = session
6
+ @instances = {}
7
+ end
8
+
9
+ def get(api_version, kind)
10
+ cache_key = [api_version, kind]
11
+ cached_inst = @instances[cache_key]
12
+ return cached_inst if cached_inst
13
+
14
+ @instances[cache_key] = self.load(api_version, kind)
15
+ end
16
+
17
+ def load(api_version, kind)
18
+ @session.load_paths
19
+ .each{ |prefix| puts(prefix / api_version / "#{kind.downcase}.rb") }
20
+
21
+ load_path =
22
+ @session.load_paths
23
+ .map{ |prefix| prefix / api_version / "#{kind.downcase}.rb" }
24
+ .find{ |f| f.file? }
25
+
26
+ raise ArgumentError, "unknown kustomize plugin #{kind}" unless load_path
27
+
28
+ Class.new(Kustomize::Plugin)
29
+ .tap{ |klass| klass.class_eval(load_path.read) }
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Kustomize; end
2
+
3
+ require 'kustomize/plugin_manager'
4
+
5
+ class Kustomize::Session
6
+ def plugin_manager
7
+ return @plugin_manager if @plugin_manager
8
+ @plugin_manager = Kustomize::PluginManager.new(session: self)
9
+ end
10
+
11
+ def load_paths
12
+ return @load_paths if @load_paths
13
+ @load_paths = [
14
+ Pathname.new(__FILE__).expand_path.parent / 'plugin'
15
+ ]
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module Kustomize; end
2
+
3
+ class Kustomize::TargetSpec
4
+ def self.create(target_spec)
5
+ self.new(
6
+ api_group: target_spec['group'],
7
+ api_version: target_spec['version'],
8
+
9
+ kind: target_spec['kind'],
10
+
11
+ namespace: target_spec['namespace'],
12
+ name: target_spec['name']
13
+ )
14
+ end
15
+
16
+ def initialize(api_group: nil, api_version: nil, kind: nil, name: nil, namespace: nil)
17
+ @match_api_group = api_group
18
+ @match_api_version = api_version
19
+ @match_kind = kind
20
+ @match_namespace = namespace
21
+ @match_name = name
22
+ end
23
+
24
+ def get_name(rc)
25
+ rc.dig('spec', 'name')
26
+ end
27
+
28
+ def get_namespace(rc)
29
+ rc.dig('spec', 'namespace') || 'default'
30
+ end
31
+
32
+ def match?(rc)
33
+ if @match_api_group or @match_api_version
34
+ api_group, api_version = (rc['apiVersion'] || '/').split('/', 2)
35
+ return false if @match_api_group and api_group != @match_api_group
36
+ return false if @match_api_version and api_version != @match_api_version
37
+ end
38
+
39
+ return false if @match_kind and (rc['kind'] != @match_kind)
40
+ return false if @match_name and get_name(resource_doc) != @match_name
41
+ return false if @match_namespace and get_namespace(resource_doc) != @match_namespace
42
+
43
+ true
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ module Kustomize; end
2
+
3
+ class Kustomize::Transform
4
+ def self.create(...)
5
+ new(...)
6
+ end
7
+
8
+ class << self
9
+ private :new
10
+ end
11
+
12
+ def apply(rc)
13
+ rc
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ require 'accessory'
2
+
3
+ require 'kustomize/transform/image_transform'
4
+
5
+ class Kustomize::Transform::ImageTransform < Kustomize::Transform
6
+ include Accessory
7
+
8
+ def self.create(op_spec)
9
+ raise ArgumentError, "cannot specify both newTag and digest" if op_spec['newTag'] and op_spec['digest']
10
+
11
+ new(
12
+ name: op_spec['name'],
13
+ new_name: op_spec['newName'],
14
+ new_tag: op_spec['newTag'],
15
+ new_digest: op_spec['digest']
16
+ )
17
+ end
18
+
19
+ def initialize(name:, new_name: nil, new_tag: nil, new_digest: nil)
20
+ @name = name
21
+ @new_name = new_name
22
+ @new_tag = new_tag
23
+ @new_digest = new_digest
24
+ end
25
+
26
+ LENS_BY_KIND = {
27
+ "Deployment" => Lens["spec", "template", "spec", "containers", Access.all, "image"]
28
+ }
29
+
30
+ def apply(rc_doc)
31
+ lens = LENS_BY_KIND[rc_doc['kind']]
32
+ return rc_doc unless lens
33
+
34
+ lens.update_in(rc_doc) do |image_str|
35
+ image_parts = /^(.+?)([:@])(.+)$/.match(image_str)
36
+
37
+ image_parts = if image_parts
38
+ {name: image_parts[1], sigil: image_parts[2], ref: image_parts[3]}
39
+ else
40
+ {name: container['image'], sigil: ':', ref: 'latest'}
41
+ end
42
+
43
+ unless image_parts[:name] == @name
44
+ next(:keep)
45
+ end
46
+
47
+ if @new_name
48
+ image_parts[:name] = new_name
49
+ end
50
+
51
+ if @new_tag
52
+ image_parts[:sigil] = ':'
53
+ image_parts[:ref] = @new_tag
54
+ elsif @new_digest
55
+ image_parts[:sigil] = '@'
56
+ image_parts[:ref] = @new_digest
57
+ end
58
+
59
+ new_image_str = "#{image_parts[:name]}#{image_parts[:sigil]}#{image_parts[:ref]}"
60
+ [:set, new_image_str]
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+
3
+ require 'kustomize/transform'
4
+ require 'kustomize/target_spec'
5
+
6
+ require 'kustomize/json_6902_patch/add_op'
7
+ require 'kustomize/json_6902_patch/replace_op'
8
+ require 'kustomize/json_6902_patch/remove_op'
9
+ require 'kustomize/json_6902_patch/gsub_op'
10
+
11
+ class Kustomize::Transform::Json6902PatchTransform < Kustomize::Transform
12
+ def self.create(kustomization_file, op_spec)
13
+ target = Kustomize::TargetSpec.create(op_spec['target'])
14
+
15
+ patch_part =
16
+ if op_spec['path']
17
+ path = kustomization_file.source_directory / op_spec['path']
18
+ YAML.load(file.read)
19
+ elsif op_spec['patch']
20
+ YAML.load(op_spec['patch'])
21
+ elsif op_spec['ops']
22
+ op_spec['ops']
23
+ else
24
+ []
25
+ end
26
+
27
+ patches = patch_part.map do |patch|
28
+ Kustomize::Json6902Patch
29
+ .const_get(patch['op'].capitalize + 'Op')
30
+ .create(patch)
31
+ end
32
+
33
+ self.new(
34
+ target: target,
35
+ patches: patches
36
+ )
37
+ end
38
+
39
+ def initialize(target:, patches:)
40
+ @target = target
41
+ @patches = patches
42
+ end
43
+
44
+ attr_reader :target
45
+ attr_reader :patches
46
+
47
+ def apply(resource_doc)
48
+ if @target.match?(resource_doc)
49
+ @patches.inject(resource_doc){ |doc, patch| patch.apply(doc) }
50
+ else
51
+ resource_doc
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,83 @@
1
+ require 'json'
2
+ require 'digest'
3
+ require 'set'
4
+
5
+ require 'accessory'
6
+ require 'digest/base32'
7
+
8
+ require 'kustomize/transform'
9
+ require 'kustomize/emitter/document_emitter/kustomization_document_emitter'
10
+
11
+ class Kustomize::Transform::NameDigestAutosuffixTransform < Kustomize::Transform
12
+ include Accessory
13
+
14
+ SUFFIX_JOINER = "-"
15
+
16
+ def self.create(kustomize_doc)
17
+ raise ArgumentError unless kustomize_doc.kind_of?(Kustomize::Emitter::DocumentEmitter::KustomizationDocumentEmitter)
18
+ self.new(kustomize_doc)
19
+ end
20
+
21
+ def initialize(kustomize_doc)
22
+ @kustomize_doc = kustomize_doc
23
+ end
24
+
25
+ SECRET_KINDS = Set[
26
+ 'Secret',
27
+ 'SealedSecret'
28
+ ]
29
+
30
+ def suffixes
31
+ return @suffixes if @suffixes
32
+
33
+ secret_docs =
34
+ @kustomize_doc.input_resources
35
+ .filter{ |rc| SECRET_KINDS.member?(rc['kind']) }
36
+
37
+ @suffixes =
38
+ secret_docs.map do |rc|
39
+ rc_kind = rc['kind']
40
+
41
+ secret_name = NAME_LENSES_BY_KIND[rc_kind].first.get_in(rc)
42
+ secret_content = CONTENT_LENSES_BY_KIND[rc_kind].get_in(rc)
43
+ content_hash_suffix = Digest::SHA256.base32digest(secret_content.to_json, :zbase32)[0, 8]
44
+
45
+ [secret_name, content_hash_suffix]
46
+ end.to_h
47
+ end
48
+
49
+ CONTENT_LENSES_BY_KIND = {
50
+ "Secret" => Lens["data"],
51
+ "SealedSecret" => Lens["spec", "encryptedData"]
52
+ }
53
+
54
+ NAME_LENSES_BY_KIND = {
55
+ "Deployment" => [
56
+ Lens["spec", "template", "spec", "containers", Access.all, "env", Access.all, "valueFrom", "secretKeyRef", "name"]
57
+ ],
58
+
59
+ "Secret" => [
60
+ Lens["metadata", "name"]
61
+ ],
62
+
63
+ "SealedSecret" => [
64
+ Lens["spec", "template", "metadata", "name"]
65
+ ]
66
+ }
67
+
68
+ def apply(rc_doc)
69
+ name_lenses = NAME_LENSES_BY_KIND[rc_doc['kind']]
70
+ return rc_doc unless name_lenses
71
+
72
+ name_lenses.inject(rc_doc) do |doc, lens|
73
+ lens.update_in(doc) do |orig_name|
74
+ if self.suffixes.has_key?(orig_name)
75
+ new_name = [orig_name, self.suffixes[orig_name]].join('-')
76
+ [:set, new_name]
77
+ else
78
+ :keep
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,48 @@
1
+ require 'accessory'
2
+
3
+ require 'kustomize/transform'
4
+
5
+ class Kustomize::Transform::NamespaceTransform < Kustomize::Transform
6
+ include Accessory
7
+
8
+ def initialize(new_ns)
9
+ @new_ns = new_ns
10
+ end
11
+
12
+ LENSES_FOR_ALL = [
13
+ Lens["metadata", "namespace"]
14
+ ]
15
+
16
+ LENSES_FOR_ALL_BLACKLIST_PAT = /^Cluster/
17
+
18
+ LENSES_FOR_KIND = {
19
+ "ClusterRoleBinding" => [
20
+ Lens["subjects", Access.all, "namespace"]
21
+ ],
22
+
23
+ "RoleBinding" => [
24
+ Lens["subjects", Access.all, "namespace"]
25
+ ],
26
+
27
+ "SealedSecret" => [
28
+ Lens["spec", "template", "metadata", "namespace"]
29
+ ]
30
+ }
31
+
32
+ def apply(rc_doc)
33
+ rc_kind = rc_doc['kind']
34
+ use_lenses = []
35
+
36
+ unless rc_kind =~ LENSES_FOR_ALL_BLACKLIST_PAT
37
+ use_lenses += LENSES_FOR_ALL
38
+ end
39
+
40
+ if lenses_for_doc_kind = LENSES_FOR_KIND[rc_kind]
41
+ use_lenses += lenses_for_doc_kind
42
+ end
43
+
44
+ use_lenses.inject(rc_doc) do |doc, lens|
45
+ lens.put_in(doc, @new_ns)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Kustomize
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kustomizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Levi Aul
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-01-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: accessory
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.9
27
+ description: A pure-ruby implementation of the Kubernetes 'kustomize' command.
28
+ email:
29
+ - levi@leviaul.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - ".rspec"
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - bin/console
42
+ - bin/setup
43
+ - kustomizer.gemspec
44
+ - lib/kustomize.rb
45
+ - lib/kustomize/emitter.rb
46
+ - lib/kustomize/emitter/directory_emitter.rb
47
+ - lib/kustomize/emitter/document_emitter.rb
48
+ - lib/kustomize/emitter/document_emitter/kustomization_document_emitter.rb
49
+ - lib/kustomize/emitter/file_emitter.rb
50
+ - lib/kustomize/emitter/plugin_emitter.rb
51
+ - lib/kustomize/json_6902_patch.rb
52
+ - lib/kustomize/json_6902_patch/add_op.rb
53
+ - lib/kustomize/json_6902_patch/gsub_op.rb
54
+ - lib/kustomize/json_6902_patch/remove_op.rb
55
+ - lib/kustomize/json_6902_patch/replace_op.rb
56
+ - lib/kustomize/pathname_refinements.rb
57
+ - lib/kustomize/plugin.rb
58
+ - lib/kustomize/plugin_manager.rb
59
+ - lib/kustomize/session.rb
60
+ - lib/kustomize/target_spec.rb
61
+ - lib/kustomize/transform.rb
62
+ - lib/kustomize/transform/image_transform.rb
63
+ - lib/kustomize/transform/json_6902_patch_transform.rb
64
+ - lib/kustomize/transform/name_digest_autosuffix_transform.rb
65
+ - lib/kustomize/transform/namespace_transform.rb
66
+ - lib/kustomize/version.rb
67
+ homepage: https://github.com/tsutsu/kustomize
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ homepage_uri: https://github.com/tsutsu/kustomize
72
+ source_code_uri: https://github.com/tsutsu/kustomize
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.7.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.2.3
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Pure-ruby impl of Kubernetes kustomize
92
+ test_files: []