Make secret file optional on install (#1699)

* Make secret file optional on install

Fixes #1689

Signed-off-by: Nolan Brubaker <brubakern@vmware.com>
pull/1721/head
Nolan Brubaker 2019-08-01 18:57:36 -04:00 committed by KubeKween
parent 2a6929d453
commit 635dd27e1a
9 changed files with 92 additions and 63 deletions

View File

@ -0,0 +1 @@
Make --secret-file argument on `velero install` optional, add --no-secret flag for explicit confirmation

View File

@ -55,6 +55,7 @@ type InstallOptions struct {
ResticPodMemLimit string
RestoreOnly bool
SecretFile string
NoSecret bool
DryRun bool
BackupStorageConfig flag.Map
VolumeSnapshotConfig flag.Map
@ -67,7 +68,8 @@ type InstallOptions struct {
func (o *InstallOptions) BindFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.ProviderName, "provider", o.ProviderName, "provider name for backup and volume storage")
flags.StringVar(&o.BucketName, "bucket", o.BucketName, "name of the object storage bucket where backups should be stored")
flags.StringVar(&o.SecretFile, "secret-file", o.SecretFile, "file containing credentials for backup and volume provider")
flags.StringVar(&o.SecretFile, "secret-file", o.SecretFile, "file containing credentials for backup and volume provider. If not specified, --no-secret must be used for confirmation. Optional.")
flags.BoolVar(&o.NoSecret, "no-secret", o.NoSecret, "flag indicating if a secret should be created. Must be used as confirmation if --secret-file is not provided. Optional.")
flags.StringVar(&o.Image, "image", o.Image, "image to use for the Velero and restic server pods. Optional.")
flags.StringVar(&o.Prefix, "prefix", o.Prefix, "prefix under which all Velero data should be stored within the bucket. Optional.")
flags.Var(&o.PodAnnotations, "pod-annotations", "annotations to add to the Velero and restic pods. Optional. Format is key1=value1,key2=value2")
@ -112,14 +114,17 @@ func NewInstallOptions() *InstallOptions {
// AsVeleroOptions translates the values provided at the command line into values used to instantiate Kubernetes resources
func (o *InstallOptions) AsVeleroOptions() (*install.VeleroOptions, error) {
var secretData []byte
if o.SecretFile != "" && !o.NoSecret {
realPath, err := filepath.Abs(o.SecretFile)
if err != nil {
return nil, err
}
secretData, err := ioutil.ReadFile(realPath)
secretData, err = ioutil.ReadFile(realPath)
if err != nil {
return nil, err
}
}
veleroPodResources, err := parseResourceRequests(o.VeleroPodCPURequest, o.VeleroPodMemRequest, o.VeleroPodCPULimit, o.VeleroPodMemLimit)
if err != nil {
return nil, err
@ -179,7 +184,7 @@ This is useful as a starting point for more customized installations.
# velero install --bucket gcp-backups --provider gcp --secret-file ./gcp-creds.json --wait
# velero install --bucket backups --provider aws --backup-location-config region=us-west-2 --secret-file ./an-empty-file --snapshot-location-config region=us-west-2 --pod-annotations iam.amazonaws.com/role=arn:aws:iam::<AWS_ACCOUNT_ID>:role/<VELERO_ROLE_NAME>
# velero install --bucket backups --provider aws --backup-location-config region=us-west-2 --snapshot-location-config region=us-west-2 --no-secret --pod-annotations iam.amazonaws.com/role=arn:aws:iam::<AWS_ACCOUNT_ID>:role/<VELERO_ROLE_NAME>
# velero install --bucket gcp-backups --provider gcp --secret-file ./gcp-creds.json --velero-pod-cpu-request=1000m --velero-pod-cpu-limit=5000m --velero-pod-mem-request=512Mi --velero-pod-mem-limit=1024Mi
@ -238,6 +243,9 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error {
return errors.Wrap(err, errorMsg)
}
}
if o.SecretFile == "" {
fmt.Printf("\nNo secret file was specified, no Secret created.\n\n")
}
fmt.Printf("Velero is installed! ⛵ Use 'kubectl logs deployment/velero -n %s' to view the status.\n", o.Namespace)
return nil
}
@ -268,8 +276,11 @@ func (o *InstallOptions) Validate(c *cobra.Command, args []string, f client.Fact
return errors.New("--provider is required")
}
if o.SecretFile == "" {
return errors.New("--secret-file is required")
switch {
case o.SecretFile == "" && !o.NoSecret:
return errors.New("One of --secret-file or --no-secret is required")
case o.SecretFile != "" && o.NoSecret:
return errors.New("Cannot use both --secret-file and --no-secret")
}
return nil

View File

@ -129,18 +129,6 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1.DaemonSet {
Name: "VELERO_SCRATCH_DIR",
Value: "/scratch",
},
{
Name: "AZURE_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
{
Name: "GOOGLE_APPLICATION_CREDENTIALS",
Value: "/credentials/cloud",
},
{
Name: "AWS_SHARED_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
},
Resources: c.resources,
},
@ -150,7 +138,7 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1.DaemonSet {
},
}
if !c.withoutCredentialsVolume {
if c.withSecret {
daemonSet.Spec.Template.Spec.Volumes = append(
daemonSet.Spec.Template.Spec.Volumes,
corev1.Volume{
@ -170,6 +158,21 @@ func DaemonSet(namespace string, opts ...podTemplateOption) *appsv1.DaemonSet {
MountPath: "/credentials",
},
)
daemonSet.Spec.Template.Spec.Containers[0].Env = append(daemonSet.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: "GOOGLE_APPLICATION_CREDENTIALS",
Value: "/credentials/cloud",
},
{
Name: "AWS_SHARED_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
{
Name: "AZURE_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
}...)
}
daemonSet.Spec.Template.Spec.Containers[0].Env = append(daemonSet.Spec.Template.Spec.Containers[0].Env, c.envVars...)

View File

@ -29,10 +29,11 @@ func TestDaemonSet(t *testing.T) {
assert.Equal(t, "restic", ds.Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, "velero", ds.ObjectMeta.Namespace)
ds = DaemonSet("velero", WithoutCredentialsVolume())
assert.Equal(t, 2, len(ds.Spec.Template.Spec.Volumes))
ds = DaemonSet("velero", WithImage("gcr.io/heptio-images/velero:v0.11"))
assert.Equal(t, "gcr.io/heptio-images/velero:v0.11", ds.Spec.Template.Spec.Containers[0].Image)
assert.Equal(t, corev1.PullIfNotPresent, ds.Spec.Template.Spec.Containers[0].ImagePullPolicy)
ds = DaemonSet("velero", WithSecret(true))
assert.Equal(t, 6, len(ds.Spec.Template.Spec.Containers[0].Env))
assert.Equal(t, 3, len(ds.Spec.Template.Spec.Volumes))
}

View File

@ -28,11 +28,11 @@ type podTemplateOption func(*podTemplateConfig)
type podTemplateConfig struct {
image string
withoutCredentialsVolume bool
envVars []corev1.EnvVar
restoreOnly bool
annotations map[string]string
resources corev1.ResourceRequirements
withSecret bool
}
func WithImage(image string) podTemplateOption {
@ -47,12 +47,6 @@ func WithAnnotations(annotations map[string]string) podTemplateOption {
}
}
func WithoutCredentialsVolume() podTemplateOption {
return func(c *podTemplateConfig) {
c.withoutCredentialsVolume = true
}
}
func WithEnvFromSecretKey(varName, secret, key string) podTemplateOption {
return func(c *podTemplateConfig) {
c.envVars = append(c.envVars, corev1.EnvVar{
@ -69,6 +63,12 @@ func WithEnvFromSecretKey(varName, secret, key string) podTemplateOption {
}
}
func WithSecret(secretPresent bool) podTemplateOption {
return func(c *podTemplateConfig) {
c.withSecret = secretPresent
}
}
func WithRestoreOnly() podTemplateOption {
return func(c *podTemplateConfig) {
c.restoreOnly = true
@ -144,18 +144,6 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment
Name: "VELERO_SCRATCH_DIR",
Value: "/scratch",
},
{
Name: "GOOGLE_APPLICATION_CREDENTIALS",
Value: "/credentials/cloud",
},
{
Name: "AWS_SHARED_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
{
Name: "AZURE_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
},
Resources: c.resources,
},
@ -179,7 +167,7 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment
},
}
if !c.withoutCredentialsVolume {
if c.withSecret {
deployment.Spec.Template.Spec.Volumes = append(
deployment.Spec.Template.Spec.Volumes,
corev1.Volume{
@ -199,6 +187,21 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment
MountPath: "/credentials",
},
)
deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: "GOOGLE_APPLICATION_CREDENTIALS",
Value: "/credentials/cloud",
},
{
Name: "AWS_SHARED_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
{
Name: "AZURE_CREDENTIALS_FILE",
Value: "/credentials/cloud",
},
}...)
}
deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, c.envVars...)

View File

@ -32,15 +32,16 @@ func TestDeployment(t *testing.T) {
assert.Equal(t, "--restore-only", deploy.Spec.Template.Spec.Containers[0].Args[1])
deploy = Deployment("velero", WithEnvFromSecretKey("my-var", "my-secret", "my-key"))
envSecret := deploy.Spec.Template.Spec.Containers[0].Env[4]
envSecret := deploy.Spec.Template.Spec.Containers[0].Env[1]
assert.Equal(t, "my-var", envSecret.Name)
assert.Equal(t, "my-secret", envSecret.ValueFrom.SecretKeyRef.LocalObjectReference.Name)
assert.Equal(t, "my-key", envSecret.ValueFrom.SecretKeyRef.Key)
deploy = Deployment("velero", WithoutCredentialsVolume())
assert.Equal(t, 2, len(deploy.Spec.Template.Spec.Volumes))
deploy = Deployment("velero", WithImage("gcr.io/heptio-images/velero:v0.11"))
assert.Equal(t, "gcr.io/heptio-images/velero:v0.11", deploy.Spec.Template.Spec.Containers[0].Image)
assert.Equal(t, corev1.PullIfNotPresent, deploy.Spec.Template.Spec.Containers[0].ImagePullPolicy)
deploy = Deployment("velero", WithSecret(true))
assert.Equal(t, 4, len(deploy.Spec.Template.Spec.Containers[0].Env))
assert.Equal(t, 3, len(deploy.Spec.Template.Spec.Volumes))
}

View File

@ -229,8 +229,10 @@ func AllResources(o *VeleroOptions) (*unstructured.UnstructuredList, error) {
sa := ServiceAccount(o.Namespace)
appendUnstructured(resources, sa)
if o.SecretData != nil {
sec := Secret(o.Namespace, o.SecretData)
appendUnstructured(resources, sec)
}
bsl := BackupStorageLocation(o.Namespace, o.ProviderName, o.Bucket, o.Prefix, o.BSLConfig)
appendUnstructured(resources, bsl)
@ -241,15 +243,19 @@ func AllResources(o *VeleroOptions) (*unstructured.UnstructuredList, error) {
appendUnstructured(resources, vsl)
}
secretPresent := o.SecretData != nil
deploy := Deployment(o.Namespace,
WithAnnotations(o.PodAnnotations),
WithImage(o.Image),
WithResources(o.VeleroPodResources),
WithSecret(secretPresent),
)
if o.RestoreOnly {
deploy = Deployment(o.Namespace,
WithAnnotations(o.PodAnnotations),
WithImage(o.Image),
WithSecret(secretPresent),
WithRestoreOnly(),
)
}
@ -257,9 +263,11 @@ func AllResources(o *VeleroOptions) (*unstructured.UnstructuredList, error) {
if o.UseRestic {
ds := DaemonSet(o.Namespace,
WithAnnotations(o.PodAnnotations),
WithImage(o.Image),
WithResources(o.ResticPodResources),
WithSecret(secretPresent),
)
appendUnstructured(resources, ds)
}

View File

@ -292,13 +292,11 @@ velero install \
--pod-annotations iam.amazonaws.com/role=arn:aws:iam::<AWS_ACCOUNT_ID>:role/<VELERO_ROLE_NAME> \
--provider aws \
--bucket $BUCKET \
--secret-file ./credentials-velero \
--backup-location-config region=$REGION \
--snapshot-location-config region=$REGION
--snapshot-location-config region=$REGION \
--no-secret
```
Note that the `--secret-file` argument is required, but it can be an empty file.
[0]: namespace.md
[5]: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html
[6]: api-types/volumesnapshotlocation.md#aws

View File

@ -14,7 +14,8 @@ The Velero client includes an `install` command to specify the settings for each
velero install \
--provider <YOUR_PROVIDER> \
--bucket <YOUR_BUCKET> \
--secret-file <PATH_TO_FILE> \
[--secret-file <PATH_TO_FILE>] \
[--no-secret] \
[--backup-location-config] \
[--snapshot-location-config] \
[--namespace] \
@ -23,6 +24,8 @@ velero install \
[--pod-annotations] \
```
When using node-based IAM policies, `--secret-file` is not required, but `--no-secret` is required for confirmation.
For provider-specific instructions, see:
* [Run Velero on AWS][0]