165 lines
3.8 KiB
Go
165 lines
3.8 KiB
Go
|
|
package k8s
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
batchv1 "k8s.io/api/batch/v1"
|
||
|
|
corev1 "k8s.io/api/core/v1"
|
||
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
|
)
|
||
|
|
|
||
|
|
func (c *Client) copySecret(ctx context.Context, srcNS, srcName, dstNS, dstName string, labels map[string]string) (*corev1.Secret, error) {
|
||
|
|
secret, err := c.Clientset.CoreV1().Secrets(srcNS).Get(ctx, srcName, metav1.GetOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("read secret %s/%s: %w", srcNS, srcName, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
copy := &corev1.Secret{
|
||
|
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
|
Name: dstName,
|
||
|
|
Namespace: dstNS,
|
||
|
|
Labels: labels,
|
||
|
|
},
|
||
|
|
Type: secret.Type,
|
||
|
|
Data: secret.Data,
|
||
|
|
}
|
||
|
|
|
||
|
|
created, err := c.Clientset.CoreV1().Secrets(dstNS).Create(ctx, copy, metav1.CreateOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("create secret %s/%s: %w", dstNS, dstName, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return created, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) bindSecretToJob(ctx context.Context, namespace, secretName string, job *batchv1.Job) error {
|
||
|
|
secret, err := c.Clientset.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
controller := true
|
||
|
|
secret.OwnerReferences = append(secret.OwnerReferences, metav1.OwnerReference{
|
||
|
|
APIVersion: "batch/v1",
|
||
|
|
Kind: "Job",
|
||
|
|
Name: job.Name,
|
||
|
|
UID: job.UID,
|
||
|
|
Controller: &controller,
|
||
|
|
})
|
||
|
|
|
||
|
|
_, err = c.Clientset.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{})
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func jobName(action, suffix string) string {
|
||
|
|
base := sanitizeName(fmt.Sprintf("soteria-%s-%s", action, suffix))
|
||
|
|
timestamp := time.Now().UTC().Format("20060102-150405")
|
||
|
|
name := fmt.Sprintf("%s-%s", base, timestamp)
|
||
|
|
if len(name) <= 63 {
|
||
|
|
return name
|
||
|
|
}
|
||
|
|
trimmed := base
|
||
|
|
maxBase := 63 - len(timestamp) - 1
|
||
|
|
if maxBase < 1 {
|
||
|
|
maxBase = 1
|
||
|
|
}
|
||
|
|
if len(trimmed) > maxBase {
|
||
|
|
trimmed = trimmed[:maxBase]
|
||
|
|
}
|
||
|
|
return fmt.Sprintf("%s-%s", trimmed, timestamp)
|
||
|
|
}
|
||
|
|
|
||
|
|
func sanitizeName(value string) string {
|
||
|
|
value = strings.ToLower(value)
|
||
|
|
value = strings.ReplaceAll(value, "_", "-")
|
||
|
|
value = strings.ReplaceAll(value, ".", "-")
|
||
|
|
value = strings.ReplaceAll(value, " ", "-")
|
||
|
|
value = strings.Trim(value, "-")
|
||
|
|
return value
|
||
|
|
}
|
||
|
|
|
||
|
|
func dedupeEnabled(raw *bool) bool {
|
||
|
|
if raw == nil {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return *raw
|
||
|
|
}
|
||
|
|
|
||
|
|
func keepLastWithDefault(raw *int) int {
|
||
|
|
if raw == nil {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
if *raw < 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return *raw
|
||
|
|
}
|
||
|
|
|
||
|
|
func resticRepositoryForBackup(base, namespace, pvc string, dedupe bool) string {
|
||
|
|
if dedupe {
|
||
|
|
return strings.TrimSpace(base)
|
||
|
|
}
|
||
|
|
ns := sanitizeRepositorySegment(namespace)
|
||
|
|
pvcName := sanitizeRepositorySegment(pvc)
|
||
|
|
suffix := strings.Trim(strings.Join([]string{"isolated", ns, pvcName}, "/"), "/")
|
||
|
|
return appendRepositoryPath(base, suffix)
|
||
|
|
}
|
||
|
|
|
||
|
|
func sanitizeRepositorySegment(value string) string {
|
||
|
|
sanitized := sanitizeName(value)
|
||
|
|
if sanitized == "" {
|
||
|
|
return "unknown"
|
||
|
|
}
|
||
|
|
return sanitized
|
||
|
|
}
|
||
|
|
|
||
|
|
func appendRepositoryPath(base, suffix string) string {
|
||
|
|
base = strings.TrimSpace(base)
|
||
|
|
suffix = strings.Trim(suffix, "/")
|
||
|
|
if base == "" || suffix == "" {
|
||
|
|
return base
|
||
|
|
}
|
||
|
|
|
||
|
|
backendPrefix := ""
|
||
|
|
location := base
|
||
|
|
if idx := strings.Index(base, ":"); idx > 0 {
|
||
|
|
backendPrefix = base[:idx+1]
|
||
|
|
location = base[idx+1:]
|
||
|
|
}
|
||
|
|
location = strings.TrimRight(location, "/")
|
||
|
|
if location == "" {
|
||
|
|
return base
|
||
|
|
}
|
||
|
|
return backendPrefix + location + "/" + suffix
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseBoolWithDefault(raw string, fallback bool) bool {
|
||
|
|
value := strings.ToLower(strings.TrimSpace(raw))
|
||
|
|
if value == "" {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
switch value {
|
||
|
|
case "1", "true", "yes", "on":
|
||
|
|
return true
|
||
|
|
case "0", "false", "no", "off":
|
||
|
|
return false
|
||
|
|
default:
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseIntWithDefault(raw string, fallback int) int {
|
||
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(raw))
|
||
|
|
if err != nil {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
if parsed < 0 {
|
||
|
|
return fallback
|
||
|
|
}
|
||
|
|
return parsed
|
||
|
|
}
|