metatron 0.11.4 → 0.11.5
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 +4 -4
- data/.dockerignore +9 -0
- data/.github/workflows/ci.yml +65 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +32 -1
- data/Gemfile +5 -0
- data/Gemfile.lock +12 -7
- data/README.md +52 -0
- data/Rakefile +5 -0
- data/integration/Dockerfile +29 -0
- data/integration/controller/Gemfile +8 -0
- data/integration/controller/Gemfile.lock +30 -0
- data/integration/controller/config.ru +21 -0
- data/integration/controller/lib/request_logger.rb +18 -0
- data/integration/controller/lib/widget_controller.rb +84 -0
- data/integration/manifests/composite-controller.yaml +62 -0
- data/integration/manifests/controller.yaml +70 -0
- data/integration/manifests/crd.yaml +46 -0
- data/integration/manifests/namespace.yaml +6 -0
- data/integration/manifests/sample-widget.yaml +9 -0
- data/lib/metatron/composite_controller.rb +4 -1
- data/lib/metatron/controller.rb +49 -7
- data/lib/metatron/middleware/auth.rb +103 -0
- data/lib/metatron/middleware/body_limit.rb +85 -0
- data/lib/metatron/problem.rb +47 -0
- data/lib/metatron/template.rb +4 -0
- data/lib/metatron/version.rb +1 -1
- data/lib/metatron.rb +10 -1
- data/metatron.gemspec +3 -2
- data/script/integration +182 -0
- metadata +30 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab7165f1ce223f66f6ae1678aec0e705cdd2b5e8e0c1753020ca6609d4da086d
|
|
4
|
+
data.tar.gz: 71b400f058f878a7b894ed4bbd94a6b0b56ad2fa4e92718fc316bc8561d226d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68786131eb7b2c690bd99e76cabbcb3aee0b30911ae91cc896953863294e276be86d46397b57708e5488d07aaf0e74a9aeccd873747abb495b5a2be766a8344d
|
|
7
|
+
data.tar.gz: 5c35532f391ff7d6b6878870ea57994124f3afb2c4dfd34eb0c6bf7228d8fa9942951dbea9fa7a86a8f3f7fd4e00cc0dfcf4a2dea36e5d7c4aca0c4d9d42d111
|
data/.dockerignore
ADDED
data/.github/workflows/ci.yml
CHANGED
|
@@ -2,6 +2,13 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: "0 6 * * *"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
5
12
|
|
|
6
13
|
jobs:
|
|
7
14
|
lint:
|
|
@@ -52,3 +59,61 @@ jobs:
|
|
|
52
59
|
|
|
53
60
|
- name: Run tests
|
|
54
61
|
run: bundle exec rake spec
|
|
62
|
+
|
|
63
|
+
security:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- name: Checkout code
|
|
67
|
+
uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- name: Set up Ruby
|
|
70
|
+
uses: ruby/setup-ruby@v1
|
|
71
|
+
with:
|
|
72
|
+
ruby-version: .ruby-version
|
|
73
|
+
bundler-cache: true
|
|
74
|
+
|
|
75
|
+
- name: Refresh the advisory database
|
|
76
|
+
run: bundle exec bundler-audit update
|
|
77
|
+
|
|
78
|
+
- name: Audit dependencies for known vulnerabilities
|
|
79
|
+
run: bundle exec bundler-audit check
|
|
80
|
+
|
|
81
|
+
integration:
|
|
82
|
+
name: integration (k3d ${{ matrix.k3d }}, Metacontroller ${{ matrix.metacontroller }})
|
|
83
|
+
# Keep cluster-backed work behind the fast, cheap jobs.
|
|
84
|
+
needs: [lint, yard, test, security]
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
timeout-minutes: 30
|
|
87
|
+
strategy:
|
|
88
|
+
fail-fast: false
|
|
89
|
+
matrix:
|
|
90
|
+
k3d: ["v5.9.0"]
|
|
91
|
+
metacontroller: ["v4.17.6"]
|
|
92
|
+
env:
|
|
93
|
+
K3D_VERSION: ${{ matrix.k3d }}
|
|
94
|
+
METACONTROLLER_VERSION: ${{ matrix.metacontroller }}
|
|
95
|
+
K3D_CLUSTER_NAME: metatron-it-${{ github.run_id }}
|
|
96
|
+
|
|
97
|
+
steps:
|
|
98
|
+
- name: Checkout code
|
|
99
|
+
uses: actions/checkout@v4
|
|
100
|
+
|
|
101
|
+
- name: Set up Ruby
|
|
102
|
+
uses: ruby/setup-ruby@v1
|
|
103
|
+
with:
|
|
104
|
+
ruby-version: .ruby-version
|
|
105
|
+
bundler-cache: true
|
|
106
|
+
|
|
107
|
+
- name: Install k3d
|
|
108
|
+
run: |
|
|
109
|
+
curl -fsSL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh \
|
|
110
|
+
| TAG="$K3D_VERSION" bash
|
|
111
|
+
|
|
112
|
+
- name: Verify container tooling
|
|
113
|
+
run: |
|
|
114
|
+
docker version
|
|
115
|
+
k3d version
|
|
116
|
+
kubectl version --client
|
|
117
|
+
|
|
118
|
+
- name: Run integration tests
|
|
119
|
+
run: script/integration
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.5](https://github.com/jgnagy/metatron/compare/metatron/v0.11.4...metatron/v0.11.5) (2026-11-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add k3d/Metacontroller integration harness ([953e331](https://github.com/jgnagy/metatron/commit/953e331fcaee3ee0a2a949582aacd821e01bace4))
|
|
9
|
+
* add k3d/Metacontroller integration harness ([16bd24a](https://github.com/jgnagy/metatron/commit/16bd24aa2e3b4202ce2f58e27baa59305286fd08))
|
|
10
|
+
* add name validation to Template base class (closes [#15](https://github.com/jgnagy/metatron/issues/15)) ([#102](https://github.com/jgnagy/metatron/issues/102)) ([caabdef](https://github.com/jgnagy/metatron/commit/caabdefbe87e92800adfb53a830674e631079b54))
|
|
11
|
+
* **security:** controller-level request body cap (M-1) ([c263e14](https://github.com/jgnagy/metatron/commit/c263e14700ecca7263f82f8a2666df887261b316))
|
|
12
|
+
* **security:** opt-in auth + body-limit middleware, controller exception boundary ([481d44c](https://github.com/jgnagy/metatron/commit/481d44cfc1782ffb7a223d4da12fe7d7ec280d05))
|
|
13
|
+
* **security:** webhook auth, request body cap, exception boundary — best of both ([dd60c8a](https://github.com/jgnagy/metatron/commit/dd60c8a1324b398031fa56b0a160e36012764576))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **deps:** raise gemspec floors past CVE-vulnerable rack/json, add bundler-audit CI ([c9e5ab0](https://github.com/jgnagy/metatron/commit/c9e5ab0c88eba4622c528d29273385f4ef86cf02))
|
|
19
|
+
* **deps:** raise gemspec floors past CVE-vulnerable rack/json, add bundler-audit CI ([5a3ed39](https://github.com/jgnagy/metatron/commit/5a3ed39c6b8a94e768c447d7cf2849e5b0df2e53))
|
|
20
|
+
* return 304 for matched ETags ([c38da6f](https://github.com/jgnagy/metatron/commit/c38da6f8b60681fd56096e6c3591d967698dfadd))
|
|
21
|
+
|
|
3
22
|
## [0.11.4](https://github.com/jgnagy/metatron/compare/metatron/v0.11.3...metatron/v0.11.4) (2026-07-06)
|
|
4
23
|
|
|
5
24
|
|
data/CONTRIBUTING.md
CHANGED
|
@@ -16,7 +16,38 @@ Before you begin, ensure that your contribution is associated with a [GitHub iss
|
|
|
16
16
|
|
|
17
17
|
Please make sure that your code passes the existing tests and add tests for any new functionality. Please write [RSpec](https://rspec.info/) examples for new features you add or modify.
|
|
18
18
|
|
|
19
|
-
To execute the tests, run `bundle exec rake spec`.
|
|
19
|
+
To execute the unit tests, run `bundle exec rake spec`.
|
|
20
|
+
|
|
21
|
+
#### Integration Testing
|
|
22
|
+
|
|
23
|
+
Metatron also has a cluster-backed suite that runs a fixture controller against a
|
|
24
|
+
real [Metacontroller](https://metacontroller.github.io/metacontroller/)
|
|
25
|
+
installation. It requires Docker, [`k3d`](https://k3d.io/), and `kubectl`.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bundle exec rake integration
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The task builds the fixture image, creates a throwaway k3d cluster, installs a
|
|
32
|
+
pinned Metacontroller release, runs `spec/integration`, and deletes the cluster.
|
|
33
|
+
Useful options:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
script/integration --keep # leave the cluster running
|
|
37
|
+
script/integration --metacontroller-version v4.17.5
|
|
38
|
+
script/integration --k3d-version v5.9.0
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Set `METACONTROLLER_VERSION` to test another Metacontroller release and
|
|
42
|
+
`K3D_VERSION` to assert the expected k3d binary is installed. CI currently pins
|
|
43
|
+
the latest k3d and Metacontroller releases, using a matrix that is ready for
|
|
44
|
+
additional versions. The integration job runs only after the faster lint,
|
|
45
|
+
test, security, and docs jobs succeed.
|
|
46
|
+
If a local Metacontroller clone exists at
|
|
47
|
+
`$HOME/Development/golang/metacontroller` with the requested tag, its manifests
|
|
48
|
+
are used instead of downloading them; otherwise the pinned release manifests
|
|
49
|
+
are fetched from GitHub. The normal `rake spec` task skips these examples
|
|
50
|
+
unless `RUN_INTEGRATION=1` is set.
|
|
20
51
|
|
|
21
52
|
### Coding Style
|
|
22
53
|
|
data/Gemfile
CHANGED
|
@@ -6,3 +6,8 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
|
6
6
|
|
|
7
7
|
# Specify your gem's dependencies in metatron.gemspec
|
|
8
8
|
gemspec
|
|
9
|
+
|
|
10
|
+
group :development, :test do
|
|
11
|
+
# Dependency vulnerability scanner; run by the `security` CI job on every PR.
|
|
12
|
+
gem "bundler-audit", "~> 0.9"
|
|
13
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
metatron (0.11.
|
|
4
|
+
metatron (0.11.5)
|
|
5
5
|
base64
|
|
6
|
-
json (
|
|
7
|
-
rack (>= 2.2.
|
|
6
|
+
json (>= 2.21.2, < 3)
|
|
7
|
+
rack (>= 2.2.25, < 4)
|
|
8
8
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
@@ -13,11 +13,14 @@ GEM
|
|
|
13
13
|
backport (1.2.0)
|
|
14
14
|
base64 (0.2.0)
|
|
15
15
|
benchmark (0.4.0)
|
|
16
|
+
bundler-audit (0.9.3)
|
|
17
|
+
bundler (>= 1.2.0)
|
|
18
|
+
thor (~> 1.0)
|
|
16
19
|
byebug (11.1.3)
|
|
17
20
|
diff-lcs (1.6.0)
|
|
18
21
|
docile (1.4.1)
|
|
19
22
|
jaro_winkler (1.6.0)
|
|
20
|
-
json (2.
|
|
23
|
+
json (2.21.2)
|
|
21
24
|
kramdown (2.5.1)
|
|
22
25
|
rexml (>= 3.3.9)
|
|
23
26
|
kramdown-parser-gfm (1.1.0)
|
|
@@ -25,9 +28,9 @@ GEM
|
|
|
25
28
|
language_server-protocol (3.17.0.4)
|
|
26
29
|
lint_roller (1.1.0)
|
|
27
30
|
logger (1.6.6)
|
|
28
|
-
nokogiri (1.19.
|
|
31
|
+
nokogiri (1.19.7-arm64-darwin)
|
|
29
32
|
racc (~> 1.4)
|
|
30
|
-
nokogiri (1.19.
|
|
33
|
+
nokogiri (1.19.7-x86_64-linux-gnu)
|
|
31
34
|
racc (~> 1.4)
|
|
32
35
|
observer (0.1.2)
|
|
33
36
|
ostruct (0.6.1)
|
|
@@ -36,7 +39,7 @@ GEM
|
|
|
36
39
|
ast (~> 2.4.1)
|
|
37
40
|
racc
|
|
38
41
|
racc (1.8.1)
|
|
39
|
-
rack (3.
|
|
42
|
+
rack (3.2.8)
|
|
40
43
|
rack-test (2.2.0)
|
|
41
44
|
rack (>= 1.3)
|
|
42
45
|
rainbow (3.1.1)
|
|
@@ -126,10 +129,12 @@ PLATFORMS
|
|
|
126
129
|
arm64-darwin-22
|
|
127
130
|
arm64-darwin-23
|
|
128
131
|
arm64-darwin-24
|
|
132
|
+
arm64-darwin-27
|
|
129
133
|
x86_64-linux
|
|
130
134
|
|
|
131
135
|
DEPENDENCIES
|
|
132
136
|
bundler (~> 2.3)
|
|
137
|
+
bundler-audit (~> 0.9)
|
|
133
138
|
byebug (~> 11)
|
|
134
139
|
metatron!
|
|
135
140
|
rack-test (~> 2.0)
|
data/README.md
CHANGED
|
@@ -8,6 +8,58 @@ For more information, see the [Metatron Wiki on GitHub](https://github.com/jgnag
|
|
|
8
8
|
|
|
9
9
|
For help on how to get started, take a look at the [User Guide](https://github.com/jgnagy/metatron/wiki/User-Guide) in the Wiki!
|
|
10
10
|
|
|
11
|
+
## Security
|
|
12
|
+
|
|
13
|
+
Metatron controllers are HTTP endpoints. By default they answer any request that
|
|
14
|
+
looks like a Metacontroller sync request, which means any pod that can reach the
|
|
15
|
+
webhook port can drive your controller — and in controllers that re-render
|
|
16
|
+
existing child `Secrets`, can read credentials.
|
|
17
|
+
|
|
18
|
+
Metatron ships two opt-in middleware for the two things that matter most:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
use Metatron::Middleware::BodyLimit # rejects bodies over 1 MiB with a 413
|
|
22
|
+
use Metatron::Middleware::Auth # requires the Bearer token shared with Metacontroller
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Both are configurable: `METATRON_AUTH_SECRET` (leave it unset and the auth
|
|
26
|
+
middleware stays inert) and `METATRON_MAX_REQUEST_BODY_BYTES` (1 MiB by default,
|
|
27
|
+
which is generous for sync payloads). See the
|
|
28
|
+
[User Guide](https://github.com/jgnagy/metatron/wiki/User-Guide#securing-the-webhook-endpoint)
|
|
29
|
+
for the full setup, including the `webhook.authorization.secretRef` half of the
|
|
30
|
+
handshake on the Metacontroller side.
|
|
31
|
+
|
|
32
|
+
Errors raised inside `sync`, `customize`, or `finalize` are caught and returned
|
|
33
|
+
as generic JSON `500` problem documents, with the details written to
|
|
34
|
+
`Metatron.logger`. Do not add `Rack::ShowExceptions` to a production stack.
|
|
35
|
+
|
|
36
|
+
## ETags
|
|
37
|
+
|
|
38
|
+
If a controller implements `calculate_sync_etag` or
|
|
39
|
+
`calculate_customize_etag`, enable ETag handling on the Metacontroller webhook
|
|
40
|
+
(`etag.enabled: true`). Metatron answers a matching `If-None-Match` request
|
|
41
|
+
with `304 Not Modified`, which Metacontroller uses to replay its cached
|
|
42
|
+
response instead of re-rendering unchanged children.
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
Unit tests run without any cluster dependencies:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
bundle exec rake spec
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The cluster-backed integration suite runs Metatron against a real
|
|
53
|
+
Metacontroller installation in a throwaway [k3d](https://k3d.io/) cluster. It
|
|
54
|
+
requires Docker, `k3d`, and `kubectl`:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
bundle exec rake integration
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
See the [Contributing Guide](CONTRIBUTING.md#integration-testing) for version
|
|
61
|
+
overrides and debugging options.
|
|
62
|
+
|
|
11
63
|
## Contributing
|
|
12
64
|
|
|
13
65
|
If you're interested in contributing to Metatron, please see the [Contributing Guide](CONTRIBUTING.md) in the repository! Be sure to check out the [Code of Conduct](CODE_OF_CONDUCT.md) as well!
|
data/Rakefile
CHANGED
|
@@ -16,4 +16,9 @@ task :demo do
|
|
|
16
16
|
system("rackup --host 0.0.0.0 -P #{File.expand_path(".")}/tmp/daemon.pid")
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
desc "run integration specs against a real Metacontroller in a k3d cluster"
|
|
20
|
+
task :integration do
|
|
21
|
+
sh File.expand_path("script/integration", __dir__)
|
|
22
|
+
end
|
|
23
|
+
|
|
19
24
|
task default: %i[spec rubocop yard]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
FROM ruby:3.3-slim
|
|
2
|
+
|
|
3
|
+
ENV BUNDLE_JOBS=4 \
|
|
4
|
+
BUNDLE_RETRY=3 \
|
|
5
|
+
RACK_ENV=production
|
|
6
|
+
|
|
7
|
+
RUN apt-get update \
|
|
8
|
+
&& apt-get install -y --no-install-recommends build-essential git \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
|
|
13
|
+
# Keep the gemspec's `git ls-files` call from warning when the build context
|
|
14
|
+
# has no .git directory.
|
|
15
|
+
RUN git init --quiet
|
|
16
|
+
|
|
17
|
+
# Copy just enough of the gem for `bundle install` to resolve the path
|
|
18
|
+
# dependency, then the fixture app itself.
|
|
19
|
+
COPY metatron.gemspec ./
|
|
20
|
+
COPY lib ./lib
|
|
21
|
+
COPY integration/controller ./integration/controller
|
|
22
|
+
|
|
23
|
+
WORKDIR /app/integration/controller
|
|
24
|
+
|
|
25
|
+
RUN bundle install
|
|
26
|
+
|
|
27
|
+
EXPOSE 9292
|
|
28
|
+
|
|
29
|
+
CMD ["bundle", "exec", "puma", "-b", "tcp://0.0.0.0:9292", "config.ru"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ../..
|
|
3
|
+
specs:
|
|
4
|
+
metatron (0.11.4)
|
|
5
|
+
base64
|
|
6
|
+
json (>= 2.21.2, < 3)
|
|
7
|
+
rack (>= 2.2.25, < 4)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
base64 (0.3.0)
|
|
13
|
+
json (2.21.2)
|
|
14
|
+
nio4r (2.7.5)
|
|
15
|
+
puma (6.6.1)
|
|
16
|
+
nio4r (~> 2.0)
|
|
17
|
+
rack (3.2.8)
|
|
18
|
+
|
|
19
|
+
PLATFORMS
|
|
20
|
+
aarch64-linux
|
|
21
|
+
arm64-darwin-27
|
|
22
|
+
ruby
|
|
23
|
+
x86_64-linux
|
|
24
|
+
|
|
25
|
+
DEPENDENCIES
|
|
26
|
+
metatron!
|
|
27
|
+
puma (~> 6.4)
|
|
28
|
+
|
|
29
|
+
BUNDLED WITH
|
|
30
|
+
2.5.22
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
require "metatron"
|
|
5
|
+
require_relative "lib/request_logger"
|
|
6
|
+
require_relative "lib/widget_controller"
|
|
7
|
+
|
|
8
|
+
# The secured stack mirrors a realistic Metatron deployment: body limits and
|
|
9
|
+
# bearer-token auth in front of the CompositeController. `/ping` stays outside
|
|
10
|
+
# the auth stack so Kubernetes probes can reach it without a credential.
|
|
11
|
+
secured = Rack::Builder.new do
|
|
12
|
+
use IntegrationController::RequestLogger
|
|
13
|
+
use Metatron::Middleware::BodyLimit
|
|
14
|
+
use Metatron::Middleware::Auth
|
|
15
|
+
run IntegrationController::WidgetController
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
run Rack::URLMap.new(
|
|
19
|
+
"/ping" => Metatron::Controllers::Ping,
|
|
20
|
+
"/widgets" => secured
|
|
21
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IntegrationController
|
|
4
|
+
# Logs the final status of each request so integration specs can prove that
|
|
5
|
+
# paths like ETag short-circuiting (304) and auth rejection (401) actually
|
|
6
|
+
# happened in the cluster.
|
|
7
|
+
class RequestLogger
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
status, headers, body = @app.call(env)
|
|
14
|
+
Metatron.logger.info("#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]} -> #{status}")
|
|
15
|
+
[status, headers, body]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module IntegrationController
|
|
7
|
+
# A deliberately small CompositeController used to exercise Metatron against
|
|
8
|
+
# a real Metacontroller installation. It produces one of each child type the
|
|
9
|
+
# gem templates most often: a ConfigMap, a Secret, a Deployment, and a
|
|
10
|
+
# Service.
|
|
11
|
+
class WidgetController < Metatron::CompositeController
|
|
12
|
+
DEFAULT_IMAGE = "registry.k8s.io/pause:3.9"
|
|
13
|
+
|
|
14
|
+
def sync # rubocop:disable Metrics/AbcSize
|
|
15
|
+
parent = params.fetch("parent")
|
|
16
|
+
metadata = parent.fetch("metadata")
|
|
17
|
+
spec = parent.fetch("spec", {})
|
|
18
|
+
name = metadata.fetch("name")
|
|
19
|
+
namespace = metadata["namespace"]
|
|
20
|
+
replicas = Integer(spec.fetch("replicas", 1))
|
|
21
|
+
image = spec["image"] || DEFAULT_IMAGE
|
|
22
|
+
config_value = spec["configValue"] || "default"
|
|
23
|
+
parent_labels = { "metatron.therubyist.org/parent" => name }
|
|
24
|
+
|
|
25
|
+
config_map = Metatron::Templates::ConfigMap.new(
|
|
26
|
+
"#{name}-config",
|
|
27
|
+
{
|
|
28
|
+
"widget.properties" => "replicas=#{replicas}\nimage=#{image}\nconfigValue=#{config_value}\n"
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
config_map.namespace = namespace
|
|
32
|
+
config_map.additional_labels = parent_labels
|
|
33
|
+
|
|
34
|
+
secret = Metatron::Templates::Secret.new(
|
|
35
|
+
"#{name}-secret",
|
|
36
|
+
{ "token" => Digest::SHA256.hexdigest("#{name}:#{metadata["uid"]}") }
|
|
37
|
+
)
|
|
38
|
+
secret.namespace = namespace
|
|
39
|
+
secret.additional_labels = parent_labels
|
|
40
|
+
|
|
41
|
+
deployment = Metatron::Templates::Deployment.new(name, replicas: replicas)
|
|
42
|
+
deployment.namespace = namespace
|
|
43
|
+
deployment.additional_labels = parent_labels
|
|
44
|
+
deployment.additional_pod_labels = { "app.kubernetes.io/component" => "widget" }
|
|
45
|
+
deployment.containers << build_container(image, config_value)
|
|
46
|
+
|
|
47
|
+
service = Metatron::Templates::Service.new(name, 80)
|
|
48
|
+
service.namespace = namespace
|
|
49
|
+
service.additional_labels = parent_labels
|
|
50
|
+
service.additional_selector_labels = deployment.additional_pod_labels
|
|
51
|
+
service.ports.first[:targetPort] = 8080
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
status: { replicas: replicas, configValue: config_value },
|
|
55
|
+
children: [config_map, secret, deployment, service]
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def finalize
|
|
60
|
+
name = params.dig("parent", "metadata", "name")
|
|
61
|
+
Metatron.logger.info("finalize #{name}")
|
|
62
|
+
{ status: {}, finalized: true }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A stable ETag lets Metacontroller short-circuit unchanged resyncs with a
|
|
66
|
+
# 304 instead of re-applying identical children. The integration suite
|
|
67
|
+
# asserts that this path is taken at least once.
|
|
68
|
+
def calculate_sync_etag
|
|
69
|
+
children = sync.fetch(:children).map do |child|
|
|
70
|
+
child.respond_to?(:render) ? child.render : child
|
|
71
|
+
end
|
|
72
|
+
Digest::SHA256.hexdigest(JSON.generate(children))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_container(image, config_value)
|
|
78
|
+
container = Metatron::Templates::Container.new("widget", image)
|
|
79
|
+
container.ports = [{ name: "http", containerPort: 8080 }]
|
|
80
|
+
container.env = { "WIDGET_CONFIG" => config_value }
|
|
81
|
+
container
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
apiVersion: metacontroller.k8s.io/v1alpha1
|
|
2
|
+
kind: CompositeController
|
|
3
|
+
metadata:
|
|
4
|
+
name: widget-controller
|
|
5
|
+
spec:
|
|
6
|
+
generateSelector: true
|
|
7
|
+
# Resync often enough that the integration suite can observe the ETag
|
|
8
|
+
# short-circuit (304) without waiting for a long default interval.
|
|
9
|
+
resyncPeriodSeconds: 5
|
|
10
|
+
parentResource:
|
|
11
|
+
apiVersion: metatron.therubyist.org/v1
|
|
12
|
+
resource: widgets
|
|
13
|
+
childResources:
|
|
14
|
+
- apiVersion: apps/v1
|
|
15
|
+
resource: deployments
|
|
16
|
+
updateStrategy:
|
|
17
|
+
method: InPlace
|
|
18
|
+
- apiVersion: v1
|
|
19
|
+
resource: services
|
|
20
|
+
updateStrategy:
|
|
21
|
+
method: InPlace
|
|
22
|
+
- apiVersion: v1
|
|
23
|
+
resource: configmaps
|
|
24
|
+
updateStrategy:
|
|
25
|
+
method: InPlace
|
|
26
|
+
- apiVersion: v1
|
|
27
|
+
resource: secrets
|
|
28
|
+
updateStrategy:
|
|
29
|
+
method: InPlace
|
|
30
|
+
hooks:
|
|
31
|
+
sync:
|
|
32
|
+
webhook:
|
|
33
|
+
service:
|
|
34
|
+
name: widget-controller
|
|
35
|
+
namespace: __NAMESPACE__
|
|
36
|
+
port: 9292
|
|
37
|
+
protocol: http
|
|
38
|
+
path: /widgets/sync
|
|
39
|
+
etag:
|
|
40
|
+
enabled: true
|
|
41
|
+
cacheTimeoutSeconds: 300
|
|
42
|
+
cacheCleanupSeconds: 60
|
|
43
|
+
authorization:
|
|
44
|
+
type: Bearer
|
|
45
|
+
secretRef:
|
|
46
|
+
name: widget-controller-auth
|
|
47
|
+
namespace: __NAMESPACE__
|
|
48
|
+
key: token
|
|
49
|
+
finalize:
|
|
50
|
+
webhook:
|
|
51
|
+
service:
|
|
52
|
+
name: widget-controller
|
|
53
|
+
namespace: __NAMESPACE__
|
|
54
|
+
port: 9292
|
|
55
|
+
protocol: http
|
|
56
|
+
path: /widgets/finalize
|
|
57
|
+
authorization:
|
|
58
|
+
type: Bearer
|
|
59
|
+
secretRef:
|
|
60
|
+
name: widget-controller-auth
|
|
61
|
+
namespace: __NAMESPACE__
|
|
62
|
+
key: token
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: Secret
|
|
3
|
+
metadata:
|
|
4
|
+
name: widget-controller-auth
|
|
5
|
+
namespace: __NAMESPACE__
|
|
6
|
+
labels:
|
|
7
|
+
app: widget-controller
|
|
8
|
+
type: Opaque
|
|
9
|
+
stringData:
|
|
10
|
+
token: __AUTH_TOKEN__
|
|
11
|
+
---
|
|
12
|
+
apiVersion: apps/v1
|
|
13
|
+
kind: Deployment
|
|
14
|
+
metadata:
|
|
15
|
+
name: widget-controller
|
|
16
|
+
namespace: __NAMESPACE__
|
|
17
|
+
labels:
|
|
18
|
+
app: widget-controller
|
|
19
|
+
spec:
|
|
20
|
+
replicas: 1
|
|
21
|
+
selector:
|
|
22
|
+
matchLabels:
|
|
23
|
+
app: widget-controller
|
|
24
|
+
template:
|
|
25
|
+
metadata:
|
|
26
|
+
labels:
|
|
27
|
+
app: widget-controller
|
|
28
|
+
spec:
|
|
29
|
+
containers:
|
|
30
|
+
- name: widget-controller
|
|
31
|
+
image: __IMAGE__
|
|
32
|
+
imagePullPolicy: IfNotPresent
|
|
33
|
+
ports:
|
|
34
|
+
- name: http
|
|
35
|
+
containerPort: 9292
|
|
36
|
+
env:
|
|
37
|
+
- name: METATRON_AUTH_SECRET
|
|
38
|
+
valueFrom:
|
|
39
|
+
secretKeyRef:
|
|
40
|
+
name: widget-controller-auth
|
|
41
|
+
key: token
|
|
42
|
+
- name: LOG_LEVEL
|
|
43
|
+
value: info
|
|
44
|
+
readinessProbe:
|
|
45
|
+
httpGet:
|
|
46
|
+
path: /ping
|
|
47
|
+
port: http
|
|
48
|
+
initialDelaySeconds: 2
|
|
49
|
+
periodSeconds: 2
|
|
50
|
+
livenessProbe:
|
|
51
|
+
httpGet:
|
|
52
|
+
path: /ping
|
|
53
|
+
port: http
|
|
54
|
+
initialDelaySeconds: 5
|
|
55
|
+
periodSeconds: 10
|
|
56
|
+
---
|
|
57
|
+
apiVersion: v1
|
|
58
|
+
kind: Service
|
|
59
|
+
metadata:
|
|
60
|
+
name: widget-controller
|
|
61
|
+
namespace: __NAMESPACE__
|
|
62
|
+
labels:
|
|
63
|
+
app: widget-controller
|
|
64
|
+
spec:
|
|
65
|
+
selector:
|
|
66
|
+
app: widget-controller
|
|
67
|
+
ports:
|
|
68
|
+
- name: http
|
|
69
|
+
port: 9292
|
|
70
|
+
targetPort: http
|