matsuri 0.1.0 → 0.3.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.
- checksums.yaml +5 -5
- data/.envrc +1 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +9 -0
- data/AGENTS.md +18 -0
- data/CHANGELOG.md +642 -0
- data/Gemfile +2 -0
- data/README.md +86 -8
- data/Rakefile +20 -0
- data/bin/console +1 -0
- data/bin/matsuri +6 -0
- data/bin/setup +2 -0
- data/docs/acceptors.md +181 -0
- data/docs/boot.md +101 -0
- data/docs/design/acceptors.md +157 -0
- data/docs/design/boot.md +131 -0
- data/docs/design/ssa.md +142 -0
- data/docs/design/stacks.md +153 -0
- data/docs/diff.md +85 -0
- data/docs/known-issues.md +19 -0
- data/docs/local-dev.md +173 -0
- data/docs/presenters.md +317 -0
- data/docs/resources.md +121 -0
- data/docs/setup.md +97 -0
- data/docs/ssa.md +149 -0
- data/docs/stacks.md +146 -0
- data/flake.lock +61 -0
- data/flake.nix +35 -0
- data/lib/matsuri/acceptor.rb +33 -0
- data/lib/matsuri/acceptors/image_tag.rb +15 -0
- data/lib/matsuri/cmd.rb +46 -0
- data/lib/matsuri/cmds/apply.rb +78 -0
- data/lib/matsuri/cmds/cli.rb +147 -0
- data/lib/matsuri/cmds/create.rb +87 -0
- data/lib/matsuri/cmds/delete.rb +86 -0
- data/lib/matsuri/cmds/diff.rb +105 -0
- data/lib/matsuri/cmds/generate.rb +24 -0
- data/lib/matsuri/cmds/kubectl.rb +16 -0
- data/lib/matsuri/cmds/recreate.rb +65 -0
- data/lib/matsuri/cmds/scale.rb +37 -0
- data/lib/matsuri/cmds/show.rb +123 -0
- data/lib/matsuri/cmds/status.rb +67 -0
- data/lib/matsuri/concerns/awaiting.rb +24 -0
- data/lib/matsuri/concerns/diffable.rb +135 -0
- data/lib/matsuri/concerns/export_manifest.rb +56 -0
- data/lib/matsuri/concerns/import_manifest.rb +64 -0
- data/lib/matsuri/concerns/pod_template.rb +37 -0
- data/lib/matsuri/concerns/pods/spread_pods.rb +47 -0
- data/lib/matsuri/concerns/pods/spread_topology.rb +63 -0
- data/lib/matsuri/concerns/registry_helpers.rb +59 -0
- data/lib/matsuri/config.rb +101 -0
- data/lib/matsuri/core_ext.rb +107 -0
- data/lib/matsuri/dsl/cluster/aggregated_cluster_role.rb +75 -0
- data/lib/matsuri/dsl/cluster/binding.rb +118 -0
- data/lib/matsuri/dsl/cluster/cluster_role.rb +41 -0
- data/lib/matsuri/dsl/cluster/role.rb +49 -0
- data/lib/matsuri/dsl/cluster/scope.rb +245 -0
- data/lib/matsuri/dsl/cluster/service_account.rb +76 -0
- data/lib/matsuri/dsl/concerns/default_binding.rb +34 -0
- data/lib/matsuri/dsl/concerns/manifest_set.rb +21 -0
- data/lib/matsuri/dsl/concerns/metadata.rb +56 -0
- data/lib/matsuri/dsl/concerns/rbac_rules.rb +39 -0
- data/lib/matsuri/initializer.rb +41 -0
- data/lib/matsuri/kubernetes/base.rb +107 -0
- data/lib/matsuri/kubernetes/config_map.rb +100 -0
- data/lib/matsuri/kubernetes/cron_job.rb +115 -0
- data/lib/matsuri/kubernetes/daemon_set.rb +62 -0
- data/lib/matsuri/kubernetes/deployment.rb +65 -0
- data/lib/matsuri/kubernetes/endpoints.rb +57 -0
- data/lib/matsuri/kubernetes/horizontal_pod_autoscaler.rb +116 -0
- data/lib/matsuri/kubernetes/imported_manifest.rb +61 -0
- data/lib/matsuri/kubernetes/ingress.rb +66 -0
- data/lib/matsuri/kubernetes/persistent_volume.rb +56 -0
- data/lib/matsuri/kubernetes/persistent_volume_claim.rb +41 -0
- data/lib/matsuri/kubernetes/pod.rb +355 -0
- data/lib/matsuri/kubernetes/replica_set.rb +46 -0
- data/lib/matsuri/kubernetes/secret.rb +86 -0
- data/lib/matsuri/kubernetes/service.rb +49 -0
- data/lib/matsuri/kubernetes/stateful_set.rb +75 -0
- data/lib/matsuri/kubernetes/storage_class.rb +45 -0
- data/lib/matsuri/platform.rb +38 -0
- data/lib/matsuri/presenter.rb +47 -0
- data/lib/matsuri/presenters/helpers.rb +39 -0
- data/lib/matsuri/presenters/prune_empty.rb +59 -0
- data/lib/matsuri/presenters/seed_image.rb +28 -0
- data/lib/matsuri/presenters/seed_replicas.rb +25 -0
- data/lib/matsuri/presenters/seed_resources.rb +29 -0
- data/lib/matsuri/registry.rb +267 -0
- data/lib/matsuri/shell_out.rb +119 -0
- data/lib/matsuri/stack.rb +276 -0
- data/lib/matsuri/task.rb +20 -0
- data/lib/matsuri/tasks/cluster.rb +79 -0
- data/lib/matsuri/tasks/docker.rb +25 -0
- data/lib/matsuri/tasks/hash_diff.rb +61 -0
- data/lib/matsuri/tasks/kubectl.rb +21 -0
- data/lib/matsuri/tasks/kubectl_ops/apply.rb +24 -0
- data/lib/matsuri/tasks/kubectl_ops/create.rb +27 -0
- data/lib/matsuri/tasks/kubectl_ops/delete.rb +21 -0
- data/lib/matsuri/tasks/kubectl_ops/diff.rb +47 -0
- data/lib/matsuri/tasks/kubectl_ops/get.rb +36 -0
- data/lib/matsuri/tasks/kubectl_ops/recreate.rb +35 -0
- data/lib/matsuri/tasks/kubectl_ops/replace.rb +29 -0
- data/lib/matsuri/tasks/kubectl_ops/rollout_status.rb +22 -0
- data/lib/matsuri/tasks/kubectl_ops/scale.rb +22 -0
- data/lib/matsuri/tasks/kubectl_ops/server_side_apply.rb +32 -0
- data/lib/matsuri/tasks/kubectl_ops/status.rb +20 -0
- data/lib/matsuri/tasks/kubectl_ops.rb +218 -0
- data/lib/matsuri/tasks/pod.rb +56 -0
- data/lib/matsuri/tasks/stacks/seed.rb +93 -0
- data/lib/matsuri/tasks/stacks.rb +31 -0
- data/lib/matsuri/transform/algebra.rb +52 -0
- data/lib/matsuri/transform/callable.rb +54 -0
- data/lib/matsuri/transform/combinators.rb +61 -0
- data/lib/matsuri/transform/env.rb +43 -0
- data/lib/matsuri/transform/kubernetes.rb +89 -0
- data/lib/matsuri/transform.rb +20 -0
- data/lib/matsuri/version.rb +3 -1
- data/lib/matsuri.rb +158 -2
- data/matsuri.gemspec +20 -7
- data/tests/bin/minikube-start.sh +27 -0
- data/tests/bin/probe-save-config.sh +46 -0
- data/tests/bin/probe-ssa.sh +206 -0
- data/tests/example/.gitignore +1 -0
- data/tests/example/Gemfile +7 -0
- data/tests/example/README.md +91 -0
- data/tests/example/bin/matsuri +14 -0
- data/tests/example/config/initializer.rb +12 -0
- data/tests/example/config/matsuri.rb +9 -0
- data/tests/example/config/platform.rb +39 -0
- data/tests/example/lib/acme/nodes/dedicated.rb +26 -0
- data/tests/example/lib/acme/nodes/preemptible.rb +17 -0
- data/tests/example/lib/acme/nodes/required_cores.rb +26 -0
- data/tests/example/lib/acme/nodes/resource_class.rb +26 -0
- data/tests/example/lib/acme/pods/cacerts.rb +15 -0
- data/tests/example/lib/acme/pods/healthcheck.rb +36 -0
- data/tests/example/lib/acme.rb +26 -0
- data/tests/example/platform/config_maps/billing-settings.rb +8 -0
- data/tests/example/platform/config_maps/cacerts.rb +9 -0
- data/tests/example/platform/deployments/billing-web.rb +5 -0
- data/tests/example/platform/deployments/checkout-web.rb +6 -0
- data/tests/example/platform/namespaces/billing-dev.yaml +7 -0
- data/tests/example/platform/namespaces/checkout-dev.yaml +7 -0
- data/tests/example/platform/pods/billing.rb +31 -0
- data/tests/example/platform/pods/checkout.rb +27 -0
- data/tests/example/platform/services/billing-web.rb +6 -0
- data/tests/example/platform/services/checkout-web.rb +4 -0
- data/tests/example/platform/stacks/billing.rb +8 -0
- data/tests/example/platform/stacks/checkout.rb +19 -0
- data/tests/k8s/save-config/deployment-declared.yaml +19 -0
- data/tests/k8s/save-config/deployment-seeded.yaml +20 -0
- data/tests/k8s/save-config/namespace.yaml +4 -0
- data/tests/k8s/ssa/declared-bumped.yaml +22 -0
- data/tests/k8s/ssa/declared.yaml +22 -0
- data/tests/k8s/ssa/namespace.yaml +4 -0
- data/tests/k8s/ssa/seed-via-create.yaml +24 -0
- metadata +288 -20
- data/.travis.yml +0 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rlet'
|
|
4
|
+
|
|
5
|
+
module Matsuri
|
|
6
|
+
# A presenter is one manifest-shaped step in a delivery pipeline:
|
|
7
|
+
# Manifest -> Manifest, zero-config and context-free. Construction takes
|
|
8
|
+
# nothing; parameterization happens before #call ever runs — definition
|
|
9
|
+
# lets filling a concern's declared holes, or declaration-time values
|
|
10
|
+
# closed over in a leaf Callable.
|
|
11
|
+
#
|
|
12
|
+
# #call is framework-owned: it makes the one boundary copy and delegates
|
|
13
|
+
# to #transform, which authors implement against a carrier that is already
|
|
14
|
+
# theirs to mutate. The base #transform is the identity, so the bare
|
|
15
|
+
# Presenter is the identity element. A fresh instance serves each call, so
|
|
16
|
+
# let memoization scopes to a single manifest pass. The Kubernetes dialect
|
|
17
|
+
# (Transform::Kubernetes, carrying Algebra + Env) is bare vocabulary
|
|
18
|
+
# inside #transform.
|
|
19
|
+
class Presenter
|
|
20
|
+
include Let
|
|
21
|
+
include Matsuri::Transform::Kubernetes
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def call(manifest)
|
|
25
|
+
new.call(manifest)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def load_path
|
|
29
|
+
Matsuri::Config.presenters_path
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def definition_module_name
|
|
33
|
+
'Presenters'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Marshal is the copy because the carrier is data — Map.new alone shares
|
|
38
|
+
# nested structure with the input.
|
|
39
|
+
def call(manifest)
|
|
40
|
+
transform(Map.new(Marshal.load(Marshal.dump(manifest))))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def transform(manifest)
|
|
44
|
+
manifest
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Presenters
|
|
5
|
+
# Declaration-surface constructors: each closes a seed concern's hole
|
|
6
|
+
# over its argument as an anonymous Presenter subclass — the named
|
|
7
|
+
# definition form minus the registry entry, with the boundary copy
|
|
8
|
+
# riding along.
|
|
9
|
+
module Helpers
|
|
10
|
+
def seed_replicas(count)
|
|
11
|
+
Class.new(Matsuri::Presenter) do
|
|
12
|
+
include Matsuri::Presenters::SeedReplicas
|
|
13
|
+
let(:replicas) { count }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def seed_image(image_value)
|
|
18
|
+
Class.new(Matsuri::Presenter) do
|
|
19
|
+
include Matsuri::Presenters::SeedImage
|
|
20
|
+
let(:image) { image_value }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def seed_resources(resources_spec)
|
|
25
|
+
Class.new(Matsuri::Presenter) do
|
|
26
|
+
include Matsuri::Presenters::SeedResources
|
|
27
|
+
let(:resources) { resources_spec }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def prune_empty(except: [])
|
|
32
|
+
Class.new(Matsuri::Presenter) do
|
|
33
|
+
include Matsuri::Presenters::PruneEmpty
|
|
34
|
+
let(:exceptions) { except }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Presenters
|
|
5
|
+
# Prunes empty values — nil, {}, [] — from the payload, bottom-up,
|
|
6
|
+
# so emptiness never rides a declaration. Under server-side apply an
|
|
7
|
+
# empty collection is an ownership claim over a field the definition
|
|
8
|
+
# does not manage, and inside an atomic struct (.spec.selector) it
|
|
9
|
+
# diverges from the server's persisted normal form, turning the
|
|
10
|
+
# seeding protocol's equal-values co-ownership into a conflict. A
|
|
11
|
+
# pruned payload declares in persistence normal form: ceding a field
|
|
12
|
+
# and saying nothing are the same act.
|
|
13
|
+
#
|
|
14
|
+
# The merge is not uniformly monoidal — for some fields an empty
|
|
15
|
+
# value is a genuine assertion (an atomic field held empty against
|
|
16
|
+
# other writers). let(:exceptions) names those paths, each kept even
|
|
17
|
+
# when empty. It ships empty; fill the let in a named definition or
|
|
18
|
+
# subclass. Paths address hash keys only — they do not descend into
|
|
19
|
+
# arrays.
|
|
20
|
+
#
|
|
21
|
+
# Empty strings are values, not emptiness ("" is meaningful to K8s —
|
|
22
|
+
# storageClassName among others), and array elements are never
|
|
23
|
+
# dropped — removing elements changes list semantics; only their
|
|
24
|
+
# interiors prune.
|
|
25
|
+
module PruneEmpty
|
|
26
|
+
extend Concern
|
|
27
|
+
|
|
28
|
+
included do
|
|
29
|
+
let(:exceptions) { [] }
|
|
30
|
+
let(:exception_paths) { exceptions.map { |e| Array(e).map(&:to_s) } }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def transform(manifest)
|
|
34
|
+
prune(manifest, [])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def prune(value, path)
|
|
40
|
+
case value
|
|
41
|
+
when Hash
|
|
42
|
+
value.each_with_object(value.class.new) do |(k, v), acc|
|
|
43
|
+
child = prune(v, path + [k.to_s])
|
|
44
|
+
acc[k] = child unless prunable?(child, path + [k.to_s])
|
|
45
|
+
end
|
|
46
|
+
when Array
|
|
47
|
+
value.map { |v| prune(v, path) }
|
|
48
|
+
else
|
|
49
|
+
value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def prunable?(value, path)
|
|
54
|
+
return false if exception_paths.include?(path)
|
|
55
|
+
value.nil? || (!value.is_a?(String) && value.respond_to?(:empty?) && value.empty?)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Presenters
|
|
5
|
+
# Completes a create payload with the boot image on every container
|
|
6
|
+
# that does not carry one. Additive only: a container that declares
|
|
7
|
+
# its image has opted out structurally, and the seed yields. The
|
|
8
|
+
# including definition owns what the value is: let(:image).
|
|
9
|
+
#
|
|
10
|
+
# One seed concern per presenter; compose seeds in the pipeline, not
|
|
11
|
+
# by stacking includes.
|
|
12
|
+
module SeedImage
|
|
13
|
+
extend Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
let(:image) { fail Matsuri::UndefinedLet, 'Must define let(:image)' }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def transform(manifest)
|
|
20
|
+
containers = manifest.get(*pod_spec_path(manifest), :containers) || []
|
|
21
|
+
containers.each do |container|
|
|
22
|
+
container.set(:image, image) if container[:image].nil?
|
|
23
|
+
end
|
|
24
|
+
manifest
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Presenters
|
|
5
|
+
# Completes a create payload with the boot replica count. Additive
|
|
6
|
+
# only: a manifest that declares spec.replicas has opted out
|
|
7
|
+
# structurally, and the seed yields. The including definition owns
|
|
8
|
+
# what the value is: let(:replicas).
|
|
9
|
+
#
|
|
10
|
+
# One seed concern per presenter; compose seeds in the pipeline, not
|
|
11
|
+
# by stacking includes.
|
|
12
|
+
module SeedReplicas
|
|
13
|
+
extend Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
let(:replicas) { fail Matsuri::UndefinedLet, 'Must define let(:replicas)' }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def transform(manifest)
|
|
20
|
+
manifest.set([:spec, :replicas], replicas) if manifest.get(:spec, :replicas).nil?
|
|
21
|
+
manifest
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Presenters
|
|
5
|
+
# Completes a create payload with boot resource settings on every
|
|
6
|
+
# container that does not carry its own. Additive only: a container
|
|
7
|
+
# that declares resources has opted out structurally, and the seed
|
|
8
|
+
# yields. The including definition owns what the value is:
|
|
9
|
+
# let(:resources).
|
|
10
|
+
#
|
|
11
|
+
# One seed concern per presenter; compose seeds in the pipeline, not
|
|
12
|
+
# by stacking includes.
|
|
13
|
+
module SeedResources
|
|
14
|
+
extend Concern
|
|
15
|
+
|
|
16
|
+
included do
|
|
17
|
+
let(:resources) { fail Matsuri::UndefinedLet, 'Must define let(:resources)' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def transform(manifest)
|
|
21
|
+
containers = manifest.get(*pod_spec_path(manifest), :containers) || []
|
|
22
|
+
containers.each do |container|
|
|
23
|
+
container.set(:resources, resources) if container[:resources].nil?
|
|
24
|
+
end
|
|
25
|
+
manifest
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require 'map'
|
|
5
|
+
require 'matsuri/core_ext'
|
|
6
|
+
|
|
7
|
+
module Matsuri
|
|
8
|
+
class Registry
|
|
9
|
+
include Singleton
|
|
10
|
+
|
|
11
|
+
def data
|
|
12
|
+
@data ||= Map.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def k8s_class_registry
|
|
16
|
+
@k8s_class_registry ||= Map.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def k8s_class_aliases
|
|
20
|
+
@k8s_class_aliases ||= Map.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# Registers a K8S class, which can then be used for definitions
|
|
25
|
+
def register_class(name, opt = {})
|
|
26
|
+
fail ArgumentError, 'need to pass class: parameter' unless opt[:class]
|
|
27
|
+
instance.k8s_class_registry[name] = opt[:class]
|
|
28
|
+
Array(opt[:aliases]).each do |class_alias|
|
|
29
|
+
instance.k8s_class_aliases[class_alias] = name
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Helper to load class definitions
|
|
34
|
+
def load_definition(type, name)
|
|
35
|
+
def_path = definition_path(type, name)
|
|
36
|
+
|
|
37
|
+
# [Hosh] Somehow, TOPLEVEL_BINDING got infected with version and str
|
|
38
|
+
# local variables. When thinking about it, we probably want to require
|
|
39
|
+
# instead of evaluating this with the TOPLEVEL_BINDING anyways.
|
|
40
|
+
|
|
41
|
+
#eval(File.read(def_path), TOPLEVEL_BINDING, def_path)
|
|
42
|
+
require def_path
|
|
43
|
+
|
|
44
|
+
_klass = instance.data.get(type, name)
|
|
45
|
+
return _klass if _klass
|
|
46
|
+
|
|
47
|
+
Matsuri.log :fatal, "Unable to find #{type}/#{name} defined in #{def_path}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def definition_path(type, name)
|
|
51
|
+
load_path = load_path_for(type)
|
|
52
|
+
def_file = "#{name}.rb"
|
|
53
|
+
env_def_path = File.join(load_path, Matsuri.environment, def_file)
|
|
54
|
+
return env_def_path if File.file?(env_def_path)
|
|
55
|
+
Matsuri.log :debug, "Unable to find #{env_def_path} ... trying global definition"
|
|
56
|
+
|
|
57
|
+
def_path = File.join(load_path, def_file)
|
|
58
|
+
return def_path if File.file?(def_path)
|
|
59
|
+
Matsuri.log :fatal, "Unable to find #{env_def_path} or #{def_path}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Helper to generate Kubernetes artifact definitions
|
|
63
|
+
def define(type, name, inherits: nil, &blk)
|
|
64
|
+
type = normalize_and_validate_type(type)
|
|
65
|
+
parent_klass = parent_class(type, inherits)
|
|
66
|
+
klass = Class.new(parent_klass)
|
|
67
|
+
module_for(type).const_set(class_name_for(name), klass)
|
|
68
|
+
klass.class_eval do
|
|
69
|
+
let(:name) { name }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
klass.class_eval(&blk) if blk
|
|
73
|
+
|
|
74
|
+
# TODO: consider whether to use the class or to go ahead
|
|
75
|
+
# and instantiate it
|
|
76
|
+
instance.data.set(type, name, klass)
|
|
77
|
+
return klass
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def register(type, name, obj)
|
|
81
|
+
_type = normalize_and_validate_type(type)
|
|
82
|
+
instance.data.set(_type, name, obj)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def fetch_or_load(type, name)
|
|
86
|
+
_type = normalize_and_validate_type(type)
|
|
87
|
+
|
|
88
|
+
if name == :not_specified
|
|
89
|
+
print_options_for_type(_type)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
resource = instance.data.get(_type, name)
|
|
93
|
+
return resource if resource
|
|
94
|
+
|
|
95
|
+
load_definition(_type, name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def print_options_for_type(type)
|
|
99
|
+
options = options_for_type(type)
|
|
100
|
+
if options.empty?
|
|
101
|
+
global_load_path = load_path_for(type)
|
|
102
|
+
env_def_path = File.join(global_load_path, Matsuri.environment)
|
|
103
|
+
Matsuri.log :fatal, "Unable to find any definition files in #{env_def_path} or #{global_load_path}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Matsuri.log :error, "Which #{type}?\n\n"
|
|
107
|
+
|
|
108
|
+
options.each do |def_name|
|
|
109
|
+
Matsuri.log :error, " #{def_name}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
Matsuri.log :fatal, "\nPlease specify a #{type} from the above list."
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def options_for_type(type)
|
|
116
|
+
global_load_path = load_path_for(type)
|
|
117
|
+
env_def_path = File.join(global_load_path, Matsuri.environment)
|
|
118
|
+
(Dir.glob(File.join(env_def_path, "*.rb")) + Dir.glob(File.join(global_load_path, "*.rb")))
|
|
119
|
+
.map {|file| File.basename(file, File.extname(file))}
|
|
120
|
+
.uniq
|
|
121
|
+
.sort
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def imported_manifest(name)
|
|
125
|
+
fetch_or_load :imported_manifest, name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def pod(name)
|
|
129
|
+
fetch_or_load :pod, name
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def service(name)
|
|
133
|
+
fetch_or_load :service, name
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def ingress(name)
|
|
137
|
+
fetch_or_load :ingress, name
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def replica_set(name)
|
|
141
|
+
fetch_or_load :replica_set, name
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def stateful_set(name)
|
|
145
|
+
fetch_or_load :stateful_set, name
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def daemon_set(name)
|
|
149
|
+
fetch_or_load :daemon_set, name
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def deployment(name)
|
|
153
|
+
fetch_or_load :deployment, name
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def cron_job(name)
|
|
157
|
+
fetch_or_load :cron_job, name
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def persistent_volume(name)
|
|
161
|
+
fetch_or_load :persistent_volume, name
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def pv(name)
|
|
165
|
+
persistent_volume(name)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def persistent_volume_claim(name)
|
|
169
|
+
fetch_or_load :persistent_volume_claim, name
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def pvc(name)
|
|
173
|
+
persistent_volume_claim(name)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def storage_class(name)
|
|
177
|
+
fetch_or_load :storage_class, name
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def endpoints(name)
|
|
181
|
+
fetch_or_load :endpoints, name
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def secret(name)
|
|
185
|
+
fetch_or_load :secret, name
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def config_map(name)
|
|
189
|
+
fetch_or_load :config_map, name
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def horizontal_pod_autoscaler(name)
|
|
193
|
+
fetch_or_load :horizontal_pod_autoscaler, name
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def hpa(name)
|
|
197
|
+
horizontal_pod_autoscaler(name)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def stack(name)
|
|
201
|
+
fetch_or_load :stack, name
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def acceptor(name)
|
|
205
|
+
fetch_or_load :acceptor, name
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def presenter(name)
|
|
209
|
+
fetch_or_load :presenter, name
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
def parent_class(type, parent_name = nil)
|
|
215
|
+
return fetch_or_load(type, parent_name) if parent_name
|
|
216
|
+
instance.k8s_class_registry[normalize_and_validate_type(type)]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def load_path_for(type)
|
|
220
|
+
parent_class(type).load_path
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def class_name_for(name)
|
|
224
|
+
name.to_s.gsub(/-/, '_').camelize
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def module_for(type)
|
|
228
|
+
maybe_define_module(parent_class(type).definition_module_name)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def maybe_define_module(mod)
|
|
232
|
+
return Object.const_get(mod) if Object.const_defined?(mod)
|
|
233
|
+
Object.const_set(mod, Module.new)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def normalize_and_validate_type(type)
|
|
237
|
+
_type = type.to_s.freeze
|
|
238
|
+
_type = instance.k8s_class_aliases[_type] if instance.k8s_class_aliases.key?(_type)
|
|
239
|
+
return _type if instance.k8s_class_registry.key?(_type)
|
|
240
|
+
|
|
241
|
+
fail "Registery type #{type.inspect} invalid. Use one of #{instance.k8s_class_registry.keys}"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Register the definition classes
|
|
248
|
+
# TODO: Consider making this dynamically and lazy loaded
|
|
249
|
+
Matsuri::Registry.register_class 'stack', class: Matsuri::Stack
|
|
250
|
+
Matsuri::Registry.register_class 'acceptor', class: Matsuri::Acceptor
|
|
251
|
+
Matsuri::Registry.register_class 'presenter', class: Matsuri::Presenter
|
|
252
|
+
Matsuri::Registry.register_class 'imported_manifest', class: Matsuri::Kubernetes::ImportedManifest
|
|
253
|
+
Matsuri::Registry.register_class 'pod', class: Matsuri::Kubernetes::Pod
|
|
254
|
+
Matsuri::Registry.register_class 'service', class: Matsuri::Kubernetes::Service
|
|
255
|
+
Matsuri::Registry.register_class 'ingress', class: Matsuri::Kubernetes::Ingress
|
|
256
|
+
Matsuri::Registry.register_class 'endpoints', class: Matsuri::Kubernetes::Endpoints
|
|
257
|
+
Matsuri::Registry.register_class 'secret', class: Matsuri::Kubernetes::Secret
|
|
258
|
+
Matsuri::Registry.register_class 'config_map', class: Matsuri::Kubernetes::ConfigMap
|
|
259
|
+
Matsuri::Registry.register_class 'replica_set', class: Matsuri::Kubernetes::ReplicaSet, aliases: %w[rs]
|
|
260
|
+
Matsuri::Registry.register_class 'stateful_set', class: Matsuri::Kubernetes::StatefulSet, aliases: %w[sts]
|
|
261
|
+
Matsuri::Registry.register_class 'daemon_set', class: Matsuri::Kubernetes::DaemonSet, aliases: %w[ds]
|
|
262
|
+
Matsuri::Registry.register_class 'deployment', class: Matsuri::Kubernetes::Deployment
|
|
263
|
+
Matsuri::Registry.register_class 'cron_job', class: Matsuri::Kubernetes::CronJob
|
|
264
|
+
Matsuri::Registry.register_class 'persistent_volume', class: Matsuri::Kubernetes::PersistentVolume, aliases: %w[pv]
|
|
265
|
+
Matsuri::Registry.register_class 'persistent_volume_claim', class: Matsuri::Kubernetes::PersistentVolumeClaim, aliases: %w[pvc]
|
|
266
|
+
Matsuri::Registry.register_class 'storage_class', class: Matsuri::Kubernetes::StorageClass
|
|
267
|
+
Matsuri::Registry.register_class 'horizontal_pod_autoscaler', class: Matsuri::Kubernetes::HorizontalPodAutoscaler, aliases: %w[hpa]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mixlib/shellout'
|
|
4
|
+
require 'rainbow/ext/string'
|
|
5
|
+
|
|
6
|
+
module Matsuri
|
|
7
|
+
module ShellOut
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Override
|
|
11
|
+
def verbose
|
|
12
|
+
Matsuri::Config.verbose
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def debug
|
|
16
|
+
Matsuri::Config.debug
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def shellout_defaults
|
|
20
|
+
Matsuri::Config.shellout_defaults
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Overridable
|
|
24
|
+
# By default, the environment name is used. However, if you are using
|
|
25
|
+
# something like GCP, it is easier to set this in config/platform.rb
|
|
26
|
+
# and pull it from there. To override this, use let() or just define
|
|
27
|
+
# a method. For example, put this in a Staging mixin:
|
|
28
|
+
# let(:kube_context) { platform.staging.kube_context }
|
|
29
|
+
def kube_context
|
|
30
|
+
if Matsuri::Config.map_env_to_kube_context
|
|
31
|
+
Matsuri::Platform.send(Matsuri::Config.environment).kube_context
|
|
32
|
+
else
|
|
33
|
+
Matsuri::Config.environment
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Here for class method to get default namespace
|
|
38
|
+
# This is overriden by Matsuri::Kubernetes::Base when used in K8S resources
|
|
39
|
+
def namespace
|
|
40
|
+
Matsuri::Platform.send(Matsuri::Config.environment).namespace || 'default'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def shell_out(_cmd, options = {})
|
|
44
|
+
echo_level = options.delete(:echo_level) || :info
|
|
45
|
+
no_stdout = options.delete(:no_stdout)
|
|
46
|
+
|
|
47
|
+
Matsuri.log echo_level, "$ #{_cmd}".color(:green)
|
|
48
|
+
options = shellout_defaults.merge(timeout: 3600).merge(options)
|
|
49
|
+
cmd = Mixlib::ShellOut.new(_cmd, options)
|
|
50
|
+
cmd.live_stream = STDOUT unless no_stdout && !debug
|
|
51
|
+
cmd.run_command
|
|
52
|
+
cmd
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def shell_out!(_cmd, options = {})
|
|
56
|
+
cmd = shell_out(_cmd, options)
|
|
57
|
+
return cmd if cmd.status.success?
|
|
58
|
+
$stderr.print "ERROR: #{cmd.exitstatus}\nSTDOUT:\n#{cmd.stdout}\n\nSTDERR:\n#{cmd.stderr}\n".color(:red).bright
|
|
59
|
+
exit 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Log and exec a command. Useful for things such as
|
|
63
|
+
# attaching to a remote shell or console on the K8S cluster
|
|
64
|
+
def log_and_exec(_cmd, options = {})
|
|
65
|
+
echo_level = options.delete(:echo_level) || :info
|
|
66
|
+
Matsuri.log echo_level, "$ #{_cmd}".color(:green)
|
|
67
|
+
exec _cmd
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# This is so that it is easier to write stack commands
|
|
71
|
+
def kubectl_cmd(_cmd, options = {})
|
|
72
|
+
kube_options = { context: kube_context, namespace: namespace }.merge(options).compact
|
|
73
|
+
kube_cli_options = kube_options.map { |k,v| "--#{k}=#{v}" }.join(' ')
|
|
74
|
+
"kubectl #{kube_cli_options} #{_cmd}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def kubectl(_cmd, options = {})
|
|
78
|
+
kube_options = options.delete(:kube_options) || {}
|
|
79
|
+
shell_out(kubectl_cmd(_cmd, kube_options), options)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def kubectl!(_cmd, options = {})
|
|
83
|
+
kube_options = options.delete(:kube_options) || {}
|
|
84
|
+
shell_out!(kubectl_cmd(_cmd, kube_options), options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def docker(_cmd, options = {})
|
|
88
|
+
shell_out("docker #{_cmd}", options)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def docker!(_cmd, options = {})
|
|
92
|
+
shell_out!("docker #{_cmd}", options)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def docker_run(_cmd, options = {})
|
|
96
|
+
docker("run #{_cmd}", options)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def docker_run!(_cmd, options = {})
|
|
100
|
+
docker!("run #{_cmd}", options)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def bundle_exec(cmd, options = {})
|
|
104
|
+
shell_out "bundle exec #{cmd}", options
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def bundle_exec!(cmd, options = {})
|
|
108
|
+
shell_out! "bundle exec #{cmd}", options
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sudo(cmd, options = {})
|
|
112
|
+
shell_out "sudo #{cmd}", options
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def sudo!(cmd, options = {})
|
|
116
|
+
shell_out! "sudo #{cmd}", options
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|