nyl 0.2.0__tar.gz → 0.3.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.0 → nyl-0.3.0}/.github/workflows/python.yaml +5 -0
- {nyl-0.2.0 → nyl-0.3.0}/PKG-INFO +8 -2
- {nyl-0.2.0 → nyl-0.3.0}/README.md +6 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/Dockerfile +15 -2
- nyl-0.3.0/docs/content/reference/glossary.md +27 -0
- nyl-0.2.0/docs/content/reference/templating/inlining.md → nyl-0.3.0/docs/content/reference/templating/inlining/helmcharts.md +5 -12
- nyl-0.3.0/docs/content/reference/templating/inlining/overview.md +10 -0
- nyl-0.3.0/docs/content/reference/templating/inlining/postprocessor.md +120 -0
- {nyl-0.2.0 → nyl-0.3.0}/pyproject.toml +2 -2
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/resource.pyi +1 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/run.py +3 -3
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/template.py +11 -5
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/__init__.py +9 -3
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/dispatch_test.py +4 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/helmchart.py +4 -4
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/kubeconfig.py +4 -4
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/placeholder.py +0 -32
- nyl-0.3.0/src/nyl/resources/postprocessor.py +128 -0
- nyl-0.3.0/src/nyl/resources/postprocessor_test.py +133 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/templating.py +59 -2
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kubernetes.py +16 -0
- nyl-0.3.0/src/nyl/tools/yaml.py +27 -0
- {nyl-0.2.0 → nyl-0.3.0}/uv.lock +4 -4
- {nyl-0.2.0 → nyl-0.3.0}/.envrc +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/.github/workflows/docker.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/.github/workflows/docs.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/.gitignore +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/.kraken.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/README.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/plugin.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.envrc +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.secrets.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.sops.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/README.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/argocd.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/nyl-project.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/index.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/applysets.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/argocd-plugin.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/cluster-connectivity.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/environment.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/profiles.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/projects.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/secrets.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration-api/profiles.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration-api/secrets.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/basics.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/components.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/secrets.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/mkdocs.yml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/pyproject.toml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/docs/uv.lock +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/example/manifests/postgres.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/example/nyl-profiles.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/example/nyl-secrets.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/example/secrets.yaml +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/README.md +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/__init__.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/client.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/discovery.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/watch/__init__.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/watch/watch.pyi +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/argocd.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/crds.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/new.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/profile.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/secrets.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/bcrypt.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/sops.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tun.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/components.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/dispatch.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/statefulsecret.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/config.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/config_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/tunnel.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/project/config.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/applyset.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/applyset_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/helmchart.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/statefulsecret.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/config.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/config_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/null.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/sops.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/sops_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/templating_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/fs.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/fs_test.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kubectl.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kvstore.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/loads.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/logging.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/shell.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/testing.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/typer.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/types.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/tests/__init__.py +0 -0
- {nyl-0.2.0 → nyl-0.3.0}/tests/conftest.py +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.0 → nyl-0.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nyl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Advanced templating engine for Kubernetes and GitOps.
|
|
5
5
|
Author-email: Niklas Rosenstein <rosensteinniklas@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,7 @@ Requires-Dist: nr-stream>=1.1.5
|
|
|
15
15
|
Requires-Dist: pyyaml>=6.0.1
|
|
16
16
|
Requires-Dist: requests>=2.32.3
|
|
17
17
|
Requires-Dist: stablehash<0.3.0,>=0.2.1
|
|
18
|
-
Requires-Dist: structured-templates
|
|
18
|
+
Requires-Dist: structured-templates>=0.1.1
|
|
19
19
|
Requires-Dist: typer>=0.12.3
|
|
20
20
|
Requires-Dist: typing-extensions>=4.12.2
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -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
|
|
@@ -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.3.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"},
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"pyyaml>=6.0.1",
|
|
21
21
|
"requests>=2.32.3",
|
|
22
22
|
"stablehash>=0.2.1,<0.3.0",
|
|
23
|
-
"structured-templates>=0.
|
|
23
|
+
"structured-templates>=0.1.1",
|
|
24
24
|
"typer>=0.12.3",
|
|
25
25
|
"typing-extensions>=4.12.2",
|
|
26
26
|
]
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
from tempfile import TemporaryDirectory
|
|
7
7
|
from loguru import logger
|
|
8
8
|
from typer import Argument, Option
|
|
9
|
-
import yaml
|
|
9
|
+
from nyl.tools import yaml
|
|
10
10
|
from nyl.profiles import ActivatedProfile, ProfileManager
|
|
11
11
|
from nyl.profiles.kubeconfig import _trim_to_context
|
|
12
12
|
from nyl.tools.logging import lazy_str
|
|
@@ -56,7 +56,7 @@ def run(
|
|
|
56
56
|
sys.exit(1)
|
|
57
57
|
|
|
58
58
|
try:
|
|
59
|
-
kubeconfig_data = yaml.
|
|
59
|
+
kubeconfig_data = yaml.loads(kubeconfig.read_text())
|
|
60
60
|
kubeconfig_data = _trim_to_context(kubeconfig_data, profile_name)
|
|
61
61
|
except ValueError:
|
|
62
62
|
logger.debug("Failed to parse the kubeconfig file/find context '{}'.", profile_name)
|
|
@@ -83,7 +83,7 @@ def run(
|
|
|
83
83
|
tmpdir = TemporaryDirectory()
|
|
84
84
|
atexit.register(tmpdir.cleanup)
|
|
85
85
|
kubeconfig = Path(tmpdir.name) / "kubeconfig"
|
|
86
|
-
kubeconfig.write_text(yaml.
|
|
86
|
+
kubeconfig.write_text(yaml.dumps(kubeconfig_data))
|
|
87
87
|
kubeconfig.chmod(0o600)
|
|
88
88
|
|
|
89
89
|
profile = ActivatedProfile(kubeconfig)
|
|
@@ -5,8 +5,9 @@ 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
|
-
import yaml
|
|
10
|
+
from nyl.tools import yaml
|
|
10
11
|
from nyl.generator import reconcile_generator
|
|
11
12
|
from nyl.generator.dispatch import DispatchingGenerator
|
|
12
13
|
from nyl.profiles import ProfileManager
|
|
@@ -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()
|
|
@@ -252,7 +258,7 @@ def template(
|
|
|
252
258
|
kubectl.diff(Manifests([applyset.dump()]))
|
|
253
259
|
else:
|
|
254
260
|
print("---")
|
|
255
|
-
print(yaml.
|
|
261
|
+
print(yaml.dumps(applyset.dump()))
|
|
256
262
|
|
|
257
263
|
# Find all manifests without a namespace and inject the namespace name into them.
|
|
258
264
|
# If there is an applyset, ensure they are marked as part of the applyset.
|
|
@@ -267,7 +273,7 @@ def template(
|
|
|
267
273
|
logger.opt(ansi=True).error(
|
|
268
274
|
"A manifest in <yellow>'{}'</> has no <cyan>metadata</> key:\n\n{}",
|
|
269
275
|
source.file,
|
|
270
|
-
indent(yaml.
|
|
276
|
+
indent(yaml.dumps(manifest), " "),
|
|
271
277
|
)
|
|
272
278
|
exit(1)
|
|
273
279
|
|
|
@@ -317,7 +323,7 @@ def template(
|
|
|
317
323
|
# If we're not going to be applying the manifests immediately via `kubectl`, we print them to stdout.
|
|
318
324
|
for manifest in source.manifests:
|
|
319
325
|
print("---")
|
|
320
|
-
print(yaml.
|
|
326
|
+
print(yaml.dumps(manifest))
|
|
321
327
|
|
|
322
328
|
|
|
323
329
|
def load_manifests(paths: list[Path]) -> list[ManifestsWithSource]:
|
|
@@ -347,7 +353,7 @@ def load_manifests(paths: list[Path]) -> list[ManifestsWithSource]:
|
|
|
347
353
|
|
|
348
354
|
result = []
|
|
349
355
|
for file in files:
|
|
350
|
-
manifests = Manifests(list(map(Manifest, filter(None, yaml.
|
|
356
|
+
manifests = Manifests(list(map(Manifest, filter(None, yaml.loads_all(file.read_text())))))
|
|
351
357
|
result.append(ManifestsWithSource(manifests, file))
|
|
352
358
|
|
|
353
359
|
return result
|
|
@@ -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)
|
|
@@ -9,7 +9,7 @@ from textwrap import indent
|
|
|
9
9
|
from urllib.parse import parse_qs, urlparse
|
|
10
10
|
|
|
11
11
|
from loguru import logger
|
|
12
|
-
import yaml
|
|
12
|
+
from nyl.tools import yaml
|
|
13
13
|
from nyl.generator import Generator
|
|
14
14
|
from nyl.resources.helmchart import ChartRef, HelmChart, ReleaseMetadata
|
|
15
15
|
from nyl.tools.shell import pretty_cmd
|
|
@@ -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:
|
|
@@ -152,7 +152,7 @@ class HelmChartGenerator(Generator[HelmChart], resource_type=HelmChart):
|
|
|
152
152
|
|
|
153
153
|
with TemporaryDirectory() as tmp:
|
|
154
154
|
values_file = Path(tmp) / "values.yaml"
|
|
155
|
-
values_file.write_text(yaml.
|
|
155
|
+
values_file.write_text(yaml.dumps(res.spec.values))
|
|
156
156
|
|
|
157
157
|
command = [
|
|
158
158
|
"helm",
|
|
@@ -207,6 +207,6 @@ class HelmChartGenerator(Generator[HelmChart], resource_type=HelmChart):
|
|
|
207
207
|
f"stderr:\n{indent(e.stderr.decode(), prefix)}"
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
manifests = Manifests(list(filter(None, yaml.
|
|
210
|
+
manifests = Manifests(list(filter(None, yaml.loads_all(result.stdout.decode()))))
|
|
211
211
|
|
|
212
212
|
return manifests
|
|
@@ -5,8 +5,8 @@ import shlex
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from typing import Any
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
|
-
import yaml
|
|
9
8
|
|
|
9
|
+
from nyl.tools import yaml
|
|
10
10
|
from nyl.tools.shell import pretty_cmd
|
|
11
11
|
from .config import KubeconfigFromSsh, LocalKubeconfig
|
|
12
12
|
from loguru import logger
|
|
@@ -87,7 +87,7 @@ class KubeconfigManager:
|
|
|
87
87
|
raise ValueError(f"Unsupported Kubeconfig type: {source.__class__.__name__}")
|
|
88
88
|
|
|
89
89
|
# Find the Kubernetes API host and port.
|
|
90
|
-
kubeconfig_data = yaml.
|
|
90
|
+
kubeconfig_data = yaml.loads(raw_kubeconfig.read_text())
|
|
91
91
|
kubeconfig_data = _trim_to_context(kubeconfig_data, source.context)
|
|
92
92
|
server: str = kubeconfig_data["clusters"][0]["cluster"]["server"]
|
|
93
93
|
api_host, api_port = (lambda parsed: (parsed.hostname, parsed.port or 443))(urlparse(server))
|
|
@@ -108,7 +108,7 @@ class KubeconfigManager:
|
|
|
108
108
|
def get_updated_kubeconfig(
|
|
109
109
|
self, *, profile_name: str, path: Path, context: str, api_host: str, api_port: int
|
|
110
110
|
) -> Path:
|
|
111
|
-
kubeconfig_data = yaml.
|
|
111
|
+
kubeconfig_data = yaml.loads(path.read_text())
|
|
112
112
|
kubeconfig_data = _trim_to_context(kubeconfig_data, context, rename_context=profile_name)
|
|
113
113
|
|
|
114
114
|
# TODO: Do we need to support the Kubernetes API hosted on a subpath?
|
|
@@ -116,7 +116,7 @@ class KubeconfigManager:
|
|
|
116
116
|
|
|
117
117
|
final_kubeconfig = self._state_dir / profile_name / "kubeconfig.local"
|
|
118
118
|
final_kubeconfig.parent.mkdir(parents=True, exist_ok=True)
|
|
119
|
-
final_kubeconfig.write_text(yaml.
|
|
119
|
+
final_kubeconfig.write_text(yaml.dumps(kubeconfig_data))
|
|
120
120
|
final_kubeconfig.chmod(0o600)
|
|
121
121
|
|
|
122
122
|
return final_kubeconfig
|
|
@@ -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(".")
|