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.
Files changed (108) hide show
  1. {nyl-0.2.0 → nyl-0.3.0}/.github/workflows/python.yaml +5 -0
  2. {nyl-0.2.0 → nyl-0.3.0}/PKG-INFO +8 -2
  3. {nyl-0.2.0 → nyl-0.3.0}/README.md +6 -0
  4. {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/Dockerfile +15 -2
  5. nyl-0.3.0/docs/content/reference/glossary.md +27 -0
  6. nyl-0.2.0/docs/content/reference/templating/inlining.md → nyl-0.3.0/docs/content/reference/templating/inlining/helmcharts.md +5 -12
  7. nyl-0.3.0/docs/content/reference/templating/inlining/overview.md +10 -0
  8. nyl-0.3.0/docs/content/reference/templating/inlining/postprocessor.md +120 -0
  9. {nyl-0.2.0 → nyl-0.3.0}/pyproject.toml +2 -2
  10. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/resource.pyi +1 -0
  11. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/run.py +3 -3
  12. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/template.py +11 -5
  13. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/__init__.py +9 -3
  14. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/dispatch_test.py +4 -0
  15. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/helmchart.py +4 -4
  16. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/kubeconfig.py +4 -4
  17. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/placeholder.py +0 -32
  18. nyl-0.3.0/src/nyl/resources/postprocessor.py +128 -0
  19. nyl-0.3.0/src/nyl/resources/postprocessor_test.py +133 -0
  20. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/templating.py +59 -2
  21. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kubernetes.py +16 -0
  22. nyl-0.3.0/src/nyl/tools/yaml.py +27 -0
  23. {nyl-0.2.0 → nyl-0.3.0}/uv.lock +4 -4
  24. {nyl-0.2.0 → nyl-0.3.0}/.envrc +0 -0
  25. {nyl-0.2.0 → nyl-0.3.0}/.github/workflows/docker.yaml +0 -0
  26. {nyl-0.2.0 → nyl-0.3.0}/.github/workflows/docs.yaml +0 -0
  27. {nyl-0.2.0 → nyl-0.3.0}/.gitignore +0 -0
  28. {nyl-0.2.0 → nyl-0.3.0}/.kraken.py +0 -0
  29. {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/README.md +0 -0
  30. {nyl-0.2.0 → nyl-0.3.0}/argocd-cmp/plugin.yaml +0 -0
  31. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.envrc +0 -0
  32. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.secrets.yaml +0 -0
  33. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/.sops.yaml +0 -0
  34. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/README.md +0 -0
  35. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/argocd.yaml +0 -0
  36. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/nyl-project.yaml +0 -0
  37. {nyl-0.2.0 → nyl-0.3.0}/argocd-with-nyl/nyl-secrets.yaml +0 -0
  38. {nyl-0.2.0 → nyl-0.3.0}/docs/content/index.md +0 -0
  39. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/applysets.md +0 -0
  40. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/argocd-plugin.md +0 -0
  41. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/cluster-connectivity.md +0 -0
  42. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/environment.md +0 -0
  43. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/profiles.md +0 -0
  44. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/projects.md +0 -0
  45. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration/secrets.md +0 -0
  46. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration-api/profiles.md +0 -0
  47. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/configuration-api/secrets.md +0 -0
  48. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/basics.md +0 -0
  49. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/components.md +0 -0
  50. {nyl-0.2.0 → nyl-0.3.0}/docs/content/reference/templating/secrets.md +0 -0
  51. {nyl-0.2.0 → nyl-0.3.0}/docs/mkdocs.yml +0 -0
  52. {nyl-0.2.0 → nyl-0.3.0}/docs/pyproject.toml +0 -0
  53. {nyl-0.2.0 → nyl-0.3.0}/docs/uv.lock +0 -0
  54. {nyl-0.2.0 → nyl-0.3.0}/example/manifests/postgres.yaml +0 -0
  55. {nyl-0.2.0 → nyl-0.3.0}/example/nyl-profiles.yaml +0 -0
  56. {nyl-0.2.0 → nyl-0.3.0}/example/nyl-secrets.yaml +0 -0
  57. {nyl-0.2.0 → nyl-0.3.0}/example/secrets.yaml +0 -0
  58. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/README.md +0 -0
  59. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/__init__.pyi +0 -0
  60. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/__init__.pyi +0 -0
  61. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/client.pyi +0 -0
  62. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/discovery.pyi +0 -0
  63. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/dynamic/exceptions.pyi +0 -0
  64. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/watch/__init__.pyi +0 -0
  65. {nyl-0.2.0 → nyl-0.3.0}/src/kubernetes/watch/watch.pyi +0 -0
  66. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/__init__.py +0 -0
  67. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/__init__.py +0 -0
  68. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/argocd.py +0 -0
  69. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/crds.py +0 -0
  70. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/new.py +0 -0
  71. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/profile.py +0 -0
  72. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/secrets.py +0 -0
  73. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/__init__.py +0 -0
  74. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/bcrypt.py +0 -0
  75. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tools/sops.py +0 -0
  76. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/commands/tun.py +0 -0
  77. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/components.py +0 -0
  78. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/dispatch.py +0 -0
  79. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/generator/statefulsecret.py +0 -0
  80. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/__init__.py +0 -0
  81. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/config.py +0 -0
  82. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/config_test.py +0 -0
  83. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/profiles/tunnel.py +0 -0
  84. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/project/config.py +0 -0
  85. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/__init__.py +0 -0
  86. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/applyset.py +0 -0
  87. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/applyset_test.py +0 -0
  88. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/helmchart.py +0 -0
  89. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/resources/statefulsecret.py +0 -0
  90. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/__init__.py +0 -0
  91. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/config.py +0 -0
  92. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/config_test.py +0 -0
  93. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/null.py +0 -0
  94. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/sops.py +0 -0
  95. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/secrets/sops_test.py +0 -0
  96. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/templating_test.py +0 -0
  97. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/fs.py +0 -0
  98. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/fs_test.py +0 -0
  99. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kubectl.py +0 -0
  100. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/kvstore.py +0 -0
  101. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/loads.py +0 -0
  102. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/logging.py +0 -0
  103. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/shell.py +0 -0
  104. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/testing.py +0 -0
  105. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/typer.py +0 -0
  106. {nyl-0.2.0 → nyl-0.3.0}/src/nyl/tools/types.py +0 -0
  107. {nyl-0.2.0 → nyl-0.3.0}/tests/__init__.py +0 -0
  108. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nyl
3
- Version: 0.2.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<0.1.0,>=0.0.6
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: $ARCH" && exit 1 ;; \
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 -LO https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.$(cat /BINARY_ARCH) \
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
- # Inlining
5
+ # HelmChart
6
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.
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.2.0" # sed'ed on publish in CI
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.0.6,<0.1.0",
23
+ "structured-templates>=0.1.1",
24
24
  "typer>=0.12.3",
25
25
  "typing-extensions>=4.12.2",
26
26
  ]
@@ -40,3 +40,4 @@ class ResourceInstance:
40
40
  attributes: ResourceField
41
41
 
42
42
  def __getitem__(self, key: str) -> ResourceField | Any: ...
43
+ def to_dict(self) -> dict[str, Any]: ...
@@ -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.safe_load(kubeconfig.read_text())
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.dump(kubeconfig_data))
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.safe_dump(applyset.dump()))
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.safe_dump(manifest), " "),
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.safe_dump(manifest))
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.safe_load_all(file.read_text())))))
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 manifest in generator.generate(resource):
67
- queue.extend(on_generated(manifest))
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(cache_dir / next(cache_dir.iterdir()))
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.safe_dump(res.spec.values))
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.safe_load_all(result.stdout.decode()))))
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.safe_load(raw_kubeconfig.read_text())
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.safe_load(path.read_text())
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.safe_dump(kubeconfig_data))
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(".")