Compare commits
16 commits
main
...
feat/etcd-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7d09b458 | ||
|
|
97eef022e5 | ||
|
|
86a93af283 | ||
|
|
040d5d4681 | ||
|
|
f8f3b29480 | ||
|
|
aaf9dd047c | ||
|
|
4d7b4bbb33 | ||
|
|
9fc99eed50 | ||
|
|
6deca1b07d | ||
|
|
ed77e9c6e3 | ||
|
|
12fc9cd415 | ||
|
|
561bc4dc23 | ||
|
|
0e03ed9ad4 | ||
|
|
ee8ceecc0e | ||
|
|
51c8192e1e | ||
|
|
8734c38da3 |
18 changed files with 1175 additions and 4 deletions
113
hack/e2e-apps/etcd.bats
Normal file
113
hack/e2e-apps/etcd.bats
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
# The etcd chart pins the Helm release name to 'etcd' via
|
||||
# packages/extra/etcd/templates/check-release-name.yaml, so every test
|
||||
# must apply its Etcd with metadata.name: etcd. setup() clears any
|
||||
# prior run so tests remain independent despite the singleton name.
|
||||
|
||||
setup() {
|
||||
kubectl -n tenant-test delete etcd.apps.cozystack.io --all --ignore-not-found --timeout=2m
|
||||
# HelmRelease teardown is async relative to the CR deletion above; wait for
|
||||
# downstream resources so the next test starts from a clean state.
|
||||
kubectl -n tenant-test wait hr/etcd --for=delete --timeout=2m --ignore-not-found
|
||||
kubectl -n tenant-test wait secret/etcd-s3-creds --for=delete --timeout=1m --ignore-not-found
|
||||
kubectl -n tenant-test wait etcdbackupschedule.etcd.aenix.io/etcd --for=delete --timeout=1m --ignore-not-found
|
||||
}
|
||||
|
||||
dump_diagnostics() {
|
||||
echo "# --- diagnostics ---" >&3
|
||||
kubectl -n tenant-test get etcdcluster,etcdbackupschedule,cronjob -o wide >&3 2>&1 || true
|
||||
kubectl -n tenant-test describe etcdbackupschedule etcd >&3 2>&1 || true
|
||||
kubectl -n cozy-etcd-operator logs -l app.kubernetes.io/name=etcd-operator --tail=100 >&3 2>&1 || true
|
||||
}
|
||||
|
||||
# Wait until the etcd HelmRelease is reconciled by Flux and its
|
||||
# condition=ready is set. kubectl wait fails immediately on a missing
|
||||
# resource, so poll for existence first.
|
||||
wait_etcd_hr_ready() {
|
||||
timeout 60 sh -ec 'until kubectl -n tenant-test get hr/etcd >/dev/null 2>&1; do sleep 2; done'
|
||||
kubectl -n tenant-test wait hr/etcd --timeout=60s --for=condition=ready
|
||||
}
|
||||
|
||||
@test "Create Etcd" {
|
||||
kubectl apply -f- <<EOF
|
||||
apiVersion: apps.cozystack.io/v1alpha1
|
||||
kind: Etcd
|
||||
metadata:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
spec:
|
||||
size: 1Gi
|
||||
replicas: 3
|
||||
storageClass: ""
|
||||
resources:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
EOF
|
||||
wait_etcd_hr_ready || { dump_diagnostics; false; }
|
||||
kubectl -n tenant-test wait etcdcluster.etcd.aenix.io etcd --timeout=180s --for=jsonpath='{.status.conditions[?(@.type=="Ready")].status}'=True || { dump_diagnostics; false; }
|
||||
}
|
||||
|
||||
@test "Create Etcd with empty backup block (disabled by default)" {
|
||||
kubectl apply -f- <<EOF
|
||||
apiVersion: apps.cozystack.io/v1alpha1
|
||||
kind: Etcd
|
||||
metadata:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
spec:
|
||||
size: 1Gi
|
||||
replicas: 3
|
||||
storageClass: ""
|
||||
resources:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
backup: {}
|
||||
EOF
|
||||
wait_etcd_hr_ready || { dump_diagnostics; false; }
|
||||
# With backup disabled, neither the schedule nor the secret should be created.
|
||||
! kubectl -n tenant-test get etcdbackupschedule.etcd.aenix.io etcd 2>/dev/null
|
||||
! kubectl -n tenant-test get secret etcd-s3-creds 2>/dev/null
|
||||
}
|
||||
|
||||
@test "Create Etcd with backup schedule" {
|
||||
kubectl apply -f- <<EOF
|
||||
apiVersion: apps.cozystack.io/v1alpha1
|
||||
kind: Etcd
|
||||
metadata:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
spec:
|
||||
size: 1Gi
|
||||
replicas: 3
|
||||
storageClass: ""
|
||||
resources:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
backup:
|
||||
enabled: true
|
||||
# Schedule is chosen far in the future so no Jobs fire during the
|
||||
# ~6-minute test window; this test only checks that the chart renders
|
||||
# EtcdBackupSchedule/Secret and that the etcd-operator materializes a
|
||||
# CronJob — it does NOT verify that backups reach S3. The endpoint
|
||||
# below intentionally resolves nowhere to keep the test self-contained.
|
||||
schedule: "0 0 1 1 *"
|
||||
destinationPath: "s3://test-bucket/etcd-backups/"
|
||||
endpointURL: "http://no-such-endpoint.invalid:9000"
|
||||
region: "us-east-1"
|
||||
forcePathStyle: true
|
||||
s3AccessKey: "e2e-access-key"
|
||||
s3SecretKey: "e2e-secret-key"
|
||||
successfulJobsHistoryLimit: 1
|
||||
failedJobsHistoryLimit: 1
|
||||
EOF
|
||||
wait_etcd_hr_ready || { dump_diagnostics; false; }
|
||||
kubectl -n tenant-test wait etcdcluster.etcd.aenix.io etcd --timeout=180s --for=jsonpath='{.status.conditions[?(@.type=="Ready")].status}'=True || { dump_diagnostics; false; }
|
||||
kubectl -n tenant-test get etcdbackupschedule.etcd.aenix.io etcd || { dump_diagnostics; false; }
|
||||
# Verify the region field propagated to the EtcdBackupSchedule.
|
||||
REGION=$(kubectl -n tenant-test get etcdbackupschedule.etcd.aenix.io etcd -o jsonpath='{.spec.destination.s3.region}')
|
||||
[ "$REGION" = "us-east-1" ]
|
||||
kubectl -n tenant-test get secret etcd-s3-creds -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 -d | grep -q '^e2e-access-key$'
|
||||
# The etcd-operator generates a CronJob from the EtcdBackupSchedule. Wait for it.
|
||||
timeout 120 sh -ec "until [ \"\$(kubectl -n tenant-test get cronjob -l etcd.aenix.io/etcdbackupschedule-name=etcd -o jsonpath='{.items[*].metadata.name}' 2>/dev/null)\" != '' ]; do sleep 5; done" || { dump_diagnostics; false; }
|
||||
}
|
||||
|
|
@ -21,3 +21,4 @@ spec:
|
|||
install:
|
||||
namespace: cozy-etcd-operator
|
||||
releaseName: etcd-operator
|
||||
upgradeCRDs: CreateReplace
|
||||
|
|
|
|||
|
|
@ -5,3 +5,6 @@ include ../../../hack/package.mk
|
|||
generate:
|
||||
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
|
||||
../../../hack/update-crd.sh
|
||||
|
||||
test:
|
||||
helm unittest .
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
# Etcd-cluster
|
||||
|
||||
## Backups
|
||||
|
||||
When `backup.enabled` is set to `true`, the chart renders an `EtcdBackupSchedule` (etcd.aenix.io/v1alpha1) and an S3 credentials `Secret`. The etcd-operator v0.4.3+ reconciles the schedule into a `CronJob` that periodically snapshots the cluster to S3.
|
||||
|
||||
Enabling backup requires the following fields to be explicitly set (defaults are empty strings so that missing values fail fast at template render time): `backup.s3AccessKey`, `backup.s3SecretKey`, `backup.destinationPath` (must start with `s3://` and have no `//` segments), and `backup.endpointURL`. S3 credentials passed through plain values end up in the HelmRelease manifest — for production deployments prefer an external secret management tool (ESO, Sealed Secrets, etc.) over committing the keys to Git.
|
||||
|
||||
**Restore** (`EtcdCluster.spec.bootstrap`) and the one-shot `EtcdBackup` custom resource shipped upstream in v0.4.3 are not yet exposed through this chart. Restoring from a snapshot or taking an ad-hoc backup currently requires hand-applying the corresponding custom resource manifest.
|
||||
|
||||
## Parameters
|
||||
|
||||
### Common parameters
|
||||
|
|
@ -13,3 +21,20 @@
|
|||
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `1000m` |
|
||||
| `resources.memory` | Amount of memory allocated. | `quantity` | `512Mi` |
|
||||
|
||||
|
||||
### Backup parameters
|
||||
|
||||
| Name | Description | Type | Value |
|
||||
| ----------------------------------- | ----------------------------------------------------------------------------- | -------- | ----------- |
|
||||
| `backup` | Backup configuration. | `object` | `{}` |
|
||||
| `backup.enabled` | Enable scheduled S3 backups. | `bool` | `false` |
|
||||
| `backup.schedule` | Cron schedule for automated backups. | `string` | `0 2 * * *` |
|
||||
| `backup.destinationPath` | Destination path for backups (e.g. s3://bucket/path/). | `string` | `""` |
|
||||
| `backup.endpointURL` | S3 endpoint URL for uploads. | `string` | `""` |
|
||||
| `backup.region` | S3 region. | `string` | `""` |
|
||||
| `backup.forcePathStyle` | Use path-style S3 URLs (required for MinIO and most S3-compatible providers). | `bool` | `true` |
|
||||
| `backup.s3AccessKey` | Access key for S3 authentication. | `string` | `""` |
|
||||
| `backup.s3SecretKey` | Secret key for S3 authentication. | `string` | `""` |
|
||||
| `backup.successfulJobsHistoryLimit` | Number of successful backup jobs to retain. | `int` | `3` |
|
||||
| `backup.failedJobsHistoryLimit` | Number of failed backup jobs to retain. | `int` | `1` |
|
||||
|
||||
|
|
|
|||
11
packages/extra/etcd/templates/backup-secret.yaml
Normal file
11
packages/extra/etcd/templates/backup-secret.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{{- if .Values.backup.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: etcd-s3-creds
|
||||
type: Opaque
|
||||
stringData:
|
||||
AWS_ACCESS_KEY_ID: {{ required "backup.s3AccessKey is required when backup.enabled=true" .Values.backup.s3AccessKey | quote }}
|
||||
AWS_SECRET_ACCESS_KEY: {{ required "backup.s3SecretKey is required when backup.enabled=true" .Values.backup.s3SecretKey | quote }}
|
||||
{{- end }}
|
||||
35
packages/extra/etcd/templates/etcd-backup-schedule.yaml
Normal file
35
packages/extra/etcd/templates/etcd-backup-schedule.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{{- if .Values.backup.enabled }}
|
||||
{{- $endpointURL := required "backup.endpointURL is required when backup.enabled=true" .Values.backup.endpointURL -}}
|
||||
{{- $destPath := required "backup.destinationPath is required when backup.enabled=true" .Values.backup.destinationPath -}}
|
||||
{{- if not (hasPrefix "s3://" $destPath) }}{{ fail "backup.destinationPath must start with s3://" }}{{- end }}
|
||||
{{- $rawPath := $destPath | trimPrefix "s3://" | trimSuffix "/" -}}
|
||||
{{- if contains "//" $rawPath }}{{ fail "backup.destinationPath must not contain '//' segments" }}{{- end }}
|
||||
{{- $parts := splitList "/" $rawPath -}}
|
||||
{{- $bucket := first $parts -}}
|
||||
{{- if eq $bucket "" }}{{ fail "backup.destinationPath must contain a bucket (e.g. s3://bucket/path/)" }}{{- end }}
|
||||
{{- $key := rest $parts | join "/" -}}
|
||||
---
|
||||
apiVersion: etcd.aenix.io/v1alpha1
|
||||
kind: EtcdBackupSchedule
|
||||
metadata:
|
||||
name: etcd
|
||||
spec:
|
||||
clusterRef:
|
||||
name: etcd
|
||||
schedule: {{ .Values.backup.schedule | quote }}
|
||||
successfulJobsHistoryLimit: {{ .Values.backup.successfulJobsHistoryLimit | int }}
|
||||
failedJobsHistoryLimit: {{ .Values.backup.failedJobsHistoryLimit | int }}
|
||||
destination:
|
||||
s3:
|
||||
endpoint: {{ $endpointURL | quote }}
|
||||
bucket: {{ $bucket | quote }}
|
||||
{{- with $key }}
|
||||
key: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.backup.region }}
|
||||
region: {{ . | quote }}
|
||||
{{- end }}
|
||||
forcePathStyle: {{ .Values.backup.forcePathStyle }}
|
||||
credentialsSecretRef:
|
||||
name: etcd-s3-creds
|
||||
{{- end }}
|
||||
104
packages/extra/etcd/tests/backup-secret_test.yaml
Normal file
104
packages/extra/etcd/tests/backup-secret_test.yaml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
suite: backup secret tests
|
||||
|
||||
templates:
|
||||
- templates/backup-secret.yaml
|
||||
|
||||
tests:
|
||||
- it: does not render when backup disabled
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: false
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
|
||||
- it: renders when backup enabled with valid credentials
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
s3AccessKey: "AKIAIOSFODNN7EXAMPLE"
|
||||
s3SecretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||
destinationPath: "s3://bucket/path/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
- isKind:
|
||||
of: Secret
|
||||
- equal:
|
||||
path: metadata.name
|
||||
value: etcd-s3-creds
|
||||
- equal:
|
||||
path: type
|
||||
value: Opaque
|
||||
|
||||
- it: secret name is hardcoded regardless of release name
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
destinationPath: "s3://b/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
asserts:
|
||||
- equal:
|
||||
path: metadata.name
|
||||
value: etcd-s3-creds
|
||||
|
||||
- it: contains AWS credentials in expected keys
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
s3AccessKey: "MYACCESSKEY"
|
||||
s3SecretKey: "MYSECRETKEY"
|
||||
destinationPath: "s3://b/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
asserts:
|
||||
- equal:
|
||||
path: stringData.AWS_ACCESS_KEY_ID
|
||||
value: "MYACCESSKEY"
|
||||
- equal:
|
||||
path: stringData.AWS_SECRET_ACCESS_KEY
|
||||
value: "MYSECRETKEY"
|
||||
|
||||
- it: fails when s3AccessKey is empty
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
s3AccessKey: ""
|
||||
s3SecretKey: "secretkey"
|
||||
destinationPath: "s3://b/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.s3AccessKey is required when backup.enabled=true"
|
||||
|
||||
- it: fails when s3SecretKey is empty
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
s3AccessKey: "accesskey"
|
||||
s3SecretKey: ""
|
||||
destinationPath: "s3://b/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.s3SecretKey is required when backup.enabled=true"
|
||||
174
packages/extra/etcd/tests/etcd-backup-schedule_test.yaml
Normal file
174
packages/extra/etcd/tests/etcd-backup-schedule_test.yaml
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
suite: etcd backup schedule tests
|
||||
|
||||
templates:
|
||||
- templates/etcd-backup-schedule.yaml
|
||||
|
||||
tests:
|
||||
- it: does not render when backup disabled
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: false
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
|
||||
- it: renders when backup enabled with valid values
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
schedule: "0 2 * * *"
|
||||
destinationPath: "s3://my-bucket/etcd/prod/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
region: "us-east-1"
|
||||
forcePathStyle: true
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 1
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
- isKind:
|
||||
of: EtcdBackupSchedule
|
||||
- equal:
|
||||
path: metadata.name
|
||||
value: etcd
|
||||
- equal:
|
||||
path: spec.clusterRef.name
|
||||
value: etcd
|
||||
- equal:
|
||||
path: spec.schedule
|
||||
value: "0 2 * * *"
|
||||
- equal:
|
||||
path: spec.destination.s3.endpoint
|
||||
value: "https://s3.example.com"
|
||||
- equal:
|
||||
path: spec.destination.s3.bucket
|
||||
value: "my-bucket"
|
||||
- equal:
|
||||
path: spec.destination.s3.key
|
||||
value: "etcd/prod"
|
||||
- equal:
|
||||
path: spec.destination.s3.region
|
||||
value: "us-east-1"
|
||||
- equal:
|
||||
path: spec.destination.s3.forcePathStyle
|
||||
value: true
|
||||
- equal:
|
||||
path: spec.destination.s3.credentialsSecretRef.name
|
||||
value: etcd-s3-creds
|
||||
|
||||
- it: omits key when destinationPath points at bucket root
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "s3://bucket-only/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.destination.s3.bucket
|
||||
value: "bucket-only"
|
||||
- notExists:
|
||||
path: spec.destination.s3.key
|
||||
|
||||
- it: omits region when not set
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "s3://b/k/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
region: ""
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.destination.s3.region
|
||||
|
||||
- it: fails when destinationPath is empty
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: ""
|
||||
endpointURL: "https://s3.example.com"
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.destinationPath is required when backup.enabled=true"
|
||||
|
||||
- it: fails when endpointURL is empty
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "s3://b/"
|
||||
endpointURL: ""
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.endpointURL is required when backup.enabled=true"
|
||||
|
||||
- it: fails when destinationPath lacks s3:// prefix
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "my-bucket/path/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.destinationPath must start with s3://"
|
||||
|
||||
- it: fails when destinationPath contains double slashes
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "s3://bucket//subdir/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.destinationPath must not contain '//' segments"
|
||||
|
||||
- it: fails when destinationPath has no bucket
|
||||
release:
|
||||
name: etcd
|
||||
namespace: tenant-test
|
||||
set:
|
||||
backup:
|
||||
enabled: true
|
||||
destinationPath: "s3:///path/"
|
||||
endpointURL: "https://s3.example.com"
|
||||
s3AccessKey: "k"
|
||||
s3SecretKey: "s"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "backup.destinationPath must contain a bucket (e.g. s3://bucket/path/)"
|
||||
|
|
@ -60,6 +60,63 @@
|
|||
"x-kubernetes-int-or-string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"description": "Backup configuration.",
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"properties": {
|
||||
"destinationPath": {
|
||||
"description": "Destination path for backups (e.g. s3://bucket/path/).",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Enable scheduled S3 backups.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"endpointURL": {
|
||||
"description": "S3 endpoint URL for uploads.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"failedJobsHistoryLimit": {
|
||||
"description": "Number of failed backup jobs to retain.",
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"description": "Use path-style S3 URLs (required for MinIO and most S3-compatible providers).",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"region": {
|
||||
"description": "S3 region.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"s3AccessKey": {
|
||||
"description": "Access key for S3 authentication.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"s3SecretKey": {
|
||||
"description": "Secret key for S3 authentication.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Cron schedule for automated backups.",
|
||||
"type": "string",
|
||||
"default": "0 2 * * *"
|
||||
},
|
||||
"successfulJobsHistoryLimit": {
|
||||
"description": "Number of successful backup jobs to retain.",
|
||||
"type": "integer",
|
||||
"default": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,3 +19,32 @@ replicas: 3
|
|||
resources:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
|
||||
##
|
||||
## @section Backup parameters
|
||||
##
|
||||
|
||||
## @typedef {struct} Backup - Backup configuration.
|
||||
## @field {bool} [enabled] - Enable scheduled S3 backups.
|
||||
## @field {string} [schedule] - Cron schedule for automated backups.
|
||||
## @field {string} [destinationPath] - Destination path for backups (e.g. s3://bucket/path/).
|
||||
## @field {string} [endpointURL] - S3 endpoint URL for uploads.
|
||||
## @field {string} [region] - S3 region.
|
||||
## @field {bool} [forcePathStyle] - Use path-style S3 URLs (required for MinIO and most S3-compatible providers).
|
||||
## @field {string} [s3AccessKey] - Access key for S3 authentication.
|
||||
## @field {string} [s3SecretKey] - Secret key for S3 authentication.
|
||||
## @field {int} [successfulJobsHistoryLimit] - Number of successful backup jobs to retain.
|
||||
## @field {int} [failedJobsHistoryLimit] - Number of failed backup jobs to retain.
|
||||
|
||||
## @param {Backup} backup - Backup configuration.
|
||||
backup:
|
||||
enabled: false
|
||||
schedule: "0 2 * * *"
|
||||
destinationPath: ""
|
||||
endpointURL: ""
|
||||
region: ""
|
||||
forcePathStyle: true
|
||||
s3AccessKey: ""
|
||||
s3SecretKey: ""
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: v2
|
||||
appVersion: v0.4.2
|
||||
appVersion: v0.4.3
|
||||
name: etcd-operator
|
||||
type: application
|
||||
version: 0.4.2
|
||||
version: 0.4.3
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: etcd-operator-system/etcd-operator-serving-cert
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
name: etcdbackupschedules.etcd.aenix.io
|
||||
spec:
|
||||
conversion:
|
||||
strategy: None
|
||||
group: etcd.aenix.io
|
||||
names:
|
||||
kind: EtcdBackupSchedule
|
||||
listKind: EtcdBackupScheduleList
|
||||
plural: etcdbackupschedules
|
||||
singular: etcdbackupschedule
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.clusterRef.name
|
||||
name: Cluster
|
||||
type: string
|
||||
- jsonPath: .spec.schedule
|
||||
name: Schedule
|
||||
type: string
|
||||
- jsonPath: .status.lastSuccessfulBackupTime
|
||||
name: Last Backup
|
||||
type: date
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: EtcdBackupSchedule is the Schema for the etcdbackupschedules API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: EtcdBackupScheduleSpec defines the desired state of EtcdBackupSchedule
|
||||
properties:
|
||||
clusterRef:
|
||||
description: ClusterRef references the EtcdCluster to back up.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
destination:
|
||||
description: Destination defines where the backup will be stored.
|
||||
properties:
|
||||
pvc:
|
||||
description: PVC defines a PersistentVolumeClaim as the backup destination.
|
||||
properties:
|
||||
claimName:
|
||||
description: ClaimName is the name of the PersistentVolumeClaim to use.
|
||||
type: string
|
||||
subPath:
|
||||
description: |-
|
||||
SubPath is an optional sub-directory within the PVC volume.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
required:
|
||||
- claimName
|
||||
type: object
|
||||
s3:
|
||||
description: S3 defines S3-compatible storage as the backup destination.
|
||||
properties:
|
||||
bucket:
|
||||
description: Bucket is the name of the S3 bucket.
|
||||
type: string
|
||||
credentialsSecretRef:
|
||||
description: CredentialsSecretRef references a Secret containing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
endpoint:
|
||||
description: Endpoint is the S3-compatible endpoint URL (e.g., "https://s3.amazonaws.com").
|
||||
type: string
|
||||
forcePathStyle:
|
||||
description: |-
|
||||
ForcePathStyle forces path-style S3 URLs (e.g., endpoint/bucket/key)
|
||||
instead of virtual-hosted-style (e.g., bucket.endpoint/key).
|
||||
Most S3-compatible providers (MinIO, Ceph) require path style.
|
||||
type: boolean
|
||||
key:
|
||||
description: |-
|
||||
Key is the key prefix (directory path) within the bucket.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
region:
|
||||
description: Region is the AWS region for the S3 bucket.
|
||||
type: string
|
||||
required:
|
||||
- bucket
|
||||
- credentialsSecretRef
|
||||
- endpoint
|
||||
type: object
|
||||
type: object
|
||||
failedJobsHistoryLimit:
|
||||
description: FailedJobsHistoryLimit is the number of failed finished CronJob children to retain.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
schedule:
|
||||
description: Schedule is a cron expression defining when backups should be taken.
|
||||
type: string
|
||||
successfulJobsHistoryLimit:
|
||||
description: SuccessfulJobsHistoryLimit is the number of successful finished CronJob children to retain.
|
||||
format: int32
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- clusterRef
|
||||
- destination
|
||||
- schedule
|
||||
type: object
|
||||
status:
|
||||
description: EtcdBackupScheduleStatus defines the observed state of EtcdBackupSchedule
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the observations of a foo's current state.\n\t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t // other fields\n\t}"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
---
|
||||
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
|
||||
useful (see .node.status.conditions), the ability to deconflict is important.
|
||||
The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
lastScheduleTime:
|
||||
format: date-time
|
||||
type: string
|
||||
lastSuccessfulBackupTime:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: etcd-operator-system/etcd-operator-serving-cert
|
||||
controller-gen.kubebuilder.io/version: v0.15.0
|
||||
name: etcdbackups.etcd.aenix.io
|
||||
spec:
|
||||
conversion:
|
||||
strategy: None
|
||||
group: etcd.aenix.io
|
||||
names:
|
||||
kind: EtcdBackup
|
||||
listKind: EtcdBackupList
|
||||
plural: etcdbackups
|
||||
singular: etcdbackup
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- jsonPath: .spec.clusterRef.name
|
||||
name: Cluster
|
||||
type: string
|
||||
- jsonPath: .status.phase
|
||||
name: Phase
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: EtcdBackup is the Schema for the etcdbackups API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: EtcdBackupSpec defines the desired state of EtcdBackup
|
||||
properties:
|
||||
clusterRef:
|
||||
description: ClusterRef references the EtcdCluster to back up.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
destination:
|
||||
description: Destination defines where the backup will be stored.
|
||||
properties:
|
||||
pvc:
|
||||
description: PVC defines a PersistentVolumeClaim as the backup destination.
|
||||
properties:
|
||||
claimName:
|
||||
description: ClaimName is the name of the PersistentVolumeClaim to use.
|
||||
type: string
|
||||
subPath:
|
||||
description: |-
|
||||
SubPath is an optional sub-directory within the PVC volume.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
required:
|
||||
- claimName
|
||||
type: object
|
||||
s3:
|
||||
description: S3 defines S3-compatible storage as the backup destination.
|
||||
properties:
|
||||
bucket:
|
||||
description: Bucket is the name of the S3 bucket.
|
||||
type: string
|
||||
credentialsSecretRef:
|
||||
description: CredentialsSecretRef references a Secret containing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
endpoint:
|
||||
description: Endpoint is the S3-compatible endpoint URL (e.g., "https://s3.amazonaws.com").
|
||||
type: string
|
||||
forcePathStyle:
|
||||
description: |-
|
||||
ForcePathStyle forces path-style S3 URLs (e.g., endpoint/bucket/key)
|
||||
instead of virtual-hosted-style (e.g., bucket.endpoint/key).
|
||||
Most S3-compatible providers (MinIO, Ceph) require path style.
|
||||
type: boolean
|
||||
key:
|
||||
description: |-
|
||||
Key is the key prefix (directory path) within the bucket.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
region:
|
||||
description: Region is the AWS region for the S3 bucket.
|
||||
type: string
|
||||
required:
|
||||
- bucket
|
||||
- credentialsSecretRef
|
||||
- endpoint
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- clusterRef
|
||||
- destination
|
||||
type: object
|
||||
status:
|
||||
description: EtcdBackupStatus defines the observed state of EtcdBackup
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the observations of a foo's current state.\n\t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t // other fields\n\t}"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: |-
|
||||
type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
---
|
||||
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
|
||||
useful (see .node.status.conditions), the ability to deconflict is important.
|
||||
The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
phase:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
|
|
@ -49,6 +49,80 @@ spec:
|
|||
spec:
|
||||
description: EtcdClusterSpec defines the desired state of EtcdCluster
|
||||
properties:
|
||||
bootstrap:
|
||||
description: |-
|
||||
Bootstrap defines initialization from an existing data source (e.g., snapshot restore).
|
||||
This is only used during initial cluster creation and is ignored afterward.
|
||||
properties:
|
||||
restore:
|
||||
description: Restore configures cluster initialization from an etcd snapshot.
|
||||
properties:
|
||||
source:
|
||||
description: Source defines where to get the snapshot for restoration.
|
||||
properties:
|
||||
pvc:
|
||||
description: PVC defines a PersistentVolumeClaim as the backup destination.
|
||||
properties:
|
||||
claimName:
|
||||
description: ClaimName is the name of the PersistentVolumeClaim to use.
|
||||
type: string
|
||||
subPath:
|
||||
description: |-
|
||||
SubPath is an optional sub-directory within the PVC volume.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
required:
|
||||
- claimName
|
||||
type: object
|
||||
s3:
|
||||
description: S3 defines S3-compatible storage as the backup destination.
|
||||
properties:
|
||||
bucket:
|
||||
description: Bucket is the name of the S3 bucket.
|
||||
type: string
|
||||
credentialsSecretRef:
|
||||
description: CredentialsSecretRef references a Secret containing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys.
|
||||
properties:
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
endpoint:
|
||||
description: Endpoint is the S3-compatible endpoint URL (e.g., "https://s3.amazonaws.com").
|
||||
type: string
|
||||
forcePathStyle:
|
||||
description: |-
|
||||
ForcePathStyle forces path-style S3 URLs (e.g., endpoint/bucket/key)
|
||||
instead of virtual-hosted-style (e.g., bucket.endpoint/key).
|
||||
Most S3-compatible providers (MinIO, Ceph) require path style.
|
||||
type: boolean
|
||||
key:
|
||||
description: |-
|
||||
Key is the key prefix (directory path) within the bucket.
|
||||
The operator appends the backup filename automatically.
|
||||
type: string
|
||||
region:
|
||||
description: Region is the AWS region for the S3 bucket.
|
||||
type: string
|
||||
required:
|
||||
- bucket
|
||||
- credentialsSecretRef
|
||||
- endpoint
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- source
|
||||
type: object
|
||||
type: object
|
||||
headlessServiceTemplate:
|
||||
description: HeadlessService defines the desired state of HeadlessService for etcd members. If not specified, default values will be used.
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,46 @@ metadata:
|
|||
{{- include "etcd-operator.labels" . | nindent 4 }}
|
||||
name: {{ include "etcd-operator.fullname" . }}-validating-webhook-configuration
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ include "etcd-operator.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validate-etcd-aenix-io-v1alpha1-etcdbackup
|
||||
failurePolicy: Fail
|
||||
name: vetcdbackup.kb.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- etcdbackups
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
service:
|
||||
name: {{ include "etcd-operator.fullname" . }}-webhook-service
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /validate-etcd-aenix-io-v1alpha1-etcdbackupschedule
|
||||
failurePolicy: Fail
|
||||
name: vetcdbackupschedule.kb.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
apiVersions:
|
||||
- v1alpha1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- etcdbackupschedules
|
||||
sideEffects: None
|
||||
- admissionReviewVersions:
|
||||
- v1
|
||||
clientConfig:
|
||||
|
|
|
|||
|
|
@ -74,6 +74,76 @@ rules:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackups
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackups/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackups/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackupschedules
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackupschedules/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
- etcdbackupschedules/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- etcd.aenix.io
|
||||
resources:
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ spec:
|
|||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.namespace
|
||||
- name: OPERATOR_IMAGE
|
||||
value: {{ .Values.etcdOperator.image.repository }}:{{ .Values.etcdOperator.image.tag | default .Chart.AppVersion }}
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||
name: cert
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ spec:
|
|||
plural: etcds
|
||||
singular: etcd
|
||||
openAPISchema: |-
|
||||
{"title":"Chart Values","type":"object","properties":{"size":{"description":"Persistent Volume size.","default":"4Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"replicas":{"description":"Number of etcd replicas.","type":"integer","default":3},"resources":{"description":"Resource configuration for etcd.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","default":"1000m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","default":"512Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}
|
||||
{"title":"Chart Values","type":"object","properties":{"size":{"description":"Persistent Volume size.","default":"4Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"replicas":{"description":"Number of etcd replicas.","type":"integer","default":3},"resources":{"description":"Resource configuration for etcd.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","default":"1000m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","default":"512Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"backup":{"description":"Backup configuration.","type":"object","default":{},"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":""},"enabled":{"description":"Enable scheduled S3 backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":""},"failedJobsHistoryLimit":{"description":"Number of failed backup jobs to retain.","type":"integer","default":1},"forcePathStyle":{"description":"Use path-style S3 URLs (required for MinIO and most S3-compatible providers).","type":"boolean","default":true},"region":{"description":"S3 region.","type":"string","default":""},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"},"successfulJobsHistoryLimit":{"description":"Number of successful backup jobs to retain.","type":"integer","default":3}}}}}
|
||||
release:
|
||||
prefix: ""
|
||||
labels:
|
||||
|
|
@ -26,7 +26,7 @@ spec:
|
|||
description: Storage for Kubernetes clusters
|
||||
module: true
|
||||
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9Ii0wLjAwMTk1MzEyIiB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgcng9IjI0IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjgzXzI5NjMpIi8+CjxwYXRoIGQ9Ik0xMjIuNDQyIDczLjQ3MjlDMTIxLjk1OSA3My41MTM0IDEyMS40NzQgNzMuNTMyMiAxMjAuOTU4IDczLjUzMjJDMTE3Ljk2NSA3My41MzIyIDExNS4wNjEgNzIuODMwNCAxMTIuNDQyIDcxLjU0NTFDMTEzLjMxNCA2Ni41NDIxIDExMy42ODUgNjEuNTAxOSAxMTMuNTg4IDU2LjQ4MDJDMTEwLjc0OCA1Mi4zNzIzIDEwNy41MDIgNDguNDk2NSAxMDMuODM4IDQ0LjkyNTdDMTA1LjQyOCA0MS45NDU0IDEwNy43NzggMzkuMzgxMSAxMTAuNzExIDM3LjU2MjhMMTExLjk3MSAzNi43ODQyTDExMC45ODkgMzUuNjc3NEMxMDUuOTMyIDI5Ljk4MzIgOTkuODk3MSAyNS41ODA5IDkzLjA1NDcgMjIuNTkzN0w5MS42OTAyIDIyTDkxLjM0MzcgMjMuNDQyM0M5MC41Mjc3IDI2LjgwMzYgODguODIyMiAyOS44MzYgODYuNDgwNyAzMi4yNjk1QzgxLjk4MDMgMjkuODc3NCA3Ny4yNzg4IDI3Ljk0NCA3Mi40MzA1IDI2LjQ3OTdDNjcuNTkzNyAyNy45NDA4IDYyLjkwMDUgMjkuODY4NiA1OC40MDIgMzIuMjU3MUM1Ni4wNzAxIDI5LjgyNjggNTQuMzY4OCAyNi44MDE4IDUzLjU1NiAyMy40NTAxTDUzLjIwNzIgMjIuMDA4M0w1MS44NDc3IDIyLjU5OTJDNDUuMDkxNCAyNS41NDMxIDM4Ljg5MDEgMzAuMDY0NyAzMy45MTYyIDM1LjY3NDJMMzIuOTMxOCAzNi43ODMzTDM0LjE5IDM3LjU2MTlDMzcuMTE0MiAzOS4zNzMzIDM5LjQ1NzYgNDEuOTIyNCA0MS4wNDQ0IDQ0Ljg4NjZDMzcuMzkxNyA0OC40NDM1IDM0LjE0OTUgNTIuMzA3IDMxLjMxMTkgNTYuMzk1OUMzMS4yMDE0IDYxLjQxNTQgMzEuNTUzNSA2Ni40OTI0IDMyLjQyOTcgNzEuNTY0NEMyOS44MjMxIDcyLjgzNzggMjYuOTM1OCA3My41MzE4IDIzLjk2MjggNzMuNTMxOEMyMy40NDA5IDczLjUzMTggMjIuOTUyNyA3My41MTI5IDIyLjQ3ODIgNzMuNDczM0wyMSA3My4zNjA2TDIxLjEzODUgNzQuODM2NUMyMS44NjI5IDgyLjMwMzMgMjQuMTgxNCA4OS40MDUzIDI4LjAzMzQgOTUuOTQ3MUwyOC43ODUzIDk3LjIyMzdMMjkuOTE0MiA5Ni4yNjU2QzMyLjUzMDUgOTQuMDQ2NSAzNS42OTE3IDkyLjU3NyAzOS4wNTMgOTEuOTg0N0M0MS4yNjg5IDk2LjUxNTUgNDMuODk1MyAxMDAuNzcyIDQ2Ljg3NDcgMTA0LjcyNUM1MS42Mjg3IDEwNi4zODcgNTYuNTgxOSAxMDcuNjI5IDYxLjY5NzEgMTA4LjM2N0M2Mi4xODc3IDExMS43NSA2MS43OTcgMTE1LjI0OSA2MC40NjI0IDExOC40ODRMNTkuODk5NSAxMTkuODU1TDYxLjM0NjkgMTIwLjE3NEM2NS4wNTI5IDEyMC45ODkgNjguNzkxNyAxMjEuNDA0IDcyLjQ1MjYgMTIxLjQwNEw4My41NTUxIDEyMC4xNzRMODUuMDAzOSAxMTkuODU1TDg0LjQzOTcgMTE4LjQ4MkM4My4xMDg3IDExNS4yNDYgODIuNzE4IDExMS43NDMgODMuMjA4NiAxMDguMzZDODguMzAzNiAxMDcuNjIxIDkzLjIzODQgMTA2LjM4MiA5Ny45NzQ4IDEwNC43MjVDMTAwLjk1NyAxMDAuNzY5IDEwMy41ODYgOTYuNTA5NSAxMDUuODA1IDkxLjk3MjhDMTA5LjE3NyA5Mi41NjE0IDExMi4zNTYgOTQuMDMxNyAxMTQuOTg5IDk2LjI1NzNMMTE2LjExOCA5Ny4yMTQxTDExNi44NjYgOTUuOTQwN0MxMjAuNzI1IDg5LjM5MDUgMTIzLjA0MyA4Mi4yODkxIDEyMy43NTYgNzQuODM0MkwxMjMuODk1IDczLjM2MUwxMjIuNDQyIDczLjQ3MjlaTTg4LjMxOTcgOTEuNTE4MUM4My4wNjczIDkyLjk0NjYgNzcuNzMzIDkzLjY2NzcgNzIuNDMwNSA5My42Njc3QzY3LjExMzcgOTMuNjY3NyA2MS43ODU5IDkyLjk0NyA1Ni41MjkgOTEuNTE4MUM1My42NDQ4IDg3LjAzNjYgNTEuMzY0NSA4Mi4yMzU3IDQ5LjcyMzQgNzcuMTgxMkM0OC4wODkyIDcyLjE1MDIgNDcuMTMyOSA2Ni44Nzk1IDQ2Ljg1NTQgNjEuNDUyMkM1MC4yNTA0IDU3LjI1NDcgNTQuMTExIDUzLjU3NzYgNTguMzc2NyA1MC40ODIzQzYyLjcxMTQgNDcuMzI5NCA2Ny40MjcxIDQ0Ljc2NzkgNzIuNDMwNSA0Mi44NDFDNzcuNDI1NiA0NC43NjgzIDgyLjEzMjYgNDcuMzI2MiA4Ni40NTcyIDUwLjQ2NTdDOTAuNzM5NCA1My41Nzc2IDk0LjYxNzEgNTcuMjgzMiA5OC4wMjg3IDYxLjUwN0M5Ny43Mzc4IDY2LjkwMzQgOTYuNzcgNzIuMTQzOCA5NS4xMzMgNzcuMTY2NUM5My40OTYxIDgyLjIyIDkxLjIwODQgODcuMDM2MSA4OC4zMTk3IDkxLjUxODFaTTc2Ljc2ODQgNjYuMTk3NEM3Ni43Njg0IDY5LjkwODEgNzkuNzc1NCA3Mi45MDk2IDgzLjQ4MSA3Mi45MDk2Qzg3LjE4NTcgNzIuOTA5NiA5MC4xODk1IDY5LjkwODYgOTAuMTg5NSA2Ni4xOTc0QzkwLjE4OTUgNjIuNTAxIDg3LjE4NTcgNTkuNDg4MSA4My40ODEgNTkuNDg4MUM3OS43NzU0IDU5LjQ4ODEgNzYuNzY4NCA2Mi41MDEgNzYuNzY4NCA2Ni4xOTc0Wk02OC4wOTU0IDY2LjE5NzRDNjguMDk1NCA2OS45MDgxIDY1LjA4ODggNzIuOTA5NiA2MS4zODMyIDcyLjkwOTZDNTcuNjc0OSA3Mi45MDk2IDU0LjY3NjYgNjkuOTA4NiA1NC42NzY2IDY2LjE5NzRDNTQuNjc2NiA2Mi41MDI0IDU3LjY3NTMgNTkuNDg5NCA2MS4zODMyIDU5LjQ4OTRDNjUuMDg4OCA1OS40ODk0IDY4LjA5NTQgNjIuNTAyNCA2OC4wOTU0IDY2LjE5NzRaIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4M18yOTYzIiB4MT0iNS41IiB5MT0iMTEiIHgyPSIxNDEiIHkyPSIxMjQuNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjNTNCMkYwIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzQxOUVEQSIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo=
|
||||
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "size"], ["spec", "storageClass"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resources", "cpu"], ["spec", "resources", "memory"]]
|
||||
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "size"], ["spec", "storageClass"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resources", "cpu"], ["spec", "resources", "memory"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "region"], ["spec", "backup", "forcePathStyle"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "backup", "successfulJobsHistoryLimit"], ["spec", "backup", "failedJobsHistoryLimit"]]
|
||||
secrets:
|
||||
exclude: []
|
||||
include: []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue