diff --git a/go.mod b/go.mod index 502d8215..50017e4d 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( gomodules.xyz/go-sh v0.2.0 gomodules.xyz/logs v0.0.7 gomodules.xyz/pointer v0.1.0 + gomodules.xyz/restic v0.0.0-20260127113541-2bdea1940793 gomodules.xyz/runtime v0.3.0 gomodules.xyz/x v0.0.17 k8s.io/api v0.34.3 @@ -25,7 +26,7 @@ require ( kmodules.xyz/offshoot-api v0.34.0 kmodules.xyz/prober v0.34.0 kubedb.dev/apimachinery v0.59.0 - kubestash.dev/apimachinery v0.23.0 + kubestash.dev/apimachinery v0.23.1-0.20260213123630-ee2e89dda76e sigs.k8s.io/controller-runtime v0.22.4 sigs.k8s.io/yaml v1.6.0 stash.appscode.dev/apimachinery v0.41.0 diff --git a/go.sum b/go.sum index 49df340b..50aeb679 100644 --- a/go.sum +++ b/go.sum @@ -780,6 +780,8 @@ gomodules.xyz/mergo v0.3.13 h1:q6cL/MMXZH/MrR2+yjSihFFq6UifXqjwaqI48B6cMEM= gomodules.xyz/mergo v0.3.13/go.mod h1:F/2rKC7j0URTnHUKDiTiLcGdLMhdv8jK2Za3cRTUVmc= gomodules.xyz/pointer v0.1.0 h1:sG2UKrYVSo6E3r4itAjXfPfe4fuXMi0KdyTHpR3vGCg= gomodules.xyz/pointer v0.1.0/go.mod h1:sPLsC0+yLTRecUiC5yVlyvXhZ6LAGojNCRWNNqoplvo= +gomodules.xyz/restic v0.0.0-20260127113541-2bdea1940793 h1:dgUY5LElbKrnsn2qyGwiojiLhFwwUDndhhSRDuYcjEo= +gomodules.xyz/restic v0.0.0-20260127113541-2bdea1940793/go.mod h1:Api8DksK5irIRJGjnxt7wSxsJ6AsSu3i97bQVRaQ5zs= gomodules.xyz/runtime v0.3.0 h1:Fgf3fjIE3xY/sswO73iRBeR3mundZAjlY42fQPigPR0= gomodules.xyz/runtime v0.3.0/go.mod h1:lJuiayVYjz8LWDwKhbDqFzUrXqr1btLbJS5/lKDz1YU= gomodules.xyz/sets v0.2.0/go.mod h1:jKgNp01/iDs+svOWXaPk5cKP3VXy0mWUoTF/ore+aMc= @@ -910,8 +912,8 @@ kubeops.dev/petset v0.0.15 h1:iwTRFAp0RNw0A87sw2c97UZ6WIA9H/nhJBpDhXLa7fk= kubeops.dev/petset v0.0.15/go.mod h1:sw96WiXfzhpmKpXj4a5AdmEHs0Bx4QMhf+iW15zY4Gg= kubeops.dev/sidekick v0.0.12 h1:pmUjQLZDKxgREiM6z0PogLR1aDbgvkE9jRjbxG6dEt0= kubeops.dev/sidekick v0.0.12/go.mod h1:RU7QH3E8DOLw15rBYlOOJSyczuwAnVVtYyZjJb00UB8= -kubestash.dev/apimachinery v0.23.0 h1:EB6w+lB0ACgnqIjtbyvRN5hVEmB8dVHSaN7SJCACHCY= -kubestash.dev/apimachinery v0.23.0/go.mod h1:zJEjHjd/nYcXFSW+RfGbLxZMJK41IOWjQGosoAWZDRg= +kubestash.dev/apimachinery v0.23.1-0.20260213123630-ee2e89dda76e h1:DlHm4ofKmuFf+QV1sj2S6jqrwPe5JTVgior7Wgh4CnQ= +kubestash.dev/apimachinery v0.23.1-0.20260213123630-ee2e89dda76e/go.mod h1:urS7nlwj1kXAbZ5wfmHlYO0RvFDoO4J3KSpXWjvjKw4= open-cluster-management.io/api v1.1.1-0.20251222023835-510285203ee6 h1:mfcUKaSOYVDLzuontUOcasesbU9whNnvgrA0qf9trKs= open-cluster-management.io/api v1.1.1-0.20251222023835-510285203ee6/go.mod h1:YcmA6SpGEekIMxdoeVIIyOaBhMA6ImWRLXP4g8n8T+4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/add_key.go b/pkg/add_key.go index 6a9ee6bf..ceab9dd8 100644 --- a/pkg/add_key.go +++ b/pkg/add_key.go @@ -23,11 +23,12 @@ import ( "github.com/spf13/cobra" "gomodules.xyz/flags" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/klog/v2" kmapi "kmodules.xyz/client-go/api/v1" v1 "kmodules.xyz/offshoot-api/api/v1" - "kubestash.dev/apimachinery/pkg/restic" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" ) func NewCmdAddKey(opt *keyOptions) *cobra.Command { @@ -130,13 +131,17 @@ func (opt *keyOptions) addResticKeyViaDocker() error { } }() + encryptSecret, err := getEncryptionSecret(klient, opt.repo.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + setupOptions := &restic.SetupOptions{ - Client: klient, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &opt.repo.Spec.StorageRef), Repository: opt.repo.Name, - BackupStorage: &opt.repo.Spec.StorageRef, - EncryptionSecret: opt.repo.Spec.EncryptionSecret, + EncryptionSecret: encryptSecret, }, }, ScratchDir: ScratchDir, diff --git a/pkg/common/helpers.go b/pkg/common/helpers.go index e06408c1..af7c5bc6 100644 --- a/pkg/common/helpers.go +++ b/pkg/common/helpers.go @@ -54,9 +54,9 @@ func NewRuntimeClient(cfg *restclient.Config) (client.Client, error) { }) } -func (opt *Options) GetSnapshot(ref kmapi.ObjectReference) (*storageapi.Snapshot, error) { +func (opt *Options) GetSnapshot(kbClient client.Client, ref kmapi.ObjectReference) (*storageapi.Snapshot, error) { snap := &storageapi.Snapshot{} - if err := opt.Client.Get(context.Background(), ref.ObjectKey(), snap); err != nil { + if err := kbClient.Get(context.Background(), ref.ObjectKey(), snap); err != nil { return nil, err } return snap, nil diff --git a/pkg/common/types.go b/pkg/common/types.go index 4390cd4c..e42f17a1 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -17,14 +17,13 @@ limitations under the License. package common import ( + "gomodules.xyz/restic" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" "kubestash.dev/apimachinery/apis" storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" - "kubestash.dev/apimachinery/pkg/restic" - "sigs.k8s.io/controller-runtime/pkg/client" ) type ResourceItems struct { @@ -51,7 +50,6 @@ type RestoredItemStatus struct { type Options struct { Config *rest.Config - Client client.Client DataDir string DryRunDir string MaxIterations uint diff --git a/pkg/download.go b/pkg/download.go index ce47ac43..9055ecc7 100644 --- a/pkg/download.go +++ b/pkg/download.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" @@ -34,7 +35,6 @@ import ( v1 "kmodules.xyz/offshoot-api/api/v1" storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" "kubestash.dev/apimachinery/pkg" - "kubestash.dev/apimachinery/pkg/restic" ) type downloadOptions struct { @@ -139,14 +139,18 @@ func NewCmdDownload(clientGetter genericclioptions.RESTClientGetter) *cobra.Comm } }() + encryptSecret, err := getEncryptionSecret(klient, repository.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + setupOptions := &restic.SetupOptions{ - Client: klient, ScratchDir: ScratchDir, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &repository.Spec.StorageRef), Repository: repository.Name, - BackupStorage: &repository.Spec.StorageRef, - EncryptionSecret: repository.Spec.EncryptionSecret, + EncryptionSecret: encryptSecret, }, }, } diff --git a/pkg/key.go b/pkg/key.go index b7d24e50..39f7bff8 100644 --- a/pkg/key.go +++ b/pkg/key.go @@ -25,13 +25,13 @@ import ( "strings" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" "k8s.io/klog/v2" storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" "kubestash.dev/apimachinery/pkg" - "kubestash.dev/apimachinery/pkg/restic" ) type keyOptions struct { diff --git a/pkg/list_key.go b/pkg/list_key.go index afe89353..6e25fe00 100644 --- a/pkg/list_key.go +++ b/pkg/list_key.go @@ -23,11 +23,12 @@ import ( "github.com/spf13/cobra" "gomodules.xyz/flags" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/klog/v2" kmapi "kmodules.xyz/client-go/api/v1" v1 "kmodules.xyz/offshoot-api/api/v1" - "kubestash.dev/apimachinery/pkg/restic" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" ) func NewCmdListKey(opt *keyOptions) *cobra.Command { @@ -119,15 +120,19 @@ func (opt *keyOptions) listResticKeysViaDocker() error { } }() + encryptionSecret, err := getEncryptionSecret(klient, opt.repo.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + for _, path := range opt.paths { setupOptions := &restic.SetupOptions{ - Client: klient, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &opt.repo.Spec.StorageRef), Directory: filepath.Join(opt.repo.Spec.Path, path), Repository: opt.repo.Name, - BackupStorage: &opt.repo.Spec.StorageRef, - EncryptionSecret: opt.repo.Spec.EncryptionSecret, + EncryptionSecret: encryptionSecret, }, }, ScratchDir: ScratchDir, diff --git a/pkg/remove_key.go b/pkg/remove_key.go index 7b6da004..bd80bbab 100644 --- a/pkg/remove_key.go +++ b/pkg/remove_key.go @@ -24,11 +24,12 @@ import ( "github.com/spf13/cobra" "gomodules.xyz/flags" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/klog/v2" kmapi "kmodules.xyz/client-go/api/v1" v1 "kmodules.xyz/offshoot-api/api/v1" - "kubestash.dev/apimachinery/pkg/restic" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" ) func NewCmdRemoveKey(opt *keyOptions) *cobra.Command { @@ -136,13 +137,17 @@ func (opt *keyOptions) removeResticKeyViaDocker() error { } }() + encryptionSecret, err := getEncryptionSecret(klient, opt.repo.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + setupOptions := &restic.SetupOptions{ - Client: klient, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &opt.repo.Spec.StorageRef), Repository: opt.repo.Name, - BackupStorage: &opt.repo.Spec.StorageRef, - EncryptionSecret: opt.repo.Spec.EncryptionSecret, + EncryptionSecret: encryptionSecret, }, }, ScratchDir: ScratchDir, diff --git a/pkg/restore.go b/pkg/restore.go index 47c54e89..16a94c43 100644 --- a/pkg/restore.go +++ b/pkg/restore.go @@ -27,12 +27,13 @@ import ( "strings" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/klog/v2" kmapi "kmodules.xyz/client-go/api/v1" v1 "kmodules.xyz/offshoot-api/api/v1" - "kubestash.dev/apimachinery/pkg/restic" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" "kubestash.dev/cli/pkg/common" "kubestash.dev/cli/pkg/common/dump" ) @@ -63,20 +64,18 @@ func NewCmdManifestRestore(clientGetter genericclioptions.RESTClientGetter) *cob return err } - opt.Client, err = common.NewRuntimeClient(opt.Config) + klient, err = common.NewRuntimeClient(opt.Config) if err != nil { return fmt.Errorf("failed to get kubernetes client: %w", err) } - klient = opt.Client - srcNamespace = opt.Namespace if err != nil { return err } - opt.Snapshot, err = opt.GetSnapshot(kmapi.ObjectReference{ + opt.Snapshot, err = opt.GetSnapshot(klient, kmapi.ObjectReference{ Name: opt.SnapshotName, Namespace: srcNamespace, }) @@ -161,14 +160,18 @@ func NewCmdManifestRestore(clientGetter genericclioptions.RESTClientGetter) *cob return nil } + encryptSecret, err := getEncryptionSecret(klient, repository.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + opt.SetupOptions = restic.SetupOptions{ - Client: opt.Client, ScratchDir: ScratchDir, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &repository.Spec.StorageRef), Repository: repository.Name, - BackupStorage: &repository.Spec.StorageRef, - EncryptionSecret: repository.Spec.EncryptionSecret, + EncryptionSecret: encryptSecret, }, }, } diff --git a/pkg/unlock.go b/pkg/unlock.go index 7dbb28ef..5b0ebd99 100644 --- a/pkg/unlock.go +++ b/pkg/unlock.go @@ -25,6 +25,7 @@ import ( "strings" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" @@ -33,7 +34,6 @@ import ( v1 "kmodules.xyz/offshoot-api/api/v1" storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" "kubestash.dev/apimachinery/pkg" - "kubestash.dev/apimachinery/pkg/restic" ) type unlockOptions struct { @@ -142,19 +142,22 @@ func (opt *unlockOptions) unlockRepositoryViaPod(pod *core.Pod) error { } func (opt *unlockOptions) unlockRepositoryViaDocker() error { + encryptSecret, err := getEncryptionSecret(klient, opt.repo.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + setupOptions := &restic.SetupOptions{ - Client: klient, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &opt.repo.Spec.StorageRef), Repository: opt.repo.Name, - BackupStorage: &opt.repo.Spec.StorageRef, - EncryptionSecret: opt.repo.Spec.EncryptionSecret, + EncryptionSecret: encryptSecret, }, }, } // apply nice, ionice settings from env - var err error setupOptions.Nice, err = v1.NiceSettingsFromEnv() if err != nil { return fmt.Errorf("failed to set nice settings: %w", err) diff --git a/pkg/update_key.go b/pkg/update_key.go index 936103ea..ea0194c7 100644 --- a/pkg/update_key.go +++ b/pkg/update_key.go @@ -22,11 +22,12 @@ import ( "path/filepath" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/klog/v2" kmapi "kmodules.xyz/client-go/api/v1" v1 "kmodules.xyz/offshoot-api/api/v1" - "kubestash.dev/apimachinery/pkg/restic" + storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" ) func NewCmdUpdateKey(opt *keyOptions) *cobra.Command { @@ -125,15 +126,19 @@ func (opt *keyOptions) updateResticKeyViaDocker() error { } }() + encryptionSecret, err := getEncryptionSecret(klient, opt.repo.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + for _, path := range opt.paths { setupOptions := &restic.SetupOptions{ - Client: klient, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &opt.repo.Spec.StorageRef), Directory: filepath.Join(opt.repo.Spec.Path, path), Repository: opt.repo.Name, - BackupStorage: &opt.repo.Spec.StorageRef, - EncryptionSecret: opt.repo.Spec.EncryptionSecret, + EncryptionSecret: encryptionSecret, }, }, ScratchDir: ScratchDir, diff --git a/pkg/util.go b/pkg/util.go index 5de3601e..e42e14bb 100644 --- a/pkg/util.go +++ b/pkg/util.go @@ -215,6 +215,19 @@ func setBackupConfigurationPausedField(value bool, name string) error { return err } +func getEncryptionSecret(kbClient client.Client, secretRef *kmapi.ObjectReference) (*core.Secret, error) { + secret := &core.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: secretRef.Namespace, + Name: secretRef.Name, + }, + } + if err := kbClient.Get(context.Background(), client.ObjectKeyFromObject(secret), secret); err != nil { + return nil, err + } + return secret, nil +} + func createTable(data [][]string) error { table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"Item", "Reason"}) diff --git a/pkg/view.go b/pkg/view.go index 4aa87d4c..4cffd3b8 100644 --- a/pkg/view.go +++ b/pkg/view.go @@ -27,6 +27,7 @@ import ( "github.com/jedib0t/go-pretty/v6/list" "github.com/spf13/cobra" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" @@ -39,7 +40,6 @@ import ( storageapi "kubestash.dev/apimachinery/apis/storage/v1alpha1" "kubestash.dev/apimachinery/pkg" "kubestash.dev/apimachinery/pkg/resourceops/filter" - "kubestash.dev/apimachinery/pkg/restic" "kubestash.dev/cli/pkg/common/dump" "sigs.k8s.io/yaml" ) @@ -166,14 +166,19 @@ func NewCmdManifestView(clientGetter genericclioptions.RESTClientGetter) *cobra. klog.Errorf("failed to remove scratch dir. Reason: %v", err) } }() + + encryptSecret, err := getEncryptionSecret(klient, repository.Spec.EncryptionSecret) + if err != nil { + return fmt.Errorf("failed to get encryption secret. Reason: %w", err) + } + setupOptions := &restic.SetupOptions{ - Client: klient, ScratchDir: ScratchDir, Backends: []*restic.Backend{ { + ConfigResolver: storageapi.NewBackupStorageResolver(klient, &repository.Spec.StorageRef), Repository: repository.Name, - BackupStorage: &repository.Spec.StorageRef, - EncryptionSecret: repository.Spec.EncryptionSecret, + EncryptionSecret: encryptSecret, }, }, } diff --git a/vendor/gomodules.xyz/restic/.gitignore b/vendor/gomodules.xyz/restic/.gitignore new file mode 100644 index 00000000..eb7268a4 --- /dev/null +++ b/vendor/gomodules.xyz/restic/.gitignore @@ -0,0 +1,36 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +.idea/ +dist/ +**/junit.xml +**/.env +.vscode/ +coverage.txt + +/bin +/.go + +apiserver.local.config/** +.DS_Store diff --git a/vendor/gomodules.xyz/restic/.golangci.yml b/vendor/gomodules.xyz/restic/.golangci.yml new file mode 100644 index 00000000..4f8a023c --- /dev/null +++ b/vendor/gomodules.xyz/restic/.golangci.yml @@ -0,0 +1,27 @@ +version: "2" +linters: + default: standard + enable: + - unparam + +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + +issues: + max-same-issues: 100 + + exclude-files: + - generated.*\\.go + + exclude-dirs: + - vendor + +run: + timeout: 10m diff --git a/vendor/gomodules.xyz/restic/LICENSE b/vendor/gomodules.xyz/restic/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/gomodules.xyz/restic/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/vendor/gomodules.xyz/restic/Makefile b/vendor/gomodules.xyz/restic/Makefile new file mode 100644 index 00000000..23a19e89 --- /dev/null +++ b/vendor/gomodules.xyz/restic/Makefile @@ -0,0 +1,249 @@ +# Copyright AppsCode Inc. and Contributors +# +# 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. + +SHELL=/bin/bash -o pipefail + +GO_PKG := gomodules.xyz +REPO := $(notdir $(shell pwd)) +BIN := restic + +# https://github.com/appscodelabs/gengo-builder +CODE_GENERATOR_IMAGE ?= ghcr.io/appscode/gengo:release-1.32 + +# This version-strategy uses git tags to set the version string +git_branch := $(shell git rev-parse --abbrev-ref HEAD) +git_tag := $(shell git describe --exact-match --abbrev=0 2>/dev/null || echo "") +commit_hash := $(shell git rev-parse --verify HEAD) +commit_timestamp := $(shell date --date="@$$(git show -s --format=%ct)" --utc +%FT%T) + +VERSION := $(shell git describe --tags --always --dirty) +version_strategy := commit_hash +ifdef git_tag + VERSION := $(git_tag) + version_strategy := tag +else + ifeq (,$(findstring $(git_branch),master HEAD)) + ifneq (,$(patsubst release-%,,$(git_branch))) + VERSION := $(git_branch) + version_strategy := branch + endif + endif +endif + +### +### These variables should not need tweaking. +### + +SRC_PKGS := +SRC_DIRS := $(SRC_PKGS) *.go + +DOCKER_PLATFORMS := linux/amd64 linux/arm linux/arm64 +BIN_PLATFORMS := $(DOCKER_PLATFORMS) + +# Used internally. Users should pass GOOS and/or GOARCH. +OS := $(if $(GOOS),$(GOOS),$(shell go env GOOS)) +ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH)) + +BASEIMAGE_PROD ?= gcr.io/distroless/static-debian12 +BASEIMAGE_DBG ?= debian:12 + +GO_VERSION ?= 1.25 +BUILD_IMAGE ?= ghcr.io/appscode/golang-dev:$(GO_VERSION) + +OUTBIN = bin/$(OS)_$(ARCH)/$(BIN) +ifeq ($(OS),windows) + OUTBIN = bin/$(OS)_$(ARCH)/$(BIN).exe +endif + +# Directories that we need created to build/test. +BUILD_DIRS := bin/$(OS)_$(ARCH) \ + .go/bin/$(OS)_$(ARCH) \ + .go/cache \ + hack/config \ + $(HOME)/.credentials \ + $(HOME)/.kube \ + $(HOME)/.minikube + +DOCKER_REPO_ROOT := /go/src/$(GO_PKG)/$(REPO) + +# If you want to build all binaries, see the 'all-build' rule. +# If you want to build all containers, see the 'all-container' rule. +# If you want to build AND push all containers, see the 'all-push' rule. +all: fmt build + +# For the following OS/ARCH expansions, we transform OS/ARCH into OS_ARCH +# because make pattern rules don't match with embedded '/' characters. + +build-%: + @$(MAKE) build \ + --no-print-directory \ + GOOS=$(firstword $(subst _, ,$*)) \ + GOARCH=$(lastword $(subst _, ,$*)) + +all-build: $(addprefix build-, $(subst /,_, $(BIN_PLATFORMS))) + +version: + @echo version=$(VERSION) + @echo version_strategy=$(version_strategy) + @echo git_tag=$(git_tag) + @echo git_branch=$(git_branch) + @echo commit_hash=$(commit_hash) + @echo commit_timestamp=$(commit_timestamp) + +.PHONY: gen +gen: + @true + +fmt: $(BUILD_DIRS) + @docker run \ + -i \ + --rm \ + -u $$(id -u):$$(id -g) \ + -v $$(pwd):/src \ + -w /src \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ + -v $$(pwd)/.go/cache:/.cache \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + $(BUILD_IMAGE) \ + /bin/bash -c " \ + REPO_PKG=$(GO_PKG)/$(REPO) \ + ./hack/fmt.sh $(SRC_DIRS) \ + " + +build: $(OUTBIN) + +.PHONY: .go/$(OUTBIN) +$(OUTBIN): $(BUILD_DIRS) + @echo "making $(OUTBIN)" + @docker run \ + -i \ + --rm \ + -u $$(id -u):$$(id -g) \ + -v $$(pwd):/src \ + -w /src \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ + -v $$(pwd)/.go/cache:/.cache \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + $(BUILD_IMAGE) \ + /bin/bash -c " \ + ARCH=$(ARCH) \ + OS=$(OS) \ + VERSION=$(VERSION) \ + version_strategy=$(version_strategy) \ + git_branch=$(git_branch) \ + git_tag=$(git_tag) \ + commit_hash=$(commit_hash) \ + commit_timestamp=$(commit_timestamp) \ + ./hack/build.sh \ + " + @echo + +test: $(BUILD_DIRS) + @docker run \ + -i \ + --rm \ + -u $$(id -u):$$(id -g) \ + -v $$(pwd):/src \ + -w /src \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ + -v $$(pwd)/.go/cache:/.cache \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + $(BUILD_IMAGE) \ + /bin/bash -c " \ + ARCH=$(ARCH) \ + OS=$(OS) \ + VERSION=$(VERSION) \ + ./hack/test.sh $(SRC_PKGS) \ + " + +.PHONY: lint +lint: $(BUILD_DIRS) + @echo "running linter" + @docker run \ + -i \ + --rm \ + -u $$(id -u):$$(id -g) \ + -v $$(pwd):/src \ + -w /src \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \ + -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ + -v $$(pwd)/.go/cache:/.cache \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + --env GO111MODULE=on \ + --env GOFLAGS="-mod=vendor" \ + $(BUILD_IMAGE) \ + golangci-lint run + +$(BUILD_DIRS): + @mkdir -p $@ + +.PHONY: dev +dev: gen fmt push + +.PHONY: verify +verify: verify-gen verify-modules + +.PHONY: verify-modules +verify-modules: + go mod tidy + go mod vendor + @if !(git diff --exit-code HEAD); then \ + echo "go module files are out of date"; exit 1; \ + fi + +.PHONY: verify-gen +verify-gen: gen fmt + @if !(git diff --exit-code HEAD); then \ + echo "file formatting is out of date, run make gen fmt"; exit 1; \ + fi + +.PHONY: add-license +add-license: + @echo "Adding license header" + @docker run --rm \ + -u $$(id -u):$$(id -g) \ + -v /tmp:/.cache \ + -v $$(pwd):$(DOCKER_REPO_ROOT) \ + -w $(DOCKER_REPO_ROOT) \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + $(BUILD_IMAGE) \ + ltag -t "./hack/license" --excludes "vendor contrib" -v + +.PHONY: check-license +check-license: + @echo "Checking files for license header" + @docker run --rm \ + -u $$(id -u):$$(id -g) \ + -v /tmp:/.cache \ + -v $$(pwd):$(DOCKER_REPO_ROOT) \ + -w $(DOCKER_REPO_ROOT) \ + --env HTTP_PROXY=$(HTTP_PROXY) \ + --env HTTPS_PROXY=$(HTTPS_PROXY) \ + $(BUILD_IMAGE) \ + ltag -t "./hack/license" --excludes "vendor contrib" --check -v + +.PHONY: ci +ci: verify lint build test #cover + +.PHONY: clean +clean: + rm -rf .go bin diff --git a/vendor/gomodules.xyz/restic/README.md b/vendor/gomodules.xyz/restic/README.md new file mode 100644 index 00000000..fe530b71 --- /dev/null +++ b/vendor/gomodules.xyz/restic/README.md @@ -0,0 +1 @@ +# restic diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/backend.go b/vendor/gomodules.xyz/restic/backend.go similarity index 54% rename from vendor/kubestash.dev/apimachinery/pkg/restic/backend.go rename to vendor/gomodules.xyz/restic/backend.go index 76de5c75..aaa5ba8b 100644 --- a/vendor/kubestash.dev/apimachinery/pkg/restic/backend.go +++ b/vendor/gomodules.xyz/restic/backend.go @@ -20,47 +20,57 @@ import ( "fmt" "os" - "kubestash.dev/apimachinery/apis/storage/v1alpha1" - core "k8s.io/api/core/v1" - kmapi "kmodules.xyz/client-go/api/v1" storage "kmodules.xyz/objectstore-api/api/v1" ) -type backend struct { - provider v1alpha1.StorageProvider - bucket string - endpoint string - region string - insecureTLS bool - path string - storageAccount string - envs map[string]string +// StorageConfig contains provider-agnostic storage configuration. +// This struct decouples the restic package from specific storage CRD types. +type StorageConfig struct { + Provider string + Bucket string + Endpoint string + Region string + Prefix string + InsecureTLS bool + MaxConnections int64 + AzureStorageAccount string } +// StorageConfigResolver is a function type that resolves storage configuration. +// This allows callers to inject their own logic for fetching storage info +// from any storage type (e.g., BackupStorage, or custom types in other projects). +type StorageConfigResolver func(b *Backend) error + +// Backend represents a backup storage backend with its configuration and runtime state type Backend struct { - backend - storageSecret *core.Secret - - EncryptionSecret *kmapi.ObjectReference - Directory string - BackupStorage *kmapi.ObjectReference - MountPath string - MaxConnections int64 - CaCertFile string - Repository string - Error error + *StorageConfig + // ConfigResolver is called during setup to populate storage configuration. + // Callers must provide this function to resolve storage info from their storage type. + ConfigResolver StorageConfigResolver + + Repository string + Directory string + MountPath string + + // Secrets for accessing the Backend Storage + StorageSecret *core.Secret + EncryptionSecret *core.Secret + + CaCertFile string + Envs map[string]string + Error error } func (b *Backend) createLocalDir() error { - if b.provider == v1alpha1.ProviderLocal { - return os.MkdirAll(b.bucket, 0o755) + if b.Provider == storage.ProviderLocal { + return os.MkdirAll(b.Bucket, 0o755) } return nil } func (b *Backend) appendInsecureTLSFlag(args []any) []any { - if b.insecureTLS { + if b.InsecureTLS { return append(args, "--insecure-tls") } return args @@ -76,7 +86,7 @@ func (b *Backend) appendCaCertFlag(args []any) []any { func (b *Backend) appendMaxConnectionsFlag(args []any) []any { var maxConOption string if b.MaxConnections > 0 { - switch b.provider { + switch b.Provider { case storage.ProviderGCS: maxConOption = fmt.Sprintf("gs.connections=%d", b.MaxConnections) case storage.ProviderAzure: diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/backup.go b/vendor/gomodules.xyz/restic/backup.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/backup.go rename to vendor/gomodules.xyz/restic/backup.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/commands.go b/vendor/gomodules.xyz/restic/commands.go similarity index 95% rename from vendor/kubestash.dev/apimachinery/pkg/restic/commands.go rename to vendor/gomodules.xyz/restic/commands.go index c360d28b..dedac49f 100644 --- a/vendor/kubestash.dev/apimachinery/pkg/restic/commands.go +++ b/vendor/gomodules.xyz/restic/commands.go @@ -80,7 +80,7 @@ func (w *ResticWrapper) listSnapshots(repository string, snapshotIDs []string) ( args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) for _, id := range snapshotIDs { args = append(args, id) } @@ -98,7 +98,7 @@ func (w *ResticWrapper) tryDeleteSnapshots(repository string, snapshotIDs []stri args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) for _, id := range snapshotIDs { args = append(args, id) } @@ -125,7 +125,7 @@ func (w *ResticWrapper) repositoryExist(repository string) bool { args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) if _, err := w.run(Command{Name: ResticCMD, Args: args}); err == nil { return true } @@ -133,12 +133,15 @@ func (w *ResticWrapper) repositoryExist(repository string) bool { } func (w *ResticWrapper) getMatchedBackend(repository string) *Backend { - for _, b := range w.Config.Backends { - if b.Repository == repository { - return b - } + // Use index for O(1) lookup if available + if b := w.Config.GetBackend(repository); b != nil { + return b + } + // Return an empty backend to avoid nil pointer dereference + return &Backend{ + StorageConfig: &StorageConfig{}, + Envs: make(map[string]string), } - return new(Backend) } func (w *ResticWrapper) initRepository(repository string) error { @@ -151,7 +154,7 @@ func (w *ResticWrapper) initRepository(repository string) error { args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) _, err := w.run(Command{Name: ResticCMD, Args: args}) return err } @@ -187,9 +190,8 @@ func (w *ResticWrapper) backup(params backupParams) ([]byte, error) { args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) - command := Command{Name: ResticCMD, Args: args} - commands = append(commands, command) + args = append(args, b.Envs) + commands = append(commands, Command{Name: ResticCMD, Args: args}) } return w.run(commands...) } @@ -212,7 +214,7 @@ func (w *ResticWrapper) backupFromStdin(options BackupOptions) ([]byte, error) { args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) command := Command{Name: ResticCMD, Args: args} commands = append(commands, command) } @@ -261,7 +263,7 @@ func (w *ResticWrapper) restore(repository string, params restoreParams) ([]byte args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) args = b.appendMaxConnectionsFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) command := Command{Name: ResticCMD, Args: args} return w.run(command) @@ -294,7 +296,7 @@ func (w *ResticWrapper) DumpOnce(repository string, dumpOptions DumpOptions) ([] args = b.appendCaCertFlag(args) args = b.appendMaxConnectionsFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) command := Command{Name: ResticCMD, Args: args} @@ -311,7 +313,7 @@ func (w *ResticWrapper) check(repository string) ([]byte, error) { args = b.appendCaCertFlag(args) args = b.appendMaxConnectionsFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -326,7 +328,7 @@ func (w *ResticWrapper) stats(repository string, snapshotID string) ([]byte, err args = append(args, "--quiet", "--json", "--mode", "raw-data", "--no-lock") args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -338,7 +340,7 @@ func (w *ResticWrapper) unlock(repository string) ([]byte, error) { args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -349,7 +351,7 @@ func (w *ResticWrapper) unlockStale(repository string) ([]byte, error) { args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -523,7 +525,7 @@ func (w *ResticWrapper) addKey(repository string, params keyParams) ([]byte, err args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -538,7 +540,7 @@ func (w *ResticWrapper) listKey(repository string) ([]byte, error) { args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -553,7 +555,7 @@ func (w *ResticWrapper) listLocks(repository string) ([]byte, error) { args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -568,7 +570,7 @@ func (w *ResticWrapper) lockStats(repository, lockID string) ([]byte, error) { args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -587,7 +589,7 @@ func (w *ResticWrapper) updateKey(repository string, params keyParams) ([]byte, args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } @@ -602,7 +604,7 @@ func (w *ResticWrapper) removeKey(repository string, params keyParams) ([]byte, args = b.appendMaxConnectionsFlag(args) args = b.appendCaCertFlag(args) args = b.appendInsecureTLSFlag(args) - args = append(args, b.envs) + args = append(args, b.Envs) return w.run(Command{Name: ResticCMD, Args: args}) } diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/config.go b/vendor/gomodules.xyz/restic/config.go similarity index 83% rename from vendor/kubestash.dev/apimachinery/pkg/restic/config.go rename to vendor/gomodules.xyz/restic/config.go index 7e6cf530..97124c74 100644 --- a/vendor/kubestash.dev/apimachinery/pkg/restic/config.go +++ b/vendor/gomodules.xyz/restic/config.go @@ -26,7 +26,6 @@ import ( shell "gomodules.xyz/go-sh" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ofst "kmodules.xyz/offshoot-api/api/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -79,14 +78,14 @@ type DumpOptions struct { type SetupOptions struct { sync.Mutex - Client client.Client + EnableCache bool + ScratchDir string Nice *ofst.NiceSettings IONice *ofst.IONiceSettings Timeout *metav1.Duration - ScratchDir string - EnableCache bool - Backends []*Backend + Backends []*Backend + backendIndex map[string]*Backend } type KeyOptions struct { @@ -128,7 +127,35 @@ func (w *ResticWrapper) configure() error { w.sh.PipeStdErrors = true // Setup restic environments - return w.setupEnv() + if err := w.setupEnv(); err != nil { + return err + } + + // Build backend index for fast lookup + w.Config.buildBackendIndex() + return nil +} + +func (s *SetupOptions) buildBackendIndex() { + s.backendIndex = make(map[string]*Backend, len(s.Backends)) + for _, b := range s.Backends { + if b.Repository != "" { + s.backendIndex[b.Repository] = b + } + } +} + +func (s *SetupOptions) GetBackend(repository string) *Backend { + if s.backendIndex != nil { + return s.backendIndex[repository] + } + // Fallback to linear search if index not built + for _, b := range s.Backends { + if b.Repository == repository { + return b + } + } + return nil } func (w *ResticWrapper) SetEnv(key, value string) { @@ -144,19 +171,25 @@ func (w *ResticWrapper) GetEnv(key string) string { return "" } +func (w *ResticWrapper) SetShowCMD(showCMD bool) { + if w.sh != nil { + w.sh.ShowCMD = showCMD + } +} + func (w *ResticWrapper) GetCaPath(repository string) string { b := w.getMatchedBackend(repository) return b.CaCertFile } -func (w *ResticWrapper) DumpEnv(repostiroy, path string, dumpedFile string) error { +func (w *ResticWrapper) DumpEnv(repository, path string, dumpedFile string) error { if err := os.MkdirAll(path, 0o755); err != nil { return err } var envs string - b := w.getMatchedBackend(repostiroy) - for key, val := range b.envs { + b := w.getMatchedBackend(repository) + for key, val := range b.Envs { envs = envs + fmt.Sprintln(key+"="+val) } diff --git a/vendor/gomodules.xyz/restic/constants.go b/vendor/gomodules.xyz/restic/constants.go new file mode 100644 index 00000000..629b2a83 --- /dev/null +++ b/vendor/gomodules.xyz/restic/constants.go @@ -0,0 +1,71 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package restic + +const ( + RESTIC_REPOSITORY = "RESTIC_REPOSITORY" + RESTIC_PASSWORD = "RESTIC_PASSWORD" + RESTIC_PROGRESS_FPS = "RESTIC_PROGRESS_FPS" + TMPDIR = "TMPDIR" + + AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" + AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" + AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" + + GOOGLE_PROJECT_ID = "GOOGLE_PROJECT_ID" + GOOGLE_SERVICE_ACCOUNT_JSON_KEY = "GOOGLE_SERVICE_ACCOUNT_JSON_KEY" + GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" + + AZURE_ACCOUNT_NAME = "AZURE_ACCOUNT_NAME" + AZURE_ACCOUNT_KEY = "AZURE_ACCOUNT_KEY" + + REST_SERVER_USERNAME = "REST_SERVER_USERNAME" + REST_SERVER_PASSWORD = "REST_SERVER_PASSWORD" + + B2_ACCOUNT_ID = "B2_ACCOUNT_ID" + B2_ACCOUNT_KEY = "B2_ACCOUNT_KEY" + + // For keystone v1 authentication + ST_AUTH = "ST_AUTH" + ST_USER = "ST_USER" + ST_KEY = "ST_KEY" + // For keystone v2 authentication (some variables are optional) + OS_AUTH_URL = "OS_AUTH_URL" + OS_REGION_NAME = "OS_REGION_NAME" + OS_USERNAME = "OS_USERNAME" + OS_PASSWORD = "OS_PASSWORD" + OS_TENANT_ID = "OS_TENANT_ID" + OS_TENANT_NAME = "OS_TENANT_NAME" + // For keystone v3 authentication (some variables are optional) + OS_USER_DOMAIN_NAME = "OS_USER_DOMAIN_NAME" + OS_PROJECT_NAME = "OS_PROJECT_NAME" + OS_PROJECT_DOMAIN_NAME = "OS_PROJECT_DOMAIN_NAME" + // For keystone v3 application credential authentication (application credential id) + OS_APPLICATION_CREDENTIAL_ID = "OS_APPLICATION_CREDENTIAL_ID" + OS_APPLICATION_CREDENTIAL_SECRET = "OS_APPLICATION_CREDENTIAL_SECRET" + // For keystone v3 application credential authentication (application credential name) + OS_APPLICATION_CREDENTIAL_NAME = "OS_APPLICATION_CREDENTIAL_NAME" + // For authentication based on tokens + OS_STORAGE_URL = "OS_STORAGE_URL" + OS_AUTH_TOKEN = "OS_AUTH_TOKEN" + + // For using certs in Minio server or REST server + CA_CERT_DATA = "CA_CERT_DATA" + + // ref: https://github.com/restic/restic/blob/master/doc/manual_rest.rst#temporary-files + resticCacheDir = "restic-cache" +) diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/key.go b/vendor/gomodules.xyz/restic/key.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/key.go rename to vendor/gomodules.xyz/restic/key.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/output.go b/vendor/gomodules.xyz/restic/output.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/output.go rename to vendor/gomodules.xyz/restic/output.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/restore.go b/vendor/gomodules.xyz/restic/restore.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/restore.go rename to vendor/gomodules.xyz/restic/restore.go diff --git a/vendor/gomodules.xyz/restic/setup.go b/vendor/gomodules.xyz/restic/setup.go new file mode 100644 index 00000000..e9c2bc71 --- /dev/null +++ b/vendor/gomodules.xyz/restic/setup.go @@ -0,0 +1,167 @@ +/* +Copyright AppsCode Inc. and Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package restic + +import ( + "fmt" + "os" + "path/filepath" + + core "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/errors" + storage "kmodules.xyz/objectstore-api/api/v1" +) + +func (w *ResticWrapper) setupEnv() error { + // Set progress report frequency. + // 0.016666 is for one report per minute. + // ref: https://restic.readthedocs.io/en/stable/manual_rest.html + w.sh.SetEnv(RESTIC_PROGRESS_FPS, "0.016666") + if w.Config.EnableCache { + cacheDir := filepath.Join(w.Config.ScratchDir, resticCacheDir) + if err := os.MkdirAll(cacheDir, 0o755); err != nil { + return err + } + } + var errs []error + for _, b := range w.Config.Backends { + err := w.setupEnvsForBackend(b) + if err != nil { + b.Error = errors.NewAggregate([]error{b.Error, err}) + errs = append(errs, err) + } + } + return errors.NewAggregate(errs) +} + +func (w *ResticWrapper) setupEnvsForBackend(b *Backend) error { + // Use the injected ConfigResolver to get storage configuration + if b.ConfigResolver == nil { + return fmt.Errorf("ConfigResolver is not set for backend %s", b.Repository) + } + if err := b.ConfigResolver(b); err != nil { + return fmt.Errorf("failed to resolve storage config: %w", err) + } + + if b.Envs == nil { + b.Envs = make(map[string]string) + } + if err := w.setEnvFromSecretIfExists(b.Envs, b.EncryptionSecret, RESTIC_PASSWORD, true); err != nil { + return fmt.Errorf("failed to set secret for backend %s: %w", b.Repository, err) + } + + tmpDir, err := os.MkdirTemp(w.Config.ScratchDir, fmt.Sprintf("%s-tmp-", filepath.Base(b.Repository))) + if err != nil { + return fmt.Errorf("failed to create tmp dir: %w", err) + } + b.Envs[TMPDIR] = tmpDir + if w.isSecretKeyExist(b.StorageSecret, CA_CERT_DATA) { + filePath, err := w.writeSecretKeyToFile(tmpDir, b.StorageSecret, CA_CERT_DATA, "ca.crt") + if err != nil { + return fmt.Errorf("failed to write secret for backend %s: %w", b.Repository, err) + } + b.CaCertFile = filePath + } + + switch b.Provider { + case storage.ProviderLocal: + b.Envs[RESTIC_REPOSITORY] = fmt.Sprintf("%s/%s", b.Bucket, b.Directory) + + case storage.ProviderS3: + b.Envs[RESTIC_REPOSITORY] = fmt.Sprintf("s3:%s/%s", b.Endpoint, filepath.Join(b.Bucket, b.Prefix, b.Directory)) + if err := w.setEnvFromSecretIfExists(b.Envs, b.StorageSecret, AWS_ACCESS_KEY_ID, false); err != nil { + return fmt.Errorf("failed to set secret for backend %s: %w", b.Repository, err) + } + if err := w.setEnvFromSecretIfExists(b.Envs, b.StorageSecret, AWS_SECRET_ACCESS_KEY, false); err != nil { + return fmt.Errorf("failed to set secret for backend %s: %w", b.Repository, err) + } + if b.Region != "" { + b.Envs[AWS_DEFAULT_REGION] = b.Region + } + + case storage.ProviderAzure: + b.Envs[RESTIC_REPOSITORY] = fmt.Sprintf("azure:%s:/%s", b.Bucket, filepath.Join(b.Prefix, b.Directory)) + if b.AzureStorageAccount == "" { + return fmt.Errorf("missing storage account for Azure storage") + } + b.Envs[AZURE_ACCOUNT_NAME] = b.AzureStorageAccount + if err := w.setEnvFromSecretIfExists(b.Envs, b.StorageSecret, AZURE_ACCOUNT_KEY, false); err != nil { + return fmt.Errorf("failed to set secret for backend %s: %w", b.Repository, err) + } + + case storage.ProviderGCS: + b.Envs[RESTIC_REPOSITORY] = fmt.Sprintf("gs:%s:/%s", b.Bucket, filepath.Join(b.Prefix, b.Directory)) + if w.isSecretKeyExist(b.StorageSecret, GOOGLE_SERVICE_ACCOUNT_JSON_KEY) { + filePath, err := w.writeSecretKeyToFile(tmpDir, b.StorageSecret, GOOGLE_SERVICE_ACCOUNT_JSON_KEY, GOOGLE_SERVICE_ACCOUNT_JSON_KEY) + if err != nil { + return err + } + b.Envs[GOOGLE_APPLICATION_CREDENTIALS] = filePath + } + default: + return fmt.Errorf("unsupported storage provider: %s", b.Provider) + } + return nil +} + +// nolint: unused +func (w *ResticWrapper) exportSecretKey(secret *core.Secret, key string, required bool) error { + if v, ok := secret.Data[key]; !ok { + if required { + return fmt.Errorf("storage Secret missing %s key", key) + } + } else { + w.sh.SetEnv(key, string(v)) + } + return nil +} + +func (w *ResticWrapper) setEnvFromSecretIfExists(envs map[string]string, secret *core.Secret, key string, required bool) error { + if required && secret == nil { + return fmt.Errorf("storage Secret is Required") + } + v, ok := secret.Data[key] + if !ok { + if required { + return fmt.Errorf("%s storage Secret missing %s key", secret.Name, key) + } + } + envs[key] = string(v) + return nil +} + +func (w *ResticWrapper) isSecretKeyExist(secret *core.Secret, key string) bool { + if secret == nil { + return false + } + _, ok := secret.Data[key] + return ok +} + +func (w *ResticWrapper) writeSecretKeyToFile(tmpDir string, secret *core.Secret, key, name string) (string, error) { + v, ok := secret.Data[key] + if !ok { + return "", fmt.Errorf("storage Secret missing %s key", key) + } + + filePath := filepath.Join(tmpDir, name) + + if err := os.WriteFile(filePath, v, 0o755); err != nil { + return "", err + } + return filePath, nil +} diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/snapshot.go b/vendor/gomodules.xyz/restic/snapshot.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/snapshot.go rename to vendor/gomodules.xyz/restic/snapshot.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/types.go b/vendor/gomodules.xyz/restic/types.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/types.go rename to vendor/gomodules.xyz/restic/types.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/unlock.go b/vendor/gomodules.xyz/restic/unlock.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/unlock.go rename to vendor/gomodules.xyz/restic/unlock.go diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/util.go b/vendor/gomodules.xyz/restic/util.go similarity index 100% rename from vendor/kubestash.dev/apimachinery/pkg/restic/util.go rename to vendor/gomodules.xyz/restic/util.go diff --git a/vendor/kubestash.dev/apimachinery/apis/constant.go b/vendor/kubestash.dev/apimachinery/apis/constant.go index 4779f6b4..dfd8e63f 100644 --- a/vendor/kubestash.dev/apimachinery/apis/constant.go +++ b/vendor/kubestash.dev/apimachinery/apis/constant.go @@ -57,14 +57,15 @@ const ( ) const ( - KubeStashBackupComponent = "kubestash-backup" - KubeStashRestoreComponent = "kubestash-restore" - KubeStashInitializerComponent = "kubestash-initializer" - KubeStashUploaderComponent = "kubestash-uploader" - KubeStashCleanerComponent = "kubestash-cleaner" - KubeStashHookComponent = "kubestash-hook" - KubeStashPopulatorComponent = "kubestash-populator" - KubeStashBackupVerifierComponent = "kubestash-backup-verifier" + KubeStashBackupComponent = "kubestash-backup" + KubeStashRestoreComponent = "kubestash-restore" + KubeStashInitializerComponent = "kubestash-initializer" + KubeStashUploaderComponent = "kubestash-uploader" + KubeStashCleanerComponent = "kubestash-cleaner" + KubeStashRetentionPolicyComponent = "kubestash-retention-policy" + KubeStashHookComponent = "kubestash-hook" + KubeStashPopulatorComponent = "kubestash-populator" + KubeStashBackupVerifierComponent = "kubestash-backup-verifier" ) // Keys for offshoot labels diff --git a/vendor/kubestash.dev/apimachinery/apis/core/v1alpha1/backupsession_types.go b/vendor/kubestash.dev/apimachinery/apis/core/v1alpha1/backupsession_types.go index f01b7760..8059f640 100644 --- a/vendor/kubestash.dev/apimachinery/apis/core/v1alpha1/backupsession_types.go +++ b/vendor/kubestash.dev/apimachinery/apis/core/v1alpha1/backupsession_types.go @@ -211,6 +211,10 @@ const ( // TypeSnapshotCleanupIncomplete indicates whether Snapshot cleanup incomplete or not TypeSnapshotCleanupIncomplete = "SnapshotCleanupIncomplete" ReasonSnapshotCleanupTerminatedBeforeCompletion = "SnapshotCleanupTerminatedBeforeCompletion" + + // TypePodHasBeenInPendingStateForLongerThanExpected indicates that the Pod has been in Pending state for longer than expected + TypePodHasBeenInPendingStateForLongerThanExpected = "PodHasBeenInPendingStateForLongerThanExpected" + ReasonPodHasBeenInPendingStateForLongerThanExpected = "PodHasBeenInPendingStateForLongerThanExpected" ) //+kubebuilder:object:root=true diff --git a/vendor/kubestash.dev/apimachinery/apis/storage/v1alpha1/backupstorage_helpers.go b/vendor/kubestash.dev/apimachinery/apis/storage/v1alpha1/backupstorage_helpers.go index 742a2d91..acdd503b 100644 --- a/vendor/kubestash.dev/apimachinery/apis/storage/v1alpha1/backupstorage_helpers.go +++ b/vendor/kubestash.dev/apimachinery/apis/storage/v1alpha1/backupstorage_helpers.go @@ -17,16 +17,22 @@ limitations under the License. package v1alpha1 import ( + "context" + "fmt" + "kubestash.dev/apimachinery/apis" "kubestash.dev/apimachinery/crds" + "gomodules.xyz/restic" core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/klog/v2" + kmapi "kmodules.xyz/client-go/api/v1" "kmodules.xyz/client-go/apiextensions" cutil "kmodules.xyz/client-go/conditions" "kmodules.xyz/client-go/meta" + "sigs.k8s.io/controller-runtime/pkg/client" ) func (BackupStorage) CustomResourceDefinition() *apiextensions.CustomResourceDefinition { @@ -92,3 +98,77 @@ func (b *BackupStorage) LocalNetworkVolume() bool { } return false } + +// NewBackupStorageResolver creates a StorageConfigResolver that resolves storage configuration +// from a BackupStorage custom resource. This is the default resolver for the kubestash project. +func NewBackupStorageResolver(kbClient client.Client, bsRef *kmapi.ObjectReference) restic.StorageConfigResolver { + return func(backend *restic.Backend) error { + bsRef.ObjectKey() + bs := &BackupStorage{ + ObjectMeta: metav1.ObjectMeta{ + Name: bsRef.Name, + Namespace: bsRef.Namespace, + }, + } + + if err := kbClient.Get(context.Background(), client.ObjectKeyFromObject(bs), bs); err != nil { + return fmt.Errorf("failed to get BackupStorage %s/%s: %w", bsRef.Namespace, bsRef.Name, err) + } + var storageSecretName string + switch { + case bs.Spec.Storage.S3 != nil: + s3 := bs.Spec.Storage.S3 + storageSecretName = s3.SecretName + backend.StorageConfig = &restic.StorageConfig{ + Provider: string(ProviderS3), + Bucket: s3.Bucket, + Endpoint: s3.Endpoint, + Region: s3.Region, + Prefix: s3.Prefix, + InsecureTLS: s3.InsecureTLS, + MaxConnections: s3.MaxConnections, + } + case bs.Spec.Storage.GCS != nil: + gcs := bs.Spec.Storage.GCS + storageSecretName = gcs.SecretName + backend.StorageConfig = &restic.StorageConfig{ + Provider: string(ProviderGCS), + Bucket: gcs.Bucket, + Prefix: gcs.Prefix, + MaxConnections: gcs.MaxConnections, + } + case bs.Spec.Storage.Azure != nil: + azure := bs.Spec.Storage.Azure + storageSecretName = azure.SecretName + backend.StorageConfig = &restic.StorageConfig{ + Provider: string(ProviderAzure), + Bucket: azure.Container, + Prefix: azure.Prefix, + AzureStorageAccount: azure.StorageAccount, + MaxConnections: azure.MaxConnections, + } + case bs.Spec.Storage.Local != nil: + local := bs.Spec.Storage.Local + backend.StorageConfig = &restic.StorageConfig{ + Provider: string(ProviderLocal), + Bucket: local.MountPath, + Prefix: local.SubPath, + MaxConnections: local.MaxConnections, + } + if backend.MountPath != "" { + backend.Bucket = backend.MountPath + } + default: + return fmt.Errorf("no storage backend configured in BackupStorage %s/%s", bsRef.Namespace, bsRef.Name) + } + + if storageSecretName != "" { + secret := &core.Secret{} + if err := kbClient.Get(context.Background(), client.ObjectKey{Name: storageSecretName, Namespace: bsRef.Namespace}, secret); err != nil { + return fmt.Errorf("failed to get storage Secret %s/%s: %w", bsRef.Namespace, storageSecretName, err) + } + backend.StorageSecret = secret + } + return nil + } +} diff --git a/vendor/kubestash.dev/apimachinery/pkg/blob/blob.go b/vendor/kubestash.dev/apimachinery/pkg/blob/blob.go index 41f07614..14533cb6 100644 --- a/vendor/kubestash.dev/apimachinery/pkg/blob/blob.go +++ b/vendor/kubestash.dev/apimachinery/pkg/blob/blob.go @@ -468,6 +468,10 @@ func (b *Blob) getS3Config(ctx context.Context, debug bool) (aws2.Config, error) } } + // S3 client behavior is updated to always calculate a checksum by default for operations, so it's needed + loadOptions = append(loadOptions, config.WithRequestChecksumCalculation(aws2.RequestChecksumCalculationWhenRequired)) + loadOptions = append(loadOptions, config.WithResponseChecksumValidation(aws2.ResponseChecksumValidationWhenRequired)) + return config.LoadDefaultConfig(ctx, loadOptions...) } diff --git a/vendor/kubestash.dev/apimachinery/pkg/restic/setup.go b/vendor/kubestash.dev/apimachinery/pkg/restic/setup.go deleted file mode 100644 index 254553a0..00000000 --- a/vendor/kubestash.dev/apimachinery/pkg/restic/setup.go +++ /dev/null @@ -1,350 +0,0 @@ -/* -Copyright AppsCode Inc. and Contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package restic - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "kubestash.dev/apimachinery/apis/storage/v1alpha1" - - core "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/errors" - kmapi "kmodules.xyz/client-go/api/v1" - meta_util "kmodules.xyz/client-go/meta" - storage "kmodules.xyz/objectstore-api/api/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - RESTIC_REPOSITORY = "RESTIC_REPOSITORY" - RESTIC_PASSWORD = "RESTIC_PASSWORD" - RESTIC_PROGRESS_FPS = "RESTIC_PROGRESS_FPS" - TMPDIR = "TMPDIR" - - AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" - AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" - AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" - - GOOGLE_PROJECT_ID = "GOOGLE_PROJECT_ID" - GOOGLE_SERVICE_ACCOUNT_JSON_KEY = "GOOGLE_SERVICE_ACCOUNT_JSON_KEY" - GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" - - AZURE_ACCOUNT_NAME = "AZURE_ACCOUNT_NAME" - AZURE_ACCOUNT_KEY = "AZURE_ACCOUNT_KEY" - - REST_SERVER_USERNAME = "REST_SERVER_USERNAME" - REST_SERVER_PASSWORD = "REST_SERVER_PASSWORD" - - B2_ACCOUNT_ID = "B2_ACCOUNT_ID" - B2_ACCOUNT_KEY = "B2_ACCOUNT_KEY" - - // For keystone v1 authentication - ST_AUTH = "ST_AUTH" - ST_USER = "ST_USER" - ST_KEY = "ST_KEY" - // For keystone v2 authentication (some variables are optional) - OS_AUTH_URL = "OS_AUTH_URL" - OS_REGION_NAME = "OS_REGION_NAME" - OS_USERNAME = "OS_USERNAME" - OS_PASSWORD = "OS_PASSWORD" - OS_TENANT_ID = "OS_TENANT_ID" - OS_TENANT_NAME = "OS_TENANT_NAME" - // For keystone v3 authentication (some variables are optional) - OS_USER_DOMAIN_NAME = "OS_USER_DOMAIN_NAME" - OS_PROJECT_NAME = "OS_PROJECT_NAME" - OS_PROJECT_DOMAIN_NAME = "OS_PROJECT_DOMAIN_NAME" - // For keystone v3 application credential authentication (application credential id) - OS_APPLICATION_CREDENTIAL_ID = "OS_APPLICATION_CREDENTIAL_ID" - OS_APPLICATION_CREDENTIAL_SECRET = "OS_APPLICATION_CREDENTIAL_SECRET" - // For keystone v3 application credential authentication (application credential name) - OS_APPLICATION_CREDENTIAL_NAME = "OS_APPLICATION_CREDENTIAL_NAME" - // For authentication based on tokens - OS_STORAGE_URL = "OS_STORAGE_URL" - OS_AUTH_TOKEN = "OS_AUTH_TOKEN" - - // For using certs in Minio server or REST server - CA_CERT_DATA = "CA_CERT_DATA" - - // ref: https://github.com/restic/restic/blob/master/doc/manual_rest.rst#temporary-files - resticCacheDir = "restic-cache" -) - -func (w *ResticWrapper) setupEnv() error { - // Set progress report frequency. - // 0.016666 is for one report per minute. - // ref: https://restic.readthedocs.io/en/stable/manual_rest.html - w.sh.SetEnv(RESTIC_PROGRESS_FPS, "0.016666") - if w.Config.EnableCache { - cacheDir := filepath.Join(w.Config.ScratchDir, resticCacheDir) - if err := os.MkdirAll(cacheDir, 0o755); err != nil { - return err - } - } - - for _, b := range w.Config.Backends { - err := w.setupEnvsForBackend(b) - if err != nil { - b.Error = errors.NewAggregate([]error{b.Error, err}) - } - } - return nil -} - -func (w *ResticWrapper) setupEnvsForBackend(b *Backend) error { - if b.BackupStorage == nil { - return fmt.Errorf("missing BackupStorage reference") - } - - if err := w.setBackupStorageVariables(b); err != nil { - return fmt.Errorf("failed to set BackupStorage variables. Reason: %w", err) - } - if b.storageSecret == nil { - return fmt.Errorf("missing storage Secret") - } - - b.envs = map[string]string{} - - if value, err := w.getSecretKey(b.storageSecret, RESTIC_PASSWORD, true); err != nil { - return err - } else { - b.envs[RESTIC_PASSWORD] = value - } - - tmpDir, err := os.MkdirTemp(w.Config.ScratchDir, fmt.Sprintf("%s-tmp-", b.Repository)) - if err != nil { - return err - } - b.envs[TMPDIR] = tmpDir - - if _, ok := b.storageSecret.Data[CA_CERT_DATA]; ok { - if filePath, err := w.writeSecretKeyToFile(tmpDir, b.storageSecret, CA_CERT_DATA, "ca.crt"); err != nil { - return err - } else { - b.CaCertFile = filePath - } - } - switch b.provider { - - case storage.ProviderLocal: - b.envs[RESTIC_REPOSITORY] = fmt.Sprintf("%s/%s", b.bucket, b.Directory) - - case storage.ProviderS3: - b.envs[RESTIC_REPOSITORY] = fmt.Sprintf("s3:%s/%s", b.endpoint, filepath.Join(b.bucket, b.path, b.Directory)) - if val, err := w.getSecretKey(b.storageSecret, AWS_ACCESS_KEY_ID, false); err != nil { - return err - } else { - b.envs[AWS_ACCESS_KEY_ID] = val - } - - if val, err := w.getSecretKey(b.storageSecret, AWS_SECRET_ACCESS_KEY, false); err != nil { - return err - } else { - b.envs[AWS_SECRET_ACCESS_KEY] = val - } - - if b.region != "" { - b.envs[AWS_DEFAULT_REGION] = b.region - } - - case storage.ProviderAzure: - b.envs[RESTIC_REPOSITORY] = fmt.Sprintf("azure:%s:/%s", b.bucket, filepath.Join(b.path, b.Directory)) - if b.storageAccount == "" { - return fmt.Errorf("missing storage account for Azure storage") - } else { - b.envs[AZURE_ACCOUNT_NAME] = b.storageAccount - } - if val, err := w.getSecretKey(b.storageSecret, AZURE_ACCOUNT_KEY, false); err != nil { - return err - } else { - b.envs[AZURE_ACCOUNT_KEY] = val - } - - case storage.ProviderGCS: - b.envs[RESTIC_REPOSITORY] = fmt.Sprintf("gs:%s:/%s", b.bucket, filepath.Join(b.path, b.Directory)) - if w.isSecretKeyExist(b.storageSecret, GOOGLE_SERVICE_ACCOUNT_JSON_KEY) { - if filePath, err := w.writeSecretKeyToFile(tmpDir, b.storageSecret, GOOGLE_SERVICE_ACCOUNT_JSON_KEY, GOOGLE_SERVICE_ACCOUNT_JSON_KEY); err != nil { - return err - } else { - b.envs[GOOGLE_APPLICATION_CREDENTIALS] = filePath - } - } - } - - return nil -} - -// nolint: unused -func (w *ResticWrapper) exportSecretKey(secret *core.Secret, key string, required bool) error { - if v, ok := secret.Data[key]; !ok { - if required { - return fmt.Errorf("storage Secret missing %s key", key) - } - } else { - w.sh.SetEnv(key, string(v)) - } - return nil -} - -func (w *ResticWrapper) getSecretKey(secret *core.Secret, key string, required bool) (string, error) { - v, ok := secret.Data[key] - if !ok { - if required { - return "", fmt.Errorf("%s storage Secret missing %s key", secret.Name, key) - } - } - - return string(v), nil -} - -func (w *ResticWrapper) isSecretKeyExist(secret *core.Secret, key string) bool { - _, ok := secret.Data[key] - return ok -} - -func (w *ResticWrapper) writeSecretKeyToFile(tmpDir string, secret *core.Secret, key, name string) (string, error) { - v, ok := secret.Data[key] - if !ok { - return "", fmt.Errorf("storage Secret missing %s key", key) - } - - filePath := filepath.Join(tmpDir, name) - - if err := os.WriteFile(filePath, v, 0o755); err != nil { - return "", err - } - return filePath, nil -} - -func (w *ResticWrapper) setBackupStorageVariables(b *Backend) error { - bs := &v1alpha1.BackupStorage{ - ObjectMeta: metav1.ObjectMeta{ - Name: b.BackupStorage.Name, - Namespace: b.BackupStorage.Namespace, - }, - } - - if err := w.Config.Client.Get(context.Background(), client.ObjectKeyFromObject(bs), bs); err != nil { - return err - } - - var secret string - - if s3 := bs.Spec.Storage.S3; s3 != nil { - b.provider = v1alpha1.ProviderS3 - b.region = s3.Region - b.bucket = s3.Bucket - b.endpoint = s3.Endpoint - b.path = s3.Prefix - b.insecureTLS = s3.InsecureTLS - b.MaxConnections = s3.MaxConnections - secret = s3.SecretName - } - - if gcs := bs.Spec.Storage.GCS; gcs != nil { - b.provider = v1alpha1.ProviderGCS - b.bucket = gcs.Bucket - b.path = gcs.Prefix - b.MaxConnections = gcs.MaxConnections - secret = gcs.SecretName - } - - if azure := bs.Spec.Storage.Azure; azure != nil { - b.provider = v1alpha1.ProviderAzure - b.storageAccount = azure.StorageAccount - b.bucket = azure.Container - b.path = azure.Prefix - b.MaxConnections = azure.MaxConnections - secret = azure.SecretName - } - - if local := bs.Spec.Storage.Local; local != nil { - b.provider = v1alpha1.ProviderLocal - b.bucket = local.MountPath - b.path = local.SubPath - b.MaxConnections = local.MaxConnections - - var err error - b.storageSecret, err = w.getSecret(b.EncryptionSecret) - if err != nil { - return err - } - - if b.MountPath != "" { - b.bucket = b.MountPath - } - } - - ss := &core.Secret{} - if secret != "" { - var err error - ss, err = w.getSecret(&kmapi.ObjectReference{ - Name: secret, - Namespace: bs.Namespace, - }) - if err != nil { - return fmt.Errorf("failed to get storage Secret %s/%s: %w", bs.Namespace, secret, err) - } - } - - es, err := w.getSecret(b.EncryptionSecret) - if err != nil { - return fmt.Errorf("failed to get Encryption Secret %s/%s: %w", b.EncryptionSecret.Namespace, b.EncryptionSecret.Name, err) - } - - b.storageSecret = mergeSecretData(ss, es) - - return nil -} - -func (w *ResticWrapper) getSecret(ref *kmapi.ObjectReference) (*core.Secret, error) { - secret := &core.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: ref.Name, - Namespace: ref.Namespace, - }, - } - - if err := w.Config.Client.Get(context.Background(), client.ObjectKeyFromObject(secret), secret); err != nil { - return nil, err - } - - return secret, nil -} - -func mergeSecretData(out, in *core.Secret) *core.Secret { - if out == nil || in == nil { - return nil - } - - if out.Data == nil { - out.Data = make(map[string][]byte) - } - - out.StringData = meta_util.MergeKeys(out.StringData, in.StringData) - - for k, v := range in.Data { - if _, ok := out.Data[k]; !ok { - out.Data[k] = v - } - } - - return out -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 073c41b2..9c5577a0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -993,6 +993,9 @@ gomodules.xyz/mergo # gomodules.xyz/pointer v0.1.0 ## explicit; go 1.15 gomodules.xyz/pointer +# gomodules.xyz/restic v0.0.0-20260127113541-2bdea1940793 +## explicit; go 1.25 +gomodules.xyz/restic # gomodules.xyz/runtime v0.3.0 ## explicit; go 1.17 gomodules.xyz/runtime @@ -1861,7 +1864,7 @@ kubeops.dev/petset/crds kubeops.dev/sidekick/apis/apps kubeops.dev/sidekick/apis/apps/v1alpha1 kubeops.dev/sidekick/crds -# kubestash.dev/apimachinery v0.23.0 +# kubestash.dev/apimachinery v0.23.1-0.20260213123630-ee2e89dda76e ## explicit; go 1.25.0 kubestash.dev/apimachinery/apis kubestash.dev/apimachinery/apis/addons/v1alpha1 @@ -1875,7 +1878,6 @@ kubestash.dev/apimachinery/pkg/resourceops/file kubestash.dev/apimachinery/pkg/resourceops/filter kubestash.dev/apimachinery/pkg/resourceops/priority kubestash.dev/apimachinery/pkg/resourceops/sanitizers -kubestash.dev/apimachinery/pkg/restic kubestash.dev/apimachinery/pkg/workerpool # open-cluster-management.io/api v1.1.1-0.20251222023835-510285203ee6 ## explicit; go 1.25.0