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,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Cmds
5
+ class Status < Thor
6
+ include Matsuri::Cmd
7
+
8
+ def self.status_cmd_for(resource_name)
9
+ define_method(resource_name) do |name = :not_specified|
10
+ status_resource { Matsuri::Registry.fetch_or_load(resource_name, name).new }
11
+ end
12
+ end
13
+
14
+ desc 'stack STACK_NAME', 'stack status'
15
+ def stack(name = :not_specified)
16
+ with_config do |opt|
17
+ Matsuri::Registry.fetch_or_load(:stack, name).new.status!
18
+ end
19
+ end
20
+
21
+ desc 'pod POD_NAME', 'pod status'
22
+ status_cmd_for :pod
23
+
24
+ desc 'pods', 'kubectl top pods'
25
+ def pods
26
+ with_config do |_|
27
+ Matsuri::ShellOut.kubectl 'top pods'
28
+ end
29
+ end
30
+
31
+ desc 'nodes', 'kubectl top nodes'
32
+ def nodes
33
+ with_config do |_|
34
+ Matsuri::ShellOut.kubectl 'top nodes'
35
+ end
36
+ end
37
+
38
+ desc 'service SERVICE_NAME', 'service status'
39
+ status_cmd_for :service
40
+
41
+ desc 'endpoints ENDPOINTS_NAME', 'endpoint set status'
42
+ status_cmd_for :endpoints
43
+
44
+ desc 'secret SECRET_NAME', 'secret status'
45
+ status_cmd_for :secret
46
+
47
+ desc 'pv PV_NAME', 'persistent volume status'
48
+ status_cmd_for :pv
49
+
50
+ desc 'pvc PVC_NAME', 'persistent volume claim status'
51
+ status_cmd_for :pvc
52
+
53
+ desc 'storage_class STORAGE_CLASS_NAME', 'storage class status'
54
+ status_cmd_for :storage_class
55
+
56
+ private
57
+
58
+ def status_resource
59
+ with_config do |opt|
60
+ resource = yield
61
+ Matsuri::Tasks::KubectlOps.status(resource)
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Concerns
5
+ module Awaiting
6
+ def awaiting!(msg, timeout: 500, interval: 10)
7
+ Matsuri.log :info, "Waiting for #{msg}: "
8
+ response = nil
9
+ # Ignore timeout for now
10
+ # Timeout::timeout(timeout) do
11
+ loop do
12
+ response = yield
13
+ break if response
14
+ sleep interval
15
+ print '.'
16
+ end
17
+ #end
18
+
19
+ Matsuri.log :info, ' Done.'
20
+ return response
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hashdiff'
4
+ require 'unit'
5
+
6
+ module Matsuri
7
+ module Concerns
8
+ # Client-side diff: compares the declared manifest against a current
9
+ # manifest handed in by the caller, with comparison noise filtered out.
10
+ # Pure computation — fetching the current manifest, presenting the
11
+ # declaration, and printing deltas belong to Tasks::HashDiff. The
12
+ # filter helpers are the per-resource override surface: a definition
13
+ # that knows about additional noise paths overrides them here.
14
+ #
15
+ # This diff answers "what drifted relative to my declaration". The
16
+ # server-side diff (kubectl diff) answers "what will the cluster do
17
+ # with this, admission injections included". Both are kept.
18
+ module Diffable
19
+ # current is a plain string-keyed hash, as parsed from kubectl
20
+ # output. desired overrides the comparison basis — Tasks::HashDiff
21
+ # hands in the presented declaration so drift reads against what
22
+ # apply would send; default is the raw declaration.
23
+ def diff(current, desired: nil)
24
+ desired ||= JSON.parse(to_json)
25
+
26
+ Hashdiff.diff(current, desired) do |path, current_value, desired_value|
27
+ filter_comparison_values(path, current_value, desired_value)
28
+ end
29
+ end
30
+
31
+ # Focused variant: compares only the primary container, projected out
32
+ # of both manifests by primary_container_path. Unfiltered — the noise
33
+ # filters address whole-manifest paths, which do not occur inside a
34
+ # container.
35
+ def primary_container_diff(current, desired: nil)
36
+ unless primary_container_path
37
+ Matsuri.log :fatal, "#{resource_type}/#{name} does not declare let(:primary_container_path)"
38
+ end
39
+
40
+ desired ||= JSON.parse(to_json)
41
+
42
+ Hashdiff.diff current.dig(*primary_container_path), desired.dig(*primary_container_path)
43
+ end
44
+
45
+ def filter_comparison_values(path, current_value, desired_value)
46
+ if empty_assignment_to_empty?(current_value, desired_value) or
47
+ readonly_path?(path) or
48
+ always_changed_path?(path) or
49
+ default_value_assigned_nothing?(path, current_value, desired_value) or
50
+ unit_comparison_is_equal?(current_value, desired_value)
51
+ # (
52
+ # path.start_with?("metadata.") and
53
+ # path !~ /^metadata\.\b(name|labels|namespace)\b/
54
+ # ) or
55
+ true
56
+ else
57
+ :perform_normal_comparison end
58
+ end
59
+
60
+ def empty_assignment_to_empty?(current_value, desired_value)
61
+ empty_value?(current_value) and empty_value?(desired_value)
62
+ end
63
+
64
+ def readonly_path?(path)
65
+ [
66
+ 'status',
67
+ ].member?(path)
68
+ end
69
+
70
+ def always_changed_path?(path)
71
+ [
72
+ 'metadata.annotations.kubectl.kubernetes.io/last-applied-configuration',
73
+ 'metadata.annotations.kubernetes.io/change-cause',
74
+ ].member?(path)
75
+ end
76
+
77
+ def default_value_assigned_nothing?(path, current_value, desired_value)
78
+ # standard case of empty value desired but current value is
79
+ # atomic/predefined
80
+ (
81
+ empty_value?(desired_value) and
82
+ not (current_value.is_a?(Hash) or current_value.is_a?(Array))
83
+ ) or
84
+
85
+ # volumeMounts are not readOnly by default, making a setting of false
86
+ # redundant
87
+ (
88
+ path =~ /volumeMount.*readOnly/ and
89
+ current_value.nil? and
90
+ desired_value == false
91
+ ) or
92
+
93
+ # minReadySeconds is 0 by default but not presented as such
94
+ (
95
+ path == 'spec.minReadySeconds' and
96
+ current_value.nil? and
97
+ desired_value == 0
98
+ )
99
+ end
100
+
101
+ def unit_comparison_is_equal?(current_value, desired_value)
102
+ # Unit cannot handle nil values, so if one one of the values
103
+ # are empty, then return false (beacuse it is not comparable)
104
+ return false if empty_value?(current_value) || empty_value?(desired_value)
105
+ # Only strings carry quantities; Object#=~ is gone as of Ruby 3.2,
106
+ # so anything else must bail before the match
107
+ return false unless current_value.is_a?(String) and desired_value.is_a?(String)
108
+
109
+ if current_value =~ /^\d+\s*[A-Za-z]+$/ or desired_value =~ /^\d+\s*[A-Za-z]+$/
110
+ (
111
+ includes_unit?(current_value) and
112
+ includes_unit?(desired_value) and
113
+ Unit(current_value) == Unit(desired_value)
114
+ ) or
115
+ (
116
+ Unit(current_value + 'B') == Unit(desired_value + 'B')
117
+ )
118
+ else
119
+ false
120
+ end
121
+ end
122
+
123
+ def includes_unit?(value)
124
+ Unit(value)
125
+ true
126
+ rescue TypeError
127
+ false
128
+ end
129
+
130
+ def empty_value?(value)
131
+ value.nil? or value == {} or value == [] or value == ""
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module Concerns
7
+ module ExportManifest
8
+ extend Concern
9
+
10
+ included do
11
+
12
+ let(:core_manifest) do
13
+ {
14
+ apiVersion: api_version,
15
+ kind: kind,
16
+ metadata: metadata
17
+ }
18
+ end
19
+
20
+ let(:manifest) { fail Matsuri::UndefinedLet, 'Must define let(:manifest)' }
21
+
22
+ # Some resources only allow certain fields to be applied. Override this
23
+ # to restrict it.
24
+ let(:applied_manifest) { manifest }
25
+ let(:manifest_for_export) { manifest.deep_stringify_keys }
26
+ let(:applied_manifest_for_export) { applied_manifest.deep_stringify_keys }
27
+
28
+ let(:api_version) { fail Matsuri::UndefinedLet, 'Must define let(:api_version)' }
29
+ let(:kind) { fail Matsuri::UndefinedLet, 'Must define let(:kind)' }
30
+ let(:metadata) { { } }
31
+ let(:name) { fail Matsuri::UndefinedLet, 'Must define let(:name)' }
32
+ let(:spec) { fail Matsuri::UndefinedLet, 'Must define let(:spec)' }
33
+ end
34
+
35
+ def to_json
36
+ manifest_for_export.to_json
37
+ end
38
+
39
+ def to_yaml
40
+ manifest_for_export.to_yaml
41
+ end
42
+
43
+ def applied_to_json
44
+ applied_manifest_for_export.to_json
45
+ end
46
+
47
+ def applied_to_yaml
48
+ applied_manifest_for_export.to_yaml
49
+ end
50
+
51
+ def pretty_print
52
+ JSON.pretty_generate(manifest)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Concerns
5
+ # The import surface: locating a vendor manifest file, extracting
6
+ # documents from it, and the set-carrier lookups that address manifests
7
+ # within a stream by {kind, name, namespace}. The transform vocabulary
8
+ # (set_env, append_volume, update_in, and friends) comes from
9
+ # Matsuri::Transform::Kubernetes, included here, so local customization
10
+ # of imported manifests uses the same dialect as presenters.
11
+ module ImportManifest
12
+ extend Concern
13
+
14
+ included do
15
+ include Matsuri::Transform::Kubernetes
16
+
17
+ let(:import_path) { fail Matsuri::UndefinedLet, 'Must define let(:import_path)' }
18
+
19
+ ### Override
20
+ let(:imported_metadata) { imported_pod_metadata }
21
+ let(:imported_labels) { final_metadata.get(:labels) }
22
+
23
+ let(:imported_pod_metadata) { imported_manifest.get(:spec, :template, :metadata) }
24
+ let(:imported_pod_spec) { imported_manifest.get(:spec, :template, :spec) }
25
+ let(:imported_manifest) { Map.new(YAML.load_file(full_import_path)) }
26
+ let(:full_import_path) { File.join(Matsuri::Config.imported_manifests_path, import_path)}
27
+
28
+ # Support for finding and extracting a manifest from a stream (multiple yaml documents in a file)
29
+ # Override with:
30
+ # let(:imported_manifest) { extracted_manifest }
31
+ # let(:import_filter_criteria) { { kind: "Something", name: "something", namespace: "something" }}
32
+
33
+ let(:extracted_manifest) { Map.new(lookup_manifests(imported_manifests, import_filter_criteria).first) }
34
+ let(:imported_manifests) { YAML.load_stream(File.read(full_import_path)) }
35
+ let(:import_filter_criteria) { fail Matsuri::UndefinedLet, 'Must define let(:import_filter_criteria)' }
36
+ end
37
+
38
+ # Assumes that the complex key of {kind, name, namespace} is unique within a given set of manifests
39
+ def lookup_manifests(set, criteria)
40
+ raise ArgumentError, "Must pass name: and kind: for criteria" unless criteria[:kind] && criteria[:name]
41
+ set.select(&criteria_matcher(criteria))
42
+ end
43
+
44
+ # This is used when we have a collection of manifests, more useful for importing a bundle
45
+ # using Matsuri::Kubernetes::ImportedManifest (importing raw manifests from a vendor upstream)
46
+ def without_manifest(set, criteria)
47
+ raise ArgumentError, "Must pass name: and kind: for criteria" unless criteria[:kind] && criteria[:name]
48
+
49
+ set.reject(&criteria_matcher(criteria))
50
+ end
51
+
52
+ def criteria_matcher(criteria)
53
+ ->(hash) { matches_criteria?(hash, criteria) }
54
+ end
55
+
56
+ def matches_criteria?(hash, criteria)
57
+ h = Map.new(hash)
58
+ h.get(:kind) == criteria[:kind] &&
59
+ h.get(:metadata, :name) == criteria[:name] &&
60
+ (criteria[:namespace] == nil || h.get(:metadata, :namespace) == criteria[:namespace])
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+ require 'rlet/concern'
6
+
7
+ module Matsuri
8
+ module Concerns
9
+ # Mixin for K8S resources that uses pod template
10
+ module PodTemplate
11
+ extend Concern
12
+
13
+ included do
14
+ # Define this to point to an existing pod definition. This is the name
15
+ # registered to Matsuri::Registry
16
+ let(:pod_name) { fail Matsuri::UndefinedLet, 'Must define let(:pod_name)' }
17
+
18
+ # By default, point the template to an existing pod definition
19
+ # Overide let(:pod_name)
20
+ let(:template) { { metadata: { labels: pod_labels, annotations: pod_annotations }, spec: pod_spec } }
21
+
22
+ let(:pod_def) { pod(pod_name, pod_options) }
23
+ let(:primary_image) { pod_def.primary_image }
24
+ let(:primary_container) { pod_def.primary_container }
25
+ let(:pod_labels) { pod_def.final_labels }
26
+ let(:pod_annotations) { pod_def.final_annotations }
27
+ let(:pod_spec) { pod_def.spec }
28
+ let(:pod_options) { default_pod_options }
29
+ # Once mapped, the acceptor instance is the accepted values; the
30
+ # template forwards it so pod definitions read the same binding the
31
+ # owning resource holds.
32
+ let(:default_pod_options) { { image_tag: image_tag, stack_config: stack_config } }
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module Concerns
7
+ module Pods
8
+ # Pod anti-affinity vocabulary for spreading pods that share the same
9
+ # labels across nodes or zones. Not included in Pod by default; include
10
+ # it in a definition and select a strategy:
11
+ #
12
+ # include Matsuri::Concerns::Pods::SpreadPods
13
+ # let(:pod_anti_affinity) { schedule_one_per_zone }
14
+ #
15
+ module SpreadPods
16
+ extend Concern
17
+
18
+ included do
19
+ let(:pod_anti_affinity) { fail Matsuri::UndefinedLet, 'define let(:pod_anti_affinity) required' }
20
+
21
+ let(:schedule_one_per_node) { required_during_scheduling(other_pods_of_type_in_node) }
22
+ let(:schedule_one_per_zone) { required_during_scheduling(other_pods_of_type_in_zone) }
23
+
24
+ # preferredDuringScheduling entries must be weighted terms
25
+ let(:prefer_one_per_node) { preferred_during_scheduling(weighted_other_pods_of_type_in_node) }
26
+ let(:prefer_one_per_zone) { preferred_during_scheduling(weighted_other_pods_of_type_in_zone) }
27
+
28
+ let(:other_pods_of_type_in_node) { pod_affinity_term match_labels: anti_affinity_labels }
29
+ let(:other_pods_of_type_in_zone) { pod_affinity_term match_labels: anti_affinity_labels, topology_key: zone_topology_key }
30
+
31
+ let(:weighted_other_pods_of_type_in_node) { weighted_pod_affinity_term match_labels: anti_affinity_labels, weight: spread_weight }
32
+ let(:weighted_other_pods_of_type_in_zone) { weighted_pod_affinity_term match_labels: anti_affinity_labels, weight: spread_weight, topology_key: zone_topology_key }
33
+
34
+ let(:spread_weight) { 100 }
35
+
36
+ # Well-known topology label, set by the cloud provider on GKE and
37
+ # EKS alike. Its deprecated predecessor,
38
+ # failure-domain.beta.kubernetes.io/zone, is no longer applied to
39
+ # nodes.
40
+ let(:zone_topology_key) { 'topology.kubernetes.io/zone' }
41
+
42
+ let(:anti_affinity_labels) { labels }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module Concerns
7
+ module Pods
8
+ # Topology spread constraint vocabulary for balancing same-labeled pods
9
+ # across topology domains. Not included in Pod by default; include it in
10
+ # a definition and select a strategy:
11
+ #
12
+ # include Matsuri::Concerns::Pods::SpreadTopology
13
+ # let(:topology_spread_constraints) { balance_across_zones }
14
+ #
15
+ # Strategies return lists, so they compose by concatenation:
16
+ #
17
+ # let(:topology_spread_constraints) { balance_across_zones + balance_across_nodes }
18
+ #
19
+ # Complements Concerns::Pods::SpreadPods: spread constraints express
20
+ # balance (bounded skew of replica counts per domain), anti-affinity
21
+ # expresses exclusion (no co-location within a domain). Definitions
22
+ # commonly carry both — zone balance here, node exclusion there.
23
+ # zone_topology_key is the same noun in both concerns, so one override
24
+ # re-points both mechanisms.
25
+ module SpreadTopology
26
+ extend Concern
27
+
28
+ included do
29
+ let(:topology_spread_constraints) { fail Matsuri::UndefinedLet, 'define let(:topology_spread_constraints) required' }
30
+
31
+ ### Strategies: one builder, specialized by topology key
32
+ let(:balance_across_zones) { [balanced_spread(zone_topology_key, max_skew: zone_max_skew, when_unsatisfiable: zone_when_unsatisfiable)] }
33
+ let(:balance_across_nodes) { [balanced_spread(node_topology_key, max_skew: node_max_skew, when_unsatisfiable: node_when_unsatisfiable)] }
34
+
35
+ ### Topology keys
36
+ let(:zone_topology_key) { 'topology.kubernetes.io/zone' }
37
+ let(:node_topology_key) { 'kubernetes.io/hostname' }
38
+
39
+ ### Knob ladder: shared default, per-domain override
40
+ let(:spread_max_skew) { 1 }
41
+ let(:spread_when_unsatisfiable) { 'ScheduleAnyway' }
42
+
43
+ let(:zone_max_skew) { spread_max_skew }
44
+ let(:zone_when_unsatisfiable) { spread_when_unsatisfiable }
45
+ let(:node_max_skew) { spread_max_skew }
46
+ let(:node_when_unsatisfiable) { spread_when_unsatisfiable }
47
+
48
+ let(:spread_label_selector) { label_selector(match_labels: spread_labels) }
49
+ let(:spread_labels) { labels }
50
+ end
51
+
52
+ def balanced_spread(topology_key, max_skew: spread_max_skew, when_unsatisfiable: spread_when_unsatisfiable)
53
+ topology_spread_constraint(
54
+ topology_key: topology_key,
55
+ max_skew: max_skew,
56
+ when_unsatisfiable: when_unsatisfiable,
57
+ label_selector: spread_label_selector
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module Concerns
7
+ # This module contains helpers to make it easy to reference things
8
+ # around the Matsuri platforms. For example, you can reference other pods,
9
+ # rcs, or reference configuration settings.
10
+ module RegistryHelpers
11
+ extend Concern
12
+
13
+ included do
14
+ def config
15
+ Matsuri::Config
16
+ end
17
+
18
+ def platform
19
+ Matsuri::Platform
20
+ end
21
+
22
+ def please_define(key, notes = '')
23
+ Matsuri.log :fatal, "Please define '#{key}' in '.platform.rb'\n#{notes}"
24
+ end
25
+
26
+ def pod(name, opt={})
27
+ Matsuri::Registry.pod(name).new(opt)
28
+ end
29
+
30
+ def replica_set(name, opt={})
31
+ Matsuri::Registry.replica_set(name).new(opt)
32
+ end
33
+
34
+ def deployment(name, opt={})
35
+ Matsuri::Registry.deployment(name).new(opt)
36
+ end
37
+
38
+ def stateful_set(name, opt={})
39
+ Matsuri::Registry.stateful_set(name).new(opt)
40
+ end
41
+
42
+ def service(name, opt={})
43
+ Matsuri::Registry.service(name).new(opt)
44
+ end
45
+
46
+ def endpoints(name, opt={})
47
+ Matsuri::Registry.endpoints(name).new(opt)
48
+ end
49
+
50
+ def horizontal_pod_autoscaler(name, opt={})
51
+ Matsuri::Registry.horizontal_pod_autoscaler(name).new(opt)
52
+ end
53
+
54
+ alias_method :hpa, :horizontal_pod_autoscaler
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mixlib/config'
4
+ require 'pathname'
5
+ require 'matsuri/core_ext'
6
+
7
+ module Matsuri
8
+ # Matsuri-specific configuration. These are things you probably
9
+ # do not want to change. User-defined configs are found in Matsuri::Platform
10
+ # TODO: Move K8S configs out to Platform
11
+ module Config
12
+ extend Mixlib::Config
13
+ config_strict_mode true # will see how annoying this is
14
+
15
+ # Base path of the platform repo, defaults to PWD
16
+ default(:base_path) { fail 'Matsuri.base_path must be set. This is usually set by the initializer' }
17
+ default :verbose, false
18
+ default :debug, false
19
+ default(:environment) { fail 'Matsuri.environment must be set. This is usually set by the initializer' }
20
+
21
+ # If set to true, then Matsuri env names will be
22
+ # mapped to kube_context via Matsuri::Platform.
23
+ # By default, Matsuri assumes that the name of the environment
24
+ # is the name of the kubectl context. However, if you are using
25
+ # this with GKE, it is easier to let gcloud handle setting up
26
+ # authentication, in which case we want to map environment names
27
+ # to kubectl context
28
+ # Example: staging -> Matsuri::Platform.staging.kube_context
29
+ # With "staging", you must define Matsuri::Platform.staging.kube_context
30
+ # to pull the correct kube context.
31
+ default :map_env_to_kube_context, false
32
+
33
+ # Networking
34
+ default :dev_addr, '127.0.0.1' # Replace with local ip
35
+
36
+ # Shellout defaults
37
+ default(:shellout_cwd) { base_path }
38
+ default(:shellout_defaults) { { cwd: shellout_cwd } }
39
+
40
+ # Platform paths
41
+ default(:config_path) { File.join base_path, 'config' }
42
+ default(:repo_defaults_path) { File.join config_path, 'matsuri.rb' }
43
+ default(:initializer_path) { File.join config_path, 'initializer.rb' }
44
+ default(:config_secrets_path) { File.join config_path, 'secrets' } # Actual secrets themselves, should not be versioned
45
+ default(:config_overlays_path) { File.join 'etc', 'config-maps' } # Actual config files themselves
46
+ default(:platform_load_paths) { [ File.join(config_path, 'platform.rb'), File.join(base_path, '.platform.rb') ] }
47
+
48
+ default(:cache_path) { File.join base_path, '.cache' }
49
+ default(:build_path) { File.join base_path, 'build' }
50
+ default(:docker_path) { File.join base_path, 'docker' }
51
+ default(:src_path) { File.join base_path, 'src' }
52
+ default(:lib_path) { File.join base_path, 'lib' }
53
+ default(:platform_path) { File.join base_path, 'platform' }
54
+
55
+ default(:images_path) { File.join platform_path, 'images' }
56
+ default(:pods_path) { File.join platform_path, 'pods' }
57
+ default(:replica_sets_path) { File.join platform_path, 'replica_sets' }
58
+ default(:daemon_sets_path) { File.join platform_path, 'daemon_sets' }
59
+ default(:deployments_path) { File.join platform_path, 'deployments' }
60
+ default(:cron_jobs_path) { File.join platform_path, 'cron_jobs' }
61
+ default(:stateful_sets_path) { File.join platform_path, 'stateful_sets' }
62
+ default(:persistent_volumes_path) { File.join platform_path, 'persistent_volumes' }
63
+ default(:persistent_volume_claims_path) { File.join platform_path, 'persistent_volume_claims' }
64
+ default(:storage_classes_path) { File.join platform_path, 'storage_classes' }
65
+ default(:services_path) { File.join platform_path, 'services' }
66
+ default(:ingresses_path) { File.join platform_path, 'ingresses' }
67
+ default(:endpoints_path) { File.join platform_path, 'endpoints' }
68
+ default(:stacks_path) { File.join platform_path, 'stacks' }
69
+ default(:acceptors_path) { File.join platform_path, 'acceptors' }
70
+ default(:presenters_path) { File.join platform_path, 'presenters' }
71
+ default(:secrets_path) { File.join platform_path, 'secrets' } # Kubernetes Secret definition, not the actual secrets
72
+ default(:config_maps_path) { File.join platform_path, 'config_maps' } # Kubernetes ConfigMaps definition, not the actual config ovelay
73
+ default(:horizontal_pod_autoscalers_path) { File.join platform_path, 'horizontal_pod_autoscalers' }
74
+
75
+ default(:clusters_path) { File.join platform_path, 'clusters' } # For Cluster DSL, managing cluster-wide configuration
76
+
77
+ default(:imported_manifests_path) { File.join platform_path, 'manifests' }
78
+ default(:manifests_path) { File.join platform_path, 'manifests' }
79
+
80
+ # API Versions
81
+ default :rbac_api_version, 'rbac.authorization.k8s.io/v1'
82
+
83
+ # DSL
84
+ default :cluster_dsl_version, 'cluster.v1'
85
+
86
+ # Helpers
87
+ def self.host_osx?
88
+ /darwin/ =~ RUBY_PLATFORM
89
+ end
90
+
91
+ def self.load_configuration(config_file)
92
+ Matsuri::Config.from_file(config_file) if File.file?(config_file)
93
+ end
94
+
95
+ def self.apply_configuration(options = {})
96
+ Matsuri::Config.verbose = options[:verbose] if options[:verbose]
97
+ Matsuri::Config.debug = options[:debug] if options[:debug]
98
+ Matsuri::Config.environment = options[:environment] if options[:environment]
99
+ end
100
+ end
101
+ end