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:
+202
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+1545
File diff suppressed because it is too large
Load Diff
+167
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user