api/v1/backend.go

Summary

Maintainability
A
0 mins
Test Coverage
C
72%
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.Ptr && 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
    }

    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 {
    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"`
}