mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-08 06:15:40 +00:00
replace ark restic repo init cmd with automatic initialization of repos
Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 the Heptio Ark 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 repo
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kclientset "k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
func NewInitCommand(f client.Factory) *cobra.Command {
|
||||
o := NewInitRepositoryOptions()
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "init NAMESPACE",
|
||||
Short: "initialize a restic repository for a specified namespace",
|
||||
Long: "initialize a restic repository for a specified namespace",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
cmd.CheckError(o.Complete(f, args))
|
||||
cmd.CheckError(o.Validate(f))
|
||||
cmd.CheckError(o.Run(f))
|
||||
},
|
||||
}
|
||||
|
||||
o.BindFlags(c.Flags())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type InitRepositoryOptions struct {
|
||||
Namespace string
|
||||
KeyFile string
|
||||
KeyData string
|
||||
KeySize int
|
||||
MaintenanceFrequency time.Duration
|
||||
|
||||
fileSystem filesystem.Interface
|
||||
kubeClient kclientset.Interface
|
||||
arkClient clientset.Interface
|
||||
keyBytes []byte
|
||||
}
|
||||
|
||||
func NewInitRepositoryOptions() *InitRepositoryOptions {
|
||||
return &InitRepositoryOptions{
|
||||
KeySize: 1024,
|
||||
MaintenanceFrequency: restic.DefaultMaintenanceFrequency,
|
||||
fileSystem: filesystem.NewFileSystem(),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errKeyFileAndKeyDataProvided = errors.Errorf("only one of --key-file and --key-data may be specified")
|
||||
errKeySizeTooSmall = errors.Errorf("--key-size must be at least 1")
|
||||
)
|
||||
|
||||
func (o *InitRepositoryOptions) BindFlags(flags *pflag.FlagSet) {
|
||||
flags.StringVar(&o.KeyFile, "key-file", o.KeyFile, "Path to file containing the encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.")
|
||||
flags.StringVar(&o.KeyData, "key-data", o.KeyData, "Encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.")
|
||||
flags.IntVar(&o.KeySize, "key-size", o.KeySize, "Size of the generated key for the restic repository")
|
||||
flags.DurationVar(&o.MaintenanceFrequency, "maintenance-frequency", o.MaintenanceFrequency, "How often maintenance (i.e. restic prune & check) is run on the repository")
|
||||
}
|
||||
|
||||
func (o *InitRepositoryOptions) Complete(f client.Factory, args []string) error {
|
||||
if o.KeyFile != "" && o.KeyData != "" {
|
||||
return errKeyFileAndKeyDataProvided
|
||||
}
|
||||
|
||||
if o.KeyFile == "" && o.KeyData == "" && o.KeySize < 1 {
|
||||
return errKeySizeTooSmall
|
||||
}
|
||||
|
||||
o.Namespace = args[0]
|
||||
|
||||
switch {
|
||||
case o.KeyFile != "":
|
||||
data, err := o.fileSystem.ReadFile(o.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.keyBytes = data
|
||||
case o.KeyData != "":
|
||||
o.keyBytes = []byte(o.KeyData)
|
||||
case o.KeySize > 0:
|
||||
o.keyBytes = make([]byte, o.KeySize)
|
||||
// rand.Reader always returns a nil error
|
||||
rand.Read(o.keyBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *InitRepositoryOptions) Validate(f client.Factory) error {
|
||||
if len(o.keyBytes) == 0 {
|
||||
return errors.Errorf("keyBytes is required")
|
||||
}
|
||||
|
||||
if o.MaintenanceFrequency <= 0 {
|
||||
return errors.Errorf("--maintenance-frequency must be greater than zero")
|
||||
}
|
||||
|
||||
kubeClient, err := f.KubeClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.kubeClient = kubeClient
|
||||
|
||||
if _, err := kubeClient.CoreV1().Namespaces().Get(o.Namespace, metav1.GetOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
arkClient, err := f.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.arkClient = arkClient
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *InitRepositoryOptions) Run(f client.Factory) error {
|
||||
if err := restic.NewRepositoryKey(o.kubeClient.CoreV1(), o.Namespace, o.keyBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo := &v1.ResticRepository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: f.Namespace(),
|
||||
Name: o.Namespace,
|
||||
},
|
||||
Spec: v1.ResticRepositorySpec{
|
||||
MaintenanceFrequency: metav1.Duration{Duration: o.MaintenanceFrequency},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := o.arkClient.ArkV1().ResticRepositories(f.Namespace()).Create(repo)
|
||||
return errors.Wrap(err, "error creating ResticRepository")
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 the Heptio Ark 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 repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
type fakeFactory struct{}
|
||||
|
||||
var _ client.Factory = &fakeFactory{}
|
||||
|
||||
func (f *fakeFactory) BindFlags(flags *pflag.FlagSet) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *fakeFactory) Client() (clientset.Interface, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *fakeFactory) KubeClient() (kubernetes.Interface, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (f *fakeFactory) Namespace() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestComplete(t *testing.T) {
|
||||
// no key options provided should error
|
||||
o := &InitRepositoryOptions{}
|
||||
err := o.Complete(&fakeFactory{}, []string{"ns"})
|
||||
assert.EqualError(t, err, errKeySizeTooSmall.Error())
|
||||
|
||||
// both KeyFile and KeyData provided should error
|
||||
o = &InitRepositoryOptions{
|
||||
KeyFile: "/foo",
|
||||
KeyData: "bar",
|
||||
}
|
||||
err = o.Complete(&fakeFactory{}, []string{"ns"})
|
||||
assert.EqualError(t, err, errKeyFileAndKeyDataProvided.Error())
|
||||
|
||||
// if KeyFile is provided, its contents are used
|
||||
fileContents := []byte("bar")
|
||||
o = &InitRepositoryOptions{
|
||||
KeyFile: "/foo",
|
||||
fileSystem: arktest.NewFakeFileSystem().WithFile("/foo", fileContents),
|
||||
}
|
||||
assert.NoError(t, o.Complete(&fakeFactory{}, []string{"ns"}))
|
||||
assert.Equal(t, fileContents, o.keyBytes)
|
||||
|
||||
// if KeyData is provided, it's used
|
||||
o = &InitRepositoryOptions{
|
||||
KeyData: "bar",
|
||||
}
|
||||
assert.NoError(t, o.Complete(&fakeFactory{}, []string{"ns"}))
|
||||
assert.Equal(t, []byte(o.KeyData), o.keyBytes)
|
||||
|
||||
// if KeySize is provided, a random key is generated
|
||||
o = &InitRepositoryOptions{
|
||||
KeySize: 10,
|
||||
}
|
||||
assert.NoError(t, o.Complete(&fakeFactory{}, []string{"ns"}))
|
||||
assert.Equal(t, o.KeySize, len(o.keyBytes))
|
||||
}
|
||||
@@ -30,7 +30,6 @@ func NewRepositoryCommand(f client.Factory) *cobra.Command {
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
NewInitCommand(f),
|
||||
NewGetCommand(f, "get"),
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/heptio/ark/pkg/controller"
|
||||
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
)
|
||||
|
||||
@@ -59,6 +60,7 @@ type resticServer struct {
|
||||
arkInformerFactory informers.SharedInformerFactory
|
||||
kubeInformerFactory kubeinformers.SharedInformerFactory
|
||||
podInformer cache.SharedIndexInformer
|
||||
secretInformer cache.SharedIndexInformer
|
||||
logger logrus.FieldLogger
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
@@ -84,7 +86,7 @@ func newResticServer(logger logrus.FieldLogger, baseName string) (*resticServer,
|
||||
// filter to only pods scheduled on this node.
|
||||
podInformer := corev1informers.NewFilteredPodInformer(
|
||||
kubeClient,
|
||||
"",
|
||||
metav1.NamespaceAll,
|
||||
0,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
func(opts *metav1.ListOptions) {
|
||||
@@ -92,6 +94,22 @@ func newResticServer(logger logrus.FieldLogger, baseName string) (*resticServer,
|
||||
},
|
||||
)
|
||||
|
||||
// use a stand-alone secrets informer so we can filter to only the restic credentials
|
||||
// secret(s) within the heptio-ark namespace
|
||||
//
|
||||
// note: using an informer to access the single secret for all ark-managed
|
||||
// restic repositories is overkill for now, but will be useful when we move
|
||||
// to fully-encrypted backups and have unique keys per repository.
|
||||
secretInformer := corev1informers.NewFilteredSecretInformer(
|
||||
kubeClient,
|
||||
os.Getenv("HEPTIO_ARK_NAMESPACE"),
|
||||
0,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
func(opts *metav1.ListOptions) {
|
||||
opts.FieldSelector = fmt.Sprintf("metadata.name=%s", restic.CredentialsSecretName)
|
||||
},
|
||||
)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
|
||||
return &resticServer{
|
||||
@@ -100,6 +118,7 @@ func newResticServer(logger logrus.FieldLogger, baseName string) (*resticServer,
|
||||
arkInformerFactory: informers.NewFilteredSharedInformerFactory(arkClient, 0, os.Getenv("HEPTIO_ARK_NAMESPACE"), nil),
|
||||
kubeInformerFactory: kubeinformers.NewSharedInformerFactory(kubeClient, 0),
|
||||
podInformer: podInformer,
|
||||
secretInformer: secretInformer,
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
@@ -118,7 +137,7 @@ func (s *resticServer) run() {
|
||||
s.arkInformerFactory.Ark().V1().PodVolumeBackups(),
|
||||
s.arkClient.ArkV1(),
|
||||
s.podInformer,
|
||||
s.kubeInformerFactory.Core().V1().Secrets(),
|
||||
s.secretInformer,
|
||||
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
|
||||
os.Getenv("NODE_NAME"),
|
||||
)
|
||||
@@ -133,7 +152,7 @@ func (s *resticServer) run() {
|
||||
s.arkInformerFactory.Ark().V1().PodVolumeRestores(),
|
||||
s.arkClient.ArkV1(),
|
||||
s.podInformer,
|
||||
s.kubeInformerFactory.Core().V1().Secrets(),
|
||||
s.secretInformer,
|
||||
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
|
||||
os.Getenv("NODE_NAME"),
|
||||
)
|
||||
@@ -146,6 +165,7 @@ func (s *resticServer) run() {
|
||||
go s.arkInformerFactory.Start(s.ctx.Done())
|
||||
go s.kubeInformerFactory.Start(s.ctx.Done())
|
||||
go s.podInformer.Run(s.ctx.Done())
|
||||
go s.secretInformer.Run(s.ctx.Done())
|
||||
|
||||
s.logger.Info("Controllers started successfully")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user