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,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
|