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.
Files changed (157) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +1 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +9 -0
  5. data/AGENTS.md +18 -0
  6. data/CHANGELOG.md +642 -0
  7. data/Gemfile +2 -0
  8. data/README.md +86 -8
  9. data/Rakefile +20 -0
  10. data/bin/console +1 -0
  11. data/bin/matsuri +6 -0
  12. data/bin/setup +2 -0
  13. data/docs/acceptors.md +181 -0
  14. data/docs/boot.md +101 -0
  15. data/docs/design/acceptors.md +157 -0
  16. data/docs/design/boot.md +131 -0
  17. data/docs/design/ssa.md +142 -0
  18. data/docs/design/stacks.md +153 -0
  19. data/docs/diff.md +85 -0
  20. data/docs/known-issues.md +19 -0
  21. data/docs/local-dev.md +173 -0
  22. data/docs/presenters.md +317 -0
  23. data/docs/resources.md +121 -0
  24. data/docs/setup.md +97 -0
  25. data/docs/ssa.md +149 -0
  26. data/docs/stacks.md +146 -0
  27. data/flake.lock +61 -0
  28. data/flake.nix +35 -0
  29. data/lib/matsuri/acceptor.rb +33 -0
  30. data/lib/matsuri/acceptors/image_tag.rb +15 -0
  31. data/lib/matsuri/cmd.rb +46 -0
  32. data/lib/matsuri/cmds/apply.rb +78 -0
  33. data/lib/matsuri/cmds/cli.rb +147 -0
  34. data/lib/matsuri/cmds/create.rb +87 -0
  35. data/lib/matsuri/cmds/delete.rb +86 -0
  36. data/lib/matsuri/cmds/diff.rb +105 -0
  37. data/lib/matsuri/cmds/generate.rb +24 -0
  38. data/lib/matsuri/cmds/kubectl.rb +16 -0
  39. data/lib/matsuri/cmds/recreate.rb +65 -0
  40. data/lib/matsuri/cmds/scale.rb +37 -0
  41. data/lib/matsuri/cmds/show.rb +123 -0
  42. data/lib/matsuri/cmds/status.rb +67 -0
  43. data/lib/matsuri/concerns/awaiting.rb +24 -0
  44. data/lib/matsuri/concerns/diffable.rb +135 -0
  45. data/lib/matsuri/concerns/export_manifest.rb +56 -0
  46. data/lib/matsuri/concerns/import_manifest.rb +64 -0
  47. data/lib/matsuri/concerns/pod_template.rb +37 -0
  48. data/lib/matsuri/concerns/pods/spread_pods.rb +47 -0
  49. data/lib/matsuri/concerns/pods/spread_topology.rb +63 -0
  50. data/lib/matsuri/concerns/registry_helpers.rb +59 -0
  51. data/lib/matsuri/config.rb +101 -0
  52. data/lib/matsuri/core_ext.rb +107 -0
  53. data/lib/matsuri/dsl/cluster/aggregated_cluster_role.rb +75 -0
  54. data/lib/matsuri/dsl/cluster/binding.rb +118 -0
  55. data/lib/matsuri/dsl/cluster/cluster_role.rb +41 -0
  56. data/lib/matsuri/dsl/cluster/role.rb +49 -0
  57. data/lib/matsuri/dsl/cluster/scope.rb +245 -0
  58. data/lib/matsuri/dsl/cluster/service_account.rb +76 -0
  59. data/lib/matsuri/dsl/concerns/default_binding.rb +34 -0
  60. data/lib/matsuri/dsl/concerns/manifest_set.rb +21 -0
  61. data/lib/matsuri/dsl/concerns/metadata.rb +56 -0
  62. data/lib/matsuri/dsl/concerns/rbac_rules.rb +39 -0
  63. data/lib/matsuri/initializer.rb +41 -0
  64. data/lib/matsuri/kubernetes/base.rb +107 -0
  65. data/lib/matsuri/kubernetes/config_map.rb +100 -0
  66. data/lib/matsuri/kubernetes/cron_job.rb +115 -0
  67. data/lib/matsuri/kubernetes/daemon_set.rb +62 -0
  68. data/lib/matsuri/kubernetes/deployment.rb +65 -0
  69. data/lib/matsuri/kubernetes/endpoints.rb +57 -0
  70. data/lib/matsuri/kubernetes/horizontal_pod_autoscaler.rb +116 -0
  71. data/lib/matsuri/kubernetes/imported_manifest.rb +61 -0
  72. data/lib/matsuri/kubernetes/ingress.rb +66 -0
  73. data/lib/matsuri/kubernetes/persistent_volume.rb +56 -0
  74. data/lib/matsuri/kubernetes/persistent_volume_claim.rb +41 -0
  75. data/lib/matsuri/kubernetes/pod.rb +355 -0
  76. data/lib/matsuri/kubernetes/replica_set.rb +46 -0
  77. data/lib/matsuri/kubernetes/secret.rb +86 -0
  78. data/lib/matsuri/kubernetes/service.rb +49 -0
  79. data/lib/matsuri/kubernetes/stateful_set.rb +75 -0
  80. data/lib/matsuri/kubernetes/storage_class.rb +45 -0
  81. data/lib/matsuri/platform.rb +38 -0
  82. data/lib/matsuri/presenter.rb +47 -0
  83. data/lib/matsuri/presenters/helpers.rb +39 -0
  84. data/lib/matsuri/presenters/prune_empty.rb +59 -0
  85. data/lib/matsuri/presenters/seed_image.rb +28 -0
  86. data/lib/matsuri/presenters/seed_replicas.rb +25 -0
  87. data/lib/matsuri/presenters/seed_resources.rb +29 -0
  88. data/lib/matsuri/registry.rb +267 -0
  89. data/lib/matsuri/shell_out.rb +119 -0
  90. data/lib/matsuri/stack.rb +276 -0
  91. data/lib/matsuri/task.rb +20 -0
  92. data/lib/matsuri/tasks/cluster.rb +79 -0
  93. data/lib/matsuri/tasks/docker.rb +25 -0
  94. data/lib/matsuri/tasks/hash_diff.rb +61 -0
  95. data/lib/matsuri/tasks/kubectl.rb +21 -0
  96. data/lib/matsuri/tasks/kubectl_ops/apply.rb +24 -0
  97. data/lib/matsuri/tasks/kubectl_ops/create.rb +27 -0
  98. data/lib/matsuri/tasks/kubectl_ops/delete.rb +21 -0
  99. data/lib/matsuri/tasks/kubectl_ops/diff.rb +47 -0
  100. data/lib/matsuri/tasks/kubectl_ops/get.rb +36 -0
  101. data/lib/matsuri/tasks/kubectl_ops/recreate.rb +35 -0
  102. data/lib/matsuri/tasks/kubectl_ops/replace.rb +29 -0
  103. data/lib/matsuri/tasks/kubectl_ops/rollout_status.rb +22 -0
  104. data/lib/matsuri/tasks/kubectl_ops/scale.rb +22 -0
  105. data/lib/matsuri/tasks/kubectl_ops/server_side_apply.rb +32 -0
  106. data/lib/matsuri/tasks/kubectl_ops/status.rb +20 -0
  107. data/lib/matsuri/tasks/kubectl_ops.rb +218 -0
  108. data/lib/matsuri/tasks/pod.rb +56 -0
  109. data/lib/matsuri/tasks/stacks/seed.rb +93 -0
  110. data/lib/matsuri/tasks/stacks.rb +31 -0
  111. data/lib/matsuri/transform/algebra.rb +52 -0
  112. data/lib/matsuri/transform/callable.rb +54 -0
  113. data/lib/matsuri/transform/combinators.rb +61 -0
  114. data/lib/matsuri/transform/env.rb +43 -0
  115. data/lib/matsuri/transform/kubernetes.rb +89 -0
  116. data/lib/matsuri/transform.rb +20 -0
  117. data/lib/matsuri/version.rb +3 -1
  118. data/lib/matsuri.rb +158 -2
  119. data/matsuri.gemspec +20 -7
  120. data/tests/bin/minikube-start.sh +27 -0
  121. data/tests/bin/probe-save-config.sh +46 -0
  122. data/tests/bin/probe-ssa.sh +206 -0
  123. data/tests/example/.gitignore +1 -0
  124. data/tests/example/Gemfile +7 -0
  125. data/tests/example/README.md +91 -0
  126. data/tests/example/bin/matsuri +14 -0
  127. data/tests/example/config/initializer.rb +12 -0
  128. data/tests/example/config/matsuri.rb +9 -0
  129. data/tests/example/config/platform.rb +39 -0
  130. data/tests/example/lib/acme/nodes/dedicated.rb +26 -0
  131. data/tests/example/lib/acme/nodes/preemptible.rb +17 -0
  132. data/tests/example/lib/acme/nodes/required_cores.rb +26 -0
  133. data/tests/example/lib/acme/nodes/resource_class.rb +26 -0
  134. data/tests/example/lib/acme/pods/cacerts.rb +15 -0
  135. data/tests/example/lib/acme/pods/healthcheck.rb +36 -0
  136. data/tests/example/lib/acme.rb +26 -0
  137. data/tests/example/platform/config_maps/billing-settings.rb +8 -0
  138. data/tests/example/platform/config_maps/cacerts.rb +9 -0
  139. data/tests/example/platform/deployments/billing-web.rb +5 -0
  140. data/tests/example/platform/deployments/checkout-web.rb +6 -0
  141. data/tests/example/platform/namespaces/billing-dev.yaml +7 -0
  142. data/tests/example/platform/namespaces/checkout-dev.yaml +7 -0
  143. data/tests/example/platform/pods/billing.rb +31 -0
  144. data/tests/example/platform/pods/checkout.rb +27 -0
  145. data/tests/example/platform/services/billing-web.rb +6 -0
  146. data/tests/example/platform/services/checkout-web.rb +4 -0
  147. data/tests/example/platform/stacks/billing.rb +8 -0
  148. data/tests/example/platform/stacks/checkout.rb +19 -0
  149. data/tests/k8s/save-config/deployment-declared.yaml +19 -0
  150. data/tests/k8s/save-config/deployment-seeded.yaml +20 -0
  151. data/tests/k8s/save-config/namespace.yaml +4 -0
  152. data/tests/k8s/ssa/declared-bumped.yaml +22 -0
  153. data/tests/k8s/ssa/declared.yaml +22 -0
  154. data/tests/k8s/ssa/namespace.yaml +4 -0
  155. data/tests/k8s/ssa/seed-via-create.yaml +24 -0
  156. metadata +288 -20
  157. 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