kustomizer 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,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: []