Skip to content

Commit caceef7

Browse files
committed
feat(platform): add implementation of backup-strategy controller for etcd
Signed-off-by: Andrey Kolkov <[email protected]>
1 parent c5e9287 commit caceef7

36 files changed

Lines changed: 5696 additions & 33 deletions

‎.gitignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,12 @@ tmp/
8484
# build revision marker (generated by make image-packages)
8585
packages/core/platform/.build-revision
8686
.claude/
87+
88+
# Credentials and runtime state minted by the backup/restore demo scripts
89+
# under examples/backups/*/. Files are written by 00-helpers.sh +
90+
# 02-create-bucket.sh + 04-create-backupjob.sh; cleanup.sh deletes them
91+
# on teardown. Keep them out of git so a tenant who runs the demo and
92+
# then `git add .` does not accidentally commit S3 keys.
93+
examples/backups/*/.bucket-info.env
94+
examples/backups/*/.sentinel.env
95+
examples/backups/*/.backup-name.env
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Package v1alpha1 defines strategy.backups.cozystack.io API types.
3+
//
4+
// Group: strategy.backups.cozystack.io
5+
// Version: v1alpha1
6+
package v1alpha1
7+
8+
import (
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
)
12+
13+
func init() {
14+
SchemeBuilder.Register(func(s *runtime.Scheme) error {
15+
s.AddKnownTypes(GroupVersion,
16+
&Etcd{},
17+
&EtcdList{},
18+
)
19+
return nil
20+
})
21+
}
22+
23+
const (
24+
EtcdStrategyKind = "Etcd"
25+
)
26+
27+
// +kubebuilder:object:root=true
28+
// +kubebuilder:subresource:status
29+
// +kubebuilder:resource:scope=Cluster
30+
31+
// Etcd defines a backup strategy that delegates execution to the
32+
// etcd-operator (etcd.aenix.io). The strategy carries a templated backup
33+
// destination configuration; the driver materialises one
34+
// etcd.aenix.io/v1alpha1 EtcdBackup per Cozystack BackupJob and surfaces
35+
// it as a Cozystack Backup artefact. Restores are driven by stamping the
36+
// rendered destination back onto EtcdCluster.spec.bootstrap.restore.source
37+
// on the target Etcd application - either by recreating the source
38+
// EtcdCluster (in-place) or by patching a freshly provisioned target
39+
// EtcdCluster before the operator marks it Ready (to-copy).
40+
type Etcd struct {
41+
metav1.TypeMeta `json:",inline"`
42+
metav1.ObjectMeta `json:"metadata,omitempty"`
43+
44+
Spec EtcdSpec `json:"spec,omitempty"`
45+
Status EtcdStatus `json:"status,omitempty"`
46+
}
47+
48+
// +kubebuilder:object:root=true
49+
50+
// EtcdList contains a list of Etcd backup strategies.
51+
type EtcdList struct {
52+
metav1.TypeMeta `json:",inline"`
53+
metav1.ListMeta `json:"metadata,omitempty"`
54+
Items []Etcd `json:"items"`
55+
}
56+
57+
// EtcdSpec specifies the desired etcd-operator-driven backup strategy.
58+
type EtcdSpec struct {
59+
// Template carries the templated EtcdBackup destination configuration
60+
// applied per BackupJob (and re-rendered against the same
61+
// .Application / .Parameters at restore time). String fields support
62+
// Helm-style Go templating with two top-level values:
63+
// .Application - the application object (apps.cozystack.io/Etcd)
64+
// .Parameters - the parameters from the matched BackupClassStrategy.
65+
// These values MUST NOT carry credentials; route S3
66+
// access keys through
67+
// Destination.S3.CredentialsSecretRef.
68+
Template EtcdTemplate `json:"template"`
69+
}
70+
71+
// EtcdTemplate describes the templated EtcdBackup-shaped configuration the
72+
// driver renders per BackupJob (and on the restore path stamps onto
73+
// EtcdCluster.spec.bootstrap.restore.source).
74+
type EtcdTemplate struct {
75+
// Destination defines where the etcd snapshot will be stored. Field
76+
// semantics mirror etcd.aenix.io/v1alpha1 EtcdBackup.spec.destination.
77+
Destination EtcdDestinationTemplate `json:"destination"`
78+
}
79+
80+
// EtcdDestinationTemplate mirrors etcd.aenix.io/v1alpha1
81+
// EtcdBackup.spec.destination, RESTRICTED to S3.
82+
//
83+
// The upstream etcd-operator EtcdBackup.spec.destination accepts either
84+
// S3 or PVC, but the operator's filename convention is asymmetric
85+
// between backup and restore on the PVC path:
86+
//
87+
// - backup-agent writes to <mount>/<subPath>/<backupName>.db (with
88+
// subPath) or <mount>/<backupName>.db (empty subPath), and may add
89+
// BACKUP_INCLUDE_REVISION / BACKUP_TIMESTAMP suffixes;
90+
// - restore-agent reads <mount>/<subPath> as a FILE (with subPath) or
91+
// <mount>/snapshot.db (empty subPath).
92+
//
93+
// With either subPath setting, a PVC backup is unrestoreable via the
94+
// strategy: the restore-agent opens the wrong path and 404s/EISDIRs.
95+
// Until the upstream gains symmetric semantics (or surfaces the final
96+
// file path via the equivalent of BackupSnapshot.URI for PVC), this
97+
// driver intentionally narrows the API contract to S3 only. The S3
98+
// path is fully exercised end-to-end on dev7 and covered by unit +
99+
// e2e tests.
100+
// +kubebuilder:validation:XValidation:rule="has(self.s3)",message="s3 destination is required (PVC is intentionally not supported by this strategy until upstream restore-agent reads the same path the backup-agent writes; see the strategy comments)"
101+
type EtcdDestinationTemplate struct {
102+
// S3 configures an S3-compatible storage target. Templating is
103+
// supported on every string field (Bucket, Endpoint, Key, Region,
104+
// CredentialsSecretRef.Name).
105+
S3 *EtcdS3Template `json:"s3"`
106+
}
107+
108+
// EtcdS3Template is a typed, kubebuilder-validated mirror of
109+
// etcd.aenix.io/v1alpha1 EtcdBackup.spec.destination.s3. Restores reuse
110+
// the same shape: the driver re-renders the strategy and stamps the
111+
// resulting block onto EtcdCluster.spec.bootstrap.restore.source.s3 of the
112+
// target Etcd application.
113+
type EtcdS3Template struct {
114+
// Bucket is the S3 (or compatible) bucket name. Templating is
115+
// supported.
116+
// +kubebuilder:validation:MinLength=1
117+
Bucket string `json:"bucket"`
118+
119+
// Endpoint is the S3-compatible endpoint URL, including scheme (e.g.
120+
// "https://s3.amazonaws.com" or
121+
// "http://seaweedfs-s3.tenant-root.svc:8333"). Templating is
122+
// supported.
123+
// +kubebuilder:validation:MinLength=1
124+
Endpoint string `json:"endpoint"`
125+
126+
// Key is the key prefix (directory path) within the bucket. The
127+
// etcd-operator appends the snapshot filename automatically.
128+
// Templating is supported.
129+
// +optional
130+
Key string `json:"key,omitempty"`
131+
132+
// Region is the AWS region for the S3 bucket. Templating is
133+
// supported.
134+
// +optional
135+
Region string `json:"region,omitempty"`
136+
137+
// ForcePathStyle forces path-style S3 URLs (e.g., endpoint/bucket/key)
138+
// instead of virtual-hosted-style (e.g., bucket.endpoint/key). Most
139+
// S3-compatible providers (MinIO, Ceph, seaweedfs-s3) require path
140+
// style.
141+
// +optional
142+
ForcePathStyle *bool `json:"forcePathStyle,omitempty"`
143+
144+
// CredentialsSecretRef references a Secret containing
145+
// AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys. The Secret must
146+
// live in the application's namespace. Templating is supported on
147+
// Name (typical pattern:
148+
// "{{ .Application.metadata.name }}-etcd-backup-creds").
149+
CredentialsSecretRef EtcdLocalObjectReference `json:"credentialsSecretRef"`
150+
}
151+
152+
// EtcdLocalObjectReference is a minimal local Secret reference. The driver
153+
// looks the Secret up in the application namespace.
154+
type EtcdLocalObjectReference struct {
155+
// Name is the Secret name. Templating is supported.
156+
// +kubebuilder:validation:MinLength=1
157+
Name string `json:"name"`
158+
}
159+
160+
// EtcdStatus reports observed state for the strategy CR. Driver
161+
// controllers surface diagnostic conditions here (e.g. validation issues).
162+
type EtcdStatus struct {
163+
// Conditions holds the latest available observations.
164+
// +optional
165+
// +listType=map
166+
// +listMapKey=type
167+
Conditions []metav1.Condition `json:"conditions,omitempty"`
168+
}

‎api/backups/strategy/v1alpha1/zz_generated.deepcopy.go‎

Lines changed: 170 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎cmd/backupstrategy-controller/main.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import (
4141
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
4242
"github.com/cozystack/cozystack/internal/backupcontroller"
4343
"github.com/cozystack/cozystack/internal/backupcontroller/cnpgtypes"
44+
"github.com/cozystack/cozystack/internal/backupcontroller/etcdapp"
45+
"github.com/cozystack/cozystack/internal/backupcontroller/etcdtypes"
4446
"github.com/cozystack/cozystack/internal/backupcontroller/foundationdbapp"
4547
"github.com/cozystack/cozystack/internal/backupcontroller/foundationdbtypes"
4648
"github.com/cozystack/cozystack/internal/backupcontroller/mariadbapp"
@@ -67,6 +69,8 @@ func init() {
6769
utilruntime.Must(mariadbapp.AddToScheme(scheme))
6870
utilruntime.Must(foundationdbtypes.AddToScheme(scheme))
6971
utilruntime.Must(foundationdbapp.AddToScheme(scheme))
72+
utilruntime.Must(etcdtypes.AddToScheme(scheme))
73+
utilruntime.Must(etcdapp.AddToScheme(scheme))
7074
// +kubebuilder:scaffold:scheme
7175
}
7276

0 commit comments

Comments
 (0)