Merge branch 'master' of github.com:heptio/ark

Signed-off-by: Thomas Runyon <runyontr@gmail.com>
This commit is contained in:
Thomas Runyon
2018-06-21 16:50:49 -04:00
54 changed files with 1774 additions and 362 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ operations can also be performed as 'ark backup get' and 'ark schedule create'.
* [ark describe](ark_describe.md) - Describe ark resources
* [ark get](ark_get.md) - Get ark resources
* [ark plugin](ark_plugin.md) - Work with plugins
* [ark restic](ark_restic.md) - Work with restic repositories
* [ark restic](ark_restic.md) - Work with restic
* [ark restore](ark_restore.md) - Work with restores
* [ark schedule](ark_schedule.md) - Work with schedules
* [ark server](ark_server.md) - Run the ark server
+3 -3
View File
@@ -1,11 +1,11 @@
## ark restic
Work with restic repositories
Work with restic
### Synopsis
Work with restic repositories
Work with restic
### Options
@@ -30,6 +30,6 @@ Work with restic repositories
### SEE ALSO
* [ark](ark.md) - Back up and restore Kubernetes cluster resources.
* [ark restic init-repository](ark_restic_init-repository.md) - create an encryption key for a restic repository
* [ark restic repo](ark_restic_repo.md) - Work with restic repositories
* [ark restic server](ark_restic_server.md) - Run the ark restic server
+35
View File
@@ -0,0 +1,35 @@
## ark restic repo
Work with restic repositories
### Synopsis
Work with restic repositories
### Options
```
-h, --help help for repo
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--kubecontext string The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
-n, --namespace string The namespace in which Ark should operate (default "heptio-ark")
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark restic](ark_restic.md) - Work with restic
* [ark restic repo get](ark_restic_repo_get.md) - Get restic repositories
* [ark restic repo init](ark_restic_repo_init.md) - initialize a restic repository for a specified namespace
@@ -1,23 +1,24 @@
## ark restic init-repository
## ark restic repo get
create an encryption key for a restic repository
Get restic repositories
### Synopsis
create an encryption key for a restic repository
Get restic repositories
```
ark restic init-repository [flags]
ark restic repo get [flags]
```
### Options
```
-h, --help help for init-repository
--key-data string Encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.
--key-file string Path to file containing the encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.
--key-size int Size of the generated key for the restic repository (default 1024)
-h, --help help for get
--label-columns stringArray a comma-separated list of labels to be displayed as columns
-o, --output string Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'. (default "table")
-l, --selector string only show items matching this label selector
--show-labels show labels in the last column
```
### Options inherited from parent commands
@@ -36,5 +37,5 @@ ark restic init-repository [flags]
```
### SEE ALSO
* [ark restic](ark_restic.md) - Work with restic repositories
* [ark restic repo](ark_restic_repo.md) - Work with restic repositories
@@ -0,0 +1,41 @@
## ark restic repo init
initialize a restic repository for a specified namespace
### Synopsis
initialize a restic repository for a specified namespace
```
ark restic repo init NAMESPACE [flags]
```
### Options
```
-h, --help help for init
--key-data string Encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.
--key-file string Path to file containing the encryption key for the restic repository. Optional; if unset, Ark will generate a random key for you.
--key-size int Size of the generated key for the restic repository (default 1024)
--maintenance-frequency duration How often maintenance (i.e. restic prune & check) is run on the repository (default 24h0m0s)
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--kubecontext string The context to use to talk to the Kubernetes apiserver. If unset defaults to whatever your current-context is (kubectl config current-context)
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
-n, --namespace string The namespace in which Ark should operate (default "heptio-ark")
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark restic repo](ark_restic_repo.md) - Work with restic repositories
+1 -1
View File
@@ -34,5 +34,5 @@ ark restic server [flags]
```
### SEE ALSO
* [ark restic](ark_restic.md) - Work with restic repositories
* [ark restic](ark_restic.md) - Work with restic
+6
View File
@@ -43,12 +43,18 @@ spec:
mountPath: /credentials
- name: plugins
mountPath: /plugins
- name: scratch
mountPath: /scratch
env:
- name: AWS_SHARED_CREDENTIALS_FILE
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
volumes:
- name: cloud-credentials
secret:
secretName: cloud-credentials
- name: plugins
emptyDir: {}
- name: scratch
emptyDir: {}
+7 -1
View File
@@ -36,6 +36,8 @@ spec:
- name: host-pods
hostPath:
path: /var/lib/kubelet/pods
- name: scratch
emptyDir: {}
containers:
- name: ark
image: gcr.io/heptio-images/ark:latest
@@ -49,6 +51,8 @@ spec:
mountPath: /credentials
- name: host-pods
mountPath: /host_pods
- name: scratch
mountPath: /scratch
env:
- name: NODE_NAME
valueFrom:
@@ -59,4 +63,6 @@ spec:
fieldRef:
fieldPath: metadata.namespace
- name: AWS_SHARED_CREDENTIALS_FILE
value: /credentials/cloud
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
+7
View File
@@ -44,11 +44,18 @@ spec:
envFrom:
- secretRef:
name: cloud-credentials
env:
- name: ARK_SCRATCH_DIR
value: /scratch
volumeMounts:
- name: plugins
mountPath: /plugins
- name: scratch
mountPath: /scratch
volumes:
- name: plugins
emptyDir: {}
- name: scratch
emptyDir: {}
nodeSelector:
beta.kubernetes.io/os: linux
+6 -1
View File
@@ -33,6 +33,8 @@ spec:
- name: host-pods
hostPath:
path: /var/lib/kubelet/pods
- name: scratch
emptyDir: {}
containers:
- name: ark
image: gcr.io/heptio-images/ark:latest
@@ -44,6 +46,8 @@ spec:
volumeMounts:
- name: host-pods
mountPath: /host_pods
- name: scratch
mountPath: /scratch
envFrom:
- secretRef:
name: cloud-credentials
@@ -66,4 +70,5 @@ spec:
secretKeyRef:
name: cloud-credentials
key: AZURE_STORAGE_KEY
- name: ARK_SCRATCH_DIR
value: /scratch
+15
View File
@@ -132,6 +132,21 @@ spec:
plural: podvolumerestores
kind: PodVolumeRestore
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: resticrepositories.ark.heptio.com
labels:
component: ark
spec:
group: ark.heptio.com
version: v1
scope: Namespaced
names:
plural: resticrepositories
kind: ResticRepository
---
apiVersion: v1
kind: Namespace
+6
View File
@@ -46,12 +46,18 @@ spec:
mountPath: /credentials
- name: plugins
mountPath: /plugins
- name: scratch
mountPath: /scratch
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
volumes:
- name: cloud-credentials
secret:
secretName: cloud-credentials
- name: plugins
emptyDir: {}
- name: scratch
emptyDir: {}
+7 -1
View File
@@ -36,6 +36,8 @@ spec:
- name: host-pods
hostPath:
path: /var/lib/kubelet/pods
- name: scratch
emptyDir: {}
containers:
- name: ark
image: gcr.io/heptio-images/ark:latest
@@ -49,6 +51,8 @@ spec:
mountPath: /credentials
- name: host-pods
mountPath: /host_pods
- name: scratch
mountPath: /scratch
env:
- name: NODE_NAME
valueFrom:
@@ -59,4 +63,6 @@ spec:
fieldRef:
fieldPath: metadata.namespace
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /credentials/cloud
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
+6
View File
@@ -46,12 +46,18 @@ spec:
mountPath: /credentials
- name: plugins
mountPath: /plugins
- name: scratch
mountPath: /scratch
env:
- name: AWS_SHARED_CREDENTIALS_FILE
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
volumes:
- name: cloud-credentials
secret:
secretName: cloud-credentials
- name: plugins
emptyDir: {}
- name: scratch
emptyDir: {}
+6
View File
@@ -46,12 +46,18 @@ spec:
mountPath: /credentials
- name: plugins
mountPath: /plugins
- name: scratch
mountPath: /scratch
env:
- name: AWS_SHARED_CREDENTIALS_FILE
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
volumes:
- name: cloud-credentials
secret:
secretName: cloud-credentials
- name: plugins
emptyDir: {}
- name: scratch
emptyDir: {}
+7 -1
View File
@@ -36,6 +36,8 @@ spec:
- name: host-pods
hostPath:
path: /var/lib/kubelet/pods
- name: scratch
emptyDir: {}
containers:
- name: ark
image: gcr.io/heptio-images/ark:latest
@@ -49,6 +51,8 @@ spec:
mountPath: /credentials
- name: host-pods
mountPath: /host_pods
- name: scratch
mountPath: /scratch
env:
- name: NODE_NAME
valueFrom:
@@ -59,4 +63,6 @@ spec:
fieldRef:
fieldPath: metadata.namespace
- name: AWS_SHARED_CREDENTIALS_FILE
value: /credentials/cloud
value: /credentials/cloud
- name: ARK_SCRATCH_DIR
value: /scratch
+2 -3
View File
@@ -33,9 +33,8 @@ type PodVolumeBackupSpec struct {
// up.
Volume string `json:"volume"`
// RepoPrefix is the restic repository prefix (i.e. not containing
// the repository name itself).
RepoPrefix string `json:"repoPrefix"`
// RepoIdentifier is the restic repository identifier.
RepoIdentifier string `json:"repoIdentifier"`
// Tags are a map of key-value pairs that should be applied to the
// volume backup as tags.
+2 -3
View File
@@ -29,9 +29,8 @@ type PodVolumeRestoreSpec struct {
// Volume is the name of the volume within the Pod to be restored.
Volume string `json:"volume"`
// RepoPrefix is the restic repository prefix (i.e. not containing
// the repository name itself).
RepoPrefix string `json:"repoPrefix"`
// RepoIdentifier is the restic repository identifier.
RepoIdentifier string `json:"repoIdentifier"`
// SnapshotID is the ID of the volume snapshot to be restored.
SnapshotID string `json:"snapshotID"`
+2
View File
@@ -59,6 +59,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&PodVolumeBackupList{},
&PodVolumeRestore{},
&PodVolumeRestoreList{},
&ResticRepository{},
&ResticRepositoryList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
+72
View File
@@ -0,0 +1,72 @@
/*
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 v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ResticRepositorySpec is the specification for a ResticRepository.
type ResticRepositorySpec struct {
// MaintenanceFrequency is how often maintenance should be run.
MaintenanceFrequency metav1.Duration `json:"maintenanceFrequency"`
// ResticIdentifier is the full restic-compatible string for identifying
// this repository.
ResticIdentifier string `json:"resticIdentifier"`
}
// ResticRepositoryPhase represents the lifecycle phase of a ResticRepository.
type ResticRepositoryPhase string
const (
ResticRepositoryPhaseNew ResticRepositoryPhase = "New"
ResticRepositoryPhaseReady ResticRepositoryPhase = "Ready"
ResticRepositoryPhaseNotReady ResticRepositoryPhase = "NotReady"
)
// ResticRepositoryStatus is the current status of a ResticRepository.
type ResticRepositoryStatus struct {
// Phase is the current state of the ResticRepository.
Phase ResticRepositoryPhase `json:"phase"`
// Message is a message about the current status of the ResticRepository.
Message string `json:"message"`
// LastMaintenanceTime is the last time maintenance was run.
LastMaintenanceTime metav1.Time `json:"lastMaintenanceTime"`
}
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ResticRepository struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec ResticRepositorySpec `json:"spec"`
Status ResticRepositoryStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ResticRepositoryList is a list of ResticRepositories.
type ResticRepositoryList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []ResticRepository `json:"items"`
}
+95
View File
@@ -843,6 +843,101 @@ func (in *PodVolumeRestoreStatus) DeepCopy() *PodVolumeRestoreStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResticRepository) DeepCopyInto(out *ResticRepository) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepository.
func (in *ResticRepository) DeepCopy() *ResticRepository {
if in == nil {
return nil
}
out := new(ResticRepository)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ResticRepository) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResticRepositoryList) DeepCopyInto(out *ResticRepositoryList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ResticRepository, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositoryList.
func (in *ResticRepositoryList) DeepCopy() *ResticRepositoryList {
if in == nil {
return nil
}
out := new(ResticRepositoryList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ResticRepositoryList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResticRepositorySpec) DeepCopyInto(out *ResticRepositorySpec) {
*out = *in
out.MaintenanceFrequency = in.MaintenanceFrequency
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositorySpec.
func (in *ResticRepositorySpec) DeepCopy() *ResticRepositorySpec {
if in == nil {
return nil
}
out := new(ResticRepositorySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResticRepositoryStatus) DeepCopyInto(out *ResticRepositoryStatus) {
*out = *in
in.LastMaintenanceTime.DeepCopyInto(&out.LastMaintenanceTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResticRepositoryStatus.
func (in *ResticRepositoryStatus) DeepCopy() *ResticRepositoryStatus {
if in == nil {
return nil
}
out := new(ResticRepositoryStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Restore) DeepCopyInto(out *Restore) {
*out = *in
+66
View File
@@ -0,0 +1,66 @@
/*
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 (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/cmd/util/output"
)
func NewGetCommand(f client.Factory, use string) *cobra.Command {
var listOptions metav1.ListOptions
c := &cobra.Command{
Use: use,
Short: "Get restic repositories",
Run: func(c *cobra.Command, args []string) {
err := output.ValidateFlags(c)
cmd.CheckError(err)
arkClient, err := f.Client()
cmd.CheckError(err)
var repos *api.ResticRepositoryList
if len(args) > 0 {
repos = new(api.ResticRepositoryList)
for _, name := range args {
repo, err := arkClient.Ark().ResticRepositories(f.Namespace()).Get(name, metav1.GetOptions{})
cmd.CheckError(err)
repos.Items = append(repos.Items, *repo)
}
} else {
repos, err = arkClient.ArkV1().ResticRepositories(f.Namespace()).List(listOptions)
cmd.CheckError(err)
}
_, err = output.PrintWithFormat(c, repos)
cmd.CheckError(err)
},
}
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector")
output.BindFlags(c.Flags())
return c
}
@@ -14,31 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package restic
package repo
import (
"crypto/rand"
"time"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/restic"
"github.com/heptio/ark/pkg/util/filesystem"
"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 NewInitRepositoryCommand(f client.Factory) *cobra.Command {
func NewInitCommand(f client.Factory) *cobra.Command {
o := NewInitRepositoryOptions()
c := &cobra.Command{
Use: "init-repository",
Short: "create an encryption key for a restic repository",
Long: "create an encryption key for a restic repository",
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))
cmd.CheckError(o.Complete(f, args))
cmd.CheckError(o.Validate(f))
cmd.CheckError(o.Run(f))
},
@@ -50,20 +56,23 @@ func NewInitRepositoryCommand(f client.Factory) *cobra.Command {
}
type InitRepositoryOptions struct {
Namespace string
KeyFile string
KeyData string
KeySize int
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,
fileSystem: filesystem.NewFileSystem(),
KeySize: 1024,
MaintenanceFrequency: restic.DefaultMaintenanceFrequency,
fileSystem: filesystem.NewFileSystem(),
}
}
@@ -76,9 +85,10 @@ 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) error {
func (o *InitRepositoryOptions) Complete(f client.Factory, args []string) error {
if o.KeyFile != "" && o.KeyData != "" {
return errKeyFileAndKeyDataProvided
}
@@ -87,7 +97,7 @@ func (o *InitRepositoryOptions) Complete(f client.Factory) error {
return errKeySizeTooSmall
}
o.Namespace = f.Namespace()
o.Namespace = args[0]
switch {
case o.KeyFile != "":
@@ -112,6 +122,10 @@ func (o *InitRepositoryOptions) Validate(f client.Factory) error {
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
@@ -122,9 +136,30 @@ func (o *InitRepositoryOptions) Validate(f client.Factory) error {
return err
}
arkClient, err := f.Client()
if err != nil {
return err
}
o.arkClient = arkClient
return nil
}
func (o *InitRepositoryOptions) Run(f client.Factory) error {
return restic.NewRepositoryKey(o.kubeClient.CoreV1(), o.Namespace, o.keyBytes)
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,4 +1,20 @@
package restic
/*
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"
@@ -36,7 +52,7 @@ func (f *fakeFactory) Namespace() string {
func TestComplete(t *testing.T) {
// no key options provided should error
o := &InitRepositoryOptions{}
err := o.Complete(&fakeFactory{})
err := o.Complete(&fakeFactory{}, []string{"ns"})
assert.EqualError(t, err, errKeySizeTooSmall.Error())
// both KeyFile and KeyData provided should error
@@ -44,7 +60,7 @@ func TestComplete(t *testing.T) {
KeyFile: "/foo",
KeyData: "bar",
}
err = o.Complete(&fakeFactory{})
err = o.Complete(&fakeFactory{}, []string{"ns"})
assert.EqualError(t, err, errKeyFileAndKeyDataProvided.Error())
// if KeyFile is provided, its contents are used
@@ -53,20 +69,20 @@ func TestComplete(t *testing.T) {
KeyFile: "/foo",
fileSystem: arktest.NewFakeFileSystem().WithFile("/foo", fileContents),
}
assert.NoError(t, o.Complete(&fakeFactory{}))
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{}))
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{}))
assert.NoError(t, o.Complete(&fakeFactory{}, []string{"ns"}))
assert.Equal(t, o.KeySize, len(o.keyBytes))
}
+38
View File
@@ -0,0 +1,38 @@
/*
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 (
"github.com/spf13/cobra"
"github.com/heptio/ark/pkg/client"
)
func NewRepositoryCommand(f client.Factory) *cobra.Command {
c := &cobra.Command{
Use: "repo",
Short: "Work with restic repositories",
Long: "Work with restic repositories",
}
c.AddCommand(
NewInitCommand(f),
NewGetCommand(f, "get"),
)
return c
}
+4 -3
View File
@@ -20,17 +20,18 @@ import (
"github.com/spf13/cobra"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd/cli/restic/repo"
)
func NewCommand(f client.Factory) *cobra.Command {
c := &cobra.Command{
Use: "restic",
Short: "Work with restic repositories",
Long: "Work with restic repositories",
Short: "Work with restic",
Long: "Work with restic",
}
c.AddCommand(
NewInitRepositoryCommand(f),
repo.NewRepositoryCommand(f),
NewServerCommand(f),
)
+20 -19
View File
@@ -235,7 +235,6 @@ func (s *server) run() error {
if err := s.initRestic(config.BackupStorageProvider); err != nil {
return err
}
s.runResticMaintenance()
// warn if restic daemonset does not exist
_, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get("restic", metav1.GetOptions{})
@@ -253,20 +252,6 @@ func (s *server) run() error {
return nil
}
func (s *server) runResticMaintenance() {
go func() {
interval := time.Hour
<-time.After(interval)
wait.Forever(func() {
if err := s.resticManager.PruneAllRepos(); err != nil {
s.logger.WithError(err).Error("error pruning repos")
}
}, interval)
}()
}
func (s *server) ensureArkNamespace() error {
logContext := s.logger.WithField("namespace", s.namespace)
@@ -491,11 +476,11 @@ func (s *server) initRestic(config api.ObjectStorageProviderConfig) error {
res, err := restic.NewRepositoryManager(
s.ctx,
s.objectStore,
config,
s.namespace,
s.arkClient,
secretsInformer,
s.kubeClient.CoreV1(),
s.sharedInformerFactory.Ark().V1().ResticRepositories(),
s.logger,
)
if err != nil {
@@ -503,8 +488,7 @@ func (s *server) initRestic(config api.ObjectStorageProviderConfig) error {
}
s.resticManager = res
s.logger.Info("Checking restic repositories")
return s.resticManager.CheckAllRepos()
return nil
}
func (s *server) runControllers(config *api.Config) error {
@@ -689,6 +673,23 @@ func (s *server) runControllers(config *api.Config) error {
wg.Done()
}()
if s.resticManager != nil {
resticRepoController := controller.NewResticRepositoryController(
s.logger,
s.sharedInformerFactory.Ark().V1().ResticRepositories(),
s.arkClient.ArkV1(),
config.BackupStorageProvider,
s.resticManager,
)
wg.Add(1)
go func() {
// TODO only having a single worker may be an issue since maintenance
// can take a long time.
resticRepoController.Run(ctx, 1)
wg.Done()
}()
}
// SHARED INFORMERS HAVE TO BE STARTED AFTER ALL CONTROLLERS
go s.sharedInformerFactory.Start(ctx.Done())
+2
View File
@@ -141,6 +141,8 @@ func printTable(cmd *cobra.Command, obj runtime.Object) (bool, error) {
printer.Handler(restoreColumns, nil, printRestoreList)
printer.Handler(scheduleColumns, nil, printSchedule)
printer.Handler(scheduleColumns, nil, printScheduleList)
printer.Handler(resticRepoColumns, nil, printResticRepo)
printer.Handler(resticRepoColumns, nil, printResticRepoList)
err = printer.PrintObj(obj, os.Stdout)
if err != nil {
@@ -0,0 +1,76 @@
/*
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 output
import (
"fmt"
"io"
"k8s.io/kubernetes/pkg/printers"
"github.com/heptio/ark/pkg/apis/ark/v1"
)
var (
resticRepoColumns = []string{"NAME", "STATUS", "LAST MAINTENANCE"}
)
func printResticRepoList(list *v1.ResticRepositoryList, w io.Writer, options printers.PrintOptions) error {
for i := range list.Items {
if err := printResticRepo(&list.Items[i], w, options); err != nil {
return err
}
}
return nil
}
func printResticRepo(repo *v1.ResticRepository, w io.Writer, options printers.PrintOptions) error {
name := printers.FormatResourceName(options.Kind, repo.Name, options.WithKind)
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", repo.Namespace); err != nil {
return err
}
}
status := repo.Status.Phase
if status == "" {
status = v1.ResticRepositoryPhaseNew
}
lastMaintenance := repo.Status.LastMaintenanceTime.String()
if repo.Status.LastMaintenanceTime.IsZero() {
lastMaintenance = "<never>"
}
if _, err := fmt.Fprintf(
w,
"%s\t%s\t%s",
name,
status,
lastMaintenance,
); err != nil {
return err
}
if _, err := fmt.Fprint(w, printers.AppendLabels(repo.Labels, options.ColumnLabels)); err != nil {
return err
}
_, err := fmt.Fprint(w, printers.AppendAllLabels(options.ShowLabels, repo.Labels))
return err
}
+4
View File
@@ -151,3 +151,7 @@ func (c *genericController) enqueue(obj interface{}) {
c.queue.Add(key)
}
func (c *genericController) enqueueSecond(_, obj interface{}) {
c.enqueue(obj)
}
+4 -36
View File
@@ -17,12 +17,9 @@ limitations under the License.
package controller
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
jsonpatch "github.com/evanphx/json-patch"
@@ -40,6 +37,7 @@ import (
informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1"
listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/restic"
arkexec "github.com/heptio/ark/pkg/util/exec"
"github.com/heptio/ark/pkg/util/kube"
)
@@ -175,8 +173,7 @@ func (c *podVolumeBackupController) processBackup(req *arkv1api.PodVolumeBackup)
defer os.Remove(file)
resticCmd := restic.BackupCommand(
req.Spec.RepoPrefix,
req.Spec.Pod.Namespace,
req.Spec.RepoIdentifier,
file,
path,
req.Spec.Tags,
@@ -184,13 +181,13 @@ func (c *podVolumeBackupController) processBackup(req *arkv1api.PodVolumeBackup)
var stdout, stderr string
if stdout, stderr, err = runCommand(resticCmd.Cmd()); err != nil {
if stdout, stderr, err = arkexec.RunCommand(resticCmd.Cmd()); err != nil {
log.WithError(errors.WithStack(err)).Errorf("Error running command=%s, stdout=%s, stderr=%s", resticCmd.String(), stdout, stderr)
return c.fail(req, fmt.Sprintf("error running restic backup, stderr=%s: %s", stderr, err.Error()), log)
}
log.Debugf("Ran command=%s, stdout=%s, stderr=%s", resticCmd.String(), stdout, stderr)
snapshotID, err := restic.GetSnapshotID(req.Spec.RepoPrefix, req.Spec.Pod.Namespace, file, req.Spec.Tags)
snapshotID, err := restic.GetSnapshotID(req.Spec.RepoIdentifier, file, req.Spec.Tags)
if err != nil {
log.WithError(err).Error("Error getting SnapshotID")
return c.fail(req, errors.Wrap(err, "error getting snapshot id").Error(), log)
@@ -210,35 +207,6 @@ func (c *podVolumeBackupController) processBackup(req *arkv1api.PodVolumeBackup)
return nil
}
// runCommand runs a command and returns its stdout, stderr, and its returned
// error (if any). If there are errors reading stdout or stderr, their return
// value(s) will contain the error as a string.
func runCommand(cmd *exec.Cmd) (string, string, error) {
stdoutBuf := new(bytes.Buffer)
stderrBuf := new(bytes.Buffer)
cmd.Stdout = stdoutBuf
cmd.Stderr = stderrBuf
runErr := cmd.Run()
var stdout, stderr string
if res, readErr := ioutil.ReadAll(stdoutBuf); readErr != nil {
stdout = errors.Wrap(readErr, "error reading command's stdout").Error()
} else {
stdout = string(res)
}
if res, readErr := ioutil.ReadAll(stderrBuf); readErr != nil {
stderr = errors.Wrap(readErr, "error reading command's stderr").Error()
} else {
stderr = string(res)
}
return stdout, stderr, runErr
}
func (c *podVolumeBackupController) patchPodVolumeBackup(req *arkv1api.PodVolumeBackup, mutate func(*arkv1api.PodVolumeBackup)) (*arkv1api.PodVolumeBackup, error) {
// Record original json
oldData, err := json.Marshal(req)
@@ -41,6 +41,7 @@ import (
listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/restic"
"github.com/heptio/ark/pkg/util/boolptr"
arkexec "github.com/heptio/ark/pkg/util/exec"
"github.com/heptio/ark/pkg/util/kube"
)
@@ -299,8 +300,7 @@ func restorePodVolume(req *arkv1api.PodVolumeRestore, credsFile, volumeDir strin
}
resticCmd := restic.RestoreCommand(
req.Spec.RepoPrefix,
req.Spec.Pod.Namespace,
req.Spec.RepoIdentifier,
credsFile,
req.Spec.SnapshotID,
volumePath,
@@ -308,7 +308,7 @@ func restorePodVolume(req *arkv1api.PodVolumeRestore, credsFile, volumeDir strin
var stdout, stderr string
if stdout, stderr, err = runCommand(resticCmd.Cmd()); err != nil {
if stdout, stderr, err = arkexec.RunCommand(resticCmd.Cmd()); err != nil {
return errors.Wrapf(err, "error running restic restore, cmd=%s, stdout=%s, stderr=%s", resticCmd.String(), stdout, stderr)
}
log.Debugf("Ran command=%s, stdout=%s, stderr=%s", resticCmd.String(), stdout, stderr)
@@ -0,0 +1,276 @@
/*
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 controller
import (
"encoding/json"
"time"
jsonpatch "github.com/evanphx/json-patch"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache"
"github.com/heptio/ark/pkg/apis/ark/v1"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1"
informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1"
listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/restic"
)
type resticRepositoryController struct {
*genericController
resticRepositoryClient arkv1client.ResticRepositoriesGetter
resticRepositoryLister listers.ResticRepositoryLister
objectStorageConfig arkv1api.ObjectStorageProviderConfig
repositoryManager restic.RepositoryManager
clock clock.Clock
}
// NewResticRepositoryController creates a new restic repository controller.
func NewResticRepositoryController(
logger logrus.FieldLogger,
resticRepositoryInformer informers.ResticRepositoryInformer,
resticRepositoryClient arkv1client.ResticRepositoriesGetter,
objectStorageConfig arkv1api.ObjectStorageProviderConfig,
repositoryManager restic.RepositoryManager,
) Interface {
c := &resticRepositoryController{
genericController: newGenericController("restic-repository", logger),
resticRepositoryClient: resticRepositoryClient,
resticRepositoryLister: resticRepositoryInformer.Lister(),
objectStorageConfig: objectStorageConfig,
repositoryManager: repositoryManager,
clock: &clock.RealClock{},
}
c.syncHandler = c.processQueueItem
c.cacheSyncWaiters = append(c.cacheSyncWaiters, resticRepositoryInformer.Informer().HasSynced)
resticRepositoryInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: c.enqueue,
},
)
c.resyncPeriod = 30 * time.Minute
c.resyncFunc = c.enqueueAllRepositories
return c
}
// enqueueAllRepositories lists all restic repositories from cache and enqueues all
// of them so we can check each one for maintenance.
func (c *resticRepositoryController) enqueueAllRepositories() {
c.logger.Debug("resticRepositoryController.enqueueAllRepositories")
repos, err := c.resticRepositoryLister.List(labels.Everything())
if err != nil {
c.logger.WithError(errors.WithStack(err)).Error("error listing restic repositories")
return
}
for _, repo := range repos {
c.enqueue(repo)
}
}
func (c *resticRepositoryController) processQueueItem(key string) error {
log := c.logger.WithField("key", key)
log.Debug("Running processQueueItem")
ns, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
log.WithError(errors.WithStack(err)).Error("error splitting queue key")
return nil
}
log = c.logger.WithField("namespace", ns).WithField("name", name)
req, err := c.resticRepositoryLister.ResticRepositories(ns).Get(name)
if apierrors.IsNotFound(err) {
log.Debug("Unable to find ResticRepository")
return nil
}
if err != nil {
return errors.Wrap(err, "error getting ResticRepository")
}
// Don't mutate the shared cache
reqCopy := req.DeepCopy()
switch req.Status.Phase {
case "", v1.ResticRepositoryPhaseNew:
return c.initializeRepo(reqCopy, log)
case v1.ResticRepositoryPhaseReady:
return c.runMaintenanceIfDue(reqCopy, log)
case v1.ResticRepositoryPhaseNotReady:
return c.checkNotReadyRepo(reqCopy, log)
}
return nil
}
func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, log logrus.FieldLogger) error {
log.Info("Initializing restic repository")
// defaulting - if the patch fails, return an error so the item is returned to the queue
if err := c.patchResticRepository(req, func(r *v1.ResticRepository) {
r.Spec.ResticIdentifier = restic.GetRepoIdentifier(c.objectStorageConfig, r.Name)
if r.Spec.MaintenanceFrequency.Duration <= 0 {
r.Spec.MaintenanceFrequency = metav1.Duration{Duration: restic.DefaultMaintenanceFrequency}
}
}); err != nil {
return err
}
if err := ensureRepo(req.Name, c.repositoryManager); err != nil {
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
return c.patchResticRepository(req, func(req *v1.ResticRepository) {
req.Status.Phase = v1.ResticRepositoryPhaseReady
req.Status.LastMaintenanceTime = metav1.Time{Time: time.Now()}
})
}
// ensureRepo first checks the repo, and returns if check passes. If it fails,
// attempts to init the repo, and returns the result.
func ensureRepo(name string, repoManager restic.RepositoryManager) error {
if repoManager.CheckRepo(name) == nil {
return nil
}
return repoManager.InitRepo(name)
}
func (c *resticRepositoryController) runMaintenanceIfDue(req *v1.ResticRepository, log logrus.FieldLogger) error {
log.Debug("resticRepositoryController.runMaintenanceIfDue")
now := c.clock.Now()
if !dueForMaintenance(req, now) {
log.Debug("not due for maintenance")
return nil
}
log.Info("Running maintenance on restic repository")
log.Debug("Checking repo before prune")
if err := c.repositoryManager.CheckRepo(req.Name); err != nil {
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
// prune failures should be displayed in the `.status.message` field but
// should not cause the repo to move to `NotReady`.
log.Debug("Pruning repo")
if err := c.repositoryManager.PruneRepo(req.Name); err != nil {
log.WithError(err).Warn("error pruning repository")
if patchErr := c.patchResticRepository(req, func(r *v1.ResticRepository) {
r.Status.Message = err.Error()
}); patchErr != nil {
return patchErr
}
}
log.Debug("Checking repo after prune")
if err := c.repositoryManager.CheckRepo(req.Name); err != nil {
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
return c.patchResticRepository(req, func(req *v1.ResticRepository) {
req.Status.LastMaintenanceTime = metav1.Time{Time: now}
})
}
func dueForMaintenance(req *v1.ResticRepository, now time.Time) bool {
return req.Status.LastMaintenanceTime.Add(req.Spec.MaintenanceFrequency.Duration).Before(now)
}
func (c *resticRepositoryController) checkNotReadyRepo(req *v1.ResticRepository, log logrus.FieldLogger) error {
log.Info("Checking restic repository for readiness")
// we need to ensure it (first check, if check fails, attempt to init)
// because we don't know if it's been successfully initialized yet.
if err := ensureRepo(req.Name, c.repositoryManager); err != nil {
return c.patchResticRepository(req, repoNotReady(err.Error()))
}
return c.patchResticRepository(req, repoReady())
}
func repoNotReady(msg string) func(*v1.ResticRepository) {
return func(r *v1.ResticRepository) {
r.Status.Phase = v1.ResticRepositoryPhaseNotReady
r.Status.Message = msg
}
}
func repoReady() func(*v1.ResticRepository) {
return func(r *v1.ResticRepository) {
r.Status.Phase = v1.ResticRepositoryPhaseReady
r.Status.Message = ""
}
}
// patchResticRepository mutates req with the provided mutate function, and patches it
// through the Kube API. After executing this function, req will be updated with both
// the mutation and the results of the Patch() API call.
func (c *resticRepositoryController) patchResticRepository(req *v1.ResticRepository, mutate func(*v1.ResticRepository)) error {
// Record original json
oldData, err := json.Marshal(req)
if err != nil {
return errors.Wrap(err, "error marshalling original ResticRepository")
}
mutate(req)
// Record new json
newData, err := json.Marshal(req)
if err != nil {
return errors.Wrap(err, "error marshalling updated ResticRepository")
}
patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
if err != nil {
return errors.Wrap(err, "error creating json merge patch for ResticRepository")
}
// empty patch: don't apply
if string(patchBytes) == "{}" {
return nil
}
// patch, and if successful, update req
var patched *v1.ResticRepository
if patched, err = c.resticRepositoryClient.ResticRepositories(req.Namespace).Patch(req.Name, types.MergePatchType, patchBytes); err != nil {
return errors.Wrap(err, "error patching ResticRepository")
}
req = patched
return nil
}
@@ -33,6 +33,7 @@ type ArkV1Interface interface {
DownloadRequestsGetter
PodVolumeBackupsGetter
PodVolumeRestoresGetter
ResticRepositoriesGetter
RestoresGetter
SchedulesGetter
}
@@ -66,6 +67,10 @@ func (c *ArkV1Client) PodVolumeRestores(namespace string) PodVolumeRestoreInterf
return newPodVolumeRestores(c, namespace)
}
func (c *ArkV1Client) ResticRepositories(namespace string) ResticRepositoryInterface {
return newResticRepositories(c, namespace)
}
func (c *ArkV1Client) Restores(namespace string) RestoreInterface {
return newRestores(c, namespace)
}
@@ -52,6 +52,10 @@ func (c *FakeArkV1) PodVolumeRestores(namespace string) v1.PodVolumeRestoreInter
return &FakePodVolumeRestores{c, namespace}
}
func (c *FakeArkV1) ResticRepositories(namespace string) v1.ResticRepositoryInterface {
return &FakeResticRepositories{c, namespace}
}
func (c *FakeArkV1) Restores(namespace string) v1.RestoreInterface {
return &FakeRestores{c, namespace}
}
@@ -0,0 +1,140 @@
/*
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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
ark_v1 "github.com/heptio/ark/pkg/apis/ark/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeResticRepositories implements ResticRepositoryInterface
type FakeResticRepositories struct {
Fake *FakeArkV1
ns string
}
var resticrepositoriesResource = schema.GroupVersionResource{Group: "ark.heptio.com", Version: "v1", Resource: "resticrepositories"}
var resticrepositoriesKind = schema.GroupVersionKind{Group: "ark.heptio.com", Version: "v1", Kind: "ResticRepository"}
// Get takes name of the resticRepository, and returns the corresponding resticRepository object, and an error if there is any.
func (c *FakeResticRepositories) Get(name string, options v1.GetOptions) (result *ark_v1.ResticRepository, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(resticrepositoriesResource, c.ns, name), &ark_v1.ResticRepository{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.ResticRepository), err
}
// List takes label and field selectors, and returns the list of ResticRepositories that match those selectors.
func (c *FakeResticRepositories) List(opts v1.ListOptions) (result *ark_v1.ResticRepositoryList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(resticrepositoriesResource, resticrepositoriesKind, c.ns, opts), &ark_v1.ResticRepositoryList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &ark_v1.ResticRepositoryList{}
for _, item := range obj.(*ark_v1.ResticRepositoryList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested resticRepositories.
func (c *FakeResticRepositories) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(resticrepositoriesResource, c.ns, opts))
}
// Create takes the representation of a resticRepository and creates it. Returns the server's representation of the resticRepository, and an error, if there is any.
func (c *FakeResticRepositories) Create(resticRepository *ark_v1.ResticRepository) (result *ark_v1.ResticRepository, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(resticrepositoriesResource, c.ns, resticRepository), &ark_v1.ResticRepository{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.ResticRepository), err
}
// Update takes the representation of a resticRepository and updates it. Returns the server's representation of the resticRepository, and an error, if there is any.
func (c *FakeResticRepositories) Update(resticRepository *ark_v1.ResticRepository) (result *ark_v1.ResticRepository, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(resticrepositoriesResource, c.ns, resticRepository), &ark_v1.ResticRepository{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.ResticRepository), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeResticRepositories) UpdateStatus(resticRepository *ark_v1.ResticRepository) (*ark_v1.ResticRepository, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(resticrepositoriesResource, "status", c.ns, resticRepository), &ark_v1.ResticRepository{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.ResticRepository), err
}
// Delete takes name of the resticRepository and deletes it. Returns an error if one occurs.
func (c *FakeResticRepositories) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(resticrepositoriesResource, c.ns, name), &ark_v1.ResticRepository{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeResticRepositories) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(resticrepositoriesResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &ark_v1.ResticRepositoryList{})
return err
}
// Patch applies the patch and returns the patched resticRepository.
func (c *FakeResticRepositories) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *ark_v1.ResticRepository, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(resticrepositoriesResource, c.ns, name, data, subresources...), &ark_v1.ResticRepository{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.ResticRepository), err
}
@@ -30,6 +30,8 @@ type PodVolumeBackupExpansion interface{}
type PodVolumeRestoreExpansion interface{}
type ResticRepositoryExpansion interface{}
type RestoreExpansion interface{}
type ScheduleExpansion interface{}
@@ -0,0 +1,174 @@
/*
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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/heptio/ark/pkg/apis/ark/v1"
scheme "github.com/heptio/ark/pkg/generated/clientset/versioned/scheme"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// ResticRepositoriesGetter has a method to return a ResticRepositoryInterface.
// A group's client should implement this interface.
type ResticRepositoriesGetter interface {
ResticRepositories(namespace string) ResticRepositoryInterface
}
// ResticRepositoryInterface has methods to work with ResticRepository resources.
type ResticRepositoryInterface interface {
Create(*v1.ResticRepository) (*v1.ResticRepository, error)
Update(*v1.ResticRepository) (*v1.ResticRepository, error)
UpdateStatus(*v1.ResticRepository) (*v1.ResticRepository, error)
Delete(name string, options *meta_v1.DeleteOptions) error
DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
Get(name string, options meta_v1.GetOptions) (*v1.ResticRepository, error)
List(opts meta_v1.ListOptions) (*v1.ResticRepositoryList, error)
Watch(opts meta_v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ResticRepository, err error)
ResticRepositoryExpansion
}
// resticRepositories implements ResticRepositoryInterface
type resticRepositories struct {
client rest.Interface
ns string
}
// newResticRepositories returns a ResticRepositories
func newResticRepositories(c *ArkV1Client, namespace string) *resticRepositories {
return &resticRepositories{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the resticRepository, and returns the corresponding resticRepository object, and an error if there is any.
func (c *resticRepositories) Get(name string, options meta_v1.GetOptions) (result *v1.ResticRepository, err error) {
result = &v1.ResticRepository{}
err = c.client.Get().
Namespace(c.ns).
Resource("resticrepositories").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of ResticRepositories that match those selectors.
func (c *resticRepositories) List(opts meta_v1.ListOptions) (result *v1.ResticRepositoryList, err error) {
result = &v1.ResticRepositoryList{}
err = c.client.Get().
Namespace(c.ns).
Resource("resticrepositories").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested resticRepositories.
func (c *resticRepositories) Watch(opts meta_v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("resticrepositories").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a resticRepository and creates it. Returns the server's representation of the resticRepository, and an error, if there is any.
func (c *resticRepositories) Create(resticRepository *v1.ResticRepository) (result *v1.ResticRepository, err error) {
result = &v1.ResticRepository{}
err = c.client.Post().
Namespace(c.ns).
Resource("resticrepositories").
Body(resticRepository).
Do().
Into(result)
return
}
// Update takes the representation of a resticRepository and updates it. Returns the server's representation of the resticRepository, and an error, if there is any.
func (c *resticRepositories) Update(resticRepository *v1.ResticRepository) (result *v1.ResticRepository, err error) {
result = &v1.ResticRepository{}
err = c.client.Put().
Namespace(c.ns).
Resource("resticrepositories").
Name(resticRepository.Name).
Body(resticRepository).
Do().
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *resticRepositories) UpdateStatus(resticRepository *v1.ResticRepository) (result *v1.ResticRepository, err error) {
result = &v1.ResticRepository{}
err = c.client.Put().
Namespace(c.ns).
Resource("resticrepositories").
Name(resticRepository.Name).
SubResource("status").
Body(resticRepository).
Do().
Into(result)
return
}
// Delete takes name of the resticRepository and deletes it. Returns an error if one occurs.
func (c *resticRepositories) Delete(name string, options *meta_v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("resticrepositories").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *resticRepositories) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("resticrepositories").
VersionedParams(&listOptions, scheme.ParameterCodec).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched resticRepository.
func (c *resticRepositories) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ResticRepository, err error) {
result = &v1.ResticRepository{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("resticrepositories").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}
@@ -36,6 +36,8 @@ type Interface interface {
PodVolumeBackups() PodVolumeBackupInformer
// PodVolumeRestores returns a PodVolumeRestoreInformer.
PodVolumeRestores() PodVolumeRestoreInformer
// ResticRepositories returns a ResticRepositoryInformer.
ResticRepositories() ResticRepositoryInformer
// Restores returns a RestoreInformer.
Restores() RestoreInformer
// Schedules returns a ScheduleInformer.
@@ -83,6 +85,11 @@ func (v *version) PodVolumeRestores() PodVolumeRestoreInformer {
return &podVolumeRestoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// ResticRepositories returns a ResticRepositoryInformer.
func (v *version) ResticRepositories() ResticRepositoryInformer {
return &resticRepositoryInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// Restores returns a RestoreInformer.
func (v *version) Restores() RestoreInformer {
return &restoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
@@ -0,0 +1,89 @@
/*
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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
time "time"
ark_v1 "github.com/heptio/ark/pkg/apis/ark/v1"
versioned "github.com/heptio/ark/pkg/generated/clientset/versioned"
internalinterfaces "github.com/heptio/ark/pkg/generated/informers/externalversions/internalinterfaces"
v1 "github.com/heptio/ark/pkg/generated/listers/ark/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ResticRepositoryInformer provides access to a shared informer and lister for
// ResticRepositories.
type ResticRepositoryInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.ResticRepositoryLister
}
type resticRepositoryInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewResticRepositoryInformer constructs a new informer for ResticRepository type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewResticRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredResticRepositoryInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredResticRepositoryInformer constructs a new informer for ResticRepository type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredResticRepositoryInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ArkV1().ResticRepositories(namespace).List(options)
},
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ArkV1().ResticRepositories(namespace).Watch(options)
},
},
&ark_v1.ResticRepository{},
resyncPeriod,
indexers,
)
}
func (f *resticRepositoryInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredResticRepositoryInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *resticRepositoryInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&ark_v1.ResticRepository{}, f.defaultInformer)
}
func (f *resticRepositoryInformer) Lister() v1.ResticRepositoryLister {
return v1.NewResticRepositoryLister(f.Informer().GetIndexer())
}
@@ -65,6 +65,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().PodVolumeBackups().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("podvolumerestores"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().PodVolumeRestores().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("resticrepositories"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().ResticRepositories().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("restores"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().Restores().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("schedules"):
@@ -66,6 +66,14 @@ type PodVolumeRestoreListerExpansion interface{}
// PodVolumeRestoreNamespaceLister.
type PodVolumeRestoreNamespaceListerExpansion interface{}
// ResticRepositoryListerExpansion allows custom methods to be added to
// ResticRepositoryLister.
type ResticRepositoryListerExpansion interface{}
// ResticRepositoryNamespaceListerExpansion allows custom methods to be added to
// ResticRepositoryNamespaceLister.
type ResticRepositoryNamespaceListerExpansion interface{}
// RestoreListerExpansion allows custom methods to be added to
// RestoreLister.
type RestoreListerExpansion interface{}
@@ -0,0 +1,94 @@
/*
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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/heptio/ark/pkg/apis/ark/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// ResticRepositoryLister helps list ResticRepositories.
type ResticRepositoryLister interface {
// List lists all ResticRepositories in the indexer.
List(selector labels.Selector) (ret []*v1.ResticRepository, err error)
// ResticRepositories returns an object that can list and get ResticRepositories.
ResticRepositories(namespace string) ResticRepositoryNamespaceLister
ResticRepositoryListerExpansion
}
// resticRepositoryLister implements the ResticRepositoryLister interface.
type resticRepositoryLister struct {
indexer cache.Indexer
}
// NewResticRepositoryLister returns a new ResticRepositoryLister.
func NewResticRepositoryLister(indexer cache.Indexer) ResticRepositoryLister {
return &resticRepositoryLister{indexer: indexer}
}
// List lists all ResticRepositories in the indexer.
func (s *resticRepositoryLister) List(selector labels.Selector) (ret []*v1.ResticRepository, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.ResticRepository))
})
return ret, err
}
// ResticRepositories returns an object that can list and get ResticRepositories.
func (s *resticRepositoryLister) ResticRepositories(namespace string) ResticRepositoryNamespaceLister {
return resticRepositoryNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// ResticRepositoryNamespaceLister helps list and get ResticRepositories.
type ResticRepositoryNamespaceLister interface {
// List lists all ResticRepositories in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1.ResticRepository, err error)
// Get retrieves the ResticRepository from the indexer for a given namespace and name.
Get(name string) (*v1.ResticRepository, error)
ResticRepositoryNamespaceListerExpansion
}
// resticRepositoryNamespaceLister implements the ResticRepositoryNamespaceLister
// interface.
type resticRepositoryNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all ResticRepositories in the indexer for a given namespace.
func (s resticRepositoryNamespaceLister) List(selector labels.Selector) (ret []*v1.ResticRepository, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.ResticRepository))
})
return ret, err
}
// Get retrieves the ResticRepository from the indexer for a given namespace and name.
func (s resticRepositoryNamespaceLister) Get(name string) (*v1.ResticRepository, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("resticrepository"), name)
}
return obj.(*v1.ResticRepository), nil
}
+9 -1
View File
@@ -31,6 +31,7 @@ type arkConfig struct {
gcSyncPeriod time.Duration
podVolumeOperationTimeout time.Duration
restoreOnly bool
resticLocation string
}
func WithBackupSyncPeriod(t time.Duration) arkConfigOption {
@@ -57,6 +58,12 @@ func WithRestoreOnly() arkConfigOption {
}
}
func WithResticLocation(location string) arkConfigOption {
return func(c *arkConfig) {
c.resticLocation = location
}
}
func Config(
namespace string,
pvCloudProviderName string,
@@ -87,7 +94,8 @@ func Config(
Name: backupCloudProviderName,
Config: backupCloudProviderConfig,
},
Bucket: bucket,
Bucket: bucket,
ResticLocation: c.resticLocation,
},
BackupSyncPeriod: metav1.Duration{
Duration: c.backupSyncPeriod,
+1
View File
@@ -36,6 +36,7 @@ func CRDs() []*apiextv1beta1.CustomResourceDefinition {
crd("DeleteBackupRequest", "deletebackuprequests"),
crd("PodVolumeBackup", "podvolumebackups"),
crd("PodVolumeRestore", "podvolumerestores"),
crd("ResticRepository", "resticrepositories"),
}
}
+48 -9
View File
@@ -25,10 +25,12 @@ import (
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
arkv1listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/util/boolptr"
)
@@ -39,18 +41,26 @@ type Backupper interface {
}
type backupper struct {
repoManager *repositoryManager
ctx context.Context
repoManager *repositoryManager
repoLister arkv1listers.ResticRepositoryLister
results map[string]chan *arkv1api.PodVolumeBackup
resultsLock sync.Mutex
}
func newBackupper(ctx context.Context, repoManager *repositoryManager, podVolumeBackupInformer cache.SharedIndexInformer) *backupper {
func newBackupper(
ctx context.Context,
repoManager *repositoryManager,
podVolumeBackupInformer cache.SharedIndexInformer,
repoLister arkv1listers.ResticRepositoryLister,
) *backupper {
b := &backupper{
repoManager: repoManager,
ctx: ctx,
results: make(map[string]chan *arkv1api.PodVolumeBackup),
repoManager: repoManager,
repoLister: repoLister,
results: make(map[string]chan *arkv1api.PodVolumeBackup),
}
podVolumeBackupInformer.AddEventHandler(
@@ -74,6 +84,31 @@ func resultsKey(ns, name string) string {
return fmt.Sprintf("%s/%s", ns, name)
}
func getRepo(repoLister arkv1listers.ResticRepositoryLister, ns, name string) (*arkv1api.ResticRepository, error) {
repo, err := repoLister.ResticRepositories(ns).Get(name)
if apierrors.IsNotFound(err) {
return nil, errors.Wrapf(err, "restic repository not found")
}
if err != nil {
return nil, errors.Wrapf(err, "error getting restic repository")
}
return repo, nil
}
func getReadyRepo(repoLister arkv1listers.ResticRepositoryLister, ns, name string) (*arkv1api.ResticRepository, error) {
repo, err := getRepo(repoLister, ns, name)
if err != nil {
return nil, err
}
if repo.Status.Phase != arkv1api.ResticRepositoryPhaseReady {
return nil, errors.New("restic repository not ready")
}
return repo, nil
}
func (b *backupper) BackupPodVolumes(backup *arkv1api.Backup, pod *corev1api.Pod, log logrus.FieldLogger) (map[string]string, []error) {
// get volumes to backup from pod's annotations
volumesToBackup := GetVolumesToBackup(pod)
@@ -81,8 +116,8 @@ func (b *backupper) BackupPodVolumes(backup *arkv1api.Backup, pod *corev1api.Pod
return nil, nil
}
// ensure a repo exists for the pod's namespace
if err := b.repoManager.ensureRepo(pod.Namespace); err != nil {
repo, err := getReadyRepo(b.repoLister, backup.Namespace, pod.Namespace)
if err != nil {
return nil, []error{err}
}
@@ -101,7 +136,7 @@ func (b *backupper) BackupPodVolumes(backup *arkv1api.Backup, pod *corev1api.Pod
b.repoManager.repoLocker.Lock(pod.Namespace)
defer b.repoManager.repoLocker.Unlock(pod.Namespace)
volumeBackup := newPodVolumeBackup(backup, pod, volumeName, b.repoManager.config.repoPrefix)
volumeBackup := newPodVolumeBackup(backup, pod, volumeName, repo.Spec.ResticIdentifier)
if err := errorOnly(b.repoManager.arkClient.ArkV1().PodVolumeBackups(volumeBackup.Namespace).Create(volumeBackup)); err != nil {
errs = append(errs, err)
@@ -135,7 +170,7 @@ ForEachVolume:
return volumeSnapshots, errs
}
func newPodVolumeBackup(backup *arkv1api.Backup, pod *corev1api.Pod, volumeName, repoPrefix string) *arkv1api.PodVolumeBackup {
func newPodVolumeBackup(backup *arkv1api.Backup, pod *corev1api.Pod, volumeName, repoIdentifier string) *arkv1api.PodVolumeBackup {
return &arkv1api.PodVolumeBackup{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
@@ -171,7 +206,11 @@ func newPodVolumeBackup(backup *arkv1api.Backup, pod *corev1api.Pod, volumeName,
"ns": pod.Namespace,
"volume": volumeName,
},
RepoPrefix: repoPrefix,
RepoIdentifier: repoIdentifier,
},
}
}
func errorOnly(_ interface{}, err error) error {
return err
}
+31 -10
View File
@@ -18,29 +18,46 @@ package restic
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// Command represents a restic command.
type Command struct {
Command string
RepoPrefix string
Repo string
PasswordFile string
Dir string
Args []string
ExtraFlags []string
Command string
RepoIdentifier string
PasswordFile string
Dir string
Args []string
ExtraFlags []string
}
func (c *Command) RepoName() string {
if c.RepoIdentifier == "" {
return ""
}
return c.RepoIdentifier[strings.LastIndex(c.RepoIdentifier, "/")+1:]
}
// StringSlice returns the command as a slice of strings.
func (c *Command) StringSlice() []string {
res := []string{"restic"}
res = append(res, c.Command, repoFlag(c.RepoPrefix, c.Repo))
res = append(res, c.Command, repoFlag(c.RepoIdentifier))
if c.PasswordFile != "" {
res = append(res, passwordFlag(c.PasswordFile))
}
// If ARK_SCRATCH_DIR is defined, put the restic cache within it. If not,
// allow restic to choose the location. This makes running either in-cluster
// or local (dev) work properly.
if scratch := os.Getenv("ARK_SCRATCH_DIR"); scratch != "" {
res = append(res, cacheDirFlag(filepath.Join(scratch, ".cache", "restic")))
}
res = append(res, c.Args...)
res = append(res, c.ExtraFlags...)
@@ -61,10 +78,14 @@ func (c *Command) Cmd() *exec.Cmd {
return cmd
}
func repoFlag(prefix, repo string) string {
return fmt.Sprintf("--repo=%s/%s", prefix, repo)
func repoFlag(repoIdentifier string) string {
return fmt.Sprintf("--repo=%s", repoIdentifier)
}
func passwordFlag(file string) string {
return fmt.Sprintf("--password-file=%s", file)
}
func cacheDirFlag(dir string) string {
return fmt.Sprintf("--cache-dir=%s", dir)
}
+32 -39
View File
@@ -6,15 +6,14 @@ import (
)
// BackupCommand returns a Command for running a restic backup.
func BackupCommand(repoPrefix, repo, passwordFile, path string, tags map[string]string) *Command {
func BackupCommand(repoIdentifier, passwordFile, path string, tags map[string]string) *Command {
return &Command{
Command: "backup",
RepoPrefix: repoPrefix,
Repo: repo,
PasswordFile: passwordFile,
Dir: path,
Args: []string{"."},
ExtraFlags: backupTagFlags(tags),
Command: "backup",
RepoIdentifier: repoIdentifier,
PasswordFile: passwordFile,
Dir: path,
Args: []string{"."},
ExtraFlags: backupTagFlags(tags),
}
}
@@ -27,26 +26,24 @@ func backupTagFlags(tags map[string]string) []string {
}
// RestoreCommand returns a Command for running a restic restore.
func RestoreCommand(repoPrefix, repo, passwordFile, snapshotID, target string) *Command {
func RestoreCommand(repoIdentifier, passwordFile, snapshotID, target string) *Command {
return &Command{
Command: "restore",
RepoPrefix: repoPrefix,
Repo: repo,
PasswordFile: passwordFile,
Dir: target,
Args: []string{snapshotID},
ExtraFlags: []string{"--target=."},
Command: "restore",
RepoIdentifier: repoIdentifier,
PasswordFile: passwordFile,
Dir: target,
Args: []string{snapshotID},
ExtraFlags: []string{"--target=."},
}
}
// GetSnapshotCommand returns a Command for running a restic (get) snapshots.
func GetSnapshotCommand(repoPrefix, repo, passwordFile string, tags map[string]string) *Command {
func GetSnapshotCommand(repoIdentifier, passwordFile string, tags map[string]string) *Command {
return &Command{
Command: "snapshots",
RepoPrefix: repoPrefix,
Repo: repo,
PasswordFile: passwordFile,
ExtraFlags: []string{"--json", "--last", getSnapshotTagFlag(tags)},
Command: "snapshots",
RepoIdentifier: repoIdentifier,
PasswordFile: passwordFile,
ExtraFlags: []string{"--json", "--last", getSnapshotTagFlag(tags)},
}
}
@@ -59,35 +56,31 @@ func getSnapshotTagFlag(tags map[string]string) string {
return fmt.Sprintf("--tag=%s", strings.Join(tagFilters, ","))
}
func InitCommand(repoPrefix, repo string) *Command {
func InitCommand(repoIdentifier string) *Command {
return &Command{
Command: "init",
RepoPrefix: repoPrefix,
Repo: repo,
Command: "init",
RepoIdentifier: repoIdentifier,
}
}
func CheckCommand(repoPrefix, repo string) *Command {
func CheckCommand(repoIdentifier string) *Command {
return &Command{
Command: "check",
RepoPrefix: repoPrefix,
Repo: repo,
Command: "check",
RepoIdentifier: repoIdentifier,
}
}
func PruneCommand(repoPrefix, repo string) *Command {
func PruneCommand(repoIdentifier string) *Command {
return &Command{
Command: "prune",
RepoPrefix: repoPrefix,
Repo: repo,
Command: "prune",
RepoIdentifier: repoIdentifier,
}
}
func ForgetCommand(repoPrefix, repo, snapshotID string) *Command {
func ForgetCommand(repoIdentifier, snapshotID string) *Command {
return &Command{
Command: "forget",
RepoPrefix: repoPrefix,
Repo: repo,
Args: []string{snapshotID},
Command: "forget",
RepoIdentifier: repoIdentifier,
Args: []string{snapshotID},
}
}
+3 -1
View File
@@ -20,6 +20,7 @@ import (
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/pkg/errors"
@@ -32,7 +33,8 @@ import (
)
const (
InitContainer = "restic-wait"
InitContainer = "restic-wait"
DefaultMaintenanceFrequency = 24 * time.Hour
podAnnotationPrefix = "snapshot.ark.heptio.com/"
volumesToBackupAnnotation = "backup.ark.heptio.com/backup-volumes"
+79
View File
@@ -0,0 +1,79 @@
/*
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 restic
import (
"fmt"
"strings"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
)
type BackendType string
const (
AWSBackend BackendType = "aws"
AzureBackend BackendType = "azure"
GCPBackend BackendType = "gcp"
)
// getRepoPrefix returns the prefix of the value of the --repo flag for
// restic commands, i.e. everything except the "/<repo-name>".
func getRepoPrefix(config arkv1api.ObjectStorageProviderConfig) string {
if BackendType(config.Name) == AWSBackend {
var url string
switch {
// non-AWS, S3-compatible object store
case config.Config["s3Url"] != "":
url = config.Config["s3Url"]
default:
url = "s3.amazonaws.com"
}
return fmt.Sprintf("s3:%s/%s", url, config.ResticLocation)
}
var (
parts = strings.SplitN(config.ResticLocation, "/", 2)
bucket, path string
)
if len(parts) >= 1 {
bucket = parts[0]
}
if len(parts) >= 2 {
path = parts[1]
}
var prefix string
switch BackendType(config.Name) {
case AzureBackend:
prefix = "azure"
case GCPBackend:
prefix = "gs"
}
return fmt.Sprintf("%s:%s:/%s", prefix, bucket, path)
}
// GetRepoIdentifier returns the string to be used as the value of the --repo flag in
// restic commands for the given repository.
func GetRepoIdentifier(config arkv1api.ObjectStorageProviderConfig, name string) string {
prefix := getRepoPrefix(config)
return fmt.Sprintf("%s/%s", strings.TrimSuffix(prefix, "/"), name)
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
// GetSnapshotID runs a 'restic snapshots' command to get the ID of the snapshot
// in the specified repo matching the set of provided tags, or an error if a
// unique snapshot cannot be identified.
func GetSnapshotID(repoPrefix, repo, passwordFile string, tags map[string]string) (string, error) {
output, err := GetSnapshotCommand(repoPrefix, repo, passwordFile, tags).Cmd().Output()
func GetSnapshotID(repoIdentifier, passwordFile string, tags map[string]string) (string, error) {
output, err := GetSnapshotCommand(repoIdentifier, passwordFile, tags).Cmd().Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return "", errors.Wrapf(err, "error running command, stderr=%s", exitErr.Stderr)
+63 -183
View File
@@ -20,40 +20,33 @@ import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kerrs "k8s.io/apimachinery/pkg/util/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/cloudprovider"
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
arkv1informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1"
"github.com/heptio/ark/pkg/util/sync"
arkv1listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
arkexec "github.com/heptio/ark/pkg/util/exec"
)
// RepositoryManager executes commands against restic repositories.
type RepositoryManager interface {
// InitRepo initializes a repo with the specified name.
InitRepo(name string) error
// CheckRepo checks the specified repo for errors.
CheckRepo(name string) error
// CheckAllRepos checks all repos for errors.
CheckAllRepos() error
// PruneRepo deletes unused data from a repo.
PruneRepo(name string) error
// PruneAllRepos deletes unused data from all
// repos.
PruneAllRepos() error
// Forget removes a snapshot from the list of
// available snapshots in a repo.
Forget(snapshot SnapshotIdentifier) error
@@ -77,87 +70,36 @@ type RestorerFactory interface {
NewRestorer(context.Context, *arkv1api.Restore) (Restorer, error)
}
type BackendType string
const (
AWSBackend BackendType = "aws"
AzureBackend BackendType = "azure"
GCPBackend BackendType = "gcp"
)
type repositoryManager struct {
objectStore cloudprovider.ObjectStore
config config
arkClient clientset.Interface
secretsLister corev1listers.SecretLister
secretsClient corev1client.SecretsGetter
log logrus.FieldLogger
repoLocker *repoLocker
}
type config struct {
repoPrefix string
bucket string
path string
}
func getConfig(objectStorageConfig arkv1api.ObjectStorageProviderConfig) config {
var (
c = config{}
parts = strings.SplitN(objectStorageConfig.ResticLocation, "/", 2)
)
switch len(parts) {
case 0:
case 1:
c.bucket = parts[0]
default:
c.bucket = parts[0]
c.path = parts[1]
}
switch BackendType(objectStorageConfig.Name) {
case AWSBackend:
var url string
switch {
// non-AWS, S3-compatible object store
case objectStorageConfig.Config != nil && objectStorageConfig.Config["s3Url"] != "":
url = objectStorageConfig.Config["s3Url"]
default:
url = "s3.amazonaws.com"
}
c.repoPrefix = fmt.Sprintf("s3:%s/%s", url, c.bucket)
if c.path != "" {
c.repoPrefix += "/" + c.path
}
case AzureBackend:
c.repoPrefix = fmt.Sprintf("azure:%s:/%s", c.bucket, c.path)
case GCPBackend:
c.repoPrefix = fmt.Sprintf("gs:%s:/%s", c.bucket, c.path)
}
return c
namespace string
arkClient clientset.Interface
secretsLister corev1listers.SecretLister
secretsClient corev1client.SecretsGetter
repoLister arkv1listers.ResticRepositoryLister
repoInformerSynced cache.InformerSynced
log logrus.FieldLogger
repoLocker *repoLocker
}
// NewRepositoryManager constructs a RepositoryManager.
func NewRepositoryManager(
ctx context.Context,
objectStore cloudprovider.ObjectStore,
config arkv1api.ObjectStorageProviderConfig,
namespace string,
arkClient clientset.Interface,
secretsInformer cache.SharedIndexInformer,
secretsClient corev1client.SecretsGetter,
repoInformer arkv1informers.ResticRepositoryInformer,
log logrus.FieldLogger,
) (RepositoryManager, error) {
rm := &repositoryManager{
objectStore: objectStore,
config: getConfig(config),
arkClient: arkClient,
secretsLister: corev1listers.NewSecretLister(secretsInformer.GetIndexer()),
secretsClient: secretsClient,
log: log,
repoLocker: newRepoLocker(),
namespace: namespace,
arkClient: arkClient,
secretsLister: corev1listers.NewSecretLister(secretsInformer.GetIndexer()),
secretsClient: secretsClient,
repoLister: repoInformer.Lister(),
repoInformerSynced: repoInformer.Informer().HasSynced,
log: log,
repoLocker: newRepoLocker(),
}
if !cache.WaitForCacheSync(ctx.Done(), secretsInformer.HasSynced) {
@@ -178,11 +120,11 @@ func (rm *repositoryManager) NewBackupper(ctx context.Context, backup *arkv1api.
},
)
b := newBackupper(ctx, rm, informer)
b := newBackupper(ctx, rm, informer, rm.repoLister)
go informer.Run(ctx.Done())
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
return nil, errors.New("timed out waiting for cache to sync")
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced, rm.repoInformerSynced) {
return nil, errors.New("timed out waiting for caches to sync")
}
return b, nil
@@ -199,7 +141,7 @@ func (rm *repositoryManager) NewRestorer(ctx context.Context, restore *arkv1api.
},
)
r := newRestorer(ctx, rm, informer)
r := newRestorer(ctx, rm, informer, rm.repoLister)
go informer.Run(ctx.Done())
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
@@ -209,142 +151,80 @@ func (rm *repositoryManager) NewRestorer(ctx context.Context, restore *arkv1api.
return r, nil
}
func (rm *repositoryManager) ensureRepo(name string) error {
repos, err := rm.getAllRepos()
func (rm *repositoryManager) InitRepo(name string) error {
repo, err := getRepo(rm.repoLister, rm.namespace, name)
if err != nil {
return err
}
for _, repo := range repos {
if repo == name {
return nil
}
}
rm.repoLocker.LockExclusive(name)
defer rm.repoLocker.UnlockExclusive(name)
// init the repo
cmd := InitCommand(rm.config.repoPrefix, name)
return errorOnly(rm.exec(cmd))
}
func (rm *repositoryManager) getAllRepos() ([]string, error) {
// TODO support rm.config.path
prefixes, err := rm.objectStore.ListCommonPrefixes(rm.config.bucket, "/")
if err != nil {
return nil, err
}
var repos []string
for _, prefix := range prefixes {
if len(prefix) <= 1 {
continue
}
// strip the trailing '/' if it exists
repos = append(repos, strings.TrimSuffix(prefix, "/"))
}
return repos, nil
}
func (rm *repositoryManager) CheckAllRepos() error {
repos, err := rm.getAllRepos()
if err != nil {
return err
}
var eg sync.ErrorGroup
for _, repo := range repos {
this := repo
eg.Go(func() error {
rm.log.WithField("repo", this).Debugf("Checking repo %s", this)
return rm.CheckRepo(this)
})
}
return kerrs.NewAggregate(eg.Wait())
}
func (rm *repositoryManager) PruneAllRepos() error {
repos, err := rm.getAllRepos()
if err != nil {
return err
}
var eg sync.ErrorGroup
for _, repo := range repos {
this := repo
eg.Go(func() error {
rm.log.WithField("repo", this).Debugf("Pre-prune checking repo %s", this)
if err := rm.CheckRepo(this); err != nil {
return err
}
rm.log.WithField("repo", this).Debugf("Pruning repo %s", this)
if err := rm.PruneRepo(this); err != nil {
return err
}
rm.log.WithField("repo", this).Debugf("Post-prune checking repo %s", this)
return rm.CheckRepo(this)
})
}
return kerrs.NewAggregate(eg.Wait())
return rm.exec(InitCommand(repo.Spec.ResticIdentifier))
}
func (rm *repositoryManager) CheckRepo(name string) error {
repo, err := getRepo(rm.repoLister, rm.namespace, name)
if err != nil {
return err
}
rm.repoLocker.LockExclusive(name)
defer rm.repoLocker.UnlockExclusive(name)
cmd := CheckCommand(rm.config.repoPrefix, name)
cmd := CheckCommand(repo.Spec.ResticIdentifier)
return errorOnly(rm.exec(cmd))
return rm.exec(cmd)
}
func (rm *repositoryManager) PruneRepo(name string) error {
repo, err := getReadyRepo(rm.repoLister, rm.namespace, name)
if err != nil {
return err
}
rm.repoLocker.LockExclusive(name)
defer rm.repoLocker.UnlockExclusive(name)
cmd := PruneCommand(rm.config.repoPrefix, name)
cmd := PruneCommand(repo.Spec.ResticIdentifier)
return errorOnly(rm.exec(cmd))
return rm.exec(cmd)
}
func (rm *repositoryManager) Forget(snapshot SnapshotIdentifier) error {
repo, err := getReadyRepo(rm.repoLister, rm.namespace, snapshot.Repo)
if err != nil {
return err
}
rm.repoLocker.LockExclusive(snapshot.Repo)
defer rm.repoLocker.UnlockExclusive(snapshot.Repo)
cmd := ForgetCommand(rm.config.repoPrefix, snapshot.Repo, snapshot.SnapshotID)
cmd := ForgetCommand(repo.Spec.ResticIdentifier, snapshot.SnapshotID)
return errorOnly(rm.exec(cmd))
return rm.exec(cmd)
}
func (rm *repositoryManager) exec(cmd *Command) ([]byte, error) {
file, err := TempCredentialsFile(rm.secretsLister, cmd.Repo)
func (rm *repositoryManager) exec(cmd *Command) error {
file, err := TempCredentialsFile(rm.secretsLister, cmd.RepoName())
if err != nil {
return nil, err
return err
}
// ignore error since there's nothing we can do and it's a temp file.
defer os.Remove(file)
cmd.PasswordFile = file
output, err := cmd.Cmd().Output()
rm.log.WithField("repository", cmd.Repo).Debugf("Ran restic command=%q, output=%s", cmd.String(), output)
stdout, stderr, err := arkexec.RunCommand(cmd.Cmd())
rm.log.WithFields(logrus.Fields{
"repository": cmd.RepoName(),
"command": cmd.String(),
"stdout": stdout,
"stderr": stderr,
}).Debugf("Ran restic command")
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return nil, errors.Wrapf(err, "error running command, stderr=%s", exitErr.Stderr)
}
return nil, errors.Wrap(err, "error running command")
return errors.Wrapf(err, "error running command=%s, stdout=%s, stderr=%s", cmd.String(), stdout, stderr)
}
return output, nil
}
func errorOnly(_ interface{}, err error) error {
return err
return nil
}
+21 -7
View File
@@ -28,6 +28,7 @@ import (
"k8s.io/client-go/tools/cache"
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
arkv1listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/util/boolptr"
)
@@ -40,16 +41,24 @@ type Restorer interface {
type restorer struct {
ctx context.Context
repoManager *repositoryManager
repoLister arkv1listers.ResticRepositoryLister
resultsLock sync.Mutex
results map[string]chan *arkv1api.PodVolumeRestore
}
func newRestorer(ctx context.Context, rm *repositoryManager, podVolumeRestoreInformer cache.SharedIndexInformer) *restorer {
func newRestorer(
ctx context.Context,
rm *repositoryManager,
podVolumeRestoreInformer cache.SharedIndexInformer,
repoLister arkv1listers.ResticRepositoryLister,
) *restorer {
r := &restorer{
ctx: ctx,
repoManager: rm,
results: make(map[string]chan *arkv1api.PodVolumeRestore),
repoLister: repoLister,
results: make(map[string]chan *arkv1api.PodVolumeRestore),
}
podVolumeRestoreInformer.AddEventHandler(
@@ -76,6 +85,11 @@ func (r *restorer) RestorePodVolumes(restore *arkv1api.Restore, pod *corev1api.P
return nil
}
repo, err := getReadyRepo(r.repoLister, restore.Namespace, pod.Namespace)
if err != nil {
return []error{err}
}
resultsChan := make(chan *arkv1api.PodVolumeRestore)
r.resultsLock.Lock()
@@ -91,7 +105,7 @@ func (r *restorer) RestorePodVolumes(restore *arkv1api.Restore, pod *corev1api.P
r.repoManager.repoLocker.Lock(pod.Namespace)
defer r.repoManager.repoLocker.Unlock(pod.Namespace)
volumeRestore := newPodVolumeRestore(restore, pod, volume, snapshot, r.repoManager.config.repoPrefix)
volumeRestore := newPodVolumeRestore(restore, pod, volume, snapshot, repo.Spec.ResticIdentifier)
if err := errorOnly(r.repoManager.arkClient.ArkV1().PodVolumeRestores(volumeRestore.Namespace).Create(volumeRestore)); err != nil {
errs = append(errs, errors.WithStack(err))
@@ -120,7 +134,7 @@ ForEachVolume:
return errs
}
func newPodVolumeRestore(restore *arkv1api.Restore, pod *corev1api.Pod, volume, snapshot, repoPrefix string) *arkv1api.PodVolumeRestore {
func newPodVolumeRestore(restore *arkv1api.Restore, pod *corev1api.Pod, volume, snapshot, repoIdentifier string) *arkv1api.PodVolumeRestore {
return &arkv1api.PodVolumeRestore{
ObjectMeta: metav1.ObjectMeta{
Namespace: restore.Namespace,
@@ -147,9 +161,9 @@ func newPodVolumeRestore(restore *arkv1api.Restore, pod *corev1api.Pod, volume,
Name: pod.Name,
UID: pod.UID,
},
Volume: volume,
SnapshotID: snapshot,
RepoPrefix: repoPrefix,
Volume: volume,
SnapshotID: snapshot,
RepoIdentifier: repoIdentifier,
},
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
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 exec
import (
"bytes"
"io/ioutil"
"os/exec"
"github.com/pkg/errors"
)
// RunCommand runs a command and returns its stdout, stderr, and its returned
// error (if any). If there are errors reading stdout or stderr, their return
// value(s) will contain the error as a string.
func RunCommand(cmd *exec.Cmd) (string, string, error) {
stdoutBuf := new(bytes.Buffer)
stderrBuf := new(bytes.Buffer)
cmd.Stdout = stdoutBuf
cmd.Stderr = stderrBuf
runErr := cmd.Run()
var stdout, stderr string
if res, readErr := ioutil.ReadAll(stdoutBuf); readErr != nil {
stdout = errors.Wrap(readErr, "error reading command's stdout").Error()
} else {
stdout = string(res)
}
if res, readErr := ioutil.ReadAll(stderrBuf); readErr != nil {
stderr = errors.Wrap(readErr, "error reading command's stderr").Error()
} else {
stderr = string(res)
}
return stdout, stderr, runErr
}