diff --git a/api/.gitattributes b/api/.gitattributes new file mode 100644 index 00000000..39e882c9 --- /dev/null +++ b/api/.gitattributes @@ -0,0 +1 @@ +zz_generated_deepcopy.go linguist-generated diff --git a/api/backups/v1alpha1/DESIGN.md b/api/backups/v1alpha1/DESIGN.md new file mode 100644 index 00000000..455dab59 --- /dev/null +++ b/api/backups/v1alpha1/DESIGN.md @@ -0,0 +1,421 @@ +# Cozystack Backups – Core API & Contracts (Draft) + +## 1. Overview + +Cozystack’s backup subsystem provides a generic, composable way to back up and restore managed applications: + +* Every **application instance** can have one or more **backup plans**. +* Backups are stored in configurable **storage locations**. +* The mechanics of *how* a backup/restore is performed are delegated to **strategy drivers**, each implementing driver-specific **BackupStrategy** CRDs. + +The core API: + +* Orchestrates **when** backups happen and **where** they’re stored. +* Tracks **what** backups exist and their status. +* Defines contracts with drivers via shared resources (`BackupJob`, `Backup`, `RestoreJob`). + +It does **not** implement the backup logic itself. + +This document covers only the **core** API and its contracts with drivers, not driver implementations. + +--- + +## 2. Goals and non-goals + +### Goals + +* Provide a **stable core API** for: + + * Declaring **backup plans** per application. + * Configuring **storage targets** (S3, in-cluster bucket, etc.). + * Tracking **backup artifacts**. + * Initiating and tracking **restores**. +* Allow multiple **strategy drivers** to plug in, each supporting specific kinds of applications and strategies. +* Let application/product authors implement backup for their kinds by: + + * Creating **Plan** objects referencing a **driver-specific strategy**. + * Not having to write a backup engine themselves. + +### Non-goals + +* Implement backup logic for any specific application or storage backend. +* Define the internal structure of driver-specific strategy CRDs. +* Handle tenant-facing UI/UX (that’s built on top of these APIs). + +--- + +## 3. Architecture + +High-level components: + +* **Core backups controller(s)** (Cozystack-owned): + + * Group: `backups.cozystack.io` + * Own: + + * `Plan` + * `BackupJob` + * `Backup` + * `RestoreJob` + * Responsibilities: + + * Schedule backups based on `Plan`. + * Create `BackupJob` objects when due. + * Provide stable contracts for drivers to: + + * Perform backups and create `Backup`s. + * Perform restores based on `Backup`s. + +* **Strategy drivers** (pluggable, possibly third-party): + + * Their own API groups, e.g. `jobdriver.backups.cozystack.io`. + * Own **strategy CRDs** (e.g. `JobBackupStrategy`). + * Implement controllers that: + + * Watch `BackupJob` / `RestoreJob`. + * Match runs whose `strategyRef` GVK they support. + * Execute backup/restore logic. + * Create and update `Backup` and run statuses. + +Strategy drivers and core communicate entirely via Kubernetes objects; there are no webhook/HTTP calls between them. + +* **Storage drivers** (pluggable, possibly third-party): + + * **TBD** + +--- + +## 4. Core API resources + +### 4.1 Plan + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=Plan` + +**Purpose** +Describe **when**, **how**, and **where** to back up a specific managed application. + +**Key fields (spec)** + +```go +type PlanSpec struct { + // Application to back up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // Where backups should be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // Driver-specific BackupStrategy to use. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // When backups should run. + Schedule PlanSchedule `json:"schedule"` +} +``` + +`PlanSchedule` (initially) supports only cron: + +```go +type PlanScheduleType string + +const ( + PlanScheduleTypeEmpty PlanScheduleType = "" + PlanScheduleTypeCron PlanScheduleType = "cron" +) +``` + +```go +type PlanSchedule struct { + // Type is the schedule type. Currently only "cron" is supported. + // Defaults to "cron". + Type PlanScheduleType `json:"type,omitempty"` + + // Cron expression (required for cron type). + Cron string `json:"cron,omitempty"` +} +``` + +**Plan reconciliation contract** + +Core Plan controller: + +1. **Read schedule** from `spec.schedule` and compute the next fire time. +2. When due: + + * Create a `BackupJob` in the same namespace: + + * `spec.planRef.name = plan.Name` + * `spec.applicationRef = plan.spec.applicationRef` + * `spec.storageRef = plan.spec.storageRef` + * `spec.strategyRef = plan.spec.strategyRef` + * `spec.triggeredBy = "Plan"` + * Set `ownerReferences` so the `BackupJob` is owned by the `Plan`. + +The Plan controller does **not**: + +* Execute backups itself. +* Modify driver resources or `Backup` objects. +* Touch `BackupJob.spec` after creation. + +--- + +### 4.2 Storage + +**API Shape** + +TBD + +**Storage usage** + +* `Plan` and `BackupJob` reference `Storage` via `TypedLocalObjectReference`. +* Drivers read `Storage` to know how/where to store or read artifacts. +* Core treats `Storage` spec as opaque; it does not directly talk to S3 or buckets. + +--- + +### 4.3 BackupJob + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=BackupJob` + +**Purpose** +Represent a single **execution** of a backup operation, typically created when a `Plan` fires or when a user triggers an ad-hoc backup. + +**Key fields (spec)** + +```go +type BackupJobSpec struct { + // Plan that triggered this run, if any. + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // Application to back up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // Storage to use. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // Driver-specific BackupStrategy to use. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Informational: what triggered this run ("Plan", "Manual", etc.). + TriggeredBy string `json:"triggeredBy,omitempty"` +} +``` + +**Key fields (status)** + +```go +type BackupJobStatus struct { + Phase BackupJobPhase `json:"phase,omitempty"` + BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"` + StartedAt *metav1.Time `json:"startedAt,omitempty"` + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + Message string `json:"message,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +`BackupJobPhase` is one of: `Pending`, `Running`, `Succeeded`, `Failed`. + +**BackupJob contract with drivers** + +* Core **creates** `BackupJob` and must treat `spec` as immutable afterwards. +* Each driver controller: + + * Watches `BackupJob`. + * Reconciles runs where `spec.strategyRef.apiGroup/kind` matches its **strategy type(s)**. +* Driver responsibilities: + + 1. On first reconcile: + + * Set `status.startedAt` if unset. + * Set `status.phase = Running`. + 2. Resolve inputs: + + * Read `Strategy` (driver-owned CRD), `Storage`, `Application`, optionally `Plan`. + 3. Execute backup logic (implementation-specific). + 4. On success: + + * Create a `Backup` resource (see below). + * Set `status.backupRef` to the created `Backup`. + * Set `status.completedAt`. + * Set `status.phase = Succeeded`. + 5. On failure: + + * Set `status.completedAt`. + * Set `status.phase = Failed`. + * Set `status.message` and conditions. + +Drivers must **not** modify `BackupJob.spec` or delete `BackupJob` themselves. + +--- + +### 4.4 Backup + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=Backup` + +**Purpose** +Represent a single **backup artifact** for a given application, decoupled from a particular run. usable as a stable, listable “thing you can restore from”. + +**Key fields (spec)** + +```go +type BackupSpec struct { + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + TakenAt metav1.Time `json:"takenAt"` + DriverMetadata map[string]string `json:"driverMetadata,omitempty"` +} +``` + +**Key fields (status)** + +```go +type BackupStatus struct { + Phase BackupPhase `json:"phase,omitempty"` // Pending, Ready, Failed, etc. + Artifact *BackupArtifact `json:"artifact,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +`BackupArtifact` describes the artifact (URI, size, checksum). + +**Backup contract with drivers** + +* On successful completion of a `BackupJob`, the **driver**: + + * Creates a `Backup` in the same namespace (typically owned by the `BackupJob`). + * Populates `spec` fields with: + + * The application, storage, strategy references. + * `takenAt`. + * Optional `driverMetadata`. + * Sets `status` with: + + * `phase = Ready` (or equivalent when fully usable). + * `artifact` describing the stored object. +* Core: + + * Treats `Backup` spec as mostly immutable and opaque. + * Uses it to: + + * List backups for a given application/plan. + * Anchor `RestoreJob` operations. + * Implement higher-level policies (retention) if needed. + +--- + +### 4.5 RestoreJob + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=RestoreJob` + +**Purpose** +Represent a single **restore operation** from a `Backup`, either back into the same application or into a new target application. + +**Key fields (spec)** + +```go +type RestoreJobSpec struct { + // Backup to restore from. + BackupRef corev1.LocalObjectReference `json:"backupRef"` + + // Target application; if omitted, drivers SHOULD restore into + // backup.spec.applicationRef. + TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"` +} +``` + +**Key fields (status)** + +```go +type RestoreJobStatus struct { + Phase RestoreJobPhase `json:"phase,omitempty"` // Pending, Running, Succeeded, Failed + StartedAt *metav1.Time `json:"startedAt,omitempty"` + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + Message string `json:"message,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +**RestoreJob contract with drivers** + +* RestoreJob is created either manually or by core. +* Driver controller: + + 1. Watches `RestoreJob`. + 2. On reconcile: + + * Fetches the referenced `Backup`. + * Determines effective: + + * **Strategy**: `backup.spec.strategyRef`. + * **Storage**: `backup.spec.storageRef`. + * **Target application**: `spec.targetApplicationRef` or `backup.spec.applicationRef`. + * If effective strategy’s GVK is one of its supported strategy types → driver is responsible. + 3. Behaviour: + + * On first reconcile, set `status.startedAt` and `phase = Running`. + * Resolve `Backup`, `Storage`, `Strategy`, target application. + * Execute restore logic (implementation-specific). + * On success: + + * Set `status.completedAt`. + * Set `status.phase = Succeeded`. + * On failure: + + * Set `status.completedAt`. + * Set `status.phase = Failed`. + * Set `status.message` and conditions. + +Drivers must not modify `RestoreJob.spec` or delete `RestoreJob`. + +--- + +## 5. Strategy drivers (high-level) + +Strategy drivers are separate controllers that: + +* Define their own **strategy CRDs** (e.g. `JobBackupStrategy`) in their own API groups: + + * e.g. `jobdriver.backups.cozystack.io/v1alpha1, Kind=JobBackupStrategy` +* Implement the **BackupJob contract**: + + * Watch `BackupJob`. + * Filter by `spec.strategyRef.apiGroup/kind`. + * Execute backup logic. + * Create/update `Backup`. +* Implement the **RestoreJob contract**: + + * Watch `RestoreJob`. + * Resolve `Backup`, then effective `strategyRef`. + * Filter by effective strategy GVK. + * Execute restore logic. + +The core backups API **does not** dictate: + +* The fields and structure of driver strategy specs. +* How drivers implement backup/restore internally (Jobs, snapshots, native operator CRDs, etc.). + +Drivers are interchangeable as long as they respect: + +* The `BackupJob` and `RestoreJob` contracts. +* The shapes and semantics of `Backup` objects. + +--- + +## 6. Summary + +The Cozystack backups core API: + +* Uses a single group, `backups.cozystack.io`, for all core CRDs. +* Cleanly separates: + + * **When & where** (Plan + Storage) – core-owned. + * **What backup artifacts exist** (Backup) – driver-created but cluster-visible. + * **Execution lifecycle** (BackupJob, RestoreJob) – shared contract boundary. +* Allows multiple strategy drivers to implement backup/restore logic without entangling their implementation with the core API. + diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go new file mode 100644 index 00000000..ff6a8612 --- /dev/null +++ b/api/backups/v1alpha1/backup_types.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupPhase represents the lifecycle phase of a Backup. +type BackupPhase string + +const ( + BackupPhaseEmpty BackupPhase = "" + BackupPhasePending BackupPhase = "Pending" + BackupPhaseReady BackupPhase = "Ready" + BackupPhaseFailed BackupPhase = "Failed" +) + +// BackupArtifact describes the stored backup object (tarball, snapshot, etc.). +type BackupArtifact struct { + // URI is a driver-/storage-specific URI pointing to the backup artifact. + // For example: s3://bucket/prefix/file.tar.gz + URI string `json:"uri"` + + // SizeBytes is the size of the artifact in bytes, if known. + // +optional + SizeBytes int64 `json:"sizeBytes,omitempty"` + + // Checksum is the checksum of the artifact, if computed. + // For example: "sha256:". + // +optional + Checksum string `json:"checksum,omitempty"` +} + +// BackupSpec describes an immutable backup artifact produced by a BackupJob. +type BackupSpec struct { + // ApplicationRef refers to the application that was backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // PlanRef refers to the Plan that produced this backup, if any. + // For manually triggered backups, this can be omitted. + // +optional + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // StorageRef refers to the Storage object that describes where the backup + // artifact is stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef refers to the driver-specific BackupStrategy that was used + // to create this backup. This allows the driver to later perform restores. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // TakenAt is the time at which the backup was taken (as reported by the + // driver). It may differ slightly from metadata.creationTimestamp. + TakenAt metav1.Time `json:"takenAt"` + + // DriverMetadata holds driver-specific, opaque metadata associated with + // this backup (for example snapshot IDs, schema versions, etc.). + // This data is not interpreted by the core backup controllers. + // +optional + DriverMetadata map[string]string `json:"driverMetadata,omitempty"` +} + +// BackupStatus represents the observed state of a Backup. +type BackupStatus struct { + // Phase is a simple, high-level summary of the backup's state. + // Typical values are: Pending, Ready, Failed. + // +optional + Phase BackupPhase `json:"phase,omitempty"` + + // Artifact describes the stored backup object, if available. + // +optional + Artifact *BackupArtifact `json:"artifact,omitempty"` + + // Conditions represents the latest available observations of a Backup's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// Backup represents a single backup artifact for a given application. +type Backup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupSpec `json:"spec,omitempty"` + Status BackupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupList contains a list of Backups. +type BackupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backup `json:"items"` +} diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go new file mode 100644 index 00000000..56b4ec89 --- /dev/null +++ b/api/backups/v1alpha1/backupjob_types.go @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupJobPhase represents the lifecycle phase of a BackupJob. +type BackupJobPhase string + +const ( + BackupJobPhaseEmpty BackupJobPhase = "" + BackupJobPhasePending BackupJobPhase = "Pending" + BackupJobPhaseRunning BackupJobPhase = "Running" + BackupJobPhaseSucceeded BackupJobPhase = "Succeeded" + BackupJobPhaseFailed BackupJobPhase = "Failed" +) + +// BackupJobSpec describes the execution of a single backup operation. +type BackupJobSpec struct { + // PlanRef refers to the Plan that requested this backup run. + // For ad-hoc/manual backups, this can be omitted. + // +optional + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // ApplicationRef holds a reference to the managed application whose state + // is being backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // StorageRef holds a reference to the Storage object that describes where + // the backup will be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef holds a reference to the driver-specific BackupStrategy object + // that describes how the backup should be created. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` +} + +// BackupJobStatus represents the observed state of a BackupJob. +type BackupJobStatus struct { + // Phase is a high-level summary of the run's state. + // Typical values: Pending, Running, Succeeded, Failed. + // +optional + Phase BackupJobPhase `json:"phase,omitempty"` + + // BackupRef refers to the Backup object created by this run, if any. + // +optional + BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"` + + // StartedAt is the time at which the backup run started. + // +optional + StartedAt *metav1.Time `json:"startedAt,omitempty"` + + // CompletedAt is the time at which the backup run completed (successfully + // or otherwise). + // +optional + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + + // Message is a human-readable message indicating details about why the + // backup run is in its current phase, if any. + // +optional + Message string `json:"message,omitempty"` + + // Conditions represents the latest available observations of a BackupJob's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupJob represents a single execution of a backup. +// It is typically created by a Plan controller when a schedule fires. +type BackupJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupJobSpec `json:"spec,omitempty"` + Status BackupJobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupJobList contains a list of BackupJobs. +type BackupJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BackupJob `json:"items"` +} diff --git a/api/backups/v1alpha1/groupversion_info.go b/api/backups/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..a524c86d --- /dev/null +++ b/api/backups/v1alpha1/groupversion_info.go @@ -0,0 +1,53 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group. +// +kubebuilder:object:generate=true +// +groupName=backups.cozystack.io +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + GroupVersion, + &Plan{}, + &PlanList{}, + &BackupJob{}, + &BackupJobList{}, + &Backup{}, + &BackupList{}, + &RestoreJob{}, + &RestoreJobList{}, + ) + metav1.AddToGroupVersion(scheme, GroupVersion) + return nil +} diff --git a/api/backups/v1alpha1/plan_types.go b/api/backups/v1alpha1/plan_types.go new file mode 100644 index 00000000..e73608aa --- /dev/null +++ b/api/backups/v1alpha1/plan_types.go @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type PlanScheduleType string + +const ( + PlanScheduleTypeEmpty PlanScheduleType = "" + PlanScheduleTypeCron PlanScheduleType = "cron" +) + +// Condtions +const ( + PlanConditionError = "Error" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Plan describes the schedule, method and storage location for the +// backup of a given target application. +type Plan struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PlanSpec `json:"spec,omitempty"` + Status PlanStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PlanList contains a list of backup Plans. +type PlanList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Plan `json:"items"` +} + +// PlanSpec references the storage, the strategy, the application to be +// backed up and specifies the timetable on which the backups will run. +type PlanSpec struct { + // ApplicationRef holds a reference to the managed application, + // whose state and configuration must be backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // StorageRef holds a reference to the Storage object that + // describes the location where the backup will be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef holds a reference to the Strategy object that + // describes, how a backup copy is to be created. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Schedule specifies when backup copies are created. + Schedule PlanSchedule `json:"schedule"` +} + +// PlanSchedule specifies when backup copies are created. +type PlanSchedule struct { + // Type is the type of schedule specification. Supported values are + // [`cron`]. If omitted, defaults to `cron`. + // +optional + Type PlanScheduleType `json:"type,omitempty"` + + // Cron contains the cron spec for scheduling backups. Must be + // specified if the schedule type is `cron`. Since only `cron` is + // supported, omitting this field is not allowed. + // +optional + Cron string `json:"cron,omitempty"` +} + +type PlanStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/backups/v1alpha1/restorejob_types.go b/api/backups/v1alpha1/restorejob_types.go new file mode 100644 index 00000000..ddab2ff3 --- /dev/null +++ b/api/backups/v1alpha1/restorejob_types.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RestoreJobPhase represents the lifecycle phase of a RestoreJob. +type RestoreJobPhase string + +const ( + RestoreJobPhaseEmpty RestoreJobPhase = "" + RestoreJobPhasePending RestoreJobPhase = "Pending" + RestoreJobPhaseRunning RestoreJobPhase = "Running" + RestoreJobPhaseSucceeded RestoreJobPhase = "Succeeded" + RestoreJobPhaseFailed RestoreJobPhase = "Failed" +) + +// RestoreJobSpec describes the execution of a single restore operation. +type RestoreJobSpec struct { + // BackupRef refers to the Backup that should be restored. + BackupRef corev1.LocalObjectReference `json:"backupRef"` + + // TargetApplicationRef refers to the application into which the backup + // should be restored. If omitted, the driver SHOULD restore into the same + // application as referenced by backup.spec.applicationRef. + // +optional + TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"` +} + +// RestoreJobStatus represents the observed state of a RestoreJob. +type RestoreJobStatus struct { + // Phase is a high-level summary of the run's state. + // Typical values: Pending, Running, Succeeded, Failed. + // +optional + Phase RestoreJobPhase `json:"phase,omitempty"` + + // StartedAt is the time at which the restore run started. + // +optional + StartedAt *metav1.Time `json:"startedAt,omitempty"` + + // CompletedAt is the time at which the restore run completed (successfully + // or otherwise). + // +optional + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + + // Message is a human-readable message indicating details about why the + // restore run is in its current phase, if any. + // +optional + Message string `json:"message,omitempty"` + + // Conditions represents the latest available observations of a RestoreJob's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// RestoreJob represents a single execution of a restore from a Backup. +type RestoreJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RestoreJobSpec `json:"spec,omitempty"` + Status RestoreJobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RestoreJobList contains a list of RestoreJobs. +type RestoreJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RestoreJob `json:"items"` +} diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..3fd7005c --- /dev/null +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,478 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2025 The Cozystack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backup) DeepCopyInto(out *Backup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backup. +func (in *Backup) DeepCopy() *Backup { + if in == nil { + return nil + } + out := new(Backup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupArtifact) DeepCopyInto(out *BackupArtifact) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupArtifact. +func (in *BackupArtifact) DeepCopy() *BackupArtifact { + if in == nil { + return nil + } + out := new(BackupArtifact) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJob) DeepCopyInto(out *BackupJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJob. +func (in *BackupJob) DeepCopy() *BackupJob { + if in == nil { + return nil + } + out := new(BackupJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJobList) DeepCopyInto(out *BackupJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackupJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobList. +func (in *BackupJobList) DeepCopy() *BackupJobList { + if in == nil { + return nil + } + out := new(BackupJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJobSpec) DeepCopyInto(out *BackupJobSpec) { + *out = *in + if in.PlanRef != nil { + in, out := &in.PlanRef, &out.PlanRef + *out = new(v1.LocalObjectReference) + **out = **in + } + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobSpec. +func (in *BackupJobSpec) DeepCopy() *BackupJobSpec { + if in == nil { + return nil + } + out := new(BackupJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJobStatus) DeepCopyInto(out *BackupJobStatus) { + *out = *in + if in.BackupRef != nil { + in, out := &in.BackupRef, &out.BackupRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } + if in.CompletedAt != nil { + in, out := &in.CompletedAt, &out.CompletedAt + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobStatus. +func (in *BackupJobStatus) DeepCopy() *BackupJobStatus { + if in == nil { + return nil + } + out := new(BackupJobStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupList) DeepCopyInto(out *BackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupList. +func (in *BackupList) DeepCopy() *BackupList { + if in == nil { + return nil + } + out := new(BackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupSpec) DeepCopyInto(out *BackupSpec) { + *out = *in + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + if in.PlanRef != nil { + in, out := &in.PlanRef, &out.PlanRef + *out = new(v1.LocalObjectReference) + **out = **in + } + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + in.TakenAt.DeepCopyInto(&out.TakenAt) + if in.DriverMetadata != nil { + in, out := &in.DriverMetadata, &out.DriverMetadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec. +func (in *BackupSpec) DeepCopy() *BackupSpec { + if in == nil { + return nil + } + out := new(BackupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupStatus) DeepCopyInto(out *BackupStatus) { + *out = *in + if in.Artifact != nil { + in, out := &in.Artifact, &out.Artifact + *out = new(BackupArtifact) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupStatus. +func (in *BackupStatus) DeepCopy() *BackupStatus { + if in == nil { + return nil + } + out := new(BackupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Plan) DeepCopyInto(out *Plan) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plan. +func (in *Plan) DeepCopy() *Plan { + if in == nil { + return nil + } + out := new(Plan) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Plan) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlanList) DeepCopyInto(out *PlanList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Plan, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanList. +func (in *PlanList) DeepCopy() *PlanList { + if in == nil { + return nil + } + out := new(PlanList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlanList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlanSchedule) DeepCopyInto(out *PlanSchedule) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSchedule. +func (in *PlanSchedule) DeepCopy() *PlanSchedule { + if in == nil { + return nil + } + out := new(PlanSchedule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlanSpec) DeepCopyInto(out *PlanSpec) { + *out = *in + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + out.Schedule = in.Schedule +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSpec. +func (in *PlanSpec) DeepCopy() *PlanSpec { + if in == nil { + return nil + } + out := new(PlanSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJob) DeepCopyInto(out *RestoreJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJob. +func (in *RestoreJob) DeepCopy() *RestoreJob { + if in == nil { + return nil + } + out := new(RestoreJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RestoreJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJobList) DeepCopyInto(out *RestoreJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RestoreJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobList. +func (in *RestoreJobList) DeepCopy() *RestoreJobList { + if in == nil { + return nil + } + out := new(RestoreJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RestoreJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJobSpec) DeepCopyInto(out *RestoreJobSpec) { + *out = *in + out.BackupRef = in.BackupRef + if in.TargetApplicationRef != nil { + in, out := &in.TargetApplicationRef, &out.TargetApplicationRef + *out = new(v1.TypedLocalObjectReference) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobSpec. +func (in *RestoreJobSpec) DeepCopy() *RestoreJobSpec { + if in == nil { + return nil + } + out := new(RestoreJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJobStatus) DeepCopyInto(out *RestoreJobStatus) { + *out = *in + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } + if in.CompletedAt != nil { + in, out := &in.CompletedAt, &out.CompletedAt + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobStatus. +func (in *RestoreJobStatus) DeepCopy() *RestoreJobStatus { + if in == nil { + return nil + } + out := new(RestoreJobStatus) + in.DeepCopyInto(out) + return out +} diff --git a/go.mod b/go.mod index 041aa96a..ee8d0bf0 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,16 @@ go 1.23.0 require ( github.com/fluxcd/helm-controller/api v1.1.0 + github.com/go-logr/logr v1.4.2 + github.com/go-logr/zapr v1.3.0 github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + github.com/prometheus/client_golang v1.19.1 + github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.2 @@ -44,9 +49,7 @@ require ( github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -74,7 +77,6 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -94,7 +96,6 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/go.sum b/go.sum index a1090f31..d36b86a8 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index e181fc76..0069651e 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -23,6 +23,11 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code- API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-rules"}" UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}" CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4" +TMPDIR=$(mktemp -d) +COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds +COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition +BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions +trap 'rm -rf ${TMPDIR}' EXIT source "${CODEGEN_PKG}/kube_codegen.sh" @@ -53,6 +58,11 @@ kube::codegen::gen_openapi \ "${SCRIPT_ROOT}/pkg/apis" $CONTROLLER_GEN object:headerFile="hack/boilerplate.go.txt" paths="./api/..." -$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/crds -mv packages/system/cozystack-controller/crds/cozystack.io_cozystackresourcedefinitions.yaml \ - packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml +$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=${TMPDIR} + +mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \ + ${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml + +mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/ + +mv ${TMPDIR}/*.yaml ${COZY_CONTROLLER_CRDDIR}/ diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go new file mode 100644 index 00000000..722e5d99 --- /dev/null +++ b/internal/backupcontroller/factory/backupjob.go @@ -0,0 +1,28 @@ +package factory + +import ( + "fmt" + "time" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { + job := &backupsv1alpha1.BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", p.Name, scheduledFor.Unix()/60), + Namespace: p.Namespace, + }, + Spec: backupsv1alpha1.BackupJobSpec{ + PlanRef: &corev1.LocalObjectReference{ + Name: p.Name, + }, + ApplicationRef: *p.Spec.ApplicationRef.DeepCopy(), + StorageRef: *p.Spec.StorageRef.DeepCopy(), + StrategyRef: *p.Spec.StrategyRef.DeepCopy(), + }, + } + return job +} diff --git a/internal/backupcontroller/plan_controller.go b/internal/backupcontroller/plan_controller.go new file mode 100644 index 00000000..fca5703b --- /dev/null +++ b/internal/backupcontroller/plan_controller.go @@ -0,0 +1,90 @@ +package backupcontroller + +import ( + "context" + "fmt" + "time" + + cron "github.com/robfig/cron/v3" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/backupcontroller/factory" +) + +const ( + minRequeueDelay = 30 * time.Second + startingDeadlineSeconds = 300 * time.Second +) + +// PlanReconciler reconciles a Plan object +type PlanReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + log.V(2).Info("reconciling") + + p := &backupsv1alpha1.Plan{} + + if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, p); err != nil { + if apierrors.IsNotFound(err) { + log.V(3).Info("Plan not found") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + tCheck := time.Now().Add(-startingDeadlineSeconds) + sch, err := cron.ParseStandard(p.Spec.Schedule.Cron) + if err != nil { + errWrapped := fmt.Errorf("could not parse cron %s: %w", p.Spec.Schedule.Cron, err) + log.Error(err, "could not parse cron", "cron", p.Spec.Schedule.Cron) + meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{ + Type: backupsv1alpha1.PlanConditionError, + Status: metav1.ConditionTrue, + Reason: "Failed to parse cron spec", + Message: errWrapped.Error(), + }) + if err := r.Status().Update(ctx, p); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + tNext := sch.Next(tCheck) + + if time.Now().Before(tNext) { + return ctrl.Result{RequeueAfter: tNext.Sub(time.Now())}, nil + } + + job := factory.BackupJob(p, tNext) + if err := controllerutil.SetControllerReference(p, job, r.Scheme); err != nil { + return ctrl.Result{}, err + } + + if err := r.Create(ctx, job); err != nil { + if apierrors.IsAlreadyExists(err) { + return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil + } + return ctrl.Result{}, err + } + + return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *PlanReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&backupsv1alpha1.Plan{}). + Complete(r) +} diff --git a/packages/system/backup-controller/definitions/.gitattributes b/packages/system/backup-controller/definitions/.gitattributes new file mode 100644 index 00000000..aae64e23 --- /dev/null +++ b/packages/system/backup-controller/definitions/.gitattributes @@ -0,0 +1 @@ +* linguist-generated diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml new file mode 100644 index 00000000..794d8121 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -0,0 +1,231 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: backupjobs.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: BackupJob + listKind: BackupJobList + plural: backupjobs + singular: backupjob + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BackupJob represents a single execution of a backup. + It is typically created by a Plan controller when a schedule fires. + 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: BackupJobSpec describes the execution of a single backup + operation. + properties: + applicationRef: + description: |- + ApplicationRef holds a reference to the managed application whose state + is being backed up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + planRef: + description: |- + PlanRef refers to the Plan that requested this backup run. + For ad-hoc/manual backups, this can be omitted. + 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. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + storageRef: + description: |- + StorageRef holds a reference to the Storage object that describes where + the backup will be stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef holds a reference to the driver-specific BackupStrategy object + that describes how the backup should be created. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - applicationRef + - storageRef + - strategyRef + type: object + status: + description: BackupJobStatus represents the observed state of a BackupJob. + properties: + backupRef: + description: BackupRef refers to the Backup object created by this + run, if any. + 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. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + completedAt: + description: |- + CompletedAt is the time at which the backup run completed (successfully + or otherwise). + format: date-time + type: string + conditions: + description: Conditions represents the latest available observations + of a BackupJob's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + 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. + 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 + message: + description: |- + Message is a human-readable message indicating details about why the + backup run is in its current phase, if any. + type: string + phase: + description: |- + Phase is a high-level summary of the run's state. + Typical values: Pending, Running, Succeeded, Failed. + type: string + startedAt: + description: StartedAt is the time at which the backup run started. + format: date-time + type: string + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml new file mode 100644 index 00000000..1bae551f --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml @@ -0,0 +1,234 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: backups.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: Backup + listKind: BackupList + plural: backups + singular: backup + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Backup represents a single backup artifact for a given application. + 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: BackupSpec describes an immutable backup artifact produced + by a BackupJob. + properties: + applicationRef: + description: ApplicationRef refers to the application that was backed + up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + driverMetadata: + additionalProperties: + type: string + description: |- + DriverMetadata holds driver-specific, opaque metadata associated with + this backup (for example snapshot IDs, schema versions, etc.). + This data is not interpreted by the core backup controllers. + type: object + planRef: + description: |- + PlanRef refers to the Plan that produced this backup, if any. + For manually triggered backups, this can be omitted. + 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. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + storageRef: + description: |- + StorageRef refers to the Storage object that describes where the backup + artifact is stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef refers to the driver-specific BackupStrategy that was used + to create this backup. This allows the driver to later perform restores. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + takenAt: + description: |- + TakenAt is the time at which the backup was taken (as reported by the + driver). It may differ slightly from metadata.creationTimestamp. + format: date-time + type: string + required: + - applicationRef + - storageRef + - strategyRef + - takenAt + type: object + status: + description: BackupStatus represents the observed state of a Backup. + properties: + artifact: + description: Artifact describes the stored backup object, if available. + properties: + checksum: + description: |- + Checksum is the checksum of the artifact, if computed. + For example: "sha256:". + type: string + sizeBytes: + description: SizeBytes is the size of the artifact in bytes, if + known. + format: int64 + type: integer + uri: + description: |- + URI is a driver-/storage-specific URI pointing to the backup artifact. + For example: s3://bucket/prefix/file.tar.gz + type: string + required: + - uri + type: object + conditions: + description: Conditions represents the latest available observations + of a Backup's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + 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. + 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: + description: |- + Phase is a simple, high-level summary of the backup's state. + Typical values are: Pending, Ready, Failed. + type: string + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml new file mode 100644 index 00000000..b41d8e45 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml @@ -0,0 +1,135 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: plans.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: Plan + listKind: PlanList + plural: plans + singular: plan + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Plan describes the schedule, method and storage location for the + backup of a given target application. + 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: |- + PlanSpec references the storage, the strategy, the application to be + backed up and specifies the timetable on which the backups will run. + properties: + applicationRef: + description: |- + ApplicationRef holds a reference to the managed application, + whose state and configuration must be backed up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + schedule: + description: Schedule specifies when backup copies are created. + properties: + cron: + description: |- + Cron contains the cron spec for scheduling backups. Must be + specified if the schedule type is `cron`. Since only `cron` is + supported, omitting this field is not allowed. + type: string + type: + description: |- + Type is the type of schedule specification. Supported values are + [`cron`]. If omitted, defaults to `cron`. + type: string + type: object + storageRef: + description: |- + StorageRef holds a reference to the Storage object that + describes the location where the backup will be stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef holds a reference to the Strategy object that + describes, how a backup copy is to be created. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - applicationRef + - schedule + - storageRef + - strategyRef + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml new file mode 100644 index 00000000..607a69b2 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml @@ -0,0 +1,168 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: restorejobs.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: RestoreJob + listKind: RestoreJobList + plural: restorejobs + singular: restorejob + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RestoreJob represents a single execution of a restore from a + Backup. + 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: RestoreJobSpec describes the execution of a single restore + operation. + properties: + backupRef: + description: BackupRef refers to the Backup that should be restored. + 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. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetApplicationRef: + description: |- + TargetApplicationRef refers to the application into which the backup + should be restored. If omitted, the driver SHOULD restore into the same + application as referenced by backup.spec.applicationRef. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - backupRef + type: object + status: + description: RestoreJobStatus represents the observed state of a RestoreJob. + properties: + completedAt: + description: |- + CompletedAt is the time at which the restore run completed (successfully + or otherwise). + format: date-time + type: string + conditions: + description: Conditions represents the latest available observations + of a RestoreJob's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + 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. + 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 + message: + description: |- + Message is a human-readable message indicating details about why the + restore run is in its current phase, if any. + type: string + phase: + description: |- + Phase is a high-level summary of the run's state. + Typical values: Pending, Running, Succeeded, Failed. + type: string + startedAt: + description: StartedAt is the time at which the restore run started. + format: date-time + type: string + type: object + type: object + served: true + storage: true