knete 0.1.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.
- knete-0.1.0/.gitignore +7 -0
- knete-0.1.0/.gitlab-ci.yml +134 -0
- knete-0.1.0/Makefile +36 -0
- knete-0.1.0/PKG-INFO +178 -0
- knete-0.1.0/README.md +155 -0
- knete-0.1.0/docs/Makefile +15 -0
- knete-0.1.0/docs/api.rst +86 -0
- knete-0.1.0/docs/cli.rst +212 -0
- knete-0.1.0/docs/conf.py +29 -0
- knete-0.1.0/docs/controller-design.md +202 -0
- knete-0.1.0/docs/controller.rst +350 -0
- knete-0.1.0/docs/examples.rst +266 -0
- knete-0.1.0/docs/index.rst +99 -0
- knete-0.1.0/docs/philosophy.rst +87 -0
- knete-0.1.0/docs/webhooks.rst +418 -0
- knete-0.1.0/examples/backblaze_crd.py +154 -0
- knete-0.1.0/examples/backblaze_operator.py +125 -0
- knete-0.1.0/examples/backblaze_operator_oop.py +135 -0
- knete-0.1.0/examples/backblazebucket_crd.yaml +34 -0
- knete-0.1.0/examples/controllerexamples.py +64 -0
- knete-0.1.0/examples/dynamic_validators.py +103 -0
- knete-0.1.0/examples/example.py +73 -0
- knete-0.1.0/examples/forbid_reserved_prefixes.py +88 -0
- knete-0.1.0/examples/forbid_reserved_prefixes_decorator.py +64 -0
- knete-0.1.0/examples/forbid_reserved_prefixes_v2.py +63 -0
- knete-0.1.0/examples/kyverno_clusterrolebinding.py +243 -0
- knete-0.1.0/examples/kyverno_clusterrolebinding_webhook.py +91 -0
- knete-0.1.0/examples/my_validators.py +71 -0
- knete-0.1.0/examples/require_cronjob_parameters.py +39 -0
- knete-0.1.0/examples/webhook_example.py +238 -0
- knete-0.1.0/examples/webhook_example_decorator.py +69 -0
- knete-0.1.0/logos/knete.png +0 -0
- knete-0.1.0/logos/knete.svg +42 -0
- knete-0.1.0/pyproject.toml +33 -0
- knete-0.1.0/setup.cfg +7 -0
- knete-0.1.0/src/knete/__init__.py +33 -0
- knete-0.1.0/src/knete/__main__.py +804 -0
- knete-0.1.0/src/knete/hook.py +461 -0
- knete-0.1.0/src/knete/knete.py +514 -0
- knete-0.1.0/src/knete.egg-info/PKG-INFO +178 -0
- knete-0.1.0/src/knete.egg-info/SOURCES.txt +46 -0
- knete-0.1.0/src/knete.egg-info/dependency_links.txt +1 -0
- knete-0.1.0/src/knete.egg-info/requires.txt +8 -0
- knete-0.1.0/src/knete.egg-info/scm_file_list.json +43 -0
- knete-0.1.0/src/knete.egg-info/scm_version.json +8 -0
- knete-0.1.0/src/knete.egg-info/top_level.txt +1 -0
- knete-0.1.0/test.sh +409 -0
knete-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# This file is a template, and might need editing before it works on your project.
|
|
2
|
+
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
|
3
|
+
# https://docs.gitlab.com/development/cicd/templates/
|
|
4
|
+
# This specific template is located at:
|
|
5
|
+
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
|
|
6
|
+
|
|
7
|
+
stages:
|
|
8
|
+
- test
|
|
9
|
+
- build
|
|
10
|
+
- deploy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Official language image. Look for the different tagged releases at:
|
|
14
|
+
# https://hub.docker.com/r/library/python/tags/
|
|
15
|
+
image: python:latest
|
|
16
|
+
|
|
17
|
+
variables:
|
|
18
|
+
TWINE_REPOSITORY: pypi
|
|
19
|
+
TWINE_NON_INTERACTIVE: "1"
|
|
20
|
+
|
|
21
|
+
# Change pip's cache directory to be inside the project directory since we can
|
|
22
|
+
# only cache local items.
|
|
23
|
+
variables:
|
|
24
|
+
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
|
|
25
|
+
|
|
26
|
+
# https://pip.pypa.io/en/stable/topics/caching/
|
|
27
|
+
cache:
|
|
28
|
+
paths:
|
|
29
|
+
- .cache/pip
|
|
30
|
+
|
|
31
|
+
before_script:
|
|
32
|
+
- python --version ; pip --version
|
|
33
|
+
- pip install virtualenv
|
|
34
|
+
- virtualenv venv
|
|
35
|
+
- source venv/bin/activate
|
|
36
|
+
|
|
37
|
+
test:
|
|
38
|
+
script:
|
|
39
|
+
- pip install pycodestyle
|
|
40
|
+
- pip install --editable ".[test]"
|
|
41
|
+
- pycodestyle src/
|
|
42
|
+
|
|
43
|
+
e2e:
|
|
44
|
+
stage: test
|
|
45
|
+
image: docker:24.0
|
|
46
|
+
services:
|
|
47
|
+
- docker:24.0-dind
|
|
48
|
+
variables:
|
|
49
|
+
DOCKER_HOST: tcp://docker:2375
|
|
50
|
+
DOCKER_TLS_CERTDIR: ""
|
|
51
|
+
KIND_VERSION: "v0.24.0"
|
|
52
|
+
KUBECTL_VERSION: "v1.31.0"
|
|
53
|
+
before_script:
|
|
54
|
+
- apk update
|
|
55
|
+
- apk upgrade --no-cache
|
|
56
|
+
- apk add --no-cache curl bash make python3 py3-pip py3-virtualenv
|
|
57
|
+
- virtualenv .venv
|
|
58
|
+
- curl -Lo /usr/local/bin/kind https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64
|
|
59
|
+
- chmod +x /usr/local/bin/kind
|
|
60
|
+
- curl -Lo /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
|
|
61
|
+
- chmod +x /usr/local/bin/kubectl
|
|
62
|
+
script:
|
|
63
|
+
- |
|
|
64
|
+
cat <<EOF > kind-config.yaml
|
|
65
|
+
kind: Cluster
|
|
66
|
+
apiVersion: kind.x-k8s.io/v1alpha4
|
|
67
|
+
networking:
|
|
68
|
+
apiServerAddress: "0.0.0.0"
|
|
69
|
+
apiServerPort: 6443
|
|
70
|
+
kubeadmConfigPatches:
|
|
71
|
+
- |
|
|
72
|
+
kind: ClusterConfiguration
|
|
73
|
+
apiServer:
|
|
74
|
+
certSANs:
|
|
75
|
+
- "docker"
|
|
76
|
+
nodes:
|
|
77
|
+
- role: control-plane
|
|
78
|
+
EOF
|
|
79
|
+
- kind create cluster --name knete --config kind-config.yaml
|
|
80
|
+
- export KUBECONFIG=$CI_PROJECT_DIR/kubeconfig
|
|
81
|
+
- export KIND_API_SERVER_HOST=docker
|
|
82
|
+
- bash test.sh
|
|
83
|
+
timeout: 20m
|
|
84
|
+
|
|
85
|
+
build:
|
|
86
|
+
script:
|
|
87
|
+
- pip install build setuptools-scm
|
|
88
|
+
- |
|
|
89
|
+
if [ -z "$CI_COMMIT_TAG" ]; then
|
|
90
|
+
export SETUPTOOLS_SCM_PRETEND_VERSION=$(python -m setuptools_scm | cut -d'+' -f1)
|
|
91
|
+
fi
|
|
92
|
+
- make build
|
|
93
|
+
artifacts:
|
|
94
|
+
paths:
|
|
95
|
+
- dist/*
|
|
96
|
+
rules:
|
|
97
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
98
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
99
|
+
|
|
100
|
+
deploy:
|
|
101
|
+
image: python:3.11
|
|
102
|
+
stage: deploy
|
|
103
|
+
dependencies:
|
|
104
|
+
- build
|
|
105
|
+
script:
|
|
106
|
+
- ls -al
|
|
107
|
+
- pip install --upgrade twine
|
|
108
|
+
- twine upload dist/*
|
|
109
|
+
rules:
|
|
110
|
+
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
|
|
111
|
+
environment:
|
|
112
|
+
name: production
|
|
113
|
+
id_tokens:
|
|
114
|
+
PYPI_ID_TOKEN:
|
|
115
|
+
aud: pypi
|
|
116
|
+
|
|
117
|
+
deploy_test:
|
|
118
|
+
image: python:3.11
|
|
119
|
+
stage: deploy
|
|
120
|
+
dependencies:
|
|
121
|
+
- build
|
|
122
|
+
variables:
|
|
123
|
+
TWINE_REPOSITORY: testpypi
|
|
124
|
+
script:
|
|
125
|
+
- ls -al
|
|
126
|
+
- pip install --upgrade twine
|
|
127
|
+
- twine upload dist/*
|
|
128
|
+
rules:
|
|
129
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
130
|
+
environment:
|
|
131
|
+
name: test-pypi
|
|
132
|
+
id_tokens:
|
|
133
|
+
TESTPYPI_ID_TOKEN:
|
|
134
|
+
aud: testpypi
|
knete-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
CLUSTER_NAME ?= knete
|
|
2
|
+
|
|
3
|
+
.PHONY: all sdist wheel dist upload upload-test clean docs kind-up kind-down kind-reset
|
|
4
|
+
|
|
5
|
+
all: dist
|
|
6
|
+
|
|
7
|
+
sdist:
|
|
8
|
+
python -m build --sdist
|
|
9
|
+
|
|
10
|
+
wheel:
|
|
11
|
+
python -m build --wheel
|
|
12
|
+
|
|
13
|
+
dist:
|
|
14
|
+
python -m build
|
|
15
|
+
|
|
16
|
+
build: dist
|
|
17
|
+
|
|
18
|
+
upload:
|
|
19
|
+
python -m twine upload dist/*
|
|
20
|
+
|
|
21
|
+
upload-test:
|
|
22
|
+
python -m twine upload --repository testpypi dist/*
|
|
23
|
+
|
|
24
|
+
docs:
|
|
25
|
+
$(MAKE) -C docs html
|
|
26
|
+
|
|
27
|
+
clean:
|
|
28
|
+
rm -rf dist/ build/ src/*.egg-info docs/_build
|
|
29
|
+
|
|
30
|
+
kind-up:
|
|
31
|
+
kind create cluster --name $(CLUSTER_NAME)
|
|
32
|
+
|
|
33
|
+
kind-down:
|
|
34
|
+
kind delete cluster --name $(CLUSTER_NAME)
|
|
35
|
+
|
|
36
|
+
kind-reset: kind-down kind-up
|
knete-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: knete
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight Kubernetes controller framework for Python
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://gitlab.com/oz123/knete
|
|
7
|
+
Keywords: kubernetes,controller,operator,k8s
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: System :: Systems Administration
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: build; extra == "dev"
|
|
19
|
+
Requires-Dist: twine; extra == "dev"
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
22
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
23
|
+
|
|
24
|
+
# knete
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="logos/knete.png" alt="knete logo" width="500">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
A lightweight Python framework for building Kubernetes controllers and admission webhooks.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Controllers** — watch any Kubernetes resource and react to create/update/delete events
|
|
35
|
+
- **Validating webhooks** — reject requests that violate policy
|
|
36
|
+
- **Mutating webhooks** — patch objects on the way in
|
|
37
|
+
- **Composite mutating webhooks** — fan-out multiple mutators to a single endpoint, accumulating all patches in one round-trip
|
|
38
|
+
- **Decorator-style webhooks** — register plain functions instead of subclasses
|
|
39
|
+
- **Manifest generator** — `python -m knete <file.py>` generates all k8s manifests (Deployment, Service, RBAC, WebhookConfigurations) including cert-manager TLS support
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install knete
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
### Controller
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from knete import Controller, Manager
|
|
53
|
+
|
|
54
|
+
class PodController(Controller):
|
|
55
|
+
class Meta:
|
|
56
|
+
resource = "v1/pods"
|
|
57
|
+
namespace = "default"
|
|
58
|
+
|
|
59
|
+
def on_create(self, name, namespace, labels, spec):
|
|
60
|
+
print(f"Pod created: {namespace}/{name}")
|
|
61
|
+
|
|
62
|
+
def on_delete(self, name):
|
|
63
|
+
print(f"Pod deleted: {name}")
|
|
64
|
+
|
|
65
|
+
Manager().register(PodController).start()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Validating webhook
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from knete import ValidatingWebhook, AdmissionResponse, WebhookServer
|
|
72
|
+
|
|
73
|
+
class NoLatestTagWebhook(ValidatingWebhook):
|
|
74
|
+
class Meta:
|
|
75
|
+
resource = "apps/v1/deployments"
|
|
76
|
+
operations = ["CREATE", "UPDATE"]
|
|
77
|
+
name = "no-latest-tag"
|
|
78
|
+
|
|
79
|
+
def validate(self, name, spec) -> AdmissionResponse:
|
|
80
|
+
for c in spec.get("template", {}).get("spec", {}).get("containers", []):
|
|
81
|
+
if c["image"].endswith(":latest") or ":" not in c["image"]:
|
|
82
|
+
return AdmissionResponse.deny(f"image {c['image']!r} uses :latest tag")
|
|
83
|
+
return AdmissionResponse.allow()
|
|
84
|
+
|
|
85
|
+
server = WebhookServer(cert_file="tls.crt", key_file="tls.key")
|
|
86
|
+
server.register(NoLatestTagWebhook)
|
|
87
|
+
server.start()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Mutating webhook
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from knete import MutatingWebhook, AdmissionResponse
|
|
94
|
+
|
|
95
|
+
class InjectLabelWebhook(MutatingWebhook):
|
|
96
|
+
class Meta:
|
|
97
|
+
resource = "v1/pods"
|
|
98
|
+
operations = ["CREATE"]
|
|
99
|
+
name = "inject-label"
|
|
100
|
+
|
|
101
|
+
def mutate(self, labels) -> AdmissionResponse:
|
|
102
|
+
if "managed-by" in labels:
|
|
103
|
+
return AdmissionResponse.allow()
|
|
104
|
+
return AdmissionResponse.patch([
|
|
105
|
+
{"op": "add", "path": "/metadata/labels/managed-by", "value": "knete"},
|
|
106
|
+
])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Multiple validators
|
|
110
|
+
|
|
111
|
+
Register each validating webhook separately — Kubernetes calls them in parallel,
|
|
112
|
+
so total latency is `max(validators)` rather than `sum(validators)`:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
server.register(NoLatestTagWebhook) # POST /validate/no-latest-tag
|
|
116
|
+
server.register(RequiredLabelsWebhook) # POST /validate/required-labels
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Decorator style
|
|
120
|
+
|
|
121
|
+
Skip the class entirely and register plain functions directly on the server:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
server = WebhookServer(cert_file="tls.crt", key_file="tls.key")
|
|
125
|
+
|
|
126
|
+
@server.validate("apps/v1/deployments", operations=["CREATE", "UPDATE"], name="no-latest-tag")
|
|
127
|
+
def no_latest_tag(name, spec) -> AdmissionResponse:
|
|
128
|
+
for c in spec.get("template", {}).get("spec", {}).get("containers", []):
|
|
129
|
+
if c["image"].endswith(":latest") or ":" not in c["image"]:
|
|
130
|
+
return AdmissionResponse.deny(f"image {c['image']!r} uses :latest tag")
|
|
131
|
+
|
|
132
|
+
@server.mutate("v1/pods", operations=["CREATE"], name="inject-label")
|
|
133
|
+
def inject_label(labels) -> AdmissionResponse:
|
|
134
|
+
if "managed-by" not in labels:
|
|
135
|
+
return AdmissionResponse.patch([
|
|
136
|
+
{"op": "add", "path": "/metadata/labels/managed-by", "value": "knete"},
|
|
137
|
+
])
|
|
138
|
+
|
|
139
|
+
server.start()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Both styles can be mixed freely. `name` defaults to the function name if omitted.
|
|
143
|
+
|
|
144
|
+
## Manifest generator
|
|
145
|
+
|
|
146
|
+
Generate all Kubernetes manifests for your operator or webhook server:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Operator
|
|
150
|
+
python -m knete examples/my_operator.py --name my-operator --image my-operator:v1
|
|
151
|
+
|
|
152
|
+
# Webhook with cert-manager TLS (generates a self-signed ClusterIssuer)
|
|
153
|
+
python -m knete examples/my_webhook.py --name my-webhook --image my-webhook:v1 --cert-manager
|
|
154
|
+
|
|
155
|
+
# Webhook using an existing ClusterIssuer
|
|
156
|
+
python -m knete examples/my_webhook.py --name my-webhook --image my-webhook:v1 --cert-manager my-issuer
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This writes `k8s/` manifests and a `Containerfile` ready to build. Apply with:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
kubectl apply -f k8s/
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Examples
|
|
166
|
+
|
|
167
|
+
| File | What it shows |
|
|
168
|
+
|------|---------------|
|
|
169
|
+
| `examples/webhook_example.py` | Validating + mutating + composite mutating webhooks |
|
|
170
|
+
| `examples/forbid_reserved_prefixes_decorator.py` | Decorator-style validating + mutating webhooks |
|
|
171
|
+
| `examples/example.py` | Basic pod controller |
|
|
172
|
+
| `examples/kyverno_clusterrolebinding.py` | Controller equivalent of a Kyverno generate rule |
|
|
173
|
+
| `examples/kyverno_clusterrolebinding_webhook.py` | Same policy as a mutating webhook with side-effects |
|
|
174
|
+
|
|
175
|
+
## Requirements
|
|
176
|
+
|
|
177
|
+
- Python 3.10+
|
|
178
|
+
- cert-manager (optional, for webhook TLS)
|
knete-0.1.0/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# knete
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="logos/knete.png" alt="knete logo" width="500">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
A lightweight Python framework for building Kubernetes controllers and admission webhooks.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Controllers** — watch any Kubernetes resource and react to create/update/delete events
|
|
12
|
+
- **Validating webhooks** — reject requests that violate policy
|
|
13
|
+
- **Mutating webhooks** — patch objects on the way in
|
|
14
|
+
- **Composite mutating webhooks** — fan-out multiple mutators to a single endpoint, accumulating all patches in one round-trip
|
|
15
|
+
- **Decorator-style webhooks** — register plain functions instead of subclasses
|
|
16
|
+
- **Manifest generator** — `python -m knete <file.py>` generates all k8s manifests (Deployment, Service, RBAC, WebhookConfigurations) including cert-manager TLS support
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install knete
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
### Controller
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from knete import Controller, Manager
|
|
30
|
+
|
|
31
|
+
class PodController(Controller):
|
|
32
|
+
class Meta:
|
|
33
|
+
resource = "v1/pods"
|
|
34
|
+
namespace = "default"
|
|
35
|
+
|
|
36
|
+
def on_create(self, name, namespace, labels, spec):
|
|
37
|
+
print(f"Pod created: {namespace}/{name}")
|
|
38
|
+
|
|
39
|
+
def on_delete(self, name):
|
|
40
|
+
print(f"Pod deleted: {name}")
|
|
41
|
+
|
|
42
|
+
Manager().register(PodController).start()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Validating webhook
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from knete import ValidatingWebhook, AdmissionResponse, WebhookServer
|
|
49
|
+
|
|
50
|
+
class NoLatestTagWebhook(ValidatingWebhook):
|
|
51
|
+
class Meta:
|
|
52
|
+
resource = "apps/v1/deployments"
|
|
53
|
+
operations = ["CREATE", "UPDATE"]
|
|
54
|
+
name = "no-latest-tag"
|
|
55
|
+
|
|
56
|
+
def validate(self, name, spec) -> AdmissionResponse:
|
|
57
|
+
for c in spec.get("template", {}).get("spec", {}).get("containers", []):
|
|
58
|
+
if c["image"].endswith(":latest") or ":" not in c["image"]:
|
|
59
|
+
return AdmissionResponse.deny(f"image {c['image']!r} uses :latest tag")
|
|
60
|
+
return AdmissionResponse.allow()
|
|
61
|
+
|
|
62
|
+
server = WebhookServer(cert_file="tls.crt", key_file="tls.key")
|
|
63
|
+
server.register(NoLatestTagWebhook)
|
|
64
|
+
server.start()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Mutating webhook
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from knete import MutatingWebhook, AdmissionResponse
|
|
71
|
+
|
|
72
|
+
class InjectLabelWebhook(MutatingWebhook):
|
|
73
|
+
class Meta:
|
|
74
|
+
resource = "v1/pods"
|
|
75
|
+
operations = ["CREATE"]
|
|
76
|
+
name = "inject-label"
|
|
77
|
+
|
|
78
|
+
def mutate(self, labels) -> AdmissionResponse:
|
|
79
|
+
if "managed-by" in labels:
|
|
80
|
+
return AdmissionResponse.allow()
|
|
81
|
+
return AdmissionResponse.patch([
|
|
82
|
+
{"op": "add", "path": "/metadata/labels/managed-by", "value": "knete"},
|
|
83
|
+
])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Multiple validators
|
|
87
|
+
|
|
88
|
+
Register each validating webhook separately — Kubernetes calls them in parallel,
|
|
89
|
+
so total latency is `max(validators)` rather than `sum(validators)`:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
server.register(NoLatestTagWebhook) # POST /validate/no-latest-tag
|
|
93
|
+
server.register(RequiredLabelsWebhook) # POST /validate/required-labels
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Decorator style
|
|
97
|
+
|
|
98
|
+
Skip the class entirely and register plain functions directly on the server:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
server = WebhookServer(cert_file="tls.crt", key_file="tls.key")
|
|
102
|
+
|
|
103
|
+
@server.validate("apps/v1/deployments", operations=["CREATE", "UPDATE"], name="no-latest-tag")
|
|
104
|
+
def no_latest_tag(name, spec) -> AdmissionResponse:
|
|
105
|
+
for c in spec.get("template", {}).get("spec", {}).get("containers", []):
|
|
106
|
+
if c["image"].endswith(":latest") or ":" not in c["image"]:
|
|
107
|
+
return AdmissionResponse.deny(f"image {c['image']!r} uses :latest tag")
|
|
108
|
+
|
|
109
|
+
@server.mutate("v1/pods", operations=["CREATE"], name="inject-label")
|
|
110
|
+
def inject_label(labels) -> AdmissionResponse:
|
|
111
|
+
if "managed-by" not in labels:
|
|
112
|
+
return AdmissionResponse.patch([
|
|
113
|
+
{"op": "add", "path": "/metadata/labels/managed-by", "value": "knete"},
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
server.start()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Both styles can be mixed freely. `name` defaults to the function name if omitted.
|
|
120
|
+
|
|
121
|
+
## Manifest generator
|
|
122
|
+
|
|
123
|
+
Generate all Kubernetes manifests for your operator or webhook server:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Operator
|
|
127
|
+
python -m knete examples/my_operator.py --name my-operator --image my-operator:v1
|
|
128
|
+
|
|
129
|
+
# Webhook with cert-manager TLS (generates a self-signed ClusterIssuer)
|
|
130
|
+
python -m knete examples/my_webhook.py --name my-webhook --image my-webhook:v1 --cert-manager
|
|
131
|
+
|
|
132
|
+
# Webhook using an existing ClusterIssuer
|
|
133
|
+
python -m knete examples/my_webhook.py --name my-webhook --image my-webhook:v1 --cert-manager my-issuer
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This writes `k8s/` manifests and a `Containerfile` ready to build. Apply with:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
kubectl apply -f k8s/
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
| File | What it shows |
|
|
145
|
+
|------|---------------|
|
|
146
|
+
| `examples/webhook_example.py` | Validating + mutating + composite mutating webhooks |
|
|
147
|
+
| `examples/forbid_reserved_prefixes_decorator.py` | Decorator-style validating + mutating webhooks |
|
|
148
|
+
| `examples/example.py` | Basic pod controller |
|
|
149
|
+
| `examples/kyverno_clusterrolebinding.py` | Controller equivalent of a Kyverno generate rule |
|
|
150
|
+
| `examples/kyverno_clusterrolebinding_webhook.py` | Same policy as a mutating webhook with side-effects |
|
|
151
|
+
|
|
152
|
+
## Requirements
|
|
153
|
+
|
|
154
|
+
- Python 3.10+
|
|
155
|
+
- cert-manager (optional, for webhook TLS)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
SPHINXOPTS ?=
|
|
2
|
+
SPHINXBUILD ?= sphinx-build
|
|
3
|
+
SOURCEDIR = .
|
|
4
|
+
BUILDDIR = _build
|
|
5
|
+
|
|
6
|
+
.PHONY: help clean html
|
|
7
|
+
|
|
8
|
+
help:
|
|
9
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
|
|
10
|
+
|
|
11
|
+
clean:
|
|
12
|
+
rm -rf $(BUILDDIR)
|
|
13
|
+
|
|
14
|
+
html:
|
|
15
|
+
@$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS)
|
knete-0.1.0/docs/api.rst
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
API reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
.. contents:: On this page
|
|
5
|
+
:local:
|
|
6
|
+
:depth: 1
|
|
7
|
+
|
|
8
|
+
KubeResource
|
|
9
|
+
------------
|
|
10
|
+
|
|
11
|
+
.. autoclass:: knete.KubeResource
|
|
12
|
+
:members:
|
|
13
|
+
:show-inheritance:
|
|
14
|
+
|
|
15
|
+
Controller
|
|
16
|
+
----------
|
|
17
|
+
|
|
18
|
+
.. autoclass:: knete.Controller
|
|
19
|
+
:members:
|
|
20
|
+
:show-inheritance:
|
|
21
|
+
|
|
22
|
+
Manager
|
|
23
|
+
-------
|
|
24
|
+
|
|
25
|
+
.. autoclass:: knete.Manager
|
|
26
|
+
:members:
|
|
27
|
+
:show-inheritance:
|
|
28
|
+
|
|
29
|
+
KubeClient
|
|
30
|
+
----------
|
|
31
|
+
|
|
32
|
+
.. autoclass:: knete.KubeClient
|
|
33
|
+
:members:
|
|
34
|
+
:show-inheritance:
|
|
35
|
+
|
|
36
|
+
auto_config
|
|
37
|
+
-----------
|
|
38
|
+
|
|
39
|
+
.. autofunction:: knete.auto_config
|
|
40
|
+
|
|
41
|
+
from_dict
|
|
42
|
+
---------
|
|
43
|
+
|
|
44
|
+
.. autofunction:: knete.from_dict
|
|
45
|
+
|
|
46
|
+
CustomResource
|
|
47
|
+
--------------
|
|
48
|
+
|
|
49
|
+
.. autoclass:: knete.CustomResource
|
|
50
|
+
:members:
|
|
51
|
+
:show-inheritance:
|
|
52
|
+
|
|
53
|
+
AdmissionResponse
|
|
54
|
+
-----------------
|
|
55
|
+
|
|
56
|
+
.. autoclass:: knete.AdmissionResponse
|
|
57
|
+
:members:
|
|
58
|
+
:show-inheritance:
|
|
59
|
+
|
|
60
|
+
ValidatingWebhook
|
|
61
|
+
-----------------
|
|
62
|
+
|
|
63
|
+
.. autoclass:: knete.ValidatingWebhook
|
|
64
|
+
:members:
|
|
65
|
+
:show-inheritance:
|
|
66
|
+
|
|
67
|
+
MutatingWebhook
|
|
68
|
+
---------------
|
|
69
|
+
|
|
70
|
+
.. autoclass:: knete.MutatingWebhook
|
|
71
|
+
:members:
|
|
72
|
+
:show-inheritance:
|
|
73
|
+
|
|
74
|
+
CompositeMutatingWebhook
|
|
75
|
+
------------------------
|
|
76
|
+
|
|
77
|
+
.. autoclass:: knete.CompositeMutatingWebhook
|
|
78
|
+
:members:
|
|
79
|
+
:show-inheritance:
|
|
80
|
+
|
|
81
|
+
WebhookServer
|
|
82
|
+
-------------
|
|
83
|
+
|
|
84
|
+
.. autoclass:: knete.WebhookServer
|
|
85
|
+
:members:
|
|
86
|
+
:show-inheritance:
|