Implement autobackup-operator controllers, tests, CI and packaging

PVCs and CloudNativePG Clusters need S3 buckets and backup schedules
provisioned consistently. This operator watches the
backups.unkin.net/{schedule,destination} annotations on those objects and
provisions everything needed to back them up, with no new CRDs.

- Add a PVC controller that provisions cephrgw ObjectStoreUser/Bucket/BucketAccess,
  auto-generates a restic repo-password Secret and creates a k8up Schedule scoped
  to the PVC via spec.backup.volumes[].persistentVolumeClaim.claimName.
- Add a CNPG Cluster controller that provisions the same bucket stack, idempotently
  patches spec.backup.barmanObjectStore (leaving a user-set destinationPath alone
  with a Warning event) and creates a ScheduledBackup.
- Resolve destinations through a ConfigMap lookup table; requeue until the
  BucketAccess is Ready before creating schedule resources; own-reference created
  resources and retain bucket data by default.
- Add schedule-mapping helpers (k8up 5-field/shortcut pass-through, CNPG 6-field
  seconds-first) and deterministic, length-bounded name derivation.
- Add unit tests (schedule mapping, name derivation, destination resolution) and
  envtest controller tests for both paths, wiring the external CRDs into envtest.
- Add kubebuilder-generated RBAC, a Dockerfile (distroless/nonroot), Woodpecker
  lint/test/build pipelines and a tag-triggered image push to the artifactapi
  docker-internal registry, plus a version-bump Makefile and deploy manifests.
This commit is contained in:
2026-08-14 00:08:37 +10:00
parent 3d63975b24
commit 9da206dc7c
4631 changed files with 1334852 additions and 1 deletions
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+125
View File
@@ -0,0 +1,125 @@
package v1
import (
"context"
"reflect"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ArchiveSpec defines the desired state of Archive.
type ArchiveSpec struct {
*RestoreSpec `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule Ref",type="string",JSONPath=`.metadata.ownerReferences[?(@.kind == "Schedule")].name`,description="Reference to Schedule"
// +kubebuilder:printcolumn:name="Completion",type="string",JSONPath=`.status.conditions[?(@.type == "Completed")].reason`,description="Status of Completion"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// Archive is the Schema for the archives API
type Archive struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArchiveSpec `json:"spec,omitempty"`
Status Status `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ArchiveList contains a list of Archive
type ArchiveList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Archive `json:"items"`
}
func init() {
SchemeBuilder.Register(&Archive{}, &ArchiveList{})
}
func (*Archive) GetType() JobType {
return ArchiveType
}
// GetStatus retrieves the Status property
func (a *Archive) GetStatus() Status {
return a.Status
}
// SetStatus sets the Status property
func (a *Archive) SetStatus(status Status) {
a.Status = status
}
// GetResources returns the resource requirements
func (a *Archive) GetResources() corev1.ResourceRequirements {
return a.Spec.Resources
}
// GetPodSecurityContext returns the pod security context
func (a *Archive) GetPodSecurityContext() *corev1.PodSecurityContext {
return a.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (a *Archive) GetActiveDeadlineSeconds() *int64 {
return a.Spec.ActiveDeadlineSeconds
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (a *Archive) GetFailedJobsHistoryLimit() *int {
if a.Spec.FailedJobsHistoryLimit != nil {
return a.Spec.FailedJobsHistoryLimit
}
return a.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (a *Archive) GetSuccessfulJobsHistoryLimit() *int {
if a.Spec.SuccessfulJobsHistoryLimit != nil {
return a.Spec.SuccessfulJobsHistoryLimit
}
return a.Spec.KeepJobs
}
func (a *Archive) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if a.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, a.Spec.PodConfigRef.Name, a.GetNamespace(), c)
}
// GetJobObjects returns a sortable list of jobs
func (a *ArchiveList) GetJobObjects() JobObjectList {
items := make(JobObjectList, len(a.Items))
for i := range a.Items {
items[i] = &a.Items[i]
}
return items
}
// GetDeepCopy returns a deep copy
func (in *ArchiveSchedule) GetDeepCopy() ScheduleSpecInterface {
return in.DeepCopy()
}
// GetRunnableSpec returns a pointer to RunnableSpec
func (in *ArchiveSchedule) GetRunnableSpec() *RunnableSpec {
return &in.RunnableSpec
}
// GetSchedule returns the schedule definition
func (in *ArchiveSchedule) GetSchedule() ScheduleDefinition {
return in.Schedule
}
var (
ArchiveKind = reflect.TypeOf(Archive{}).Name()
)
+291
View File
@@ -0,0 +1,291 @@
package v1
import (
"fmt"
"reflect"
"strings"
corev1 "k8s.io/api/core/v1"
"github.com/k8up-io/k8up/v2/operator/cfg"
)
type (
// Backend allows configuring several backend implementations.
// It is expected that users only configure one storage type.
Backend struct {
// RepoPasswordSecretRef references a secret key to look up the restic repository password
RepoPasswordSecretRef *corev1.SecretKeySelector `json:"repoPasswordSecretRef,omitempty"`
// EnvFrom adds all environment variables from a an external source to the Restic job.
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"`
Local *LocalSpec `json:"local,omitempty"`
S3 *S3Spec `json:"s3,omitempty"`
GCS *GCSSpec `json:"gcs,omitempty"`
Azure *AzureSpec `json:"azure,omitempty"`
Swift *SwiftSpec `json:"swift,omitempty"`
B2 *B2Spec `json:"b2,omitempty"`
Rest *RestServerSpec `json:"rest,omitempty"`
TLSOptions *TLSOptions `json:"tlsOptions,omitempty"`
VolumeMounts *[]corev1.VolumeMount `json:"volumeMounts,omitempty"`
}
// +k8s:deepcopy-gen=false
// BackendInterface represents a Backend for internal use.
BackendInterface interface {
fmt.Stringer
EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource
}
)
// GetCredentialEnv will return a map containing the credentials for the given backend.
func (in *Backend) GetCredentialEnv() map[string]*corev1.EnvVarSource {
vars := make(map[string]*corev1.EnvVarSource)
if in.RepoPasswordSecretRef != nil {
vars[cfg.ResticPasswordEnvName] = &corev1.EnvVarSource{
SecretKeyRef: in.RepoPasswordSecretRef,
}
}
for _, backend := range in.getSupportedBackends() {
if IsNil(backend) {
continue
}
return backend.EnvVars(vars)
}
return nil
}
// String returns the string representation of the repository. If no repo is
// defined it'll return empty string.
func (in *Backend) String() string {
for _, backend := range in.getSupportedBackends() {
if IsNil(backend) {
continue
}
return backend.String()
}
return ""
}
// IsBackendEqualTo returns true if the restic repository string is equal to the other's string.
// If other is nil, it returns false.
func (in *Backend) IsBackendEqualTo(other *Backend) bool {
if other == nil {
return false
}
return in.String() == other.String()
}
func (in *Backend) getSupportedBackends() []BackendInterface {
return []BackendInterface{in.Azure, in.B2, in.GCS, in.Local, in.Rest, in.S3, in.Swift}
}
// IsNil returns true if the given value is nil using reflect.
func IsNil(v interface{}) bool {
// Unfortunately "v == nil" doesn't work with Interfaces, since they are tuples containing type and value.
return v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil())
}
func addEnvVarFromSecret(vars map[string]*corev1.EnvVarSource, key string, ref *corev1.SecretKeySelector) {
if ref != nil {
vars[key] = &corev1.EnvVarSource{
SecretKeyRef: ref,
}
}
}
type LocalSpec struct {
MountPath string `json:"mountPath,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *LocalSpec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
return vars
}
// String returns the mountpath.
func (in *LocalSpec) String() string {
return in.MountPath
}
type S3Spec struct {
Endpoint string `json:"endpoint,omitempty"`
Bucket string `json:"bucket,omitempty"`
AccessKeyIDSecretRef *corev1.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"`
SecretAccessKeySecretRef *corev1.SecretKeySelector `json:"secretAccessKeySecretRef,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *S3Spec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
addEnvVarFromSecret(vars, cfg.AwsAccessKeyIDEnvName, in.AccessKeyIDSecretRef)
addEnvVarFromSecret(vars, cfg.AwsSecretAccessKeyEnvName, in.SecretAccessKeySecretRef)
return vars
}
// String returns "s3:endpoint/bucket".
// If endpoint or bucket are empty, it uses their global setting accordingly.
func (in *S3Spec) String() string {
endpoint := cfg.Config.GlobalS3Endpoint
if in.Endpoint != "" {
endpoint = in.Endpoint
}
endpoint = strings.TrimRight(endpoint, "/")
bucket := cfg.Config.GlobalS3Bucket
if in.Bucket != "" {
bucket = in.Bucket
}
return fmt.Sprintf("s3:%v/%v", endpoint, bucket)
}
// RestoreEnvVars returns the env vars for this backend when using Restore jobs.
func (in *S3Spec) RestoreEnvVars() map[string]*corev1.EnvVar {
if in == nil {
return nil
}
vars := make(map[string]*corev1.EnvVar)
if in.AccessKeyIDSecretRef != nil {
vars[cfg.RestoreS3AccessKeyIDEnvName] = &corev1.EnvVar{
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: in.AccessKeyIDSecretRef,
},
}
} else {
vars[cfg.RestoreS3AccessKeyIDEnvName] = &corev1.EnvVar{
Value: cfg.Config.GlobalRestoreS3AccessKey,
}
}
if in.SecretAccessKeySecretRef != nil {
vars[cfg.RestoreS3SecretAccessKeyEnvName] = &corev1.EnvVar{
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: in.SecretAccessKeySecretRef,
},
}
} else {
vars[cfg.RestoreS3SecretAccessKeyEnvName] = &corev1.EnvVar{
Value: cfg.Config.GlobalRestoreS3SecretAccessKey,
}
}
bucket := in.Bucket
endpoint := in.Endpoint
if bucket == "" {
bucket = cfg.Config.GlobalRestoreS3Bucket
}
if endpoint == "" {
endpoint = cfg.Config.GlobalRestoreS3Endpoint
}
vars[cfg.RestoreS3EndpointEnvName] = &corev1.EnvVar{
Value: fmt.Sprintf("%v/%v", endpoint, bucket),
}
return vars
}
type GCSSpec struct {
Bucket string `json:"bucket,omitempty"`
ProjectIDSecretRef *corev1.SecretKeySelector `json:"projectIDSecretRef,omitempty"`
AccessTokenSecretRef *corev1.SecretKeySelector `json:"accessTokenSecretRef,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *GCSSpec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
addEnvVarFromSecret(vars, cfg.GcsProjectIDEnvName, in.ProjectIDSecretRef)
addEnvVarFromSecret(vars, cfg.GcsAccessTokenEnvName, in.AccessTokenSecretRef)
return vars
}
// String returns "gs:bucket:/"
func (in *GCSSpec) String() string {
return fmt.Sprintf("gs:%s:/", in.Bucket)
}
type AzureSpec struct {
Container string `json:"container,omitempty"`
Path string `json:"path,omitempty"`
AccountNameSecretRef *corev1.SecretKeySelector `json:"accountNameSecretRef,omitempty"`
AccountKeySecretRef *corev1.SecretKeySelector `json:"accountKeySecretRef,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *AzureSpec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
addEnvVarFromSecret(vars, cfg.AzureAccountKeyEnvName, in.AccountKeySecretRef)
addEnvVarFromSecret(vars, cfg.AzureAccountEnvName, in.AccountNameSecretRef)
return vars
}
// String returns "azure:container:path"
// If Path is empty, the default value "/" will be used as path
func (in *AzureSpec) String() string {
path := "/"
if in.Path != "" {
path = in.Path
}
return fmt.Sprintf("azure:%s:%s", in.Container, path)
}
type SwiftSpec struct {
Container string `json:"container,omitempty"`
Path string `json:"path,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *SwiftSpec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
return vars
}
// String returns "swift:container:path"
func (in *SwiftSpec) String() string {
return fmt.Sprintf("swift:%s:%s", in.Container, in.Path)
}
type B2Spec struct {
Bucket string `json:"bucket,omitempty"`
Path string `json:"path,omitempty"`
AccountIDSecretRef *corev1.SecretKeySelector `json:"accountIDSecretRef,omitempty"`
AccountKeySecretRef *corev1.SecretKeySelector `json:"accountKeySecretRef,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *B2Spec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
addEnvVarFromSecret(vars, cfg.B2AccountIDEnvName, in.AccountIDSecretRef)
addEnvVarFromSecret(vars, cfg.B2AccountKeyEnvName, in.AccountKeySecretRef)
return vars
}
// String returns "b2:bucket:path"
func (in *B2Spec) String() string {
return fmt.Sprintf("b2:%s:%s", in.Bucket, in.Path)
}
type RestServerSpec struct {
URL string `json:"url,omitempty"`
UserSecretRef *corev1.SecretKeySelector `json:"userSecretRef,omitempty"`
PasswordSecretReg *corev1.SecretKeySelector `json:"passwordSecretReg,omitempty"`
}
// EnvVars returns the env vars for this backend.
func (in *RestServerSpec) EnvVars(vars map[string]*corev1.EnvVarSource) map[string]*corev1.EnvVarSource {
addEnvVarFromSecret(vars, cfg.RestPasswordEnvName, in.PasswordSecretReg)
addEnvVarFromSecret(vars, cfg.RestUserEnvName, in.UserSecretRef)
return vars
}
// String returns "rest:URL"
func (in *RestServerSpec) String() string {
protocol, url, _ := strings.Cut(in.URL, "://")
return fmt.Sprintf("rest:%s://%s:%s@%s", protocol, "$(USER)", "$(PASSWORD)", url)
}
type TLSOptions struct {
CACert string `json:"caCert,omitempty"`
ClientCert string `json:"clientCert,omitempty"`
ClientKey string `json:"clientKey,omitempty"`
}
+174
View File
@@ -0,0 +1,174 @@
package v1
import (
"context"
"reflect"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// BackupSpec defines a single backup. It must contain all information to connect to
// the backup repository when applied. If used with defaults or schedules the operator will
// ensure that the defaults are applied before creating the object on the API.
type BackupSpec struct {
RunnableSpec `json:",inline"`
// KeepJobs amount of jobs to keep for later analysis.
//
// Deprecated: Use FailedJobsHistoryLimit and SuccessfulJobsHistoryLimit respectively.
// +optional
KeepJobs *int `json:"keepJobs,omitempty"`
// FailedJobsHistoryLimit amount of failed jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
FailedJobsHistoryLimit *int `json:"failedJobsHistoryLimit,omitempty"`
// SuccessfulJobsHistoryLimit amount of successful jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
SuccessfulJobsHistoryLimit *int `json:"successfulJobsHistoryLimit,omitempty"`
// PromURL sets a prometheus push URL where the backup container send metrics to
// +optional
PromURL string `json:"promURL,omitempty"`
// ClusterName sets the kubernetes cluster name to send to pushgateway for grouping metrics
// +optional
ClusterName string `json:"clusterName,omitempty"`
// StatsURL sets an arbitrary URL where the restic container posts metrics and
// information about the snapshots to. This is in addition to the prometheus
// pushgateway.
StatsURL string `json:"statsURL,omitempty"`
// Tags is a list of arbitrary tags that get added to the backup via Restic's tagging system
Tags []string `json:"tags,omitempty"`
// LabelSelectors is a list of selectors that we filter for.
// When defined, only PVCs and PreBackupPods matching them are backed up.
// +optional
LabelSelectors []metav1.LabelSelector `json:"labelSelectors,omitempty"`
}
type BackupTemplate struct {
Tags *[]string `json:"tags,omitempty"`
Backend Backend `json:"backend,omitempty"`
Env Env `json:"env,omitempty"`
}
type Env struct {
Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule Ref",type="string",JSONPath=`.metadata.ownerReferences[?(@.kind == "Schedule")].name`,description="Reference to Schedule"
// +kubebuilder:printcolumn:name="Completion",type="string",JSONPath=`.status.conditions[?(@.type == "Completed")].reason`,description="Status of Completion"
// +kubebuilder:printcolumn:name="PreBackup",type="string",JSONPath=`.status.conditions[?(@.type == "PreBackupPodReady")].reason`,description="Status of PreBackupPods"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// Backup is the Schema for the backups API
type Backup struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BackupSpec `json:"spec,omitempty"`
Status Status `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// BackupList contains a list of Backup
type BackupList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Backup `json:"items"`
}
func init() {
SchemeBuilder.Register(&Backup{}, &BackupList{})
}
func (*Backup) GetType() JobType {
return BackupType
}
// GetStatus retrieves the Status property
func (b *Backup) GetStatus() Status {
return b.Status
}
// SetStatus sets the Status property
func (b *Backup) SetStatus(status Status) {
b.Status = status
}
// GetResources returns the resource requirements
func (b *Backup) GetResources() corev1.ResourceRequirements {
return b.Spec.Resources
}
// GetPodSecurityContext returns the pod security context
func (b *Backup) GetPodSecurityContext() *corev1.PodSecurityContext {
return b.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (b *Backup) GetActiveDeadlineSeconds() *int64 {
return b.Spec.ActiveDeadlineSeconds
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (b *Backup) GetFailedJobsHistoryLimit() *int {
if b.Spec.FailedJobsHistoryLimit != nil {
return b.Spec.FailedJobsHistoryLimit
}
return b.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (b *Backup) GetSuccessfulJobsHistoryLimit() *int {
if b.Spec.SuccessfulJobsHistoryLimit != nil {
return b.Spec.SuccessfulJobsHistoryLimit
}
return b.Spec.KeepJobs
}
func (b *Backup) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if b.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, b.Spec.PodConfigRef.Name, b.GetNamespace(), c)
}
// GetJobObjects returns a sortable list of jobs
func (b *BackupList) GetJobObjects() JobObjectList {
items := make(JobObjectList, len(b.Items))
for i := range b.Items {
items[i] = &b.Items[i]
}
return items
}
// GetDeepCopy returns a deep copy
func (in *BackupSchedule) GetDeepCopy() ScheduleSpecInterface {
return in.DeepCopy()
}
// GetRunnableSpec returns a pointer to RunnableSpec
func (in *BackupSchedule) GetRunnableSpec() *RunnableSpec {
return &in.RunnableSpec
}
// GetSchedule returns the schedule definition
func (in *BackupSchedule) GetSchedule() ScheduleDefinition {
return in.Schedule
}
var (
BackupKind = reflect.TypeOf(Backup{}).Name()
)
+148
View File
@@ -0,0 +1,148 @@
package v1
import (
"context"
"reflect"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// CheckSpec defines the desired state of Check. It needs to contain the repository
// information.
type CheckSpec struct {
RunnableSpec `json:",inline"`
// PromURL sets a prometheus push URL where the backup container send metrics to
// +optional
PromURL string `json:"promURL,omitempty"`
// ClusterName sets the kubernetes cluster name to send to pushgateway for grouping metrics
// +optional
ClusterName string `json:"clusterName,omitempty"`
// KeepJobs amount of jobs to keep for later analysis.
//
// Deprecated: Use FailedJobsHistoryLimit and SuccessfulJobsHistoryLimit respectively.
// +optional
KeepJobs *int `json:"keepJobs,omitempty"`
// FailedJobsHistoryLimit amount of failed jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
FailedJobsHistoryLimit *int `json:"failedJobsHistoryLimit,omitempty"`
// SuccessfulJobsHistoryLimit amount of successful jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
SuccessfulJobsHistoryLimit *int `json:"successfulJobsHistoryLimit,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule Ref",type="string",JSONPath=`.metadata.ownerReferences[?(@.kind == "Schedule")].name`,description="Reference to Schedule"
// +kubebuilder:printcolumn:name="Completion",type="string",JSONPath=`.status.conditions[?(@.type == "Completed")].reason`,description="Status of Completion"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// Check is the Schema for the checks API
type Check struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CheckSpec `json:"spec,omitempty"`
Status Status `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// CheckList contains a list of Check
type CheckList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Check `json:"items"`
}
func init() {
SchemeBuilder.Register(&Check{}, &CheckList{})
}
func (c *Check) GetType() JobType {
return CheckType
}
// GetStatus retrieves the Status property
func (c *Check) GetStatus() Status {
return c.Status
}
// SetStatus sets the Status property
func (c *Check) SetStatus(status Status) {
c.Status = status
}
// GetResources returns the resource requirements
func (c *Check) GetResources() corev1.ResourceRequirements {
return c.Spec.Resources
}
// GetPodSecurityContext returns the pod security context
func (c *Check) GetPodSecurityContext() *corev1.PodSecurityContext {
return c.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (c *Check) GetActiveDeadlineSeconds() *int64 {
return c.Spec.ActiveDeadlineSeconds
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (c *Check) GetFailedJobsHistoryLimit() *int {
if c.Spec.FailedJobsHistoryLimit != nil {
return c.Spec.FailedJobsHistoryLimit
}
return c.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (c *Check) GetSuccessfulJobsHistoryLimit() *int {
if c.Spec.SuccessfulJobsHistoryLimit != nil {
return c.Spec.SuccessfulJobsHistoryLimit
}
return c.Spec.KeepJobs
}
func (b *Check) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if b.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, b.Spec.PodConfigRef.Name, b.GetNamespace(), c)
}
// GetJobObjects returns a sortable list of jobs
func (c *CheckList) GetJobObjects() JobObjectList {
items := make(JobObjectList, len(c.Items))
for i := range c.Items {
items[i] = &c.Items[i]
}
return items
}
// GetDeepCopy returns a deep copy
func (in *CheckSchedule) GetDeepCopy() ScheduleSpecInterface {
return in.DeepCopy()
}
// GetRunnableSpec returns a pointer to RunnableSpec
func (in *CheckSchedule) GetRunnableSpec() *RunnableSpec {
return &in.RunnableSpec
}
// GetSchedule returns the schedule definition
func (in *CheckSchedule) GetSchedule() ScheduleDefinition {
return in.Schedule
}
var (
CheckKind = reflect.TypeOf(Check{}).Name()
)
+114
View File
@@ -0,0 +1,114 @@
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
type (
// JobType defines what job type this is.
JobType string
// ConditionType defines what condition type this is.
ConditionType string
// ConditionReason is a static/programmatic representation of the cause of a status condition.
ConditionReason string
// +k8s:deepcopy-gen=false
// ScheduleSpecInterface represents a Job for internal use.
ScheduleSpecInterface interface {
GetDeepCopy() ScheduleSpecInterface
GetRunnableSpec() *RunnableSpec
GetSchedule() ScheduleDefinition
}
// +k8s:deepcopy-gen=false
)
// The job types that k8up deals with
const (
BackupType JobType = "backup"
CheckType JobType = "check"
ArchiveType JobType = "archive"
RestoreType JobType = "restore"
PruneType JobType = "prune"
ScheduleType JobType = "schedule"
// ConditionCompleted is given when the resource has completed its main function.
ConditionCompleted ConditionType = "Completed"
// ConditionReady is given when all preconditions are met.
ConditionReady ConditionType = "Ready"
// ConditionScrubbed is given when the resource has done its housework to clean up similar but outdated resources.
ConditionScrubbed ConditionType = "Scrubbed"
// ConditionProgressing is given when the resource is in the process of doing its main function.
ConditionProgressing ConditionType = "Progressing"
// ConditionPreBackupPodReady is True if Deployments for all Container definitions were created and are ready
ConditionPreBackupPodReady ConditionType = "PreBackupPodReady"
// ReasonReady indicates the condition is ready for work
ReasonReady ConditionReason = "Ready"
// ReasonStarted indicates the resource has started progressing
ReasonStarted ConditionReason = "Started"
// ReasonFinished indicates the resource has finished the work without specifying its success.
ReasonFinished ConditionReason = "Finished"
// ReasonSucceeded indicates the condition is succeeded
ReasonSucceeded ConditionReason = "Succeeded"
// ReasonFailed indicates there was a general failure not further categorized
ReasonFailed ConditionReason = "Failed"
// ReasonCreationFailed indicates that a dependent resource could not be created
ReasonCreationFailed ConditionReason = "CreationFailed"
// ReasonUpdateFailed indicates that a dependent resource could not be created
ReasonUpdateFailed ConditionReason = "UpdateFailed"
// ReasonDeletionFailed indicates that a dependent resource could not be deleted
ReasonDeletionFailed ConditionReason = "DeletionFailed"
// ReasonRetrievalFailed indicates that dependent resource(s) could not be retrieved for further processing
ReasonRetrievalFailed ConditionReason = "RetrievalFailed"
// ReasonNoPreBackupPodsFound is given when no PreBackupPods are found in the same namespace
ReasonNoPreBackupPodsFound ConditionReason = "NoPreBackupPodsFound"
// ReasonWaiting is given when PreBackupPods are waiting to be started
ReasonWaiting ConditionReason = "Waiting"
// LabelK8upType is the label key that identifies the job type
LabelK8upType = "k8up.io/type"
// LabelK8upOwnedBy is a label used to indicated which resource owns this resource to make it easy to fetch owned resources.
LabelK8upOwnedBy = "k8up.io/owned-by"
// LabelK8upScheduleName is the label key that records which Schedule created a job object.
// Set by the schedule controller; read by the monitoring package to populate per-schedule metrics.
LabelK8upScheduleName = "k8up.io/schedule-name"
// Deprecated: LegacyLabelK8upType is the former label key that identified the job type
LegacyLabelK8upType = "k8up.syn.tools/type"
// LabelManagedBy identifies the tool being used to manage the operation of a resource
LabelManagedBy = "app.kubernetes.io/managed-by"
// LabelRepositoryHash is the label key that identifies the Restic repository
LabelRepositoryHash = "k8up.io/repository-hash"
// AnnotationK8upHostname is an annotation one can set on RWO PVCs to try to back up them on the specified node.
AnnotationK8upHostname = "k8up.io/hostname"
)
// String casts the value to string.
// "aJobType.String()" and "string(aJobType)" are equivalent.
func (j JobType) String() string {
return string(j)
}
// String casts the value to string.
// "c.String()" and "string(c)" are equivalent.
func (c ConditionType) String() string {
return string(c)
}
// String casts the value to string.
// "r.String()" and "string(r)" are equivalent.
func (r ConditionReason) String() string {
return string(r)
}
// MapToNamespacedName translates the given object meta into NamespacedName object
func MapToNamespacedName(obj metav1.Object) types.NamespacedName {
return types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}
}
+21
View File
@@ -0,0 +1,21 @@
// Package v1 contains API Schema definitions for the k8up v1 API group
// +kubebuilder:object:generate=true
// +groupName=k8up.io
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "k8up.io", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
+41
View File
@@ -0,0 +1,41 @@
package v1
import (
"context"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// +k8s:deepcopy-gen=false
// JobObject is an interface that must be implemented by all CRDs that implement a job.
type JobObject interface {
client.Object
GetStatus() Status
SetStatus(s Status)
GetType() JobType
// GetResources returns the specified resource requirements
GetResources() corev1.ResourceRequirements
// GetPodSecurityContext returns the specified pod security context
GetPodSecurityContext() *corev1.PodSecurityContext
// GetActiveDeadlineSeconds returns the specified active deadline seconds timeout.
GetActiveDeadlineSeconds() *int64
// GetPodConfig returns the defined PodSpec
GetPodConfig(context.Context, client.Client) (*PodConfig, error)
}
// +k8s:deepcopy-gen=false
// JobObjectList is a sortable list of job objects
type JobObjectList []JobObject
func (jo JobObjectList) Len() int { return len(jo) }
func (jo JobObjectList) Swap(i, j int) { jo[i], jo[j] = jo[j], jo[i] }
func (jo JobObjectList) Less(i, j int) bool {
if jo[i].GetCreationTimestamp().Time.Equal(jo[j].GetCreationTimestamp().Time) {
return jo[i].GetName() < jo[j].GetName()
}
return jo[i].GetCreationTimestamp().Time.Before(jo[j].GetCreationTimestamp().Time)
}
+60
View File
@@ -0,0 +1,60 @@
package v1
import (
"context"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// +kubebuilder:rbac:groups=k8up.io,resources=podconfigs,verbs=get;list;watch
// PodConfigSpec contains the podTemplate definition.
type PodConfigSpec struct {
Template corev1.PodTemplateSpec `json:"template,omitempty"`
}
// PodConfigStatus defines the observed state of Snapshot
type PodConfigStatus struct {
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// PodConfig is the Schema for the PodConcig API
// Any annotations and labels set on this object will also be set on
// the final pod.
type PodConfig struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PodConfigSpec `json:"spec,omitempty"`
Status PodConfigStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// SnapshotList contains a list of Snapshot
type PodConfigList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PodConfig `json:"items"`
}
func NewPodConfig(ctx context.Context, name, namespace string, c client.Client) (*PodConfig, error) {
config := &PodConfig{}
err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, config)
if err != nil {
if apierrors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
return config, nil
}
func init() {
SchemeBuilder.Register(&PodConfig{}, &PodConfigList{})
}
+46
View File
@@ -0,0 +1,46 @@
package v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PreBackupPodSpec define pods that will be launched during the backup. After the backup
// has finished (successfully or not), they should be removed again automatically
// by the operator.
type PreBackupPodSpec struct {
// BackupCommand will be added to the backupcommand annotation on the pod.
BackupCommand string `json:"backupCommand,omitempty"`
FileExtension string `json:"fileExtension,omitempty"`
// +kubebuilder:validation:Required
Pod *Pod `json:"pod,omitempty"`
}
// Pod is a dummy struct to fix some code generation issues.
type Pod struct {
corev1.PodTemplateSpec `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// PreBackupPod is the Schema for the prebackuppods API
type PreBackupPod struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PreBackupPodSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// PreBackupPodList contains a list of PreBackupPod
type PreBackupPodList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PreBackupPod `json:"items"`
}
func init() {
SchemeBuilder.Register(&PreBackupPod{}, &PreBackupPodList{})
}
+157
View File
@@ -0,0 +1,157 @@
package v1
import (
"context"
"reflect"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// PruneSpec needs to contain the repository information as well as the desired
// retention policies.
type PruneSpec struct {
RunnableSpec `json:",inline"`
// Retention sets how many backups should be kept after a forget and prune
Retention RetentionPolicy `json:"retention,omitempty"`
// KeepJobs amount of jobs to keep for later analysis.
//
// Deprecated: Use FailedJobsHistoryLimit and SuccessfulJobsHistoryLimit respectively.
// +optional
KeepJobs *int `json:"keepJobs,omitempty"`
// FailedJobsHistoryLimit amount of failed jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
FailedJobsHistoryLimit *int `json:"failedJobsHistoryLimit,omitempty"`
// SuccessfulJobsHistoryLimit amount of successful jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
SuccessfulJobsHistoryLimit *int `json:"successfulJobsHistoryLimit,omitempty"`
}
type RetentionPolicy struct {
KeepLast int `json:"keepLast,omitempty"`
KeepHourly int `json:"keepHourly,omitempty"`
KeepDaily int `json:"keepDaily,omitempty"`
KeepWeekly int `json:"keepWeekly,omitempty"`
KeepMonthly int `json:"keepMonthly,omitempty"`
KeepYearly int `json:"keepYearly,omitempty"`
KeepTags []string `json:"keepTags,omitempty"`
// Tags is a filter on what tags the policy should be applied
// DO NOT CONFUSE THIS WITH KeepTags OR YOU'LL have a bad time
Tags []string `json:"tags,omitempty"`
// Hostnames is a filter on what hostnames the policy should be applied
Hostnames []string `json:"hostnames,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule Ref",type="string",JSONPath=`.metadata.ownerReferences[?(@.kind == "Schedule")].name`,description="Reference to Schedule"
// +kubebuilder:printcolumn:name="Completion",type="string",JSONPath=`.status.conditions[?(@.type == "Completed")].reason`,description="Status of Completion"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// Prune is the Schema for the prunes API
type Prune struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PruneSpec `json:"spec,omitempty"`
Status Status `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// PruneList contains a list of Prune
type PruneList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Prune `json:"items"`
}
func (p *Prune) GetType() JobType {
return PruneType
}
// GetStatus retrieves the Status property
func (p *Prune) GetStatus() Status {
return p.Status
}
// SetStatus sets the Status property
func (p *Prune) SetStatus(status Status) {
p.Status = status
}
// GetResources returns the resource requirements
func (p *Prune) GetResources() corev1.ResourceRequirements {
return p.Spec.Resources
}
// GetPodSecurityContext returns the pod security context
func (p *Prune) GetPodSecurityContext() *corev1.PodSecurityContext {
return p.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (p *Prune) GetActiveDeadlineSeconds() *int64 {
return p.Spec.ActiveDeadlineSeconds
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (p *Prune) GetFailedJobsHistoryLimit() *int {
if p.Spec.FailedJobsHistoryLimit != nil {
return p.Spec.FailedJobsHistoryLimit
}
return p.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (p *Prune) GetSuccessfulJobsHistoryLimit() *int {
if p.Spec.SuccessfulJobsHistoryLimit != nil {
return p.Spec.SuccessfulJobsHistoryLimit
}
return p.Spec.KeepJobs
}
func (p *Prune) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if p.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, p.Spec.PodConfigRef.Name, p.GetNamespace(), c)
}
// GetJobObjects returns a sortable list of jobs
func (p *PruneList) GetJobObjects() JobObjectList {
items := make(JobObjectList, len(p.Items))
for i := range p.Items {
items[i] = &p.Items[i]
}
return items
}
// GetDeepCopy returns a deep copy
func (in *PruneSchedule) GetDeepCopy() ScheduleSpecInterface {
return in.DeepCopy()
}
// GetRunnableSpec returns a pointer to RunnableSpec
func (in *PruneSchedule) GetRunnableSpec() *RunnableSpec {
return &in.RunnableSpec
}
// GetSchedule returns the schedule definition
func (in *PruneSchedule) GetSchedule() ScheduleDefinition {
return in.Schedule
}
func init() {
SchemeBuilder.Register(&Prune{}, &PruneList{})
}
var (
PruneKind = reflect.TypeOf(Prune{}).Name()
)
+165
View File
@@ -0,0 +1,165 @@
package v1
import (
"context"
"reflect"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// RestoreSpec can either contain an S3 restore point or a local one. For the local
// one you need to define an existing PVC.
type RestoreSpec struct {
RunnableSpec `json:",inline"`
RestoreMethod *RestoreMethod `json:"restoreMethod,omitempty"`
RestoreFilter string `json:"restoreFilter,omitempty"`
// Simple filter to define a timestamp (prefix, YYYY-MM-DD hh:mm:ss) for snapshot selection instead of latest (or latest if nothing matches)
RestoreTimeFilter string `json:"restoreTimeFilter,omitempty"`
Snapshot string `json:"snapshot,omitempty"`
// KeepJobs amount of jobs to keep for later analysis.
//
// Deprecated: Use FailedJobsHistoryLimit and SuccessfulJobsHistoryLimit respectively.
// +optional
KeepJobs *int `json:"keepJobs,omitempty"`
// FailedJobsHistoryLimit amount of failed jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
FailedJobsHistoryLimit *int `json:"failedJobsHistoryLimit,omitempty"`
// SuccessfulJobsHistoryLimit amount of successful jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
SuccessfulJobsHistoryLimit *int `json:"successfulJobsHistoryLimit,omitempty"`
// Tags is a list of arbitrary tags that get added to the backup via Restic's tagging system
Tags []string `json:"tags,omitempty"`
// Paths is a list of paths that are contained with in a snapshot and can be filtered by
Paths []string `json:"paths,omitempty"`
// Delete ensures the state after restoring a snapshot is identical to the snapshot
// Deletes files from target if they do not exist in snapshot
Delete bool `json:"delete,omitempty"`
}
// RestoreMethod contains how and where the restore should happen
// all the settings are mutual exclusive.
type RestoreMethod struct {
S3 *S3Spec `json:"s3,omitempty"`
Folder *FolderRestore `json:"folder,omitempty"`
TLSOptions *TLSOptions `json:"tlsOptions,omitempty"`
VolumeMounts *[]corev1.VolumeMount `json:"volumeMounts,omitempty"`
}
type FolderRestore struct {
*corev1.PersistentVolumeClaimVolumeSource `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Schedule Ref",type="string",JSONPath=`.metadata.ownerReferences[?(@.kind == "Schedule")].name`,description="Reference to Schedule"
// +kubebuilder:printcolumn:name="Completion",type="string",JSONPath=`.status.conditions[?(@.type == "Completed")].reason`,description="Status of Completion"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// Restore is the Schema for the restores API
type Restore struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RestoreSpec `json:"spec,omitempty"`
Status Status `json:"status,omitempty"`
}
func (r *Restore) GetType() JobType {
return RestoreType
}
// GetStatus retrieves the Status property
func (r *Restore) GetStatus() Status {
return r.Status
}
// SetStatus sets the Status property
func (r *Restore) SetStatus(status Status) {
r.Status = status
}
// GetResources returns the resource requirements
func (r *Restore) GetResources() corev1.ResourceRequirements {
return r.Spec.Resources
}
// GetPodSecurityContext returns the pod security context
func (r *Restore) GetPodSecurityContext() *corev1.PodSecurityContext {
return r.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (r *Restore) GetActiveDeadlineSeconds() *int64 {
return r.Spec.ActiveDeadlineSeconds
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (r *Restore) GetFailedJobsHistoryLimit() *int {
if r.Spec.FailedJobsHistoryLimit != nil {
return r.Spec.FailedJobsHistoryLimit
}
return r.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (r *Restore) GetSuccessfulJobsHistoryLimit() *int {
if r.Spec.SuccessfulJobsHistoryLimit != nil {
return r.Spec.SuccessfulJobsHistoryLimit
}
return r.Spec.KeepJobs
}
func (r *Restore) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if r.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, r.Spec.PodConfigRef.Name, r.GetNamespace(), c)
}
// GetJobObjects returns a sortable list of jobs
func (r *RestoreList) GetJobObjects() JobObjectList {
items := make(JobObjectList, len(r.Items))
for i := range r.Items {
items[i] = &r.Items[i]
}
return items
}
// GetDeepCopy returns a deep copy
func (in *RestoreSchedule) GetDeepCopy() ScheduleSpecInterface {
return in.DeepCopy()
}
// GetRunnableSpec returns a pointer to RunnableSpec
func (in *RestoreSchedule) GetRunnableSpec() *RunnableSpec {
return &in.RunnableSpec
}
// GetSchedule returns the schedule definition
func (in *RestoreSchedule) GetSchedule() ScheduleDefinition {
return in.Schedule
}
// +kubebuilder:object:root=true
// RestoreList contains a list of Restore
type RestoreList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Restore `json:"items"`
}
func init() {
SchemeBuilder.Register(&Restore{}, &RestoreList{})
}
var (
RestoreKind = reflect.TypeOf(Restore{}).Name()
)
+57
View File
@@ -0,0 +1,57 @@
package v1
import (
corev1 "k8s.io/api/core/v1"
)
// RunnableSpec defines the fields that are necessary on the specs of all actions that are translated to k8s jobs eventually.
type RunnableSpec struct {
// Backend contains the restic repo where the job should backup to.
Backend *Backend `json:"backend,omitempty"`
// Resources describes the compute resource requirements (cpu, memory, etc.)
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
// PodSecurityContext describes the security context with which this action shall be executed.
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
// PodConfigRef describes the pod spec with wich this action shall be executed.
// It takes precedence over the Resources or PodSecurityContext field.
// It does not allow changing the image or the command of the resulting pod.
// This is for advanced use-cases only. Please only set this if you know what you're doing.
PodConfigRef *corev1.LocalObjectReference `json:"podConfigRef,omitempty"`
// Volumes List of volumes that can be mounted by containers belonging to the pod.
Volumes *[]RunnableVolumeSpec `json:"volumes,omitempty"`
// ActiveDeadlineSeconds specifies the duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it.
// Value must be positive integer if given.
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
}
type RunnableVolumeSpec struct {
// name of the volume.
// Must be a DNS_LABEL and unique within the pod.
// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Name string `json:"name"`
// persistentVolumeClaimVolumeSource represents a reference to a
// PersistentVolumeClaim in the same namespace.
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
// +optional
PersistentVolumeClaim *corev1.PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty"`
// secret represents a secret that should populate this volume.
// More info: https://kubernetes.io/docs/concepts/storage/volumes#secret
// +optional
Secret *corev1.SecretVolumeSource `json:"secret,omitempty"`
// configMap represents a configMap that should populate this volume
// +optional
ConfigMap *corev1.ConfigMapVolumeSource `json:"configMap,omitempty"`
}
// AppendEnvFromToContainer will add EnvFromSource from the given RunnableSpec to the Container
func (in *RunnableSpec) AppendEnvFromToContainer(containerSpec *corev1.Container) {
if in.Backend != nil {
containerSpec.EnvFrom = append(containerSpec.EnvFrom, in.Backend.EnvFrom...)
}
}
+219
View File
@@ -0,0 +1,219 @@
package v1
import (
"context"
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ScheduleSpec defines the schedules for the various job types.
type ScheduleSpec struct {
Restore *RestoreSchedule `json:"restore,omitempty"`
Backup *BackupSchedule `json:"backup,omitempty"`
Archive *ArchiveSchedule `json:"archive,omitempty"`
Check *CheckSchedule `json:"check,omitempty"`
Prune *PruneSchedule `json:"prune,omitempty"`
Backend *Backend `json:"backend,omitempty"`
// KeepJobs amount of jobs to keep for later analysis.
//
// Deprecated: Use FailedJobsHistoryLimit and SuccessfulJobsHistoryLimit respectively.
// +optional
KeepJobs *int `json:"keepJobs,omitempty"`
// FailedJobsHistoryLimit amount of failed jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
FailedJobsHistoryLimit *int `json:"failedJobsHistoryLimit,omitempty"`
// SuccessfulJobsHistoryLimit amount of successful jobs to keep for later analysis.
// KeepJobs is used property is not specified.
// +optional
SuccessfulJobsHistoryLimit *int `json:"successfulJobsHistoryLimit,omitempty"`
// ResourceRequirementsTemplate describes the compute resource requirements (cpu, memory, etc.)
ResourceRequirementsTemplate corev1.ResourceRequirements `json:"resourceRequirementsTemplate,omitempty"`
// PodSecurityContext describes the security context with which actions (such as backups) shall be executed.
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
// PodConfigRef will apply the given template to all job definitions in this Schedule.
// It can be overriden for specific jobs if necessary.
PodConfigRef *corev1.LocalObjectReference `json:"podConfigRef,omitempty"`
}
// ScheduleDefinition is the actual cron-type expression that defines the interval of the actions.
type ScheduleDefinition string
// ScheduleCommon contains fields every schedule needs
type ScheduleCommon struct {
Schedule ScheduleDefinition `json:"schedule,omitempty"`
ConcurrentRunsAllowed bool `json:"concurrentRunsAllowed,omitempty"`
}
// RestoreSchedule manages schedules for the restore service
type RestoreSchedule struct {
RestoreSpec `json:",inline"`
*ScheduleCommon `json:",inline"`
}
// BackupSchedule manages schedules for the backup service
type BackupSchedule struct {
BackupSpec `json:",inline"`
*ScheduleCommon `json:",inline"`
}
// ArchiveSchedule manages schedules for the archival service
type ArchiveSchedule struct {
ArchiveSpec `json:",inline"`
*ScheduleCommon `json:",inline"`
}
// CheckSchedule manages the schedules for the checks
type CheckSchedule struct {
CheckSpec `json:",inline"`
*ScheduleCommon `json:",inline"`
}
// PruneSchedule manages the schedules for the prunes
type PruneSchedule struct {
PruneSpec `json:",inline"`
*ScheduleCommon `json:",inline"`
}
// ScheduleStatus defines the observed state of Schedule
type ScheduleStatus struct {
// Conditions provide a standard mechanism for higher-level status reporting from a controller.
// They are an extension mechanism which allows tools and other controllers to collect summary information about
// resources without needing to understand resource-specific status details.
Conditions []metav1.Condition `json:"conditions,omitempty"`
// EffectiveSchedules contains a list of schedules generated from randomizing schedules.
EffectiveSchedules []EffectiveSchedule `json:"effectiveSchedules,omitempty"`
}
type EffectiveSchedule struct {
JobType JobType `json:"jobType,omitempty"`
GeneratedSchedule ScheduleDefinition `json:"generatedSchedule,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// Schedule is the Schema for the schedules API
type Schedule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ScheduleSpec `json:"spec,omitempty"`
Status ScheduleStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ScheduleList contains a list of Schedule
type ScheduleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Schedule `json:"items"`
}
const (
// ScheduleFinalizerName is a Finalizer added to resources that need cleanup cron schedules before deleting them.
ScheduleFinalizerName = "k8up.io/schedule"
// Deprecated: Migrate to ScheduleFinalizerName
LegacyScheduleFinalizerName = "k8up.syn.tools/schedule"
)
func init() {
SchemeBuilder.Register(&Schedule{}, &ScheduleList{})
}
func (s *Schedule) GetRuntimeObject() runtime.Object {
return s
}
func (s *Schedule) GetMetaObject() metav1.Object {
return s
}
// GetJobName implements the JobObject interface.
func (s *Schedule) GetJobName() string {
return s.GetType().String() + "-" + s.Name
}
func (*Schedule) GetType() JobType {
return ScheduleType
}
// GetStatus retrieves the Status property
func (s *Schedule) GetStatus() Status {
return Status{Conditions: s.Status.Conditions}
}
// SetStatus sets the Status.Conditions property
func (s *Schedule) SetStatus(status Status) {
s.Status.Conditions = status.Conditions
}
// GetResources returns the resource requirements
func (s *Schedule) GetResources() corev1.ResourceRequirements {
return s.Spec.ResourceRequirementsTemplate
}
// GetPodSecurityContext returns the pod security context
func (s *Schedule) GetPodSecurityContext() *corev1.PodSecurityContext {
return s.Spec.PodSecurityContext
}
// GetActiveDeadlineSeconds implements JobObject
func (s *Schedule) GetActiveDeadlineSeconds() *int64 {
return nil
}
// GetFailedJobsHistoryLimit returns failed jobs history limit.
// Returns KeepJobs if unspecified.
func (s *Schedule) GetFailedJobsHistoryLimit() *int {
if s.Spec.FailedJobsHistoryLimit != nil {
return s.Spec.FailedJobsHistoryLimit
}
return s.Spec.KeepJobs
}
// GetSuccessfulJobsHistoryLimit returns successful jobs history limit.
// Returns KeepJobs if unspecified.
func (s *Schedule) GetSuccessfulJobsHistoryLimit() *int {
if s.Spec.SuccessfulJobsHistoryLimit != nil {
return s.Spec.SuccessfulJobsHistoryLimit
}
return s.Spec.KeepJobs
}
func (s *Schedule) GetPodConfig(ctx context.Context, c client.Client) (*PodConfig, error) {
if s.Spec.PodConfigRef == nil {
return nil, nil
}
return NewPodConfig(ctx, s.Spec.PodConfigRef.Name, s.GetNamespace(), c)
}
// String casts the value to string.
// "aScheduleDefinition.String()" and "string(aScheduleDefinition)" are equivalent.
func (s ScheduleDefinition) String() string {
return string(s)
}
// IsNonStandard returns true if the value begins with "@",
// indicating a special definition.
// Two examples are '@daily' and '@daily-random'.
func (s ScheduleDefinition) IsNonStandard() bool {
return strings.HasPrefix(string(s), "@")
}
// IsRandom is true if the value is a special definition (as indicated by IsNonStandard)
// and if it ends with '-random'.
// Two examples are '@daily-random' and '@weekly-random'.
func (s ScheduleDefinition) IsRandom() bool {
return s.IsNonStandard() && strings.HasSuffix(string(s), "-random")
}
+49
View File
@@ -0,0 +1,49 @@
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:rbac:groups=k8up.io,resources=snapshots,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=k8up.io,resources=snapshots/status;snapshots/finalizers,verbs=get;update;patch
// SnapshotSpec contains all information needed about a restic snapshot so it
// can be restored.
type SnapshotSpec struct {
ID *string `json:"id,omitempty"`
Date *metav1.Time `json:"date,omitempty"`
Paths *[]string `json:"paths,omitempty"`
Repository *string `json:"repository,omitempty"`
}
// SnapshotStatus defines the observed state of Snapshot
type SnapshotStatus struct {
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Date taken",type="string",JSONPath=`.spec.date`,description="Date when snapshot was taken"
// +kubebuilder:printcolumn:name="Paths",type="string",JSONPath=`.spec.paths[*]`,description="Snapshot's paths"
// +kubebuilder:printcolumn:name="Repository",type="string",JSONPath=`.spec.repository`,description="Repository Url"
// Snapshot is the Schema for the snapshots API
type Snapshot struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SnapshotSpec `json:"spec,omitempty"`
Status SnapshotStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// SnapshotList contains a list of Snapshot
type SnapshotList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Snapshot `json:"items"`
}
func init() {
SchemeBuilder.Register(&Snapshot{}, &SnapshotList{})
}
+163
View File
@@ -0,0 +1,163 @@
package v1
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Status defines the observed state of a generic K8up job. It is used for the
// operator to determine what to do.
type Status struct {
Started bool `json:"started,omitempty"`
Finished bool `json:"finished,omitempty"`
Exclusive bool `json:"exclusive,omitempty"`
// Conditions provide a standard mechanism for higher-level status reporting from a controller.
// They are an extension mechanism which allows tools and other controllers to collect summary information about
// resources without needing to understand resource-specific status details.
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// HasFailed returns true in the following cases:
//
// * If ConditionCompleted is true with any other reason than ReasonSucceeded.
//
// * If ConditionPreBackupPodReady is false with any of the "failed" reasons.
func (in Status) HasFailed() bool {
if in.HasFailedPreBackup() {
return true
}
completedCond := meta.FindStatusCondition(in.Conditions, ConditionCompleted.String())
if completedCond != nil && !matchAnyReason(*completedCond, ReasonSucceeded) {
return completedCond.Status == metav1.ConditionTrue
}
return false
}
// HasSucceeded returns true if all cases are true:
//
// * If ConditionCompleted is true with ReasonSucceeded.
//
// * If ConditionPreBackupPodReady has no failure reason.
func (in Status) HasSucceeded() bool {
if in.HasFailedPreBackup() {
return false
}
completedCond := meta.FindStatusCondition(in.Conditions, ConditionCompleted.String())
if completedCond != nil && matchAnyReason(*completedCond, ReasonSucceeded) {
return completedCond.Status == metav1.ConditionTrue
}
return false
}
// HasFinished returns true if either HasFailed() or HasSucceeded() return true.
func (in Status) HasFinished() bool {
return in.HasFailed() || in.HasSucceeded()
}
// HasFailedPreBackup returns true if ConditionPreBackupPodReady is false with any of the "failed" reasons.
func (in Status) HasFailedPreBackup() bool {
preBackupCond := meta.FindStatusCondition(in.Conditions, ConditionPreBackupPodReady.String())
// Failed pre backups also count as finished
if preBackupCond != nil && isPreBackupFailed(*preBackupCond) {
return preBackupCond.Status == metav1.ConditionFalse
}
return false
}
// HasStarted returns true if ConditionProgressing is true with ReasonStarted, false otherwise.
func (in Status) HasStarted() bool {
condition := meta.FindStatusCondition(in.Conditions, ConditionProgressing.String())
if condition != nil && matchAnyReason(*condition, ReasonStarted) {
return condition.Status == metav1.ConditionTrue
}
return false
}
// IsWaitingForPreBackup returns true if the ConditionPreBackupPodReady is Unknown with ReasonWaiting, false otherwise.
func (in Status) IsWaitingForPreBackup() bool {
condition := meta.FindStatusCondition(in.Conditions, ConditionPreBackupPodReady.String())
if condition != nil && matchAnyReason(*condition, ReasonWaiting) {
return condition.Status == metav1.ConditionUnknown
}
return false
}
// SetStarted sets ConditionReady to true with ReasonReady and ConditionProgressing with ReasonStarted.
// The given message parameter is set as the message for ConditionReady.
// It also sets the deprecated Started flag to true.
func (in *Status) SetStarted(message string) {
in.Started = true
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: ConditionReady.String(),
Status: metav1.ConditionTrue,
Reason: ReasonReady.String(),
Message: message,
})
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: ConditionProgressing.String(),
Status: metav1.ConditionTrue,
Reason: ReasonStarted.String(),
Message: "The job is progressing",
})
}
// SetFinished sets ConditionProgressing to false with ReasonFinished.
// It also sets the deprecated Finished flag to true.
func (in *Status) SetFinished(message string) {
in.Finished = true
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: ConditionProgressing.String(),
Status: metav1.ConditionFalse,
Reason: ReasonFinished.String(),
Message: message,
})
meta.RemoveStatusCondition(&in.Conditions, ConditionReady.String())
}
// SetFailed sets ConditionCompleted to true with ReasonFailed.
func (in *Status) SetFailed(message string) {
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: ConditionCompleted.String(),
Status: metav1.ConditionTrue,
Reason: ReasonFailed.String(),
Message: message,
})
}
// SetSucceeded sets ConditionCompleted to true with ReasonSucceeded.
func (in *Status) SetSucceeded(message string) {
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: ConditionCompleted.String(),
Status: metav1.ConditionTrue,
Reason: ReasonSucceeded.String(),
Message: message,
})
}
// SetCondition sets a generic condition, overwriting existing one by type if present.
func (in *Status) SetCondition(typ ConditionType, reason ConditionReason, status metav1.ConditionStatus, message string) {
meta.SetStatusCondition(&in.Conditions, metav1.Condition{
Type: typ.String(),
Status: status,
Reason: reason.String(),
Message: message,
})
}
func isPreBackupFailed(condition metav1.Condition) bool {
return !matchAnyReason(condition,
ReasonSucceeded,
ReasonWaiting,
ReasonNoPreBackupPodsFound,
ReasonReady)
}
func matchAnyReason(condition metav1.Condition, reasons ...ConditionReason) bool {
for _, reason := range reasons {
if condition.Reason == reason.String() {
return true
}
}
return false
}
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
package cfg
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
const (
RestoreS3EndpointEnvName = "RESTORE_S3ENDPOINT"
RestoreS3AccessKeyIDEnvName = "RESTORE_ACCESSKEYID"
RestoreS3SecretAccessKeyEnvName = "RESTORE_SECRETACCESSKEY"
ResticRepositoryEnvName = "RESTIC_REPOSITORY"
ResticPasswordEnvName = "RESTIC_PASSWORD"
ResticOptionsEnvName = "RESTIC_OPTIONS"
AwsAccessKeyIDEnvName = "AWS_ACCESS_KEY_ID"
AwsSecretAccessKeyEnvName = "AWS_SECRET_ACCESS_KEY"
AzureAccountEnvName = "AZURE_ACCOUNT_NAME"
AzureAccountKeyEnvName = "AZURE_ACCOUNT_KEY"
GcsProjectIDEnvName = "GOOGLE_PROJECT_ID"
GcsAccessTokenEnvName = "GOOGLE_ACCESS_TOKEN"
B2AccountIDEnvName = "B2_ACCOUNT_ID"
B2AccountKeyEnvName = "B2_ACCOUNT_KEY"
RestUserEnvName = "USER"
RestPasswordEnvName = "PASSWORD"
InsecureAllowPodExecSPDYFallback = "INSECURE_ALLOW_PODEXEC_SPDY_FALLBACK"
)
var (
// Config contains the values of the user-provided configuration of the operator module,
// combined with the default values as defined in operator.Command.
Config = &Configuration{}
)
// Configuration holds a strongly-typed tree of the configuration
type Configuration struct {
MountPath string
BackupAnnotation string
BackupContainerAnnotation string
BackupCommandAnnotation string
FileExtensionAnnotation string
BackupResticArgsAnnotation string
RestoreResticArgsAnnotation string
ServiceAccount string
BackupCheckSchedule string
GlobalAccessKey string
GlobalKeepJobs int
GlobalFailedJobsHistoryLimit int
GlobalSuccessfulJobsHistoryLimit int
GlobalBackoffLimit int
GlobalRepoPassword string
GlobalRestoreS3AccessKey string
GlobalRestoreS3Bucket string
GlobalRestoreS3Endpoint string
GlobalRestoreS3SecretAccessKey string
GlobalS3Bucket string
GlobalS3Endpoint string
GlobalSecretAccessKey string
GlobalStatsURL string
GlobalConcurrentArchiveJobsLimit int
GlobalConcurrentBackupJobsLimit int
GlobalConcurrentCheckJobsLimit int
GlobalConcurrentPruneJobsLimit int
GlobalConcurrentRestoreJobsLimit int
GlobalCPUResourceRequest string
GlobalCPUResourceLimit string
GlobalMemoryResourceRequest string
GlobalMemoryResourceLimit string
BackupImage string
BackupImagePullSecret string
BackupCommandRestic []string
MetricsBindAddress string
PodExecRoleName string
PodFilter string
PromURL string
ClusterName string
RestartPolicy string
SkipWithoutAnnotation bool
SkipSnapshotSync bool
EnableRelaxedScheduling bool
InsecureAllowPodExecSPDYFallback bool
// Enabling this will ensure there is only one active controller manager.
EnableLeaderElection bool
OperatorNamespace string
// the var data dir for read/write k8up data or temp file in the backup pod
PodVarDir string
// Allows to pass options to restic, see https://restic.readthedocs.io/en/stable/manual_rest.html?highlight=--option#usage-help
// Format: `key=value,key2=value2`
ResticOptions string
}
func (c Configuration) GetGlobalDefaultResources() (res corev1.ResourceRequirements) {
if r, err := resource.ParseQuantity(c.GlobalMemoryResourceRequest); err == nil && c.GlobalMemoryResourceRequest != "" {
if res.Requests == nil {
res.Requests = make(corev1.ResourceList)
}
res.Requests[corev1.ResourceMemory] = r
}
if r, err := resource.ParseQuantity(c.GlobalCPUResourceRequest); err == nil && c.GlobalCPUResourceRequest != "" {
if res.Requests == nil {
res.Requests = make(corev1.ResourceList)
}
res.Requests[corev1.ResourceCPU] = r
}
if r, err := resource.ParseQuantity(c.GlobalMemoryResourceLimit); err == nil && c.GlobalMemoryResourceLimit != "" {
if res.Limits == nil {
res.Limits = make(corev1.ResourceList)
}
res.Limits[corev1.ResourceMemory] = r
}
if r, err := resource.ParseQuantity(c.GlobalCPUResourceLimit); err == nil && c.GlobalCPUResourceLimit != "" {
if res.Limits == nil {
res.Limits = make(corev1.ResourceList)
}
res.Limits[corev1.ResourceCPU] = r
}
return res
}
// GetGlobalRepository is a shortcut for building an S3 string "s3:<endpoint>/<bucket>"
func (c Configuration) GetGlobalRepository() string {
return fmt.Sprintf("s3:%s/%s", c.GlobalS3Endpoint, c.GlobalS3Bucket)
}
// GetGlobalFailedJobsHistoryLimit returns the global failed jobs history limit.
// Returns global KeepJobs if unspecified.
func (c Configuration) GetGlobalFailedJobsHistoryLimit() int {
if c.GlobalKeepJobs < 0 {
return maxInt(0, c.GlobalFailedJobsHistoryLimit)
}
if c.GlobalFailedJobsHistoryLimit < 0 {
return maxInt(0, c.GlobalKeepJobs)
}
return maxInt(0, c.GlobalFailedJobsHistoryLimit)
}
// GetGlobalSuccessfulJobsHistoryLimit returns the global successful jobs history limit.
// Returns global KeepJobs if unspecified.
func (c Configuration) GetGlobalSuccessfulJobsHistoryLimit() int {
if c.GlobalKeepJobs < 0 {
return maxInt(0, c.GlobalSuccessfulJobsHistoryLimit)
}
if c.GlobalSuccessfulJobsHistoryLimit < 0 {
return maxInt(0, c.GlobalKeepJobs)
}
return maxInt(0, c.GlobalSuccessfulJobsHistoryLimit)
}
func maxInt(x, y int) int {
if x > y {
return x
}
return y
}