Compare commits

..

No commits in common. "main" and "Lerentis/issue60" have entirely different histories.

18 changed files with 183 additions and 579 deletions

View File

@ -24,7 +24,7 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com" git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm - name: Install Helm
uses: azure/setup-helm@v4 uses: azure/setup-helm@v3
with: with:
version: v3.10.0 version: v3.10.0
@ -36,7 +36,7 @@ jobs:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Get app version from chart - name: Get app version from chart
uses: mikefarah/yq@v4.44.3 uses: mikefarah/yq@v4.40.5
id: app_version id: app_version
with: with:
cmd: yq '.appVersion' charts/bitwarden-crd-operator/Chart.yaml cmd: yq '.appVersion' charts/bitwarden-crd-operator/Chart.yaml
@ -56,7 +56,7 @@ jobs:
- name: "GHCR Build and Push" - name: "GHCR Build and Push"
id: docker_build id: docker_build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
push: true push: true
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

View File

@ -12,7 +12,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up Helm - name: Set up Helm
uses: azure/setup-helm@v4 uses: azure/setup-helm@v3
with: with:
version: v3.11.2 version: v3.11.2
@ -36,18 +36,6 @@ jobs:
if: steps.list-changed.outputs.changed == 'true' if: steps.list-changed.outputs.changed == 'true'
run: ct lint --target-branch ${{ github.event.repository.default_branch }} run: ct lint --target-branch ${{ github.event.repository.default_branch }}
- name: Install ah cli
run: |
export AH_VERSION=1.17.0
curl -LO https://github.com/artifacthub/hub/releases/download/v${AH_VERSION}/ah_${AH_VERSION}_linux_amd64.tar.gz
tar -xf ah_${AH_VERSION}_linux_amd64.tar.gz
chmod +x ./ah
sudo mv ./ah /usr/bin/ah
rm LICENSE
- name: ah lint
run: |
ah lint
pr-build: pr-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -57,10 +45,11 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: GHCR Build - name: "GHCR Build"
id: docker_build id: docker_build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
push: false push: false
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: ghcr.io/lerentis/bitwarden-crd-operator:dev tags: ghcr.io/lerentis/bitwarden-crd-operator:dev

View File

@ -1,29 +1,44 @@
FROM alpine:3.20.3 FROM alpine:3.18.4
LABEL org.opencontainers.image.source=https://github.com/Lerentis/bitwarden-crd-operator LABEL org.opencontainers.image.source=https://github.com/Lerentis/bitwarden-crd-operator
LABEL org.opencontainers.image.description="Kubernetes Operator to create k8s secrets from bitwarden" LABEL org.opencontainers.image.description="Kubernetes Operator to create k8s secrets from bitwarden"
LABEL org.opencontainers.image.licenses=MIT LABEL org.opencontainers.image.licenses=MIT
ARG PYTHON_VERSION=3.12.6-r0 ARG PYTHON_VERSION=3.11.6-r0
ARG PIP_VERSION=24.0-r2 ARG PIP_VERSION=23.1.2-r0
ARG GCOMPAT_VERSION=1.1.0-r4 ARG GCOMPAT_VERSION=1.1.0-r1
ARG LIBCRYPTO_VERSION=3.3.2-r0 ARG LIBCRYPTO_VERSION=3.1.3-r0
ARG BW_VERSION=2024.7.2 ARG BW_VERSION=2023.1.0
ARG NODE_VERSION=20.15.1-r0
COPY requirements.txt /requirements.txt COPY requirements.txt /requirements.txt
RUN set -eux; \ RUN set -eux; \
apk update; \ apk add --virtual build-dependencies wget unzip; \
apk del nodejs-current; \ ARCH="$(apk --print-arch)"; \
apk add nodejs=${NODE_VERSION} npm; \ case "${ARCH}" in \
npm install -g @bitwarden/cli@${BW_VERSION}; \ aarch64|arm64) \
apk add npm; \
npm install -g @bitwarden/cli@${BW_VERSION}; \
;; \
amd64|x86_64) \
cd /tmp; \
wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_VERSION}/bw-linux-${BW_VERSION}.zip; \
unzip /tmp/bw-linux-${BW_VERSION}.zip; \
mv /tmp/bw /usr/local/bin/bw; \
chmod +x /usr/local/bin/bw; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
apk del --purge build-dependencies; \
addgroup -S -g 1000 bw-operator; \ addgroup -S -g 1000 bw-operator; \
adduser -S -D -u 1000 -G bw-operator bw-operator; \ adduser -S -D -u 1000 -G bw-operator bw-operator; \
mkdir -p /home/bw-operator; \ mkdir -p /home/bw-operator; \
chown -R bw-operator /home/bw-operator; \ chown -R bw-operator /home/bw-operator; \
apk add gcc musl-dev libstdc++ gcompat=${GCOMPAT_VERSION} python3=${PYTHON_VERSION} py3-pip=${PIP_VERSION} libcrypto3=${LIBCRYPTO_VERSION}; \ apk add gcc musl-dev libstdc++ gcompat=${GCOMPAT_VERSION} python3=${PYTHON_VERSION} py3-pip=${PIP_VERSION} libcrypto3=${LIBCRYPTO_VERSION}; \
pip install -r /requirements.txt --no-warn-script-location --break-system-packages; \ pip install -r /requirements.txt --no-warn-script-location; \
rm /requirements.txt; \ rm /requirements.txt; \
apk del --purge gcc musl-dev libstdc++; apk del --purge gcc musl-dev libstdc++;

View File

@ -56,29 +56,23 @@ And you are set to create your first secret using this operator. For that you ne
```yaml ```yaml
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenSecret kind: BitwardenSecret
metadata: metadata:
name: name-of-your-management-object name: name-of-your-management-object
spec: spec:
content: content:
- element: - element:
secretName: nameOfTheFieldInBitwarden # for example username or filename secretName: nameOfTheFieldInBitwarden # for example username
secretRef: nameOfTheKeyInTheSecretToBeCreated secretRef: nameOfTheKeyInTheSecretToBeCreated
secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment' secretScope: login # for custom entries on bitwarden use 'fields'
- element: - element:
secretName: nameOfAnotherFieldInBitwarden # for example password or filename secretName: nameOfAnotherFieldInBitwarden # for example password
secretRef: nameOfAnotherKeyInTheSecretToBeCreated secretRef: nameOfAnotherKeyInTheSecretToBeCreated
secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment' secretScope: login # for custom entries on bitwarden use 'fields'
id: "A Secret ID from bitwarden" id: "A Secret ID from bitwarden"
name: "Name of the secret to be created" name: "Name of the secret to be created"
secretType: # Optional (Default: Opaque)
namespace: "Namespace of the secret to be created" namespace: "Namespace of the secret to be created"
labels: # Optional
key: value
annotations: # Optional
key: value
``` ```
The ID can be extracted from the browser when you open a item the ID is in the URL. The resulting secret looks something like this: The ID can be extracted from the browser when you open a item the ID is in the URL. The resulting secret looks something like this:
@ -93,8 +87,6 @@ metadata:
annotations: annotations:
managed: bitwarden-secrets.lerentis.uploadfilter24.eu managed: bitwarden-secrets.lerentis.uploadfilter24.eu
managedObject: bw-operator/test managedObject: bw-operator/test
labels:
key: value
name: name-of-your-management-object name: name-of-your-management-object
namespace: default namespace: default
type: Opaque type: Opaque
@ -106,7 +98,7 @@ For managing registry credentials, or pull secrets, you can create another kind
```yaml ```yaml
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: RegistryCredential kind: RegistryCredential
metadata: metadata:
name: name-of-your-management-object name: name-of-your-management-object
@ -117,10 +109,6 @@ spec:
id: "A Secret ID from bitwarden" id: "A Secret ID from bitwarden"
name: "Name of the secret to be created" name: "Name of the secret to be created"
namespace: "Namespace of the secret to be created" namespace: "Namespace of the secret to be created"
labels: # Optional
key: value
annotations: # Optional
key: value
``` ```
The resulting secret looks something like this: The resulting secret looks something like this:
@ -134,8 +122,6 @@ metadata:
annotations: annotations:
managed: bitwarden-secrets.lerentis.uploadfilter24.eu managed: bitwarden-secrets.lerentis.uploadfilter24.eu
managedObject: bw-operator/test managedObject: bw-operator/test
labels:
key: value
name: name-of-your-management-object name: name-of-your-management-object
namespace: default namespace: default
type: dockerconfigjson type: dockerconfigjson
@ -147,43 +133,24 @@ One of the more freely defined types that can be used with this operator you can
```yaml ```yaml
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenTemplate kind: BitwardenTemplate
metadata: metadata:
name: name-of-your-management-object name: name-of-your-management-object
spec: spec:
filename: "Key of the secret to be created"
name: "Name of the secret to be created" name: "Name of the secret to be created"
secretType: # Optional (Default: Opaque)
namespace: "Namespace of the secret to be created" namespace: "Namespace of the secret to be created"
labels: # Optional template: |
key: value ---
annotations: # Optional api:
key: value enabled: True
content: key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
- element: allowCrossOrigin: false
filename: config.yaml apps:
template: | "some.app.identifier:some_version":
--- pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
api: enabled: true
enabled: True
key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
enabled: true
- element:
filename: config2.yaml
template: |
---
api:
enabled: True
key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
enabled: false
``` ```
This will result in something like the following object: This will result in something like the following object:
@ -197,8 +164,6 @@ metadata:
annotations: annotations:
managed: bitwarden-template.lerentis.uploadfilter24.eu managed: bitwarden-template.lerentis.uploadfilter24.eu
managedObject: namespace/name-of-your-management-object managedObject: namespace/name-of-your-management-object
labels:
key: value
name: Name of the secret to be created name: Name of the secret to be created
namespace: Namespace of the secret to be created namespace: Namespace of the secret to be created
type: Opaque type: Opaque
@ -218,4 +183,4 @@ Please note that the rendering engine for this template is jinja2, with an addit
The operator uses the bitwarden cli in the background and does not communicate to the api directly. The cli mirrors the credential store locally but doesn't sync it on every get request. Instead it will sync each secret every 15 minutes (900 seconds). You can adjust the interval by setting `BW_SYNC_INTERVAL` in the values. If your secrets update very very frequently, you can force the operator to do a sync before each get by setting `BW_FORCE_SYNC="true"`. You might run into rate limits if you do this too frequent. The operator uses the bitwarden cli in the background and does not communicate to the api directly. The cli mirrors the credential store locally but doesn't sync it on every get request. Instead it will sync each secret every 15 minutes (900 seconds). You can adjust the interval by setting `BW_SYNC_INTERVAL` in the values. If your secrets update very very frequently, you can force the operator to do a sync before each get by setting `BW_FORCE_SYNC="true"`. You might run into rate limits if you do this too frequent.
Additionally the bitwarden cli session may expire at some time. In order to create a new session, the login command is triggered from time to time. In what interval exactly can be configured with the env `BW_RELOGIN_INTERVAL` which defaults to `3600` seconds. Additionally the bitwarden cli session may expire at some time. In order to create a new session, the login command is triggered from time to time. In what interval exactly can be configured with the env `BW_RELOGIN_INTERVAL` which defaults to 3600s.

View File

@ -4,9 +4,9 @@ description: Deploy the Bitwarden CRD Operator
type: application type: application
version: "v0.15.0" version: "v0.11.0"
appVersion: "0.14.0" appVersion: "0.10.0"
keywords: keywords:
- operator - operator
@ -20,7 +20,7 @@ home: https://lerentis.github.io/bitwarden-crd-operator/
sources: sources:
- https://github.com/Lerentis/bitwarden-crd-operator - https://github.com/Lerentis/bitwarden-crd-operator
kubeVersion: ">= 1.28.0-0" kubeVersion: ">= 1.23.0-0"
maintainers: maintainers:
- name: lerentis - name: lerentis
@ -32,22 +32,22 @@ annotations:
url: https://github.com/Lerentis/bitwarden-crd-operator url: https://github.com/Lerentis/bitwarden-crd-operator
artifacthub.io/crds: | artifacthub.io/crds: |
- kind: BitwardenSecret - kind: BitwardenSecret
version: v1beta8 version: v1beta5
name: bitwarden-secret name: bitwarden-secret
displayName: Bitwarden Secret displayName: Bitwarden Secret
description: Management Object to create secrets from bitwarden description: Management Object to create secrets from bitwarden
- kind: RegistryCredential - kind: RegistryCredential
version: v1beta8 version: v1beta5
name: registry-credential name: registry-credential
displayName: Regestry Credentials displayName: Regestry Credentials
description: Management Object to create regestry secrets from bitwarden description: Management Object to create regestry secrets from bitwarden
- kind: BitwardenTemplate - kind: BitwardenTemplate
version: v1beta8 version: v1beta5
name: bitwarden-template name: bitwarden-template
displayName: Bitwarden Template displayName: Bitwarden Template
description: Management Object to create secrets from a jinja template with a bitwarden lookup description: Management Object to create secrets from a jinja template with a bitwarden lookup
artifacthub.io/crdsExamples: | artifacthub.io/crdsExamples: |
- apiVersion: lerentis.uploadfilter24.eu/v1beta8 - apiVersion: lerentis.uploadfilter24.eu/v1beta5
kind: BitwardenSecret kind: BitwardenSecret
metadata: metadata:
name: test name: test
@ -61,13 +61,10 @@ annotations:
secretRef: passwordOfUser secretRef: passwordOfUser
id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
name: "test-secret" name: "test-secret"
secretType: Obaque #Optional
namespace: "default" namespace: "default"
labels: labels:
key: value key: value
annotations: - apiVersion: lerentis.uploadfilter24.eu/v1beta5
key: value
- apiVersion: lerentis.uploadfilter24.eu/v1beta8
kind: RegistryCredential kind: RegistryCredential
metadata: metadata:
name: test name: test
@ -80,46 +77,32 @@ annotations:
namespace: "default" namespace: "default"
labels: labels:
key: value key: value
annotations: - apiVersion: "lerentis.uploadfilter24.eu/v1beta5"
key: value
- apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
kind: BitwardenTemplate kind: BitwardenTemplate
metadata: metadata:
name: test name: test
spec: spec:
filename: "config.yaml"
name: "test-regcred" name: "test-regcred"
secretType: Obaque #Optional
namespace: "default" namespace: "default"
labels: labels:
key: value key: value
annotations: template: |
key: value ---
content: api:
- element: enabled: True
filename: "config.yaml" key: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "key") }}
template: | allowCrossOrigin: false
--- apps:
api: "some.app.identifier:some_version":
enabled: True pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "public_key") }}
key: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "key") }} enabled: true
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "public_key") }}
enabled: true
artifacthub.io/license: MIT artifacthub.io/license: MIT
artifacthub.io/operator: "true" artifacthub.io/operator: "true"
artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/containsSecurityUpdates: "false"
artifacthub.io/changes: | artifacthub.io/changes: |
- kind: changed - kind: changed
description: "BitwardenTemplate can now handle multiple files" description: "Added the possibility to add labels to generated secrets"
- kind: changed
description: "Removed long deprecated versions"
- kind: changed
description: "Update kubernetes from v29.0.0 to v30.1.0"
- kind: changed
description: "Update alpine from 3.20.2 to 3.20.3"
artifacthub.io/images: | artifacthub.io/images: |
- name: bitwarden-crd-operator - name: bitwarden-crd-operator
image: ghcr.io/lerentis/bitwarden-crd-operator:0.14.0 image: ghcr.io/lerentis/bitwarden-crd-operator:0.10.0

View File

@ -13,10 +13,9 @@ spec:
shortNames: shortNames:
- bws - bws
versions: versions:
- name: v1beta7 - name: v1beta4
served: true served: false
storage: false storage: true
deprecated: true
schema: schema:
openAPIV3Schema: openAPIV3Schema:
type: object type: object
@ -46,19 +45,11 @@ spec:
type: string type: string
name: name:
type: string type: string
secretType:
type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required: required:
- id - id
- namespace - namespace
- name - name
- name: v1beta8 - name: v1beta5
served: true served: true
storage: true storage: true
schema: schema:
@ -90,14 +81,22 @@ spec:
type: string type: string
name: name:
type: string type: string
secretType:
type: string
labels: labels:
type: object type: array
x-kubernetes-preserve-unknown-fields: true items:
annotations: type: object
type: object properties:
x-kubernetes-preserve-unknown-fields: true json:
x-kubernetes-preserve-unknown-fields: true
type: object
properties:
spec:
type: object
properties:
foo:
type: string
bar:
type: string
required: required:
- id - id
- namespace - namespace

View File

@ -13,10 +13,9 @@ spec:
shortNames: shortNames:
- bwt - bwt
versions: versions:
- name: v1beta7 - name: v1beta4
served: true served: false
storage: false storage: true
deprecated: true
schema: schema:
openAPIV3Schema: openAPIV3Schema:
type: object type: object
@ -32,20 +31,12 @@ spec:
type: string type: string
name: name:
type: string type: string
secretType:
type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required: required:
- filename - filename
- template - template
- namespace - namespace
- name - name
- name: v1beta8 - name: v1beta5
served: true served: true
storage: true storage: true
schema: schema:
@ -55,33 +46,32 @@ spec:
spec: spec:
type: object type: object
properties: properties:
filename:
type: string
template:
type: string
namespace: namespace:
type: string type: string
name: name:
type: string type: string
secretType: labels:
type: string
content:
type: array type: array
items: items:
type: object type: object
properties: properties:
element: json:
x-kubernetes-preserve-unknown-fields: true
type: object type: object
properties: properties:
filename: spec:
type: string type: object
template: properties:
type: string foo:
required: type: string
- filename bar:
- template type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required: required:
- filename
- template
- namespace - namespace
- name - name

View File

@ -13,10 +13,9 @@ spec:
shortNames: shortNames:
- rgc - rgc
versions: versions:
- name: v1beta7 - name: v1beta4
served: true served: false
storage: false storage: true
deprecated: true
schema: schema:
openAPIV3Schema: openAPIV3Schema:
type: object type: object
@ -36,12 +35,6 @@ spec:
type: string type: string
name: name:
type: string type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required: required:
- id - id
- namespace - namespace
@ -49,7 +42,7 @@ spec:
- usernameRef - usernameRef
- passwordRef - passwordRef
- registry - registry
- name: v1beta8 - name: v1beta5
served: true served: true
storage: true storage: true
schema: schema:
@ -72,11 +65,21 @@ spec:
name: name:
type: string type: string
labels: labels:
type: object type: array
x-kubernetes-preserve-unknown-fields: true items:
annotations: type: object
type: object properties:
x-kubernetes-preserve-unknown-fields: true json:
x-kubernetes-preserve-unknown-fields: true
type: object
properties:
spec:
type: object
properties:
foo:
type: string
bar:
type: string
required: required:
- id - id
- namespace - namespace

View File

@ -8,8 +8,6 @@ spec:
{{- if not .Values.autoscaling.enabled }} {{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.replicaCount }}
{{- end }} {{- end }}
strategy:
type: {{ .Values.deploymentStrategy }}
selector: selector:
matchLabels: matchLabels:
{{- include "bitwarden-crd-operator.selectorLabels" . | nindent 6 }} {{- include "bitwarden-crd-operator.selectorLabels" . | nindent 6 }}

View File

@ -14,8 +14,6 @@ imagePullSecrets: []
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
deploymentStrategy: "Recreate"
# env: # env:
# - name: BW_FORCE_SYNC # - name: BW_FORCE_SYNC
# value: "false" # value: "false"

View File

@ -1,9 +1,8 @@
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta5"
kind: BitwardenSecret kind: BitwardenSecret
metadata: metadata:
name: test name: test
namespace: default
spec: spec:
content: content:
- element: - element:
@ -16,15 +15,11 @@ spec:
secretScope: login secretScope: login
id: "88781348-c81c-4367-9801-550360c21295" id: "88781348-c81c-4367-9801-550360c21295"
name: "test-secret" name: "test-secret"
secretType: Opaque
namespace: "default" namespace: "default"
labels: labels:
key: value - key: value
app: example-app
annotations:
custom.annotation: is-used
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta5"
kind: BitwardenSecret kind: BitwardenSecret
metadata: metadata:
name: test-scope name: test-scope
@ -37,3 +32,5 @@ spec:
id: "466fc4b0-ffca-4444-8d88-b59d4de3d928" id: "466fc4b0-ffca-4444-8d88-b59d4de3d928"
name: "test-scope" name: "test-scope"
namespace: "default" namespace: "default"
labels:
- key: value

View File

@ -1,5 +1,5 @@
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: RegistryCredential kind: RegistryCredential
metadata: metadata:
name: test name: test
@ -10,8 +10,3 @@ spec:
id: "3b249ec7-9ce7-440a-9558-f34f3ab10680" id: "3b249ec7-9ce7-440a-9558-f34f3ab10680"
name: "test-regcred" name: "test-regcred"
namespace: "default" namespace: "default"
labels:
namespace: default
tenant: example-team
annotations:
custom.annotation: is-used

View File

@ -1,38 +1,19 @@
--- ---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8" apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenTemplate kind: BitwardenTemplate
metadata: metadata:
name: test name: test
spec: spec:
filename: "config.yaml"
name: "test-template" name: "test-template"
namespace: "default" namespace: "default"
labels: template: |
key: value ---
app: example-app api:
annotations: enabled: True
custom.annotation: is-used key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }}
content: allowCrossOrigin: false
- element: apps:
filename: config.yaml "some.app.identifier:some_version":
template: | pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }}
--- enabled: true
api:
enabled: True
key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }}
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }}
enabled: true
- element:
filename: config2.yaml
template: |
---
api:
enabled: True
key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }}
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }}
enabled: false

View File

@ -1,4 +1,4 @@
kopf==1.37.2 kopf==1.36.2
kubernetes==30.1.0 kubernetes==26.1.0
Jinja2==3.1.4 Jinja2==3.1.2
schedule==1.2.2 schedule==1.2.1

View File

@ -1,10 +1,8 @@
apiVersion: skaffold/v4beta9 apiVersion: skaffold/v4beta5
kind: Config kind: Config
metadata: metadata:
name: bitwarden-crd-operator name: bitwarden-crd-operator
build: build:
tagPolicy:
sha256: {}
artifacts: artifacts:
- image: ghcr.io/lerentis/bitwarden-crd-operator - image: ghcr.io/lerentis/bitwarden-crd-operator
docker: docker:
@ -15,43 +13,5 @@ deploy:
- name: bitwarden-crd-operator - name: bitwarden-crd-operator
chartPath: charts/bitwarden-crd-operator chartPath: charts/bitwarden-crd-operator
valuesFiles: valuesFiles:
- ./charts/bitwarden-crd-operator/myvalues.yaml - env/values.yaml
setValueTemplates: version: v0.7.4
image.repository: "{{.IMAGE_REPO_ghcr_io_lerentis_bitwarden_crd_operator}}"
image.tag: "{{.IMAGE_TAG_ghcr_io_lerentis_bitwarden_crd_operator}}@{{.IMAGE_DIGEST_ghcr_io_lerentis_bitwarden_crd_operator}}"
hooks:
after:
- host:
command:
- kubectl
- apply
- -f
- ./example*.yaml
- host:
command:
- sleep
- '5'
- host:
command:
- kubectl
- get
- secret
- test-regcred
- host:
command:
- kubectl
- get
- secret
- test-scope
- host:
command:
- kubectl
- get
- secret
- test-secret
- host:
command:
- kubectl
- get
- secret
- test-template

View File

@ -45,7 +45,6 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels') labels = spec.get('labels')
custom_annotations = spec.get('annotations')
unlock_bw(logger) unlock_bw(logger)
logger.info(f"Locking up secret with ID: {id}") logger.info(f"Locking up secret with ID: {id}")
@ -58,9 +57,6 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not labels: if not labels:
labels = {} labels = {}
@ -75,11 +71,6 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
password_ref, password_ref,
registry) registry)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
api.create_namespaced_secret( api.create_namespaced_secret(
secret_namespace, secret secret_namespace, secret
) )
@ -105,8 +96,6 @@ def update_managed_registry_secret(
id = spec.get('id') id = spec.get('id')
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
old_config = None old_config = None
old_secret_name = None old_secret_name = None
@ -143,16 +132,9 @@ def update_managed_registry_secret(
"managed": "registry-credential.lerentis.uploadfilter24.eu", "managed": "registry-credential.lerentis.uploadfilter24.eu",
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not labels:
labels = {}
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels) name=secret_name, annotations=annotations)
secret = create_dockerlogin( secret = create_dockerlogin(
logger, logger,
secret, secret,
@ -160,25 +142,16 @@ def update_managed_registry_secret(
username_ref, username_ref,
password_ref, password_ref,
registry) registry)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
try: try:
api.replace_namespaced_secret( obj = api.replace_namespaced_secret(
name=secret_name, name=secret_name,
body=secret, body=secret,
namespace="{}".format(secret_namespace)) namespace="{}".format(secret_namespace))
logger.info( logger.info(
f"Secret {secret_namespace}/{secret_name} has been updated") f"Secret {secret_namespace}/{secret_name} has been updated")
except BaseException as e: except BaseException:
logger.warn( logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!") f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('registry-credential.lerentis.uploadfilter24.eu') @kopf.on.delete('registry-credential.lerentis.uploadfilter24.eu')

View File

@ -3,9 +3,10 @@ import kubernetes
import base64 import base64
import json import json
from utils.utils import unlock_bw, get_secret_from_bitwarden, parse_login_scope, parse_fields_scope, get_attachment, bw_sync_interval from utils.utils import unlock_bw, get_secret_from_bitwarden, parse_login_scope, parse_fields_scope, bw_sync_interval
def create_kv(logger, id, secret, secret_json, content_def): def create_kv(secret, secret_json, content_def):
secret.type = "Opaque"
secret.data = {} secret.data = {}
for eleml in content_def: for eleml in content_def:
for k, elem in eleml.items(): for k, elem in eleml.items():
@ -30,13 +31,6 @@ def create_kv(logger, id, secret, secret_json, content_def):
f"Field {_secret_key} has no value in bitwarden secret") f"Field {_secret_key} has no value in bitwarden secret")
secret.data[_secret_ref] = str(base64.b64encode( secret.data[_secret_ref] = str(base64.b64encode(
value.encode("utf-8")), "utf-8") value.encode("utf-8")), "utf-8")
if _secret_scope == "attachment":
value = get_attachment(logger, id, _secret_key)
if value is None:
raise Exception(
f"Attachment {_secret_key} has no value in bitwarden secret")
secret.data[_secret_ref] = str(base64.b64encode(
value.encode("utf-8")), "utf-8")
return secret return secret
@ -48,8 +42,6 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels') labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
unlock_bw(logger) unlock_bw(logger)
logger.info(f"Locking up secret with ID: {id}") logger.info(f"Locking up secret with ID: {id}")
@ -62,25 +54,13 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not custom_secret_type:
custom_secret_type = 'Opaque'
if not labels: if not labels:
labels = {} labels = {}
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels) name=secret_name, annotations=annotations, labels=labels)
secret.type = custom_secret_type secret = create_kv(secret, secret_json_object, content_def)
secret = create_kv(logger, id, secret, secret_json_object, content_def)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
api.create_namespaced_secret( api.create_namespaced_secret(
namespace="{}".format(secret_namespace), namespace="{}".format(secret_namespace),
@ -106,30 +86,19 @@ def update_managed_secret(
old_config = None old_config = None
old_secret_name = None old_secret_name = None
old_secret_namespace = None old_secret_namespace = None
old_secret_type = None
if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations: if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations:
old_config = json.loads( old_config = json.loads(
body.metadata.annotations['kopf.zalando.org/last-handled-configuration']) body.metadata.annotations['kopf.zalando.org/last-handled-configuration'])
old_secret_name = old_config['spec'].get('name') old_secret_name = old_config['spec'].get('name')
old_secret_namespace = old_config['spec'].get('namespace') old_secret_namespace = old_config['spec'].get('namespace')
old_secret_type = old_config['spec'].get('secretType')
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
if not custom_secret_type:
custom_secret_type = 'Opaque'
if not old_secret_type:
old_secret_type = 'Opaque'
if old_config is not None and ( if old_config is not None and (
old_secret_name != secret_name or old_secret_namespace != secret_namespace or old_secret_type != custom_secret_type): old_secret_name != secret_name or old_secret_namespace != secret_namespace):
# If the name of the secret or the namespace of the secret is different # If the name of the secret or the namespace of the secret is different
# We have to delete the secret an recreate it # We have to delete the secret an recreate it
logger.info("Secret name, namespace or type changed, let's recreate it") logger.info("Secret name or namespace changed, let's recreate it")
delete_managed_secret( delete_managed_secret(
old_config['spec'], old_config['spec'],
name, name,
@ -150,36 +119,21 @@ def update_managed_secret(
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not labels:
labels = {}
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels) name=secret_name, annotations=annotations)
secret.type = custom_secret_type secret = create_kv(secret, secret_json_object, content_def)
secret = create_kv(logger, id, secret, secret_json_object, content_def)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
try: try:
api.replace_namespaced_secret( obj = api.replace_namespaced_secret(
name=secret_name, name=secret_name,
body=secret, body=secret,
namespace="{}".format(secret_namespace)) namespace="{}".format(secret_namespace))
logger.info( logger.info(
f"Secret {secret_namespace}/{secret_name} has been updated") f"Secret {secret_namespace}/{secret_name} has been updated")
except BaseException as e: except BaseException:
logger.warn( logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!") f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('bitwarden-secret.lerentis.uploadfilter24.eu') @kopf.on.delete('bitwarden-secret.lerentis.uploadfilter24.eu')

View File

@ -17,6 +17,7 @@ def render_template(logger, template):
def create_template_secret(logger, secret, filename, template): def create_template_secret(logger, secret, filename, template):
secret.type = "Opaque"
secret.data = {} secret.data = {}
secret.data[filename] = str( secret.data[filename] = str(
base64.b64encode( base64.b64encode(
@ -24,80 +25,15 @@ def create_template_secret(logger, secret, filename, template):
"utf-8") "utf-8")
return secret return secret
def create_template_obj(logger, secret, content_def):
secret.data = {}
for eleml in content_def:
for k, elem in eleml.items():
for key, value in elem.items():
if key == "filename":
_file_name = value
if key == "template":
_template = value
secret.data[_file_name] = str(
base64.b64encode(
render_template(logger, _template).encode("utf-8")),
"utf-8")
return secret
@kopf.on.create('bitwarden-template.lerentis.uploadfilter24.eu') @kopf.on.create('bitwarden-template.lerentis.uploadfilter24.eu')
def create_managed_secret(spec, name, namespace, logger, body, **kwargs): def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
template = spec.get('template')
if template is not None:
create_beta7_secret(spec, name, namespace, logger, body, **kwargs)
secret_name = spec.get('name')
secret_namespace = spec.get('namespace')
custom_secret_type = spec.get('secretType')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
content_def = spec.get('content')
unlock_bw(logger)
api = kubernetes.client.CoreV1Api()
annotations = {
"managed": "bitwarden-template.lerentis.uploadfilter24.eu",
"managedObject": f"{namespace}/{name}"
}
if custom_annotations:
annotations.update(custom_annotations)
if not custom_secret_type:
custom_secret_type = 'Opaque'
if not labels:
labels = {}
secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels)
secret.type = custom_secret_type
secret = create_template_obj(logger, secret, content_def)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
api.create_namespaced_secret(
namespace="{}".format(secret_namespace),
body=secret
)
logger.info(f"Secret {secret_namespace}/{secret_name} has been created")
def create_beta7_secret(spec, name, namespace, logger, body, **kwargs):
template = spec.get('template') template = spec.get('template')
filename = spec.get('filename') filename = spec.get('filename')
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels') labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
unlock_bw(logger) unlock_bw(logger)
@ -108,123 +44,20 @@ def create_beta7_secret(spec, name, namespace, logger, body, **kwargs):
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not custom_secret_type:
custom_secret_type = 'Opaque'
if not labels: if not labels:
labels = {} labels = {}
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels) name=secret_name, annotations=annotations, labels=labels)
secret.type = custom_secret_type
secret = create_template_secret(logger, secret, filename, template) secret = create_template_secret(logger, secret, filename, template)
# Garbage collection will delete the generated secret if the owner obj = api.create_namespaced_secret(
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
api.create_namespaced_secret(
secret_namespace, secret secret_namespace, secret
) )
logger.info(f"Secret {secret_namespace}/{secret_name} has been created") logger.info(f"Secret {secret_namespace}/{secret_name} has been created")
def update_beta7_secret(
spec,
status,
name,
namespace,
logger,
body,
**kwargs):
template = spec.get('template')
filename = spec.get('filename')
secret_name = spec.get('name')
secret_namespace = spec.get('namespace')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
if not custom_secret_type:
custom_secret_type = 'Opaque'
old_config = None
old_secret_name = None
old_secret_namespace = None
old_secret_type = None
if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations:
old_config = json.loads(
body.metadata.annotations['kopf.zalando.org/last-handled-configuration'])
old_secret_name = old_config['spec'].get('name')
old_secret_namespace = old_config['spec'].get('namespace')
old_secret_type = old_config['spec'].get('secretType')
secret_name = spec.get('name')
secret_namespace = spec.get('namespace')
if not old_secret_type:
old_secret_type = 'Opaque'
if old_config is not None and (
old_secret_name != secret_name or old_secret_namespace != secret_namespace or old_secret_type != custom_secret_type):
# If the name of the secret or the namespace of the secret is different
# We have to delete the secret an recreate it
logger.info("Secret name or namespace changed, let's recreate it")
delete_managed_secret(
old_config['spec'],
name,
namespace,
logger,
**kwargs)
create_managed_secret(spec, name, namespace, logger, body, **kwargs)
return
unlock_bw(logger)
api = kubernetes.client.CoreV1Api()
annotations = {
"managed": "bitwarden-template.lerentis.uploadfilter24.eu",
"managedObject": f"{namespace}/{name}"
}
if custom_annotations:
annotations.update(custom_annotations)
if not labels:
labels = {}
secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels)
secret.type = custom_secret_type
secret = create_template_secret(logger, secret, filename, template)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
try:
api.replace_namespaced_secret(
name=secret_name,
body=secret,
namespace="{}".format(secret_namespace))
logger.info(
f"Secret {secret_namespace}/{secret_name} has been updated")
except BaseException as e:
logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.update('bitwarden-template.lerentis.uploadfilter24.eu') @kopf.on.update('bitwarden-template.lerentis.uploadfilter24.eu')
@kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=bw_sync_interval) @kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=bw_sync_interval)
@ -238,36 +71,23 @@ def update_managed_secret(
**kwargs): **kwargs):
template = spec.get('template') template = spec.get('template')
if template is not None: filename = spec.get('filename')
update_beta7_secret(spec, status, name, namespace, logger, body, **kwargs)
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
content_def = spec.get('content')
if not custom_secret_type:
custom_secret_type = 'Opaque'
old_config = None old_config = None
old_secret_name = None old_secret_name = None
old_secret_namespace = None old_secret_namespace = None
old_secret_type = None
if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations: if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations:
old_config = json.loads( old_config = json.loads(
body.metadata.annotations['kopf.zalando.org/last-handled-configuration']) body.metadata.annotations['kopf.zalando.org/last-handled-configuration'])
old_secret_name = old_config['spec'].get('name') old_secret_name = old_config['spec'].get('name')
old_secret_namespace = old_config['spec'].get('namespace') old_secret_namespace = old_config['spec'].get('namespace')
old_secret_type = old_config['spec'].get('secretType')
secret_name = spec.get('name') secret_name = spec.get('name')
secret_namespace = spec.get('namespace') secret_namespace = spec.get('namespace')
if not old_secret_type:
old_secret_type = 'Opaque'
if old_config is not None and ( if old_config is not None and (
old_secret_name != secret_name or old_secret_namespace != secret_namespace or old_secret_type != custom_secret_type): old_secret_name != secret_name or old_secret_namespace != secret_namespace):
# If the name of the secret or the namespace of the secret is different # If the name of the secret or the namespace of the secret is different
# We have to delete the secret an recreate it # We have to delete the secret an recreate it
logger.info("Secret name or namespace changed, let's recreate it") logger.info("Secret name or namespace changed, let's recreate it")
@ -288,37 +108,21 @@ def update_managed_secret(
"managed": "bitwarden-template.lerentis.uploadfilter24.eu", "managed": "bitwarden-template.lerentis.uploadfilter24.eu",
"managedObject": f"{namespace}/{name}" "managedObject": f"{namespace}/{name}"
} }
if custom_annotations:
annotations.update(custom_annotations)
if not labels:
labels = {}
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations, labels=labels) name=secret_name, annotations=annotations)
secret.type = custom_secret_type secret = create_template_secret(logger, secret, filename, template)
secret = create_template_obj(logger, secret, content_def)
# Garbage collection will delete the generated secret if the owner
# Is not in the same namespace as the generated secret
if secret_namespace == namespace:
kopf.append_owner_reference(secret)
try: try:
api.replace_namespaced_secret( obj = api.replace_namespaced_secret(
name=secret_name, name=secret_name,
body=secret, body=secret,
namespace="{}".format(secret_namespace)) namespace="{}".format(secret_namespace))
logger.info( logger.info(
f"Secret {secret_namespace}/{secret_name} has been updated") f"Secret {secret_namespace}/{secret_name} has been updated")
except BaseException as e: except BaseException:
logger.warn( logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!") f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('bitwarden-template.lerentis.uploadfilter24.eu') @kopf.on.delete('bitwarden-template.lerentis.uploadfilter24.eu')