nyl 0.2.2__tar.gz → 0.4.0__tar.gz
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.
- {nyl-0.2.2 → nyl-0.4.0}/.github/workflows/python.yaml +5 -0
- {nyl-0.2.2 → nyl-0.4.0}/PKG-INFO +7 -1
- {nyl-0.2.2 → nyl-0.4.0}/README.md +6 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-cmp/Dockerfile +15 -2
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration/profiles.md +21 -4
- nyl-0.4.0/docs/content/reference/glossary.md +27 -0
- nyl-0.2.2/docs/content/reference/templating/inlining.md → nyl-0.4.0/docs/content/reference/templating/inlining/helmcharts.md +5 -12
- nyl-0.4.0/docs/content/reference/templating/inlining/overview.md +10 -0
- nyl-0.4.0/docs/content/reference/templating/inlining/postprocessor.md +120 -0
- {nyl-0.2.2 → nyl-0.4.0}/pyproject.toml +1 -1
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/template.py +6 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/__init__.py +9 -3
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/dispatch_test.py +4 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/helmchart.py +1 -1
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/profiles/config.py +14 -1
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/profiles/kubeconfig.py +11 -2
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/placeholder.py +0 -32
- nyl-0.4.0/src/nyl/resources/postprocessor.py +128 -0
- nyl-0.4.0/src/nyl/resources/postprocessor_test.py +133 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/kubernetes.py +16 -0
- {nyl-0.2.2 → nyl-0.4.0}/.envrc +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/.github/workflows/docker.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/.github/workflows/docs.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/.gitignore +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/.kraken.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-cmp/README.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-cmp/plugin.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/.envrc +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/.secrets.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/.sops.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/README.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/argocd.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/nyl-project.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/index.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/applysets.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/argocd-plugin.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/cluster-connectivity.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration/environment.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration/projects.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration/secrets.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration-api/profiles.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/configuration-api/secrets.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/templating/basics.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/templating/components.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/content/reference/templating/secrets.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/mkdocs.yml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/pyproject.toml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/docs/uv.lock +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/example/manifests/postgres.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/example/nyl-profiles.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/example/nyl-secrets.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/example/secrets.yaml +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/README.md +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/__init__.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/dynamic/client.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/dynamic/discovery.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/dynamic/resource.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/watch/__init__.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/kubernetes/watch/watch.pyi +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/argocd.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/crds.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/new.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/profile.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/run.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/secrets.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/tools/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/tools/bcrypt.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/tools/sops.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/commands/tun.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/components.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/dispatch.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/generator/statefulsecret.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/profiles/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/profiles/config_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/profiles/tunnel.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/project/config.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/applyset.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/applyset_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/helmchart.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/resources/statefulsecret.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/config.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/config_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/null.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/sops.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/secrets/sops_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/templating.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/templating_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/fs.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/fs_test.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/kubectl.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/kvstore.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/loads.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/logging.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/shell.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/testing.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/typer.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/types.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/src/nyl/tools/yaml.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/tests/__init__.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/tests/conftest.py +0 -0
- {nyl-0.2.2 → nyl-0.4.0}/uv.lock +0 -0
|
@@ -38,6 +38,11 @@ jobs:
|
|
|
38
38
|
- name: Run ${{ matrix.step }}
|
|
39
39
|
run: krakenw run fmt lint
|
|
40
40
|
|
|
41
|
+
- name: Install the Kyverno CLI (required for some Nyl features)
|
|
42
|
+
uses: kyverno/action-install-cli@v0.2.0
|
|
43
|
+
with:
|
|
44
|
+
release: 'v1.13.0'
|
|
45
|
+
|
|
41
46
|
# TODO: Until krakenw run test works (currently fails because of doctest modules)
|
|
42
47
|
- name: Run tests
|
|
43
48
|
run: uv run pytest -vv
|
{nyl-0.2.2 → nyl-0.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nyl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Advanced templating engine for Kubernetes and GitOps.
|
|
5
5
|
Author-email: Niklas Rosenstein <rosensteinniklas@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -31,6 +31,12 @@ Requires Python 3.11 or newer.
|
|
|
31
31
|
|
|
32
32
|
$ uvx nyl
|
|
33
33
|
|
|
34
|
+
For some features, additional programs must be available:
|
|
35
|
+
|
|
36
|
+
* [kubectl](https://kubernetes.io/de/docs/reference/kubectl/) for most operations
|
|
37
|
+
* [kyverno](https://kyverno.io/docs/kyverno-cli/) when using the Nyl `PostProcessor` resource
|
|
38
|
+
* [sops](https://github.com/getsops/sops) when using the SOPS secrets provider
|
|
39
|
+
|
|
34
40
|
## Local development
|
|
35
41
|
|
|
36
42
|
Install the project with [Uv](https://docs.astral.sh/uv/).
|
|
@@ -9,6 +9,12 @@ Requires Python 3.11 or newer.
|
|
|
9
9
|
|
|
10
10
|
$ uvx nyl
|
|
11
11
|
|
|
12
|
+
For some features, additional programs must be available:
|
|
13
|
+
|
|
14
|
+
* [kubectl](https://kubernetes.io/de/docs/reference/kubectl/) for most operations
|
|
15
|
+
* [kyverno](https://kyverno.io/docs/kyverno-cli/) when using the Nyl `PostProcessor` resource
|
|
16
|
+
* [sops](https://github.com/getsops/sops) when using the SOPS secrets provider
|
|
17
|
+
|
|
12
18
|
## Local development
|
|
13
19
|
|
|
14
20
|
Install the project with [Uv](https://docs.astral.sh/uv/).
|
|
@@ -29,7 +29,7 @@ RUN \
|
|
|
29
29
|
case "$(uname -m)" in \
|
|
30
30
|
x86_64) BIN_ARCH='amd64' ;; \
|
|
31
31
|
aarch64) BIN_ARCH='arm64' ;; \
|
|
32
|
-
*) echo "Unsupported architecture: $
|
|
32
|
+
*) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \
|
|
33
33
|
esac \
|
|
34
34
|
&& printf "%s" "$BIN_ARCH" > /BINARY_ARCH
|
|
35
35
|
|
|
@@ -57,12 +57,24 @@ RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/s
|
|
|
57
57
|
FROM fetch-bin-base AS sops-bin
|
|
58
58
|
# Find SOPS releases at https://github.com/getsops/sops/releases
|
|
59
59
|
ARG SOPS_VERSION=v3.9.0
|
|
60
|
-
RUN curl -
|
|
60
|
+
RUN curl -fLO https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.$(cat /BINARY_ARCH) \
|
|
61
61
|
&& mv sops-${SOPS_VERSION}.linux.$(cat /BINARY_ARCH) /usr/local/bin/sops \
|
|
62
62
|
&& chmod +x /usr/local/bin/sops \
|
|
63
63
|
&& upx /usr/local/bin/sops \
|
|
64
64
|
&& sops --version
|
|
65
65
|
|
|
66
|
+
FROM fetch-bin-base AS kyverno-bin
|
|
67
|
+
ARG KYVERNO_VERSION=1.13.0
|
|
68
|
+
RUN \
|
|
69
|
+
case "$(uname -m)" in \
|
|
70
|
+
x86_64) KYVERNO_ARCH='x86_64' ;; \
|
|
71
|
+
aarch64) KYVERNO_ARCH='arm64' ;; \
|
|
72
|
+
*) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \
|
|
73
|
+
esac \
|
|
74
|
+
&& curl -fLO https://github.com/kyverno/kyverno/releases/download/v${KYVERNO_VERSION}/kyverno-cli_v${KYVERNO_VERSION}_linux_${KYVERNO_ARCH}.tar.gz \
|
|
75
|
+
&& tar -xvf kyverno-cli_v${KYVERNO_VERSION}_linux_${KYVERNO_ARCH}.tar.gz \
|
|
76
|
+
&& mv kyverno /usr/local/bin/ \
|
|
77
|
+
&& upx /usr/local/bin/kyverno
|
|
66
78
|
|
|
67
79
|
#: Build the ArgoCD CMP server image.
|
|
68
80
|
#: ----------------------------------
|
|
@@ -73,6 +85,7 @@ COPY --from=argocd-bin /usr/local/bin/argocd /usr/local/bin/argocd
|
|
|
73
85
|
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-cmp-server && chown 999:999 /usr/local/bin/argocd-cmp-server
|
|
74
86
|
COPY --from=helm-bin /usr/local/bin/helm /usr/local/bin/helm
|
|
75
87
|
COPY --from=sops-bin /usr/local/bin/sops /usr/local/bin/sops
|
|
88
|
+
COPY --from=kyverno-bin /usr/local/bin/kyverno /usr/local/bin/kyverno
|
|
76
89
|
COPY --from=build /opt/nyl /opt/nyl
|
|
77
90
|
RUN ln -s /opt/nyl/.venv/bin/nyl /usr/local/bin/nyl
|
|
78
91
|
COPY --chown=999 argocd-cmp/plugin.yaml /home/argocd/cmp-server/config/plugin.yaml
|
|
@@ -45,9 +45,12 @@ machine and use that to connect to the Kubernetes cluster.
|
|
|
45
45
|
```toml title="nyl-profile.toml"
|
|
46
46
|
[default.kubeconfig]
|
|
47
47
|
type = "ssh"
|
|
48
|
+
sudo = false # default, set to true to use `sudo cat <path>`
|
|
48
49
|
user = "root"
|
|
49
50
|
host = "mycluster.example.com"
|
|
51
|
+
port = 22 # default
|
|
50
52
|
path = "/etc/rancher/k3s/k3s.yaml"
|
|
53
|
+
# replace_apiserver_hostname
|
|
51
54
|
|
|
52
55
|
[default.tunnel]
|
|
53
56
|
type = "ssh"
|
|
@@ -61,9 +64,12 @@ machine and use that to connect to the Kubernetes cluster.
|
|
|
61
64
|
default:
|
|
62
65
|
kubeconfig:
|
|
63
66
|
type: ssh
|
|
67
|
+
sudo: false
|
|
64
68
|
user: root
|
|
65
69
|
host: mycluster.example.com
|
|
70
|
+
port: 22
|
|
66
71
|
path: /etc/rancher/k3s/k3s.yaml
|
|
72
|
+
replace_apiserver_hostname: null
|
|
67
73
|
tunnel:
|
|
68
74
|
type: ssh
|
|
69
75
|
user: root
|
|
@@ -77,9 +83,12 @@ machine and use that to connect to the Kubernetes cluster.
|
|
|
77
83
|
"default": {
|
|
78
84
|
"kubeconfig": {
|
|
79
85
|
"type": "ssh",
|
|
86
|
+
"sudo": false,
|
|
80
87
|
"user": "root",
|
|
81
88
|
"host": "mycluster.example.com",
|
|
82
|
-
"
|
|
89
|
+
"port": 22,
|
|
90
|
+
"path": "/etc/rancher/k3s/k3s.yaml",
|
|
91
|
+
"replace_apiserver_hostname": null
|
|
83
92
|
},
|
|
84
93
|
"tunnel": {
|
|
85
94
|
"type": "ssh",
|
|
@@ -100,7 +109,7 @@ is reachable from your local machine.
|
|
|
100
109
|
user = "root"
|
|
101
110
|
host = "mycluster.example.com"
|
|
102
111
|
path = "/etc/rancher/k3s/k3s.yaml"
|
|
103
|
-
replace_apiserver_hostname = "
|
|
112
|
+
replace_apiserver_hostname = "reachable-address.com"
|
|
104
113
|
```
|
|
105
114
|
|
|
106
115
|
=== "YAML"
|
|
@@ -112,7 +121,7 @@ is reachable from your local machine.
|
|
|
112
121
|
user: root
|
|
113
122
|
host: mycluster.example.com
|
|
114
123
|
path: /etc/rancher/k3s/k3s.yaml
|
|
115
|
-
replace_apiserver_hostname:
|
|
124
|
+
replace_apiserver_hostname: reachable-address.com
|
|
116
125
|
```
|
|
117
126
|
|
|
118
127
|
=== "JSON"
|
|
@@ -125,11 +134,19 @@ is reachable from your local machine.
|
|
|
125
134
|
"user": "root",
|
|
126
135
|
"host": "mycluster.example.com",
|
|
127
136
|
"path": "/etc/rancher/k3s/k3s.yaml",
|
|
128
|
-
"replace_apiserver_hostname": "
|
|
137
|
+
"replace_apiserver_hostname": "reachable-address.com"
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
```
|
|
132
141
|
|
|
142
|
+
!!! note
|
|
143
|
+
|
|
144
|
+
When retrieving a Kubeconfig via SSH that has `127.0.0.1`, `0.0.0.0` or `localhost` as the hostname in the `server`
|
|
145
|
+
field, and `replace_apiserver_hostname` is not specified, Nyl will automatically replace it with the `host` field
|
|
146
|
+
specified that was used to execute the remote command to read the Kubeconfig. This is because we expect the two
|
|
147
|
+
hosts to be the same, and you cannot reach the Kubernetes API server through your local machine without using tunnel
|
|
148
|
+
(which you can use, see [Tunnel management](#tunnel-management)).
|
|
149
|
+
|
|
133
150
|
If you are you are already setup with a kubeconfig file, you can specify the path to the file directly or have it
|
|
134
151
|
automatically use your `~/.kube/config` file/`KUBECONFIG` environment variable. You may specify the context to use
|
|
135
152
|
from that kubeconfig file which ensures Nyl interacts with the correct cluster, even if your
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
[Nyl-inline]: ./templating/inlining/overview.md
|
|
4
|
+
|
|
5
|
+
### Manifest
|
|
6
|
+
|
|
7
|
+
A manifest is a file that may define zero or more Kubernetes or [Nyl inline resources][Nyl-inline]. Nyl understands
|
|
8
|
+
individual manifests, i.e. YAML files, and certain behaviours operate on that level, such as Kubernetes Namespace
|
|
9
|
+
auto-filling and resource post-processing applying only to all resources defined in the same manifest as the
|
|
10
|
+
[`PostProcessor`](./templating/inlining/postprocessor.md).
|
|
11
|
+
|
|
12
|
+
### Profile
|
|
13
|
+
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
### Project
|
|
17
|
+
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
### Resource
|
|
21
|
+
|
|
22
|
+
A resource is a YAML document that follows the schema of a Kubernetes API resource or [Nyl inline resource][Nyl-inline].
|
|
23
|
+
An example would be a Kubernetes `ConfigMap`, `Deployment`, `Pod` or a Nyl `HelmChart` or `PostProcessor`.
|
|
24
|
+
|
|
25
|
+
### Secrets provider
|
|
26
|
+
|
|
27
|
+
...
|
|
@@ -2,19 +2,11 @@
|
|
|
2
2
|
weight: 5
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
#
|
|
5
|
+
# HelmChart
|
|
6
6
|
|
|
7
|
-
Nyl
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
inlined resources.
|
|
11
|
-
|
|
12
|
-
## Helm charts
|
|
13
|
-
|
|
14
|
-
Nyl currently only supports inlining of Helm charts. Helm charts are inlined by specifying a `HelmChart` resource in
|
|
15
|
-
your configuration. Just like any other resource, the Helm chart resource values can be templated using Nyl's
|
|
16
|
-
[structured templating](../templating/basics.md) (for example to inject secrets), and are then rendered to Kubernetes
|
|
17
|
-
resources that are inlined in your configuration.
|
|
7
|
+
Nyl allows you to inline Helm charts by specifying a `HelmChart` resource in your manifest. Just like any other
|
|
8
|
+
resource, the Helm chart resource values can be templated using Nyl's [structured templating](../templating/basics.md)
|
|
9
|
+
(for example to inject secrets), and are then rendered to Kubernetes resources that are inlined in your configuration.
|
|
18
10
|
|
|
19
11
|
__Example__
|
|
20
12
|
|
|
@@ -28,6 +20,7 @@ spec:
|
|
|
28
20
|
chart:
|
|
29
21
|
repository: https://charts.bitnami.com/bitnami
|
|
30
22
|
name: nginx
|
|
23
|
+
# version: "..."
|
|
31
24
|
values:
|
|
32
25
|
controller:
|
|
33
26
|
service:
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Overview
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Inlining Overview
|
|
6
|
+
|
|
7
|
+
Nyl supports inlining of Kubernetes resources generated from templates in your configuration. Resources that are
|
|
8
|
+
inlined by Nyl are typically in the `inline.nyl.io/v1` API group, however Nyl also has the concept of
|
|
9
|
+
[components](./components.md) which effectively allow you to define your own API groups and resource kinds for
|
|
10
|
+
inlined resources.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# PostProcessor
|
|
2
|
+
|
|
3
|
+
The `inline.nyl.io/v1/PostProcessor` resource can be used to configure post-processing steps for all Kubernetes
|
|
4
|
+
resources defined in the same [Manifest](../../glossary.md#manifest) as the `PostProcessor`. This resource does
|
|
5
|
+
not accept a `metadata` field.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
* You need the [`kyverno` CLI](https://kyverno.io/docs/kyverno-cli/) installed
|
|
10
|
+
|
|
11
|
+
## API Spec
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
apiVersion: inline.nyl.io/v1
|
|
15
|
+
kind: PostProcessor
|
|
16
|
+
spec:
|
|
17
|
+
kyverno:
|
|
18
|
+
# A list of files that each contain a Kyverno policy resource, usually a `ClusterPolicy`. The paths are first
|
|
19
|
+
# considered relative to the manifest that this resource is defined in, and will then be searched in the project
|
|
20
|
+
# search path.
|
|
21
|
+
policyFiles:
|
|
22
|
+
- path/to/policy.yaml
|
|
23
|
+
|
|
24
|
+
# A mapping of policy names (what you would give as a filename) and a YAML document that represents the Kyverno
|
|
25
|
+
# policy resource (usually a `ClusterPolicy`).
|
|
26
|
+
inlinePolicies:
|
|
27
|
+
my-policy:
|
|
28
|
+
apiVersion: kyverno.io/v1
|
|
29
|
+
kind: ClusterPolicy
|
|
30
|
+
metadata:
|
|
31
|
+
name: enforce-pod-security-context
|
|
32
|
+
spec: {} # ...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Example
|
|
36
|
+
|
|
37
|
+
If you're deploying to hardened RKE2, your pods must have a specific `securityContext` configuration in order to be
|
|
38
|
+
allowed by the PodSecurityPolicy. The application's Helm charts that you deploy may have options to inject the
|
|
39
|
+
required options, but if they are not you're usually out of luck unless you fork the Helm chart, or materialize
|
|
40
|
+
the resources and edit them in your project.
|
|
41
|
+
|
|
42
|
+
With the Nyl `PostProcessor`, you can apply Kyverno policies to validate or mutate the resources in a
|
|
43
|
+
[manifest](../../glossary.md#manifest).
|
|
44
|
+
|
|
45
|
+
=== "Manifest"
|
|
46
|
+
|
|
47
|
+
```yaml title="forgejo.yaml"
|
|
48
|
+
---
|
|
49
|
+
apiVersion: inline.nyl.io/v1
|
|
50
|
+
kind: PostProcessor
|
|
51
|
+
spec:
|
|
52
|
+
kyverno:
|
|
53
|
+
policyFiles:
|
|
54
|
+
- ./policies/security-profile.yaml
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
apiVersion: v1
|
|
58
|
+
kind: Namespace
|
|
59
|
+
metadata:
|
|
60
|
+
name: forgejo
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
apiVersion: inline.nyl.io/v1
|
|
64
|
+
kind: HelmChart
|
|
65
|
+
metadata:
|
|
66
|
+
name: forgejo
|
|
67
|
+
namespace: forgejo
|
|
68
|
+
spec:
|
|
69
|
+
chart:
|
|
70
|
+
repository: oci://code.forgejo.org/forgejo-helm # https://artifacthub.io/packages/helm/forgejo-helm/forgejo
|
|
71
|
+
name: forgejo
|
|
72
|
+
version: "10.0.1"
|
|
73
|
+
values: {}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
=== "Kyverno Policy"
|
|
77
|
+
|
|
78
|
+
```yaml title="policies/security-profile.yaml"
|
|
79
|
+
apiVersion: kyverno.io/v1
|
|
80
|
+
kind: ClusterPolicy
|
|
81
|
+
metadata:
|
|
82
|
+
name: enforce-security-context
|
|
83
|
+
spec:
|
|
84
|
+
validationFailureAction: enforce
|
|
85
|
+
rules:
|
|
86
|
+
- name: mutate-pod-security-context
|
|
87
|
+
match:
|
|
88
|
+
resources:
|
|
89
|
+
kinds:
|
|
90
|
+
- Pod
|
|
91
|
+
mutate:
|
|
92
|
+
patchStrategicMerge: &podSpec
|
|
93
|
+
spec:
|
|
94
|
+
securityContext:
|
|
95
|
+
runAsNonRoot: true
|
|
96
|
+
seccompProfile:
|
|
97
|
+
type: RuntimeDefault
|
|
98
|
+
containers: &containers
|
|
99
|
+
- (name): "*"
|
|
100
|
+
securityContext:
|
|
101
|
+
runAsNonRoot: true
|
|
102
|
+
allowPrivilegeEscalation: false
|
|
103
|
+
capabilities:
|
|
104
|
+
drop:
|
|
105
|
+
- "ALL"
|
|
106
|
+
initContainers: *containers
|
|
107
|
+
- name: mutate-deployment-security-context
|
|
108
|
+
match:
|
|
109
|
+
resources:
|
|
110
|
+
kinds:
|
|
111
|
+
- Deployment
|
|
112
|
+
mutate:
|
|
113
|
+
patchStrategicMerge:
|
|
114
|
+
spec:
|
|
115
|
+
template: *podSpec
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Running `nyl template forgejo.yaml` will use the `kyverno` CLI to apply the policy to the manifests generated by
|
|
119
|
+
the Helm chart. Note that the post processing happens at the very end after all other Kubernetes manifests have
|
|
120
|
+
been generated.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nyl"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0" # sed'ed on publish in CI
|
|
8
8
|
description = "Advanced templating engine for Kubernetes and GitOps."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Niklas Rosenstein", email = "rosensteinniklas@gmail.com"},
|
|
@@ -5,6 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from textwrap import indent
|
|
6
6
|
from typing import Optional
|
|
7
7
|
from loguru import logger
|
|
8
|
+
from nyl.resources.postprocessor import PostProcessor
|
|
8
9
|
from typer import Argument, Option
|
|
9
10
|
from nyl.tools import yaml
|
|
10
11
|
from nyl.generator import reconcile_generator
|
|
@@ -177,8 +178,13 @@ def template(
|
|
|
177
178
|
generator,
|
|
178
179
|
source.manifests,
|
|
179
180
|
on_generated=lambda m: template_engine.evaluate(Manifests([m])),
|
|
181
|
+
skip_resources=[PostProcessor],
|
|
180
182
|
)
|
|
181
183
|
|
|
184
|
+
# Find any PostProcessor resources and apply them.
|
|
185
|
+
source.manifests, post_processors = PostProcessor.extract_from_list(source.manifests)
|
|
186
|
+
source.manifests = PostProcessor.apply_all(source.manifests, post_processors, source.file)
|
|
187
|
+
|
|
182
188
|
# Find the namespaces that are defined in the file. If we find any manifests without a namespace, we will
|
|
183
189
|
# inject that namespace name into them. Also find the applyset defined in the file.
|
|
184
190
|
namespaces: set[str] = set()
|
|
@@ -4,8 +4,9 @@ This package contains everything related to the generation of Kubernetes manifes
|
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
-
from typing import Any, ClassVar, Generic, TypeVar
|
|
7
|
+
from typing import Any, ClassVar, Generic, Sequence, TypeVar
|
|
8
8
|
|
|
9
|
+
from nyl.resources import NylResource
|
|
9
10
|
from stablehash import stablehash
|
|
10
11
|
|
|
11
12
|
from nyl.tools.types import Manifest, Manifests
|
|
@@ -37,6 +38,7 @@ def reconcile_generator(
|
|
|
37
38
|
generator: Generator[Manifest],
|
|
38
39
|
manifests: Manifests,
|
|
39
40
|
on_generated: Callable[[Manifest], Manifests],
|
|
41
|
+
skip_resources: Sequence[type[NylResource]] = (),
|
|
40
42
|
) -> Manifests:
|
|
41
43
|
"""
|
|
42
44
|
Recursively reconcile all Nyl resources in the manifests using the given generator.
|
|
@@ -46,6 +48,7 @@ def reconcile_generator(
|
|
|
46
48
|
manifests: The list of manifests to reconcile.
|
|
47
49
|
on_generated: A callback to call on each generated manifest, giving the opportunity to modify it.
|
|
48
50
|
This is used to pass the manifest through the `structured-templates` engine.
|
|
51
|
+
skip_resources: A list of Nyl resources to ignore.
|
|
49
52
|
"""
|
|
50
53
|
|
|
51
54
|
queue = Manifests(manifests)
|
|
@@ -63,8 +66,11 @@ def reconcile_generator(
|
|
|
63
66
|
result.append(resource)
|
|
64
67
|
else:
|
|
65
68
|
seen.add(resource_hash)
|
|
66
|
-
for
|
|
67
|
-
queue.
|
|
69
|
+
if any(t.matches(resource) for t in skip_resources):
|
|
70
|
+
queue.append(resource)
|
|
71
|
+
else:
|
|
72
|
+
for manifest in generator.generate(resource):
|
|
73
|
+
queue.extend(on_generated(manifest))
|
|
68
74
|
loops += 1
|
|
69
75
|
|
|
70
76
|
return result
|
|
@@ -46,6 +46,10 @@ def test__DispatchingGenerator__default__creates_generator_for_every_nyl_inline_
|
|
|
46
46
|
kube_api_versions=set(),
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
+
# We make an exception for the PostProcessor resource as it has no generator.
|
|
50
|
+
|
|
49
51
|
for kind in resource_kinds:
|
|
52
|
+
if kind == "PostProcessor":
|
|
53
|
+
continue
|
|
50
54
|
assert kind in generator.generators.keys()
|
|
51
55
|
assert isinstance(generator.generators[kind], Generator)
|
|
@@ -91,7 +91,7 @@ class HelmChartGenerator(Generator[HelmChart], resource_type=HelmChart):
|
|
|
91
91
|
else:
|
|
92
92
|
logger.opt(ansi=True).debug("Using cached Helm chart '{}' from repository '{}'", chart, repository)
|
|
93
93
|
|
|
94
|
-
chart = str(
|
|
94
|
+
chart = str(next(cache_dir.iterdir()))
|
|
95
95
|
repository = None
|
|
96
96
|
|
|
97
97
|
elif chart_ref.git:
|
|
@@ -58,11 +58,21 @@ class KubeconfigFromSsh:
|
|
|
58
58
|
The username to connect to the remote host with.
|
|
59
59
|
"""
|
|
60
60
|
|
|
61
|
+
sudo: bool = False
|
|
62
|
+
"""
|
|
63
|
+
Use `sudo cat` to retrieve the file instead of ust `cat`.
|
|
64
|
+
"""
|
|
65
|
+
|
|
61
66
|
host: str
|
|
62
67
|
"""
|
|
63
68
|
The remote host to connect to.
|
|
64
69
|
"""
|
|
65
70
|
|
|
71
|
+
port: int = 22
|
|
72
|
+
"""
|
|
73
|
+
SSH port to use.
|
|
74
|
+
"""
|
|
75
|
+
|
|
66
76
|
identity_file: str | None = None
|
|
67
77
|
"""
|
|
68
78
|
An SSH private key file to use for authentication.
|
|
@@ -82,7 +92,10 @@ class KubeconfigFromSsh:
|
|
|
82
92
|
"""
|
|
83
93
|
Replace the hostname in the apiserver configuration of the Kubeconfig. This is useful for example with K3s when
|
|
84
94
|
reading reading the `/etc/rancher/k3s/k3s.yaml` file from a remote host, but the API server in that file is not
|
|
85
|
-
reachable from
|
|
95
|
+
reachable from that machine.
|
|
96
|
+
|
|
97
|
+
Note that if the host in the retrieve Kubeconfig is `localhost`, `0.0.0.0` or `127.0.0.1`, it will be automatically
|
|
98
|
+
replaced with the specified `host` that was also SSH-ed to, unless this option is set.
|
|
86
99
|
"""
|
|
87
100
|
|
|
88
101
|
|
|
@@ -67,9 +67,13 @@ class KubeconfigManager:
|
|
|
67
67
|
|
|
68
68
|
command = [
|
|
69
69
|
"ssh",
|
|
70
|
+
"-p",
|
|
71
|
+
str(source.port),
|
|
70
72
|
f"{source.user}@{source.host}",
|
|
71
73
|
f"cat {shlex.quote(source.path)}",
|
|
72
74
|
]
|
|
75
|
+
if source.sudo:
|
|
76
|
+
command[4] = "sudo " + command[4]
|
|
73
77
|
if source.identity_file:
|
|
74
78
|
command[1:1] = ["-i", str(self._cwd / source.identity_file)]
|
|
75
79
|
|
|
@@ -94,8 +98,13 @@ class KubeconfigManager:
|
|
|
94
98
|
if api_host is None:
|
|
95
99
|
raise ValueError(f"no hostname in Kubeconfig server URL: {server!r}")
|
|
96
100
|
|
|
97
|
-
if isinstance(source, KubeconfigFromSsh)
|
|
98
|
-
|
|
101
|
+
if isinstance(source, KubeconfigFromSsh):
|
|
102
|
+
if source.replace_apiserver_hostname:
|
|
103
|
+
api_host = source.replace_apiserver_hostname
|
|
104
|
+
elif api_host in ("127.0.0.1", "0.0.0.0", "localhost"):
|
|
105
|
+
# Automatically replace the API server since we won't be able to reach the Kubernetes API server
|
|
106
|
+
# pointing to localhost from the current machine.
|
|
107
|
+
api_host = source.host
|
|
99
108
|
|
|
100
109
|
raw_kubeconfig.chmod(0o600)
|
|
101
110
|
return GetRawKubeconfigResult(
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import base64
|
|
2
1
|
from dataclasses import dataclass
|
|
3
|
-
import hashlib
|
|
4
2
|
from typing import Annotated, ClassVar
|
|
5
3
|
from nyl.resources import API_VERSION_K8S, NylResource, ObjectMetadata
|
|
6
4
|
from databind.core import SerializeDefaults
|
|
@@ -78,33 +76,3 @@ class Placeholder(NylResource, api_version=API_VERSION_K8S):
|
|
|
78
76
|
namespace=namespace,
|
|
79
77
|
)
|
|
80
78
|
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def calculate_applyset_id(*, name: str, namespace: str = "", group: str) -> str:
|
|
84
|
-
"""
|
|
85
|
-
Calculate the ID of a Kubernetes ApplySet with the specified name.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
# reference: https://kubernetes.io/docs/reference/labels-annotations-taints/#applyset-kubernetes-io-id
|
|
89
|
-
hash = hashlib.sha256(f"{name}.{namespace}.ApplySet.{group}".encode()).digest()
|
|
90
|
-
uid = base64.b64encode(hash).decode().rstrip("=").replace("/", "_").replace("+", "-")
|
|
91
|
-
return f"applyset-{uid}-v1"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_canonical_resource_kind_name(api_version: str, kind: str) -> str:
|
|
95
|
-
"""
|
|
96
|
-
Given the apiVersion and kind of a Kubernetes resource, return the canonical name of the resource. This name can
|
|
97
|
-
be used to identify the resource in an ApplySet's `applyset.kubernetes.io/contains-group-kinds` annotation.
|
|
98
|
-
|
|
99
|
-
Note that according to the [reference][1], the resource name should use the plural form, but it appears that the
|
|
100
|
-
resource kind name is also accepted. Deriving the plural form will be difficult without querying the Kubernetes
|
|
101
|
-
API.
|
|
102
|
-
|
|
103
|
-
[1]: https://kubernetes.io/docs/reference/labels-annotations-taints/#applyset-kubernetes-io-contains-group-kinds
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
api_version: The apiVersion of the resource.
|
|
107
|
-
kind: The kind of the resource.
|
|
108
|
-
"""
|
|
109
|
-
|
|
110
|
-
return (f"{kind}." + (api_version.split("/")[0] if "/" in api_version else "")).rstrip(".")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from tempfile import TemporaryDirectory
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from loguru import logger
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from nyl.resources import API_VERSION_INLINE, NylResource
|
|
12
|
+
from nyl.tools.kubernetes import resource_locator
|
|
13
|
+
from nyl.tools.logging import lazy_str
|
|
14
|
+
from nyl.tools.shell import pretty_cmd
|
|
15
|
+
from nyl.tools.types import Manifests
|
|
16
|
+
|
|
17
|
+
KyvernoPolicyDocument = dict[str, Any]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(kw_only=True)
|
|
21
|
+
class KyvernoSpec:
|
|
22
|
+
policyFiles: list[str] = field(default_factory=list)
|
|
23
|
+
"""
|
|
24
|
+
A list of Kyverno policy filenames, either relative to the file that defined the #PostProcessor resource
|
|
25
|
+
or discoverable in the project search path.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
inlinePolicies: dict[str, KyvernoPolicyDocument] = field(default_factory=dict)
|
|
29
|
+
"""
|
|
30
|
+
A mapping of policy name to the Kyverno policy document. Allows specifying Kyverno policies to be applied
|
|
31
|
+
to the generated manifests inline.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(kw_only=True)
|
|
36
|
+
class PostProcessorSpec:
|
|
37
|
+
kyverno: KyvernoSpec
|
|
38
|
+
"""
|
|
39
|
+
Apply Kyverno policies.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(kw_only=True)
|
|
44
|
+
class PostProcessor(NylResource, api_version=API_VERSION_INLINE):
|
|
45
|
+
"""
|
|
46
|
+
Configuration for post-processing Kubernetes manifests in a file. Note that the post-processing is always
|
|
47
|
+
scoped to the file that the processor is defined in. Post processors will be applied after all inline resources
|
|
48
|
+
are reconciled.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# metadata: ObjectMetadata
|
|
52
|
+
|
|
53
|
+
spec: PostProcessorSpec
|
|
54
|
+
|
|
55
|
+
def process(self, manifests: Manifests, source_file: Path) -> Manifests:
|
|
56
|
+
"""
|
|
57
|
+
Post-process the given manifests.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
if self.spec.kyverno.policyFiles or self.spec.kyverno.inlinePolicies:
|
|
61
|
+
logger.info("Applying Kyverno policies to manifests from '{}': {}", source_file.name, self.spec.kyverno)
|
|
62
|
+
|
|
63
|
+
policy_paths = []
|
|
64
|
+
|
|
65
|
+
for policy in map(Path, self.spec.kyverno.policyFiles):
|
|
66
|
+
if (source_file.parent / policy).exists():
|
|
67
|
+
policy = (source_file.parent / policy).resolve()
|
|
68
|
+
|
|
69
|
+
assert policy.is_file() or policy.is_dir(), f"Path '{policy}' must be a directory or file"
|
|
70
|
+
# TODO: Resolve relative paths to full paths.
|
|
71
|
+
policy_paths.append(Path(policy))
|
|
72
|
+
|
|
73
|
+
with TemporaryDirectory() as _tmp:
|
|
74
|
+
tmp = Path(_tmp)
|
|
75
|
+
|
|
76
|
+
# Write inline policies to files.
|
|
77
|
+
inline_dir = tmp / "inline-policies"
|
|
78
|
+
inline_dir.mkdir()
|
|
79
|
+
for key, value in self.spec.kyverno.inlinePolicies.items():
|
|
80
|
+
# If the file name does not end with a YAML suffix, Kyverno will ignore the input file.
|
|
81
|
+
if not key.endswith(".yml") and not key.endswith(".yaml"):
|
|
82
|
+
key += ".yaml"
|
|
83
|
+
policy_paths.append(inline_dir.joinpath(key))
|
|
84
|
+
policy_paths[-1].write_text(yaml.safe_dump(value))
|
|
85
|
+
|
|
86
|
+
# Write manifests to a file.
|
|
87
|
+
manifests_file = tmp / "manifests.yaml"
|
|
88
|
+
manifests_file.write_text(yaml.safe_dump_all(manifests))
|
|
89
|
+
output_file = tmp / "output.yaml"
|
|
90
|
+
|
|
91
|
+
command = [
|
|
92
|
+
"kyverno",
|
|
93
|
+
"apply",
|
|
94
|
+
*map(str, policy_paths),
|
|
95
|
+
f"--resource={manifests_file}",
|
|
96
|
+
"-o",
|
|
97
|
+
str(output_file),
|
|
98
|
+
]
|
|
99
|
+
logger.debug("$ {}", lazy_str(pretty_cmd, command))
|
|
100
|
+
subprocess.check_call(command, stdout=sys.stderr) # TODO: Catch output?
|
|
101
|
+
|
|
102
|
+
# Kyverno only prints the mutated manifests. We assume their name/kind doesn't change and
|
|
103
|
+
# merge them back into the original list.
|
|
104
|
+
mutated_manifests = Manifests(list(filter(None, yaml.safe_load_all(output_file.read_text()))))
|
|
105
|
+
|
|
106
|
+
logger.info(" Mutated manifests: {}", ", ".join(map(resource_locator, mutated_manifests)))
|
|
107
|
+
keyed_manifests = {resource_locator(m): m for m in manifests}
|
|
108
|
+
keyed_manifests.update({resource_locator(m): m for m in mutated_manifests})
|
|
109
|
+
manifests = Manifests(list(keyed_manifests.values()))
|
|
110
|
+
|
|
111
|
+
return manifests
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def extract_from_list(manifests: Manifests) -> tuple[Manifests, list["PostProcessor"]]:
|
|
115
|
+
processors = []
|
|
116
|
+
new_manifests = Manifests([])
|
|
117
|
+
for manifest in list(manifests):
|
|
118
|
+
if processor := PostProcessor.maybe_load(manifest):
|
|
119
|
+
processors.append(processor)
|
|
120
|
+
else:
|
|
121
|
+
new_manifests.append(manifest)
|
|
122
|
+
return new_manifests, processors
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def apply_all(manifests: Manifests, processors: list["PostProcessor"], source_file: Path) -> Manifests:
|
|
126
|
+
for processor in processors:
|
|
127
|
+
manifests = processor.process(manifests, source_file)
|
|
128
|
+
return manifests
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from nyl.resources.postprocessor import KyvernoSpec, PostProcessor, PostProcessorSpec
|
|
3
|
+
from nyl.tools.types import Manifest, Manifests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test__PostProcessor__extract_from_list() -> None:
|
|
7
|
+
manifest = Manifests(
|
|
8
|
+
[
|
|
9
|
+
Manifest(
|
|
10
|
+
{
|
|
11
|
+
"apiVersion": "v1",
|
|
12
|
+
"kind": "Pod",
|
|
13
|
+
"metadata": {
|
|
14
|
+
"name": "foo",
|
|
15
|
+
"namespace": "bar",
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
),
|
|
19
|
+
Manifest({"apiVersion": "inline.nyl.io/v1", "kind": "PostProcessor", "spec": {"kyverno": {}}}),
|
|
20
|
+
]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
updated_manifest, processors = PostProcessor.extract_from_list(manifest)
|
|
24
|
+
assert updated_manifest == [manifest[0]]
|
|
25
|
+
assert len(processors) == 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test__PostProcessor__process__inlinePolicy() -> None:
|
|
29
|
+
manifest = Manifests(
|
|
30
|
+
[
|
|
31
|
+
Manifest(
|
|
32
|
+
{
|
|
33
|
+
"apiVersion": "v1",
|
|
34
|
+
"kind": "Pod",
|
|
35
|
+
"metadata": {"name": "foo", "namespace": "foo"},
|
|
36
|
+
"spec": {
|
|
37
|
+
"containers": [
|
|
38
|
+
{
|
|
39
|
+
"name": "main",
|
|
40
|
+
"image": "nginx:latest",
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
processor = PostProcessor(
|
|
50
|
+
spec=PostProcessorSpec(
|
|
51
|
+
kyverno=KyvernoSpec(
|
|
52
|
+
inlinePolicies={
|
|
53
|
+
"security-profile": {
|
|
54
|
+
"apiVersion": "kyverno.io/v1",
|
|
55
|
+
"kind": "ClusterPolicy",
|
|
56
|
+
"metadata": {"name": "enforce-pod-security-context"},
|
|
57
|
+
"spec": {
|
|
58
|
+
"validationFailureAction": "enforce",
|
|
59
|
+
"rules": [
|
|
60
|
+
{
|
|
61
|
+
"name": "mutate-pod-security-context",
|
|
62
|
+
"match": {"resources": {"kinds": ["Pod"]}},
|
|
63
|
+
"mutate": {
|
|
64
|
+
"patchStrategicMerge": {
|
|
65
|
+
"spec": {
|
|
66
|
+
"securityContext": {
|
|
67
|
+
"runAsNonRoot": True,
|
|
68
|
+
"seccompProfile": {"type": "RuntimeDefault"},
|
|
69
|
+
},
|
|
70
|
+
"containers": [
|
|
71
|
+
{
|
|
72
|
+
"(name)": "*",
|
|
73
|
+
"securityContext": {
|
|
74
|
+
"runAsNonRoot": True,
|
|
75
|
+
"allowPrivilegeEscalation": False,
|
|
76
|
+
"capabilities": {"drop": ["ALL"]},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"initContainers": [
|
|
81
|
+
{
|
|
82
|
+
"(name)": "*",
|
|
83
|
+
"securityContext": {
|
|
84
|
+
"runAsNonRoot": True,
|
|
85
|
+
"allowPrivilegeEscalation": False,
|
|
86
|
+
"capabilities": {"drop": ["ALL"]},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
updated_manifest = processor.process(manifest, Path("/"))
|
|
103
|
+
|
|
104
|
+
assert updated_manifest == [
|
|
105
|
+
{
|
|
106
|
+
"apiVersion": "v1",
|
|
107
|
+
"kind": "Pod",
|
|
108
|
+
"metadata": {"name": "foo", "namespace": "foo"},
|
|
109
|
+
"spec": {
|
|
110
|
+
"containers": [
|
|
111
|
+
{
|
|
112
|
+
"image": "nginx:latest",
|
|
113
|
+
"name": "main",
|
|
114
|
+
"securityContext": {
|
|
115
|
+
"allowPrivilegeEscalation": False,
|
|
116
|
+
"capabilities": {
|
|
117
|
+
"drop": [
|
|
118
|
+
"ALL",
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
"runAsNonRoot": True,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
"securityContext": {
|
|
126
|
+
"runAsNonRoot": True,
|
|
127
|
+
"seccompProfile": {
|
|
128
|
+
"type": "RuntimeDefault",
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from loguru import logger
|
|
2
2
|
from kubernetes.dynamic import DynamicClient
|
|
3
3
|
from kubernetes.client.api_client import ApiClient
|
|
4
|
+
from nyl.tools.types import Manifest
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def discover_kubernetes_api_versions(client: ApiClient) -> set[str]:
|
|
@@ -15,3 +16,18 @@ def discover_kubernetes_api_versions(client: ApiClient) -> set[str]:
|
|
|
15
16
|
all_versions.add(f"{resource.group_version}/{resource.kind}")
|
|
16
17
|
logger.info("Discovered {} Kubernetes API version(s).", len(all_versions))
|
|
17
18
|
return all_versions
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resource_locator(manifest: Manifest) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Create a string that contains the apiVersion, kind, namespace and name of a Kubernetes resource formatted as
|
|
24
|
+
|
|
25
|
+
apiVersion/kind/namespace/name
|
|
26
|
+
|
|
27
|
+
This can be used to uniquely identify a resource.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
f"{manifest['apiVersion']}/{manifest['kind']}/"
|
|
32
|
+
f"{manifest['metadata'].get('namespace', '')}/{manifest['metadata']['name']}"
|
|
33
|
+
)
|
{nyl-0.2.2 → nyl-0.4.0}/.envrc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nyl-0.2.2 → nyl-0.4.0}/uv.lock
RENAMED
|
File without changes
|