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
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-11-02
|
|
9
|
+
|
|
10
|
+
Architecture release: resources become pure manifest generators with
|
|
11
|
+
delivery moved into tasks, `App` becomes `Stack`, and the release adds
|
|
12
|
+
presenter pipelines over a pure transform layer, acceptors and
|
|
13
|
+
stack-config, and server-side apply with a seeding protocol for shared
|
|
14
|
+
field ownership. Several client-side-rolling-update-era surfaces are
|
|
15
|
+
removed. Review the Breaking and Removed sections before upgrading.
|
|
16
|
+
|
|
17
|
+
### Breaking
|
|
18
|
+
|
|
19
|
+
- **The environment has exactly one source: the first positional
|
|
20
|
+
argument.** The `MATSURI_ENV` variable and symlink-name detection
|
|
21
|
+
(any binary name other than `matsuri` becoming the environment) are
|
|
22
|
+
removed. A per-environment entry point is argument injection in a
|
|
23
|
+
repo wrapper — `bin/staging` is `exec bin/matsuri staging "$@"`. The
|
|
24
|
+
command is now self-describing: what was typed, what `ps` shows, and
|
|
25
|
+
what matsuri resolved are the same string. `Initializer.start` takes
|
|
26
|
+
`(args)` instead of `(cmd, args)`. See docs/design/boot.md.
|
|
27
|
+
- **Delivery moves off the resource classes into `Matsuri::Tasks::KubectlOps`.**
|
|
28
|
+
Resources are pure manifest generators; the kubectl mechanism lives in
|
|
29
|
+
one task per verb (`Apply`, `Create`, `Replace`, `Delete`, `Recreate`,
|
|
30
|
+
`Scale`, plus the `Get` read), and the CLI selects the task. The
|
|
31
|
+
module-level boundary functions (`KubectlOps.apply/create/delete/
|
|
32
|
+
recreate/scale/converge`) are the single site deriving task inputs from
|
|
33
|
+
a rendered resource — post-generation manifest transforms will slot in
|
|
34
|
+
there. Consequences:
|
|
35
|
+
- `Kubernetes::Base` loses `create!`, `apply!`, `replace!`, `delete!`,
|
|
36
|
+
`recreate!`, `annotate!`, `converge!`, `converge_by_apply!`, and
|
|
37
|
+
`converge_by_recreate!`. Definitions overriding these must declare
|
|
38
|
+
delivery policy instead: `let(:converge_via)` (`:apply` default,
|
|
39
|
+
`:recreate`), `let(:apply_via)` (`:apply` default, `:replace`), and
|
|
40
|
+
`let(:save_config)` (default `true`). Pod, Secret, and ConfigMap
|
|
41
|
+
express their converge-by-recreate behavior through these lets.
|
|
42
|
+
- `annotate!` is removed outright: nothing bound it since `--record`
|
|
43
|
+
died, and deliberate annotation belongs to the manifest + apply path.
|
|
44
|
+
- `status!` is removed from `Kubernetes::Base`. It was a fatal stub no
|
|
45
|
+
resource ever implemented — every `status <type> <name>` died with
|
|
46
|
+
"I don't know how to display a status". The `Status` task renders
|
|
47
|
+
`kubectl get -o wide` instead, so the verb works for the first time.
|
|
48
|
+
`Stack#status!` remains the overridable hook for stack definitions.
|
|
49
|
+
- `Concerns::Scalable` is removed; `scale` is a task, and the CLI table
|
|
50
|
+
already names which workloads scale.
|
|
51
|
+
- `ImportedManifest` drops its five fatal verb stubs. Apply-only is now
|
|
52
|
+
structural — the CLI never bound the other verbs — and its custom
|
|
53
|
+
`apply!` is the same `Apply` task fed multi-doc YAML.
|
|
54
|
+
- Create/apply payloads are sent as YAML rather than JSON (one payload
|
|
55
|
+
shape serves single- and multi-doc manifests); semantics unchanged.
|
|
56
|
+
- `ExportManifest#applied_to_yaml` is fixed (previously raised
|
|
57
|
+
`NoMethodError`); it is now the canonical apply payload.
|
|
58
|
+
- `Kubernetes::Base#created?` is removed; the existence check is
|
|
59
|
+
`Tasks::KubectlOps.exists?(resource)` (the `Get` read task, which
|
|
60
|
+
`converge` already used). The verbose "already started / not
|
|
61
|
+
started" narration moves to `Get#exists?`. With it goes Base's
|
|
62
|
+
`ShellOut` include — resources no longer shell out at all.
|
|
63
|
+
|
|
64
|
+
- **`Concerns::ImportManifest` keeps only the import surface.** The
|
|
65
|
+
transform ops move to `Matsuri::Transform` (the concern includes the
|
|
66
|
+
Kubernetes dialect, so call sites inside definitions are unchanged),
|
|
67
|
+
and the set-carrier lookups (`lookup_manifests`, `without_manifest`,
|
|
68
|
+
criteria matching) stay. Two declarations change shape:
|
|
69
|
+
`with_resources` was a let holding a curried lambda
|
|
70
|
+
(`with_resources.(name, spec)`) and is now the dialect method
|
|
71
|
+
(`with_resources(name, spec)` — same returned step, no lambda-call on
|
|
72
|
+
the name); `node_selector_terms_path` was an overridable let and is
|
|
73
|
+
now the dialect constant `NODE_SELECTOR_TERMS_PATH`.
|
|
74
|
+
|
|
75
|
+
- **The client-side diff moves off the resource classes into
|
|
76
|
+
`Matsuri::Concerns::Diffable` + `Matsuri::Tasks::HashDiff`.** The
|
|
77
|
+
comparison is the resource's (`Diffable#diff(current)` takes the
|
|
78
|
+
manifest compared against and returns filtered deltas; the noise
|
|
79
|
+
filters remain the per-resource override surface); fetching the
|
|
80
|
+
current manifest and printing belong to the `HashDiff` task. This
|
|
81
|
+
diff is a permanent companion to the planned server-side
|
|
82
|
+
`KubectlOps::Diff` (`kubectl diff`), not a stopgap: with admission
|
|
83
|
+
controllers in play, the server-side diff shows what admission
|
|
84
|
+
injects, while this one shows drift relative to the declaration
|
|
85
|
+
with that noise filtered out. Consequences:
|
|
86
|
+
- `Kubernetes::Base` loses `diff!`, `diff` (zero-arg), `print_diff`,
|
|
87
|
+
and `current_manifest`. Library callers of `current_manifest` use
|
|
88
|
+
`Tasks::KubectlOps.manifest(resource)` (a plain parsed hash — the
|
|
89
|
+
`Map` wrapping and `raw:` flag are gone).
|
|
90
|
+
- The per-class `primary_container_diff`/`containers_diff` overrides
|
|
91
|
+
on Pod, ReplicaSet, and Deployment are replaced by a declarative
|
|
92
|
+
`let(:primary_container_path)`; the focused diff projects both
|
|
93
|
+
manifests by that path generically.
|
|
94
|
+
- `--primary-container` works for the first time on `pod` (previously
|
|
95
|
+
`NoMethodError`: `Pod#diff!` called a method that never existed)
|
|
96
|
+
and on `stateful_set`, `daemon_set`, and `cron_job` (previously
|
|
97
|
+
silently fell back to a full diff). All workloads with a pod
|
|
98
|
+
template now declare the path.
|
|
99
|
+
- `unit_comparison_is_equal?` no longer crashes on non-string values
|
|
100
|
+
(`Object#=~` was removed in Ruby 3.2; any diff touching a Hash or
|
|
101
|
+
Array value raised `NoMethodError` on modern Ruby).
|
|
102
|
+
|
|
103
|
+
- **Bare `diff` is now the server-side diff.** `matsuri <env> diff <type>
|
|
104
|
+
<name>` runs `kubectl diff` (`Tasks::KubectlOps::Diff`): the apiserver
|
|
105
|
+
dry-runs the same payload `apply` would send through defaulting and
|
|
106
|
+
admission, and the output shows live-vs-admitted — admission injections
|
|
107
|
+
included. `--hash-diff` restores the client-side filtered diff
|
|
108
|
+
(`Tasks::HashDiff`), which answers a different question: drift relative
|
|
109
|
+
to the declaration with cluster-owned noise filtered out. Both are
|
|
110
|
+
permanent. `--primary-container` is a HashDiff-only projection and
|
|
111
|
+
implies `--hash-diff`. kubectl's exit-code convention is honored: no
|
|
112
|
+
output and exit 0 means no differences; only exit codes above 1 are
|
|
113
|
+
errors.
|
|
114
|
+
|
|
115
|
+
- **`Matsuri::App` is renamed `Matsuri::Stack`.** A stack is a bundle of
|
|
116
|
+
resources managed together — one owner, one converge act; "app" never
|
|
117
|
+
described that. Registry type is `stack`, definitions load from
|
|
118
|
+
`platform/stacks`, the definition module is `Stacks`, and the CLI verbs
|
|
119
|
+
address stacks. There is no back-compat alias for the `app` type;
|
|
120
|
+
definitions must rename their directory and `Matsuri.define :app` calls.
|
|
121
|
+
- **Resources carry no imperative side-effects — the last cluster reads
|
|
122
|
+
move to `Tasks::Pod`.** `Pod#up?`, `Service#selected_pods`(`_json`),
|
|
123
|
+
and `Concerns::PodTemplate#selected_pods`(`_json`) — all dead since
|
|
124
|
+
resources stopped shelling out, with zero callers in the codebase —
|
|
125
|
+
are removed from the resource classes. `Tasks::Pod` carries them
|
|
126
|
+
instead, parameterized: `up?` takes `name:`/`namespace:`,
|
|
127
|
+
`selected_pods` takes `selector:` (a flat labels hash), and
|
|
128
|
+
`Tasks::Pod.pod_selector_for(resource)` derives that selector from
|
|
129
|
+
what each kind declares (workloads' `match_labels`, Service's
|
|
130
|
+
`selector`). Unwired by design — these are part of the incomplete
|
|
131
|
+
local-dev lifecycle work. `ShellOut` is now included exactly once in
|
|
132
|
+
the library, by `Matsuri::Task`: tasks are the only things that shell.
|
|
133
|
+
|
|
134
|
+
- **Read-back support is dropped in favor of seeds + server-side
|
|
135
|
+
apply.** Matsuri no longer reads live cluster state to make a
|
|
136
|
+
declaration decision. `Deployment#current_image`/`#current_image_tag`
|
|
137
|
+
are removed, along with the apply/create/recreate ladder rung that
|
|
138
|
+
backfilled a tag-less invocation from the live image — a tag-less
|
|
139
|
+
`apply deployment` now renders the acceptor default, same as `diff`
|
|
140
|
+
always did (the rung made diff and apply disagree for exactly the
|
|
141
|
+
invocations it served). The rolling-update-era `Deployment#update!`
|
|
142
|
+
and `#rollout!` (already broken since resources stopped shelling out)
|
|
143
|
+
are removed with it. The field the read-back protected is owned
|
|
144
|
+
structurally instead: a workload whose tag is rolled externally seeds
|
|
145
|
+
its image on create and omits it from the apply payload, so ongoing
|
|
146
|
+
applies have nothing to say about it — under client-side apply this
|
|
147
|
+
is pinned by `tests/bin/probe-save-config.sh`, and under an ongoing
|
|
148
|
+
deployment operator the tag belongs to that operator's field manager
|
|
149
|
+
via server-side apply.
|
|
150
|
+
|
|
151
|
+
- **`Matsuri::Stack` no longer shells out.** The `ShellOut` include is
|
|
152
|
+
gone, matching the resources. `docker!` remains on stacks as the hook
|
|
153
|
+
surface for definition-supplied `build!`/`push!` — it delegates to
|
|
154
|
+
`Tasks::Docker`, so definitions calling it are unchanged. A stack
|
|
155
|
+
definition that shelled with anything else (`kubectl!`, `sh_exec`)
|
|
156
|
+
must route through tasks (`Tasks::KubectlOps` for delivery verbs).
|
|
157
|
+
|
|
158
|
+
- **Workload `image_tag` defaults are removed.** Pod, Deployment,
|
|
159
|
+
ReplicaSet, ReplicationController, StatefulSet, DaemonSet, and CronJob no
|
|
160
|
+
longer default `image_tag` to `latest`; the default lives in the built-in
|
|
161
|
+
`Matsuri::Acceptors::ImageTag` registered under the stack's `:image_tag`
|
|
162
|
+
collection. Definitions instantiated outside a stack (bare `.new`,
|
|
163
|
+
library use) that relied on the implicit `latest` must declare their own:
|
|
164
|
+
`let(:image_tag) { options[:image_tag] || 'latest' }`. Resource-level
|
|
165
|
+
CLI verbs supply the tag explicitly and are unchanged.
|
|
166
|
+
|
|
167
|
+
- **Ingress emits `networking.k8s.io/v1`** (was `extensions/v1beta1`,
|
|
168
|
+
removed in Kubernetes 1.22). `spec.backend` is now
|
|
169
|
+
`spec.defaultBackend`; the `path` helper emits `pathType` (default
|
|
170
|
+
`Prefix`) and the v1 backend shape
|
|
171
|
+
(`service: { name:, port: { number: | name: } }` — an Integer port is
|
|
172
|
+
a number, anything else names a port); `ingress_class` now renders as
|
|
173
|
+
`spec.ingressClassName` instead of the retired
|
|
174
|
+
`kubernetes.io/ingress.class` annotation. Definitions that hand-rolled
|
|
175
|
+
`rules` or `default_backend` in the v1beta1 shape must move to the v1
|
|
176
|
+
shape.
|
|
177
|
+
|
|
178
|
+
### Removed
|
|
179
|
+
|
|
180
|
+
- **`maybe_param_replicas`** on Deployment, ReplicaSet, and StatefulSet —
|
|
181
|
+
the client-side rolling-update era's `--replicas` passthrough, consumed
|
|
182
|
+
by nothing since `scale` became a task.
|
|
183
|
+
- **`Kubernetes::Base#build!`** — a NotImplementedError stub with no
|
|
184
|
+
caller: no CLI verb binds a per-resource build, and the working build
|
|
185
|
+
path has always been the stack-level hook (`Stack#build!`, supplied by
|
|
186
|
+
the stack definition and driven by `matsuri <env> build <stack>`).
|
|
187
|
+
A definition that implements its own `build!` is unaffected; direct
|
|
188
|
+
callers of the stub now get NoMethodError instead of
|
|
189
|
+
NotImplementedError.
|
|
190
|
+
- **`--record` everywhere kubectl is invoked** — create, apply, replace,
|
|
191
|
+
scale, imported-manifest apply, and `Deployment#rollout!`. kubectl
|
|
192
|
+
deprecated the flag (~1.22): it stamped the
|
|
193
|
+
`kubernetes.io/change-cause` annotation as a side effect of imperative
|
|
194
|
+
verbs, recording a command rather than an intent. Nothing replaced it
|
|
195
|
+
server-side — SSA's managedFields attributes who owns a field, not why
|
|
196
|
+
it changed. The intention here is to support
|
|
197
|
+
`kubernetes.io/change-cause` deliberately, as a post-generation
|
|
198
|
+
manifest transform stamping a platform-supplied value (release id,
|
|
199
|
+
ticket, ref) on the declarative path. The diff logic keeps treating
|
|
200
|
+
change-cause as an always-changed path.
|
|
201
|
+
- **ReplicationController support.** The resource type, its registry
|
|
202
|
+
entries and `rc` aliases, the `rc` subcommands on
|
|
203
|
+
show/diff/create/delete/recreate/apply/scale/status, the `rcs_path`
|
|
204
|
+
config key, and the `rollout` verb. `rollout` was the client-side
|
|
205
|
+
rolling update — mint `name-rN+1`, shell to `kubectl rolling-update` —
|
|
206
|
+
and `kubectl rolling-update` itself was removed from kubectl years
|
|
207
|
+
ago; the Deployment controller has owned rollouts since. Definitions
|
|
208
|
+
using `:rc` should move to Deployments (or ReplicaSets where the
|
|
209
|
+
controller semantics are genuinely wanted).
|
|
210
|
+
- **Support for legacy locally-bootstrapped Kubernetes.** Matsuri once
|
|
211
|
+
helped stand up a local cluster by hand — etcd, hyperkube, and SkyDNS
|
|
212
|
+
on the dev machine — from before minikube and k3s made that someone
|
|
213
|
+
else's job. The DNS add-on (`Matsuri::AddOns::DNS`, a SkyDNS/kube2sky
|
|
214
|
+
deployment pinned to Kubernetes v1.0) and its `show dns` /
|
|
215
|
+
`create dns` / `delete dns` verbs are removed, along with the
|
|
216
|
+
bootstrap-era config keys: `etcd_version`, `hyperkube_version`,
|
|
217
|
+
`kube2dns_version`, `skydns_version`, `kubernetes_cidr`, `etcd_addr`,
|
|
218
|
+
`etcd_bind_addr`, `master_addr`, `master_bind_addr`, `api_servers`,
|
|
219
|
+
`master_url`, `cluster_dns`, and `cluster_domain`. `dev_addr` stays —
|
|
220
|
+
Endpoints definitions still default `public_ip_address` to it.
|
|
221
|
+
- **`let(:release)` and `maybe_param_name`.** Both served the client-side
|
|
222
|
+
rolling-update machinery and lost their last consumer with it.
|
|
223
|
+
`release` was the incrementing `-rN` rollout counter, defined on every
|
|
224
|
+
resource (default `'0'`) and forwarded through pod options; nothing
|
|
225
|
+
read it. `maybe_param_name` let `options[:name]` override the manifest
|
|
226
|
+
name so a successor controller could be minted alongside the current
|
|
227
|
+
one — no caller ever passed it to Deployment, ReplicaSet, or
|
|
228
|
+
StatefulSet, and dropping it collapses their `default_metadata`
|
|
229
|
+
overrides back to the base (which also restores `final_annotations`
|
|
230
|
+
assembly on those three kinds). Its real requirement — apply should
|
|
231
|
+
not clobber live replica counts set by an HPA or manual scale — is
|
|
232
|
+
answered structurally by the seed family: the manifest omits the
|
|
233
|
+
field, so apply has nothing to say about it.
|
|
234
|
+
|
|
235
|
+
### Changed
|
|
236
|
+
|
|
237
|
+
- **Unknown `apply_via` values are fatal.** Delivery routing is a total
|
|
238
|
+
case over `:apply` / `:replace` / `:server_side_apply`; a typo'd value
|
|
239
|
+
raises instead of silently falling through to client-side apply and
|
|
240
|
+
delivering under the wrong merge semantics.
|
|
241
|
+
- **create/recreate consult last-applied policy before seeding it.**
|
|
242
|
+
`--save-config` is gated by `consults_last_applied?(apply_via)`:
|
|
243
|
+
resources delivered by `:replace` or `:server_side_apply` create with
|
|
244
|
+
`--save-config=false` regardless of their `save_config`, because a
|
|
245
|
+
seeded last-applied is a prune trap for any later client-side apply.
|
|
246
|
+
- **The `latest` positional default has one owner.** The resource-level
|
|
247
|
+
CLI verbs (`show`/`diff`/`apply`/`create`/`recreate` with an image-tag
|
|
248
|
+
positional) no longer hardcode `'latest'` per cmd file; the positional
|
|
249
|
+
defaults to nil and routes through the built-in `ImageTag` acceptor —
|
|
250
|
+
the same single owner the stack `:image_tag` collection uses. With
|
|
251
|
+
read-back dropped, every verb resolves the tag the same way:
|
|
252
|
+
positional, else the acceptor default.
|
|
253
|
+
- **ConfigMap applies normally.** The replace-instead-of-apply override
|
|
254
|
+
dated to 2016 kubectl issues (kubernetes#32432, kubernetes#41711) that
|
|
255
|
+
kubectl fixed years ago; `kubectl apply` handles configmaps fine. Apply
|
|
256
|
+
now goes through `apply` like every other resource, and create seeds
|
|
257
|
+
`last-applied-configuration` (`--save-config`) so the three-way merge
|
|
258
|
+
has its base. Converge-by-recreate policy is unchanged.
|
|
259
|
+
- **Untagged stack converges propagate `latest` explicitly** (previously
|
|
260
|
+
propagated nil and each workload class defaulted individually). Rendered
|
|
261
|
+
output is unchanged; drift between per-class defaults is no longer
|
|
262
|
+
possible.
|
|
263
|
+
- **Declared holes raise `Matsuri::UndefinedLet` instead of
|
|
264
|
+
`NotImplementedError`.** Ruby reserves `NotImplementedError` for
|
|
265
|
+
features the current platform does not implement, and it descends from
|
|
266
|
+
`ScriptError`, so a bare `rescue` lets it fly past.
|
|
267
|
+
`UndefinedLet < NoMethodError` names what actually happened: the
|
|
268
|
+
definition has no working version of that let. The raise lands in the
|
|
269
|
+
code that expected the value, so the implementer can inspect that
|
|
270
|
+
context and see what the definition needs. Specs
|
|
271
|
+
asserting on unfilled holes should expect the new class. Relatedly,
|
|
272
|
+
`Stack#status!` (an abstract method, not a let) raises `NoMethodError`
|
|
273
|
+
directly, and the `ImportedManifest` namespace hole — previously a call
|
|
274
|
+
to the undefined `Matsuri.fail`, which died as the wrong `NoMethodError`
|
|
275
|
+
with its message lost — raises `UndefinedLet` with its message intact.
|
|
276
|
+
|
|
277
|
+
### Added
|
|
278
|
+
|
|
279
|
+
- **`Presenters::PruneEmpty`** — prunes empty values (`nil`, `{}`, `[]`)
|
|
280
|
+
from payloads, bottom-up; empty strings survive and array elements are
|
|
281
|
+
never dropped. Under server-side apply an empty collection is an
|
|
282
|
+
ownership claim over a field the definition does not manage, and
|
|
283
|
+
inside an atomic struct (`.spec.selector`) it diverges from the
|
|
284
|
+
server's persisted normal form, breaking the seeding protocol's
|
|
285
|
+
equal-values co-ownership with a conflict. Declared on both intent
|
|
286
|
+
regimes, a pruned payload declares in persistence normal form: ceding
|
|
287
|
+
a field and saying nothing are the same act. `let(:exceptions)` names
|
|
288
|
+
paths kept even when empty (ships with none); the `prune_empty(except:)`
|
|
289
|
+
helper closes it declaration-side. See docs/presenters.md.
|
|
290
|
+
- **The repo's `lib/` joins the load path at boot.** Immediately after
|
|
291
|
+
repo discovery, the initializer prepends `Config.lib_path` (the key
|
|
292
|
+
existed, nothing consumed it), so a repo's own extension library
|
|
293
|
+
resolves identically under `bundle exec`, a bare gem install, or a
|
|
294
|
+
checkout — no gemspec and no wrapper-exported load paths. Boot
|
|
295
|
+
promise layers are documented in docs/design/boot.md.
|
|
296
|
+
- **Server-side apply.** `apply_via :server_side_apply` delivers through
|
|
297
|
+
`Tasks::KubectlOps::ServerSideApply` (`kubectl apply --server-side
|
|
298
|
+
--field-manager=<m>`); the manager name comes from the resource's
|
|
299
|
+
`field_manager` let, defaulting to the repo-wide
|
|
300
|
+
`Matsuri::Platform.field_manager` (default `matsuri`). The name
|
|
301
|
+
declares the repo's ongoing-delivery identity: the conflict frontier
|
|
302
|
+
forms exactly where manager names differ, and a shared name is a
|
|
303
|
+
single manager whose writes never conflict within it. Conflicts stay
|
|
304
|
+
loud — `--force-conflicts` is never passed; forcing through a frontier
|
|
305
|
+
is an incident-time human judgment made with raw kubectl. `diff` on a
|
|
306
|
+
server-side resource routes `--server-side` under the same manager, so
|
|
307
|
+
the preview shows the same conflict frontier delivery will hit
|
|
308
|
+
(conflicts surface as diff errors). Usage is documented in
|
|
309
|
+
docs/ssa.md, the constraints in docs/design/ssa.md.
|
|
310
|
+
- **`Tasks::Stacks::Seed` — the creation protocol for
|
|
311
|
+
server-side-applied resources.** Server-side apply cannot safely
|
|
312
|
+
create an object whose payload carries seeds: seeds sent by apply are
|
|
313
|
+
owned and then pruned by the first omitting apply, and an apply
|
|
314
|
+
against empty managedFields attributes every pre-existing field to the
|
|
315
|
+
synthetic manager `before-first-apply`, which never relinquishes. The
|
|
316
|
+
protocol: create the seeded payload as `<field_manager>-seed` (the
|
|
317
|
+
seed owner, no `--save-config`), deliver the declared payload with an
|
|
318
|
+
ordinary server-side apply, then atomically remove the seed owner's
|
|
319
|
+
managedFields entry (a test+remove JSON patch — a stale index is a
|
|
320
|
+
loud rejection with a bounded reread retry, never a wrong removal).
|
|
321
|
+
The field manager ends sole owner of the declared fields; seeds end
|
|
322
|
+
unowned until a controller or deliverer takes them. The delivery verbs
|
|
323
|
+
run the protocol themselves: `apply` (and so converge) routes a
|
|
324
|
+
not-yet-live server-side resource through `Seed`, `create` brings one
|
|
325
|
+
into existence through it, and `recreate` tears down and reseeds —
|
|
326
|
+
every later apply is the ordinary mechanism, and sequential
|
|
327
|
+
`build_order` means a member that hands a field to another manager can
|
|
328
|
+
simply ride later in the stack.
|
|
329
|
+
`tests/bin/probe-ssa.sh` pins the protocol and the refuted
|
|
330
|
+
alternatives against a live apiserver.
|
|
331
|
+
- **The seed family.** `Matsuri::Presenters::SeedReplicas` / `SeedImage`
|
|
332
|
+
/ `SeedResources` — concern + declared-hole pairs that complete a
|
|
333
|
+
create payload with a field's boot value, with `seed_replicas(n)` /
|
|
334
|
+
`seed_image(img)` / `seed_resources(spec)` declaration-surface
|
|
335
|
+
constructors on stacks and resources. Additive on create: a field only
|
|
336
|
+
seeds when the manifest omits it, so a declared field is the opt-out
|
|
337
|
+
and nothing ever overwrites a declaration; the `:on_apply` payload
|
|
338
|
+
never carries seeded fields, so controllers (HPA) take ownership
|
|
339
|
+
uncontested — the cluster mechanics are pinned by
|
|
340
|
+
`tests/bin/probe-save-config.sh`. `save_config` becomes a resolution
|
|
341
|
+
chain, most-specific-first: a definition's own let, the owning stack's
|
|
342
|
+
`save_config false` declaration, then repo-wide
|
|
343
|
+
`Matsuri::Platform.save_config` (default `true`); normal apply path
|
|
344
|
+
only, never crossing nested stacks. The dialect gains
|
|
345
|
+
`pod_spec_path(manifest)` — the pod spec within a pod-template
|
|
346
|
+
workload or bare pod. See the Seeds section of docs/presenters.md.
|
|
347
|
+
- **`tests/` — a live-cluster probe harness.** `bin/minikube-start.sh`
|
|
348
|
+
runs a dedicated `matsuri` minikube profile (`--keep-context`; vfkit
|
|
349
|
+
driver on macOS), and `bin/probe-save-config.sh` pins the delivery
|
|
350
|
+
semantics the seed model rests on: a create without `--save-config`
|
|
351
|
+
keeps seeded fields out of last-applied, so ongoing applies of a
|
|
352
|
+
manifest omitting them never prune them, and a controller takes
|
|
353
|
+
ownership uncontested. minikube, kubectl, and (on macOS) vfkit join
|
|
354
|
+
the nix devshell. These are delivery-semantics probes against a real
|
|
355
|
+
API server, deliberately outside the rspec suite.
|
|
356
|
+
- **`present` pipelines on resources.** The same `present` grammar on
|
|
357
|
+
`Kubernetes::Base`; default none. Through a stack, the two declaration
|
|
358
|
+
sites merge into one pipeline — resource members first, stack members
|
|
359
|
+
last. Both run; position is who sees the completed manifest: the
|
|
360
|
+
resource's pipeline completes its self-knowledge, the stack's steps
|
|
361
|
+
admit the finished manifest (where the server's own mutating admission
|
|
362
|
+
sits relative to the client's payload). Direct resource verbs assemble
|
|
363
|
+
the resource's own declarations alone, so a resource behaves
|
|
364
|
+
identically converged directly or through an undeclared stack.
|
|
365
|
+
- **`present` pipelines on stacks.** `present(scenario, *members)`
|
|
366
|
+
appends transform members to a scenario's pipeline — unlike `accept`,
|
|
367
|
+
a scenario accumulates, and declaration order is member order. Members
|
|
368
|
+
are presenter names, classes, Callables, or anything `#call`-able; the
|
|
369
|
+
combinator vocabulary (`match`, `match_kind`, `match_deployments`) is
|
|
370
|
+
available in the stack class body. Scenarios are intent regimes, not
|
|
371
|
+
verbs: `:on_create` (brought into existence — `create`, recreate's
|
|
372
|
+
create leg) and `:on_apply` (delivered to something existing —
|
|
373
|
+
`apply`, `replace`, `diff`). `apply_via` picks how the payload
|
|
374
|
+
travels; the scenario picks what it says — flipping delivery mechanism
|
|
375
|
+
never changes which presenters run. Declarations ride to the
|
|
376
|
+
`KubectlOps` boundary as pure data and each verb assembles its own
|
|
377
|
+
scenario there; one site builds the presented payload for both diff
|
|
378
|
+
and apply, so diff previews delivery through presenters. The empty
|
|
379
|
+
pipeline is the identity: undeclared stacks send byte-identical
|
|
380
|
+
payloads. Declarations never cross a stack boundary. Multi-doc
|
|
381
|
+
manifests present per document. See docs/presenters.md. Boundary
|
|
382
|
+
consequences: the module functions serialize payloads from
|
|
383
|
+
`*_manifest_for_export` themselves (resources' `to_yaml`/
|
|
384
|
+
`applied_to_yaml` overrides no longer shape delivery payloads, only
|
|
385
|
+
`show`), and `converge` grew a `presenters:` keyword — a literal
|
|
386
|
+
trailing-hash `converge(r, restart: true)` call now needs braces.
|
|
387
|
+
The client-side HashDiff diffs against the presented declaration too:
|
|
388
|
+
it assembles the same `:on_apply` pipeline over the same basis apply
|
|
389
|
+
sends — the applied manifest (stack declarations ride in from
|
|
390
|
+
`diff stack`; direct verbs merge the resource's own) — so
|
|
391
|
+
`--hash-diff` drift reads against what apply would send: a value a
|
|
392
|
+
presenter pins is not drift, a field the resource restricts out of
|
|
393
|
+
`applied_manifest` is not drift, and drift the pipeline does not
|
|
394
|
+
account for still reports. `Diffable#diff`/`#primary_container_diff`
|
|
395
|
+
accept the basis as `desired:`.
|
|
396
|
+
- **`Matsuri::Presenter` — zero-config, context-free manifest steps.** A
|
|
397
|
+
presenter is Manifest→Manifest: construction takes nothing, and
|
|
398
|
+
parameterization happens before `#call` ever runs — definition lets
|
|
399
|
+
filling a concern's declared holes, or declaration-time values closed
|
|
400
|
+
over in a leaf `Callable`. Authors implement `#transform`; `#call` is
|
|
401
|
+
framework-owned and makes the one deep boundary copy (Marshal, because
|
|
402
|
+
the carrier is data), so input-never-mutated is a constraint rather
|
|
403
|
+
than a convention, and a fresh instance per call scopes let memoization
|
|
404
|
+
to a single manifest pass. The Kubernetes dialect is bare vocabulary
|
|
405
|
+
inside `#transform`. Registry type `presenter`, definitions in
|
|
406
|
+
`platform/presenters`, `Registry.presenter(name)` reader, bare classes
|
|
407
|
+
equally valid. No built-ins ship yet.
|
|
408
|
+
- **`Transform::Kubernetes` — the manifest dialect.** Algebra + Env
|
|
409
|
+
addressed to Kubernetes shapes: `set_env`, `set_all_env`,
|
|
410
|
+
`set_resources` (strict, `EntryNotFound` on unknown container) and its
|
|
411
|
+
curried `with_resources`, `set_label`, `append_volume`,
|
|
412
|
+
`append_volume_mount`, `without_container`, and the node-affinity
|
|
413
|
+
helpers over a well-known path constant — a misspelled path cannot
|
|
414
|
+
exist at a call site. Pod now draws `expand_env` from `Transform::Env`
|
|
415
|
+
instead of defining its own (`sorted_env` unchanged).
|
|
416
|
+
- **`Transform::Combinators` — Element→Element machinery.** `Match`
|
|
417
|
+
applies its element only to manifests meeting the declared
|
|
418
|
+
`{kind:, name:, namespace:}` criteria (the import-lookup addressing
|
|
419
|
+
grammar; unknown keys are an error) and is the identity otherwise.
|
|
420
|
+
The `Helpers` module carries the declaration vocabulary — `match`,
|
|
421
|
+
`match_kind`, `match_deployments`; sugars grow on demand. Combinators
|
|
422
|
+
are never registry-named: names cross the declaration boundary,
|
|
423
|
+
machinery does not.
|
|
424
|
+
- **`Matsuri::Transform` — the pure manifest-transform strata.** Nothing
|
|
425
|
+
under the namespace shells out; everything operates on plain data and
|
|
426
|
+
composes as values. `Transform::Algebra` carries the path-addressed ops
|
|
427
|
+
(`update_in`, `append_to`, `update_each`, `append_each`, `index_by_name`
|
|
428
|
+
plus a strict `index_by_name!`, `except_entry_by_name`); contributions
|
|
429
|
+
are values composed by `+`, never super()-chained deltas.
|
|
430
|
+
`Transform::Callable` is the free monoid over transforms: `new` =
|
|
431
|
+
identity, `new(label:) { }` = leaf, `new([members])` = composite, `+` =
|
|
432
|
+
operand member-list concat into a new instance — never flattened, so a
|
|
433
|
+
composite member stays one member and its label rides along as trace
|
|
434
|
+
metadata. Coercion happens at construction and fails fast (#call-ables
|
|
435
|
+
as-is; Symbol/String will name registered presenters). `Transform::Env`
|
|
436
|
+
is the array↔map isomorphism for container env entries. Errors
|
|
437
|
+
(`EntryNotFound`, `PathNotFound`) raise rather than log fatal. Inert in
|
|
438
|
+
this phase: nothing calls the strata yet; `Concerns::ImportManifest` and
|
|
439
|
+
Pod's env helpers are untouched until they rebuild on top. Law specs pin
|
|
440
|
+
identity, associativity, zero-point, structural homomorphism,
|
|
441
|
+
no-flatten representation, carrier-is-data, and the env round trip.
|
|
442
|
+
- **Acceptor collections on stacks.** `accept(collection, acceptor)`
|
|
443
|
+
registers an acceptor (class or registry name) under a named collection;
|
|
444
|
+
last declaration per collection wins, so re-declaring overrides. The
|
|
445
|
+
collection name is the options key: each acceptor admits its own
|
|
446
|
+
component of the invocation options, whole, and cannot see another
|
|
447
|
+
collection's. `Matsuri::Stack` ships two built-ins — `:image_tag`
|
|
448
|
+
carries the converge positional (a scalar, admitted whole, owning the
|
|
449
|
+
`latest` default) and `:config` carries the stack-config artifact (the
|
|
450
|
+
built-in is the empty acceptor: the collection is total, its surface is
|
|
451
|
+
empty until a platform re-declares it).
|
|
452
|
+
- **stack-config.** `--stack-config=PATH` on `converge` and the new
|
|
453
|
+
`show stack` / `diff stack` verbs names a YAML artifact for the stack
|
|
454
|
+
instance being addressed. The CLI owns the boundary mapping — it loads
|
|
455
|
+
the file and supplies data under the `:config` key; the stack itself
|
|
456
|
+
never loads anything, so specs, controllers, and tooling construct
|
|
457
|
+
stacks with data directly. A platform declares its accepted surface
|
|
458
|
+
with `accept :config, <acceptor>`; resource members read the bound
|
|
459
|
+
acceptor instance as `stack_config` (nil when a definition is invoked
|
|
460
|
+
standalone), and the pod template forwards it, so pod definitions read
|
|
461
|
+
the same binding. `Stack#delete!` instantiates members with the same
|
|
462
|
+
options as converge, so a member whose identity derives from an
|
|
463
|
+
accepted value addresses the same resource it created. No flag is the
|
|
464
|
+
empty value and every declared read lands on its default; a flag with a
|
|
465
|
+
bad path fails loudly, never falls back. stack-config never crosses a
|
|
466
|
+
stack boundary — nested stack deps do not inherit it.
|
|
467
|
+
- **Acceptor** — a class that admits a value whose vocabulary matsuri does
|
|
468
|
+
not own, such as a per-instance config artifact. The raw value arrives
|
|
469
|
+
whole as `options`; `let` declarations are the accepted surface.
|
|
470
|
+
Undeclared reads raise `NoMethodError`, unconsumed fields pass in
|
|
471
|
+
silence, and every declared read carries its default, so the empty value
|
|
472
|
+
is valid. Registry type `acceptor`, definitions in `platform/acceptors`,
|
|
473
|
+
bare classes equally valid. See docs/acceptors.md and
|
|
474
|
+
docs/design/acceptors.md.
|
|
475
|
+
|
|
476
|
+
### Fixed
|
|
477
|
+
|
|
478
|
+
- **The rollout watch returns as a task.**
|
|
479
|
+
`Tasks::KubectlOps.rollout_status(resource)`
|
|
480
|
+
(`kubectl rollout status`) is the post-apply progress watch for
|
|
481
|
+
manually delivered changes. `Deployment#watch_rollout!` — dead since
|
|
482
|
+
resources stopped shelling out — is removed rather than revived:
|
|
483
|
+
resources are pure generators and carry no verbs, delegating ones
|
|
484
|
+
included. Callers address the boundary function with the resource.
|
|
485
|
+
- **Two latent `ImportManifest` bugs die by construction with the move
|
|
486
|
+
to the strata.** `append_node_affinity` referenced a misspelled path
|
|
487
|
+
name and raised `NameError` on every call; the dialect reads the
|
|
488
|
+
`NODE_SELECTOR_TERMS_PATH` constant, so a misspelled path cannot
|
|
489
|
+
exist at a call site. `set_env` from an imported manifest crashed
|
|
490
|
+
because `update_kv` called `expand_env`, which only existed on Pod;
|
|
491
|
+
both now live in `Transform::Env`, and the vendor-app fixture pins
|
|
492
|
+
the repaired path end-to-end.
|
|
493
|
+
|
|
494
|
+
## [0.2.0] - 2026-10-24
|
|
495
|
+
|
|
496
|
+
Modernization release: the gem now targets Ruby >= 3.2 (developed and tested
|
|
497
|
+
on Ruby 4.0), drops ActiveSupport, and fixes a series of latent
|
|
498
|
+
manifest-rendering bugs — several of which change rendered output or diff
|
|
499
|
+
behavior. Review the Breaking and Changed sections before upgrading.
|
|
500
|
+
|
|
501
|
+
### Breaking
|
|
502
|
+
|
|
503
|
+
- **Ruby floor is now 3.2** (`required_ruby_version >= 3.2`). Developed and
|
|
504
|
+
tested on Ruby 4.0. Ruby 2.x is no longer supported.
|
|
505
|
+
- **ActiveSupport is no longer a dependency.** The nine core_ext slices
|
|
506
|
+
matsuri used (deep key transforms, `try`, `blank?`/`present?`/`presence`,
|
|
507
|
+
`camelize`, `class_attribute`) are vendored in `matsuri/core_ext` as
|
|
508
|
+
monkeypatches, so definition files keep their ambient grammar. Downstream
|
|
509
|
+
repos that relied on matsuri transitively loading the rest of ActiveSupport
|
|
510
|
+
must now require it themselves.
|
|
511
|
+
- **Pod security context lets renamed**: `priviledged` →
|
|
512
|
+
`privileged`, `allow_priviledge_escalation` → `allow_privilege_escalation`.
|
|
513
|
+
The old misspelled keys rendered field names the SecurityContext API does
|
|
514
|
+
not have (latent while nil-and-compacted); definitions overriding the old
|
|
515
|
+
lets must rename them.
|
|
516
|
+
- **`frozen_string_literal: true` across the codebase.** Strings returned by
|
|
517
|
+
matsuri lets are frozen; definition code that mutated them in place will
|
|
518
|
+
raise `FrozenError`.
|
|
519
|
+
- `awesome_print` removed (debugging residue in `imported_manifest`, never a
|
|
520
|
+
declared dependency).
|
|
521
|
+
|
|
522
|
+
### Changed
|
|
523
|
+
|
|
524
|
+
Semantics changes — same DSL, different rendered output or runtime behavior:
|
|
525
|
+
|
|
526
|
+
- **`mem_request` now defaults to `mem_limit`** (previously defaulted to
|
|
527
|
+
`cpu_limit`, a copy-paste error): pods defining only limits requested
|
|
528
|
+
cpu-shaped quantities as memory.
|
|
529
|
+
- **Diff output is stricter.** The nil-vs-zero diff filter is now scoped to
|
|
530
|
+
`spec.minReadySeconds` as originally intended (a missing `and` had made the
|
|
531
|
+
path comparison a void expression). Declared zeros against a cluster nil —
|
|
532
|
+
e.g. `runAsUser: 0` — now surface on all other paths.
|
|
533
|
+
- **Binding subjects render `apiGroup`** (camelCase) instead of a snake_case
|
|
534
|
+
`api_group` field that lenient decoding silently dropped. ServiceAccount
|
|
535
|
+
subjects had survived on the implicit `""` default; User and Group subjects
|
|
536
|
+
would fail reconcile on strict decoding. The DSL-facing `api_group:`
|
|
537
|
+
parameter is unchanged.
|
|
538
|
+
- **Roles without `bind_to` no longer emit bindings with empty subjects**;
|
|
539
|
+
the subjects guard in `Binding#rbac_manifests` is honored, and subject-less
|
|
540
|
+
bindings return nil (not false) so they compact away instead of crashing
|
|
541
|
+
downstream key stringification.
|
|
542
|
+
- **ServiceAccount collections (`secrets`, `imagePullSecrets`) render only
|
|
543
|
+
when non-empty.** An explicit empty list in applied configuration claims
|
|
544
|
+
field ownership and deletes entries other actors add; an absent field makes
|
|
545
|
+
no claim.
|
|
546
|
+
- **CronJob `timeZone`** renders the correct camelCase API field (the old
|
|
547
|
+
snake_case key was invisible while unset, wrong the moment anyone set it);
|
|
548
|
+
the job template spec now compacts, no longer emitting explicit nulls that
|
|
549
|
+
strategic merge reads as field clears. The `sucess_policy` let is renamed
|
|
550
|
+
`success_policy`.
|
|
551
|
+
- **Metadata default/override/final triple completed** in the cluster DSL:
|
|
552
|
+
`final_metadata` is the user merge hook, and Binding, Role, ClusterRole,
|
|
553
|
+
and ServiceAccount consume it. Rendered output is unchanged for existing
|
|
554
|
+
definitions. `default_metadata` now compacts (cluster-scoped resources stop
|
|
555
|
+
carrying a null `namespace` key into last-applied state), and namespaces
|
|
556
|
+
coerce with `&.to_s` so symbol namespaces render identically on the YAML
|
|
557
|
+
and JSON paths.
|
|
558
|
+
- Pod affinity `label_selector` compacts: affinity terms no longer render an
|
|
559
|
+
explicit `matchExpressions` null.
|
|
560
|
+
|
|
561
|
+
### Added
|
|
562
|
+
|
|
563
|
+
- **`Concerns::Pods::SpreadPods`** — opt-in pod anti-affinity vocabulary for
|
|
564
|
+
spreading same-labeled pods across nodes or zones. Not included in Pod by
|
|
565
|
+
default; the includer selects a strategy via `let(:pod_anti_affinity)`.
|
|
566
|
+
The zone topology key is its own let, defaulting to the well-known
|
|
567
|
+
`topology.kubernetes.io/zone`.
|
|
568
|
+
- **`Concerns::Pods::SpreadTopology`** — opt-in topology spread constraint
|
|
569
|
+
vocabulary complementing SpreadPods: constraints express balance (bounded
|
|
570
|
+
replica skew per domain) where anti-affinity expresses exclusion. One
|
|
571
|
+
`balanced_spread` builder specialized by topology-key lets; knobs ladder
|
|
572
|
+
from shared defaults (`spread_max_skew`, `spread_when_unsatisfiable`) to
|
|
573
|
+
per-domain overrides; strategies return lists and compose by
|
|
574
|
+
concatenation. `zone_topology_key` is shared with SpreadPods so one
|
|
575
|
+
override re-points both mechanisms.
|
|
576
|
+
- **CronJob** resource.
|
|
577
|
+
- **ImportedManifest** resource with `show`/`apply` command support, plus
|
|
578
|
+
`ImportManifest` manifest-transform helpers and `ExportManifest`
|
|
579
|
+
(refactored from `TransformManifest`).
|
|
580
|
+
- Pod template helpers; updated volume mount helper.
|
|
581
|
+
- Golden-manifest spec suite replacing the untouched gem-template skeleton:
|
|
582
|
+
fixture-driven goldens for pod, deployment, stateful set, cron job, and the
|
|
583
|
+
cluster RBAC DSL; specs for the vendored core_ext shims; load-order specs
|
|
584
|
+
that touch each entry constant first in a fresh process (two load-order
|
|
585
|
+
bugs surfaced during conversion); worked examples of bulkhead deployments
|
|
586
|
+
over a shared podspec with a two-axis identity scheme (`app_role` ×
|
|
587
|
+
`resource_class`) and a selector pairwise-disjointness law.
|
|
588
|
+
- `base64` gemspec dependency (left Ruby's default gems in 3.4; `secret` and
|
|
589
|
+
`config_map` fail to load under bundler without it).
|
|
590
|
+
- Nix flake development environment.
|
|
591
|
+
|
|
592
|
+
### Fixed
|
|
593
|
+
|
|
594
|
+
- `Matsuri.define(:type, name, inherits: parent)` raised `ArgumentError` on
|
|
595
|
+
Ruby 3+: keyword separation turned kwargs passed through the bare `*args`
|
|
596
|
+
forwarder into a positional hash. The facade now mirrors
|
|
597
|
+
`Registry.define`'s real signature.
|
|
598
|
+
- **`AggregatedClusterRole` is now actually defined.** Its file reopened
|
|
599
|
+
`ClusterRole` instead of defining its own class, so the autoload constant
|
|
600
|
+
never resolved — and loading the file would have replaced the regular
|
|
601
|
+
ClusterRole manifest (aggregationRule in place of rules) for every cluster
|
|
602
|
+
role.
|
|
603
|
+
- **The namespace/scope cluster DSL was broken end to end**: `scope()`
|
|
604
|
+
referenced a wrong constant, discarded the child scope instead of appending
|
|
605
|
+
it to definitions, and `require_namespace!` ignored the enclosing namespace
|
|
606
|
+
scope its own error message offered. `aggregated_cluster_role()` now
|
|
607
|
+
instantiates `AggregatedClusterRole`; it previously built a plain
|
|
608
|
+
ClusterRole whose block would fail on the `match` helper.
|
|
609
|
+
- ServiceAccount `image_pull_secret` and `secret` helpers pushed into
|
|
610
|
+
singular keys that initialize never created, raising `NoMethodError`; the
|
|
611
|
+
storage and k8s fields are plural.
|
|
612
|
+
- `maybe_param_replicas` read `options[:relicas]` in all four workload
|
|
613
|
+
classes, so passing `replicas` through options never worked.
|
|
614
|
+
- HPA: `average_value_metric_target` typo.
|
|
615
|
+
- Comparing a unit against an empty value no longer errors.
|
|
616
|
+
- Config: capital-F `Fail` in the `base_path` default raised `NoMethodError`
|
|
617
|
+
instead of the intended message.
|
|
618
|
+
- Registry `options_for_type` listing: the basename/uniq/sort chain applied
|
|
619
|
+
only to the second glob, so "Which <type>?" listings printed full paths for
|
|
620
|
+
environment-dir definitions and never deduplicated.
|
|
621
|
+
- `Matsuri::Cmds::Cli` subclasses Thor but never required it, freeloading off
|
|
622
|
+
the initializer; loading it first raised `NameError`.
|
|
623
|
+
- App: removed a duplicate `build!` stub silently shadowed by the later
|
|
624
|
+
definition, and dead `call_on_failure` referencing a `failure_hooks` that
|
|
625
|
+
was defined nowhere.
|
|
626
|
+
- `rlet/lazy_options` alone cannot resolve `Concern` under rlet 0.7;
|
|
627
|
+
`app.rb` now requires the rlet umbrella so Registry/App load as a library
|
|
628
|
+
without the CLI.
|
|
629
|
+
|
|
630
|
+
### Removed
|
|
631
|
+
|
|
632
|
+
- ActiveSupport dependency (see Breaking).
|
|
633
|
+
- `awesome_print` require (see Breaking).
|
|
634
|
+
- `.travis.yml` (targeted ruby 2.2.2 and bundler 1.10.6).
|
|
635
|
+
|
|
636
|
+
## [0.1.6] - 2022-03-24
|
|
637
|
+
|
|
638
|
+
Last release before this changelog was introduced. See git history.
|
|
639
|
+
|
|
640
|
+
[0.3.0]: https://github.com/matsuri-rb/matsuri/compare/v0.2.0...v0.3.0
|
|
641
|
+
[0.2.0]: https://github.com/matsuri-rb/matsuri/compare/v0.1.6...v0.2.0
|
|
642
|
+
[0.1.6]: https://github.com/matsuri-rb/matsuri/releases/tag/v0.1.6
|