Repository: kinvolk/lokomotive
Pull request #881 (2020-08-29 16:51:02)

Update Velero component for Packet [openebs and restic plugin support]
This PR adds supports for Packet platform for Packet platform with the help of OpenEBS and Restic plugin.

TODO:

* e2e tests
* Documentation.


Please review and provide your valuable feedback.

ipochi: I removed the commit that is associated with this Pr #971 since the direction of the mentioned PR is still not sure.

There was an issue with OpenEBS plugin which has been addressed.

For e2e tests there are a few options that we could use (we need velero binary to create a backup)

1. Create a scheduled backup for e2e tests. One of th open bugs in Velero is that once a scheduled is created the backup is triggered immediately. We could exploit that behaviour in CI for e2e tests.

2. For restore we can use an already backup snapshot that can be restored on every CI job. Successful restore to end the test. 

However we need to find a way to trigger the backup(if not using scheduled backup)/restore using the velero binary.
invidian: >However we need to find a way to trigger the backup(if not using scheduled backup)/restore without using the velero binary.

I'd create a pod/job with velero binary to just manually create a backup `velero create backup --include-namespaces <foo>` and wait for backup to finish.

Then remove the namespace.

Then try to recover it from the backup.

Then run e2e tests again? E.g. we could remove prometheus-operator namespace and run it's tests to make sure it recovered.

BTW, should we do e2e tests as separate PR? This one is already quite large.
ipochi: > > However we need to find a way to trigger the backup(if not using scheduled backup)/restore without using the velero binary.
> 
> I'd create a pod/job with velero binary to just manually create a backup `velero create backup --include-namespaces <foo>` and wait for backup to finish.
> 
> Then remove the namespace.
> 
> Then try to recover it from the backup.
> 
> Then run e2e tests again? E.g. we could remove prometheus-operator namespace and run it's tests to make sure it recovered.
> 
> BTW, should we do e2e tests as separate PR? This one is already quite large.

Yes @invidian thats the idea to cover e2e tests in a separate PR.

Patch:
diff --git a/docs/configuration-reference/components/velero.md b/docs/configuration-reference/components/velero.md
index 151c4d72..ada418f8 100644
--- a/docs/configuration-reference/components/velero.md
+++ b/docs/configuration-reference/components/velero.md
@@ -16,13 +16,12 @@ cluster resources and persistent volumes.
 
 ## Prerequisites
 
-* A Lokomotive cluster accessible via `kubectl` deployed on Azure AKS.
-
-* Permissions to create Service Principals with Azure AKS.
+* A Lokomotive cluster accessible via `kubectl` deployed.
 
 ## Configuration
 
-The Velero component is currently supported only on Azure AKS. Support for another platforms will be added in the future.
+
+### Velero on AKS
 
 In order to use Velero on Azure, you need to have Application (Service Principal) created
 for it. This service account needs to have access to a storage account with blob storage,
@@ -30,41 +29,77 @@ where backups will be stored.
 
 Follow [velero-plugin-for-microsoft-azure#setup](https://github.com/vmware-tanzu/velero-plugin-for-microsoft-azure#setup) to set it up.
 
+### Example
+
 Velero component configuration example:
 
 ```tf
 # velero.lokocfg
 component "velero" {
 
-  # Required.
-  azure {
-    # Required arguments.
-    subscription_id = "9e5ac23c-6df8-44c4-9790-6f6decf96268"
-    tenant_id       = "78bdc534-b34f-4bda-a6ca-6df52915b0b5"
-    client_id       = "d44117a8-b69d-437b-9073-e4e3b25e164a"
-    client_secret   = "c26f9698-a563-409e-87ee-4dcf96007b73"
-    resource_group  = "my-resource-group"
-
-    backup_storage_location {
-      resource_group  = "my-resource-group"
-      storage_account = "mybackupstorageaccount"
-      bucket          = "backupscontainer"
-    }
-
-    # Optional parameters
-    volume_snapshot_location {
-      resource_group = "my-resource-group"
-      api_timeout    = "10m"
-    }
-  }
+  # azure {
+  #   # Required arguments.
+  #   subscription_id = "9e5ac23c-6df8-44c4-9790-6f6decf96268"
+  #   tenant_id       = "78bdc534-b34f-4bda-a6ca-6df52915b0b5"
+  #   client_id       = "d44117a8-b69d-437b-9073-e4e3b25e164a"
+  #   client_secret   = "c26f9698-a563-409e-87ee-4dcf96007b73"
+  #   resource_group  = "my-resource-group"
+  #
+  #   backup_storage_location {
+  #     resource_group  = "my-resource-group"
+  #     storage_account = "mybackupstorageaccount"
+  #     bucket          = "backupscontainer"
+  #   }
+  #
+  #   # Optional parameters
+  #   volume_snapshot_location {
+  #     resource_group = "my-resource-group"
+  #     api_timeout    = "10m"
+  #   }
+  # }
+
+  # openebs {
+  #   credentials = file("cloud-credentails-file")
+  #		provider		= "aws"
+	#
+  #   backup_storage_location {
+  #     provider = "aws"
+  #     region 	 = "my-region"
+  #     bucket 	 = "my-bucket"
+  #			name     = "my-backup-location"
+  #   }
+  #
+  #   volume_snapshot_location {
+  #			bucket 	 = "my-bucket"
+  #			region 	 = "my-region"
+  #			provider = "aws"
+  #			name 		 = "my-snapshot-location"
+  #     prefix   = "backup-prefix"
+  #     local    = false
+  #
+  #     openebs_namespace = "openebs"
+  #
+  #     s3_url = "mybucket.example.com"
+  #   }
+  # }
+
+  # restic {
+  #   credentials = file("cloud-credentials-file")
+  #
+  #   backup_storage_location {
+  #     provider = "aws"
+  #     bucket   = "my-bucket"
+  #     name     = "my-backup-location"
+  #   }
+  # }
 
   # Optional.
   metrics {
-    enabled = false
+    enabled         = false
     service_monitor = false
   }
 
-  provider = "azure"
+  provider  = "azure"
   namespace = "velero"
 }
 ```
@@ -75,27 +110,47 @@ Table of all the arguments accepted by the component.
 
 Example:
 
-| Argument                                        | Description                                                                                                                 |                      Default                      |  Type  | Required |
-|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------:|:------:|:--------:|
-| `provider`                                      | Supported provider name. Only `azure` is supported for now.                                                                 |                      "azure"                      | string |  false   |
-| `namespace`                                     | Namespace to install Velero.                                                                                                |                     "velero"                      | string |  false   |
-| `metrics`                                       | Configure Prometheus to scrape Velero metrics. Needs the [Prometheus Operator component](prometheus-operator.md) installed. |                         -                         | object |  false   |
-| `metrics.enabled`                               | Adds Prometheus annotations to Velero deployment if enabled.                                                                |                       false                       |  bool  |  false   |
-| `metrics.service_monitor`                       | Adds ServiceMonitor resource for Prometheus. Requires `metrics.enabled` as true.                                            |                       false                       |  bool  |  false   |
-| `azure`                                         | Configure Azure provider for Velero.                                                                                        |                         -                         | object |   true   |
-| `azure.subscription_id`                         | Azure Subscription ID where client application is created. Can be obtained with `az account list`.                          |                         -                         | string |   true   |
-| `azure.tenant_id`                               | Azure Tenant ID where your subscription is created. Can be obtained with `az account list`.                                 |                         -                         | string |   true   |
-| `azure.client_id`                               | Azure Application Client ID to perform Azure operations.                                                                    |                         -                         | string |   true   |
-| `azure.client_secret`                           | Azure Application Client secret.                                                                                            |                         -                         | string |   true   |
-| `azure.resource_group`                          | Azure resource group, where PVC Disks are created. If this argument is wrong, Velero will fail to create PVC snapshots.     |                         -                         | string |   true   |
-| `azure.backup_storage_location`                 | Configure backup storage location and metadata.                                                                             |                         -                         | object |   true   |
-| `azure.backup_storage_location.resource_group`  | Name of the resource group containing the storage account for this backup storage location.                                 |                         -                         | string |   true   |
-| `azure.backup_storage_location.storage_account` | Name of the storage account for this backup storage location.                                                               |                         -                         | string |   true   |
-| `azure.backup_storage_location.bucket`          | Name of the storage container to store backups.                                                                             |                         -                         | string |   true   |
-| `azure.volume_snapshot_location`                | Configure PVC snapshot location.                                                                                            |                         -                         | object |  false   |
-| `azure.volume_snapshot_location.resource_group` | Azure Resource Group where snapshots will be stored.                                                                        | Stored in the same resource group as the cluster. | string |  false   |
-| `azure.volume_snapshot_location.api_timeout`    | Azure API timeout.                                                                                                          |                       "10m"                       | string |  false   |
-
+| Argument                                             | Description                                                                                                                 | Default                                           | Type   | Required |
+|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|--:-:----------------------------------------------|--:-:---|--:-:-----|
+| `namespace`                                          | Namespace to install Velero.                                                                                                | "velero"                                          | string | false    |
+| `metrics`                                            | Configure Prometheus to scrape Velero metrics. Needs the [Prometheus Operator component](prometheus-operator.md) installed. | -                                                 | object | false    |
+| `metrics.enabled`                                    | Adds Prometheus annotations to Velero deployment if enabled.                                                                | false                                             | bool   | false    |
+| `metrics.service_monitor`                            | Adds ServiceMonitor resource for Prometheus. Requires `metrics.enabled` as true.                                            | false                                             | bool   | false    |
+| `azure`                                              | Configure Azure provider for Velero.                                                                                        | -                                                 | object | false    |
+| `azure.subscription_id`                              | Azure Subscription ID where client application is created. Can be obtained with `az account list`.                          | -                                                 | string | true     |
+| `azure.tenant_id`                                    | Azure Tenant ID where your subscription is created. Can be obtained with `az account list`.                                 | -                                                 | string | true     |
+| `azure.client_id`                                    | Azure Application Client ID to perform Azure operations.                                                                    | -                                                 | string | true     |
+| `azure.client_secret`                                | Azure Application Client secret.                                                                                            | -                                                 | string | true     |
+| `azure.resource_group`                               | Azure resource group, where PVC Disks are created. If this argument is wrong, Velero will fail to create PVC snapshots.     | -                                                 | string | true     |
+| `azure.backup_storage_location`                      | Configure backup storage location and metadata.                                                                             | -                                                 | object | true     |
+| `azure.backup_storage_location.resource_group`       | Name of the resource group containing the storage account for this backup storage location.                                 | -                                                 | string | true     |
+| `azure.backup_storage_location.storage_account`      | Name of the storage account for this backup storage location.                                                               | -                                                 | string | true     |
+| `azure.backup_storage_location.bucket`               | Name of the storage container to store backups.                                                                             | -                                                 | string | true     |
+| `azure.volume_snapshot_location`                     | Configure PVC snapshot location.                                                                                            | -                                                 | object | false    |
+| `azure.volume_snapshot_location.resource_group`      | Azure Resource Group where snapshots will be stored.                                                                        | Stored in the same resource group as the cluster. | string | false    |
+| `azure.volume_snapshot_location.api_timeout`         | Azure API timeout.                                                                                                          | "10m"                                             | string | false    |
+| `openebs`                                            | Configure OpenEBS provider for Velero.                                                                                      | -                                                 | object | false    |
+| `openebs.credentials`                                | Content of cloud provider credentials.                                                                                      | -                                                 | string | true     |
+| `openebs.provider`                                   | Cloud provider to use for backup and snapshot storage. Supported values are `gcp` and `aws`.                                | -                                                 | string | false    |
+| `openebs.backup_storage_location`                    | Configure backup storage location.                                                                                          | -                                                 | object | true     |
+| `openebs.backup_storage_location.region`             | Cloud provider region for storing backups.                                                                                  | -                                                 | string | true     |
+| `openebs.backup_storage_location.bucket`             | Cloud storage bucket name for storing backups.                                                                              | -                                                 | string | true     |
+| `openebs.backup_storage_location.provider`           | Cloud provider name for storing backups. Overrides `openebs.provider` field for backup storage.                             | -                                                 | string | false    |
+| `openebs.backup_storage_location.name`               | Name for backup location object on the cluster.                                                                             | -                                                 | string | false    |
+| `openebs.volume_snapshot_location`                   | Configure volume snapshot location.                                                                                         | -                                                 | object | true     |
+| `openebs.volume_snapshot_location.bucket`            | Cloud storage bucket name for storing volume snapshots.                                                                     | -                                                 | string | true     |
+| `openebs.volume_snapshot_location.region`            | Cloud provider region for storing snapshots.                                                                                |                                                   | string | true     |
+| `openebs.volume_snapshot_location.provider`          | Cloud provider name for storing snapshots. Overrides `openebs.provider` field for backup storage.                           | -                                                 | string | false    |
+| `openebs.volume_snapshot_location.name`              | Name for snapshot location object on the cluster.                                                                           | -                                                 | string | false    |
+| `openebs.volume_snapshot_location.prefix`            | Prefix for snapshot names.                                                                                                  | -                                                 | string | false    |
+| `openebs.volume_snapshot_location.local`             | If `true`, backups won't be copied to cloud storage.                                                                        | false                                             | bool   | false    |
+| `openebs.volume_snapshot_location.openebs_namespace` | Name of the namespace where OpenEBS runs.                                                                                   | -                                                 | string | true     |
+| `openebs.volume_snapshot_location.s3_url`            | S3 API URL.                                                                                                                 | -                                                 | string | false    |
+| `restic`                                             | Configure Restic provider for Velero.                                                                                       | -                                                 | object | false    |
+| `restic.credentials`                                 | Content of cloud provider credentials.                                                                                      | -                                                 | string | true     |
+| `restic.backup_storage_location.provider`            | Cloud provider name for storing backups.                                                                                    | -                                                 | string | false    |
+| `restic.backup_storage_location.bucket`              | Cloud storage bucket name for storing backups.                                                                              | -                                                 | string | true     |
+| `restic.backup_storage_location.name`                | Name for backup location object on the cluster.                                                                             | -                                                 | string | false    |
 
 ## Applying
 
@@ -105,7 +160,7 @@ To apply the Velero component:
 lokoctl component apply velero
 ```
 
-### Post-insallation
+### Post-installation
 
 For day-to-day tasks, the `velero` CLI tool is the recommended way to interact with Velero.
 
diff --git a/internal/util.go b/internal/util.go
index 9c03354b..6d4ce6b1 100644
--- a/internal/util.go
+++ b/internal/util.go
@@ -15,6 +15,10 @@
 // Package internal contains the utility functions used across the codebase.
 package internal
 
+import (
+	"strings"
+)
+
 const (
 	// NamespaceLabelKey acts a placeholder for the generic key name
 	// `lokomotive.kinvolk.io/name`.
@@ -57,3 +61,28 @@ func MergeMaps(m1, m2 map[string]string) map[string]string {
 
 	return final
 }
+
+// Indent indents the given string after splitting it first on `\n`
+// and adds the space padding to each token by the provided indent number.
+func Indent(data string, indent int) string {
+	lines := strings.Split(data, "\n")
+
+	var gap string
+
+	// Calculate the gap/indent.
+	for i := 0; i < indent; i++ {
+		gap += " "
+	}
+
+	// For each line add the gap/indent.
+	for ind := range lines {
+		lines[ind] = gap + lines[ind]
+	}
+
+	// If the last line is empty then remove the indent from it.
+	if lines[len(lines)-1] == gap {
+		lines[len(lines)-1] = ""
+	}
+
+	return strings.Join(lines, "\n")
+}
diff --git a/pkg/components/gangway/component.go b/pkg/components/gangway/component.go
index 2c4aa9e3..2071ebda 100644
--- a/pkg/components/gangway/component.go
+++ b/pkg/components/gangway/component.go
@@ -28,7 +28,7 @@ import (
 
 const name = "gangway"
 
-func init() { //nolint:gocheckinit
+func init() { //nolint:gochecknoinits
 	components.Register(name, newComponent())
 }
 
diff --git a/pkg/components/linkerd/component.go b/pkg/components/linkerd/component.go
index d095a00b..de36c880 100644
--- a/pkg/components/linkerd/component.go
+++ b/pkg/components/linkerd/component.go
@@ -16,13 +16,13 @@ package linkerd
 
 import (
 	"fmt"
-	"strings"
 
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/gohcl"
 	"github.com/linkerd/linkerd2/pkg/tls"
 	"helm.sh/helm/v3/pkg/chartutil"
 
+	"github.com/kinvolk/lokomotive/internal"
 	internaltemplate "github.com/kinvolk/lokomotive/internal/template"
 	"github.com/kinvolk/lokomotive/pkg/components"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
@@ -134,29 +134,6 @@ func (c *component) Metadata() components.Metadata {
 	}
 }
 
-func indent(data string, indent int) string {
-	lines := strings.Split(data, "\n")
-
-	var gap string
-
-	// Calculate the gap/indent.
-	for i := 0; i < indent; i++ {
-		gap += " "
-	}
-
-	// For each line add the gap/indent.
-	for ind := range lines {
-		lines[ind] = gap + lines[ind]
-	}
-
-	// If the last line is empty then remove the indent from it.
-	if lines[len(lines)-1] == gap {
-		lines[len(lines)-1] = ""
-	}
-
-	return strings.Join(lines, "\n")
-}
-
 func mergeValuesFiles(dst, src string) (string, error) {
 	d, err := chartutil.ReadValues([]byte(dst))
 	if err != nil {
@@ -178,9 +155,9 @@ func generateCertificates() (cert, error) {
 	}
 
 	return cert{
-		Key:    indent(root.Cred.EncodePrivateKeyPEM(), 8),
-		Cert:   indent(root.Cred.Crt.EncodeCertificatePEM(), 8),
-		CA:     indent(root.Cred.Crt.EncodeCertificatePEM(), 4),
+		Key:    internal.Indent(root.Cred.EncodePrivateKeyPEM(), 8),
+		Cert:   internal.Indent(root.Cred.Crt.EncodeCertificatePEM(), 8),
+		CA:     internal.Indent(root.Cred.Crt.EncodeCertificatePEM(), 4),
 		Expiry: root.Cred.Crt.Certificate.NotAfter.String(),
 	}, nil
 }
diff --git a/pkg/components/velero/azure/azure.go b/pkg/components/velero/azure/azure.go
index cf3711b5..b5f8e836 100644
--- a/pkg/components/velero/azure/azure.go
+++ b/pkg/components/velero/azure/azure.go
@@ -12,9 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Package azure deals with configuring Velero azure plugin.
 package azure
 
 import (
+	"bytes"
+	"fmt"
+	"text/template"
+
 	"github.com/hashicorp/hcl/v2"
 )
 
@@ -31,6 +36,7 @@ type Configuration struct {
 
 // BackupStorageLocation stores information about storage account used for backups on Azure
 type BackupStorageLocation struct {
+	Name           string `hcl:"name,optional"`
 	ResourceGroup  string `hcl:"resource_group,optional"`
 	StorageAccount string `hcl:"storage_account,optional"`
 	Bucket         string `hcl:"bucket,optional"`
@@ -38,10 +44,24 @@ type BackupStorageLocation struct {
 
 // VolumeSnapshotLocation stores information where disk snapshots will be stored on Azure
 type VolumeSnapshotLocation struct {
+	Name          string `hcl:"name,optional"`
 	ResourceGroup string `hcl:"resource_group,optional"`
 	APITimeout    string `hcl:"api_timeout,optional"`
 }
 
+// Values returns Azure-specific values for Velero Helm chart.
+func (c *Configuration) Values() (string, error) {
+	t := template.Must(template.New("values").Parse(chartValuesTmpl))
+
+	var buf bytes.Buffer
+
+	if err := t.Execute(&buf, c); err != nil {
+		return "", fmt.Errorf("rendering azure values: %w", err)
+	}
+
+	return buf.String(), nil
+}
+
 // Validate validates azure specific parts in the configuration
 func (c *Configuration) Validate() hcl.Diagnostics {
 	var diagnostics hcl.Diagnostics
diff --git a/pkg/components/velero/azure/template.go b/pkg/components/velero/azure/template.go
new file mode 100644
index 00000000..2a0260e3
--- /dev/null
+++ b/pkg/components/velero/azure/template.go
@@ -0,0 +1,55 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// 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 azure
+
+const chartValuesTmpl = `
+configuration:
+  provider: azure
+  backupStorageLocation:
+    name: azure
+    provider: velero.io/azure
+    bucket: {{ .BackupStorageLocation.Bucket }}
+    config:
+      resourceGroup: {{ .BackupStorageLocation.ResourceGroup }}
+      storageAccount: {{ .BackupStorageLocation.StorageAccount }}
+  volumeSnapshotLocation:
+    {{- if .VolumeSnapshotLocation.Name }}
+    name: {{ .VolumeSnapshotLocation.Name }}
+    {{- end }}
+    provider: velero.io/azure
+    config:
+      {{- if .VolumeSnapshotLocation.ResourceGroup }}
+      resourceGroup: {{ .VolumeSnapshotLocation.ResourceGroup }}
+      {{- end }}
+      apitimeout: {{ .VolumeSnapshotLocation.APITimeout }}
+credentials:
+  secretContents:
+    cloud: |
+      AZURE_SUBSCRIPTION_ID: "{{ .SubscriptionID }}"
+      AZURE_TENANT_ID: "{{ .TenantID }}"
+      AZURE_CLIENT_ID: "{{ .ClientID }}"
+      AZURE_CLIENT_SECRET: "{{ .ClientSecret }}"
+      AZURE_RESOURCE_GROUP: "{{ .ResourceGroup }}"
+initContainers:
+- image: velero/velero-plugin-for-microsoft-azure:v1.0.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-azure
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+`
diff --git a/pkg/components/velero/velero.go b/pkg/components/velero/component.go
similarity index 65%
rename from pkg/components/velero/velero.go
rename to pkg/components/velero/component.go
index 1784cbc7..c7afe9a0 100644
--- a/pkg/components/velero/velero.go
+++ b/pkg/components/velero/component.go
@@ -25,6 +25,8 @@ import (
 	"github.com/kinvolk/lokomotive/pkg/components"
 	"github.com/kinvolk/lokomotive/pkg/components/util"
 	"github.com/kinvolk/lokomotive/pkg/components/velero/azure"
+	"github.com/kinvolk/lokomotive/pkg/components/velero/openebs"
+	"github.com/kinvolk/lokomotive/pkg/components/velero/restic"
 	"github.com/kinvolk/lokomotive/pkg/k8sutil"
 )
 
@@ -37,8 +39,6 @@ func init() {
 
 // component represents component configuration data
 type component struct {
-	// Once we support more than one provider, this field should not be optional anymore
-	Provider string `hcl:"provider,optional"`
 	// Namespace where velero resources should be installed. Defaults to 'velero'.
 	Namespace string `hcl:"namespace,optional"`
 	// Metrics specific configuration
@@ -46,6 +46,10 @@ type component struct {
 
 	// Azure specific parameters
 	Azure *azure.Configuration `hcl:"azure,block"`
+	// OpenEBS specific parameters.
+	OpenEBS *openebs.Configuration `hcl:"openebs,block"`
+	// Restic specific parameters.
+	Restic *restic.Configuration `hcl:"restic,block"`
 }
 
 // Metrics represents prometheus specific parameters
@@ -56,6 +60,7 @@ type Metrics struct {
 
 // Provider requires implementing config validation function for each provider
 type provider interface {
+	Values() (string, error)
 	Validate() hcl.Diagnostics
 }
 
@@ -63,51 +68,20 @@ type provider interface {
 func newComponent() *component {
 	return &component{
 		Namespace: "velero",
-		// Once we have more than one provider supported, we should remove the default value
-		Provider: "azure",
+		Metrics: &Metrics{
+			Enabled:        false,
+			ServiceMonitor: false,
+		},
 	}
 }
 
 const chartValuesTmpl = `
-configuration:
-  provider: {{ .Provider }}
-  backupStorageLocation:
-    name: {{ .Provider }}
-    bucket: {{ .Azure.BackupStorageLocation.Bucket }}
-    config:
-      resourceGroup: {{ .Azure.BackupStorageLocation.ResourceGroup }}
-      storageAccount: {{ .Azure.BackupStorageLocation.StorageAccount }}
-  volumeSnapshotLocation:
-    name: {{ .Provider }}
-    config:
-      {{- if .Azure.VolumeSnapshotLocation.ResourceGroup }}
-      resourceGroup: {{ .Azure.VolumeSnapshotLocation.ResourceGroup }}
-      {{- end }}
-      apitimeout: {{ .Azure.VolumeSnapshotLocation.APITimeout }}
-credentials:
-  secretContents:
-    cloud: |
-      AZURE_SUBSCRIPTION_ID: "{{ .Azure.SubscriptionID }}"
-      AZURE_TENANT_ID: "{{ .Azure.TenantID }}"
-      AZURE_CLIENT_ID: "{{ .Azure.ClientID }}"
-      AZURE_CLIENT_SECRET: "{{ .Azure.ClientSecret }}"
-      AZURE_RESOURCE_GROUP: "{{ .Azure.ResourceGroup }}"
 metrics:
   enabled: {{ .Metrics.Enabled }}
   serviceMonitor:
     enabled: {{ .Metrics.ServiceMonitor }}
     additionalLabels:
       release: prometheus-operator
-initContainers:
-- image: velero/velero-plugin-for-microsoft-azure:v1.0.0
-  imagePullPolicy: IfNotPresent
-  name: velero-plugin-for-azure
-  resources: {}
-  terminationMessagePath: /dev/termination-log
-  terminationMessagePolicy: File
-  volumeMounts:
-  - mountPath: /target
-    name: plugins
 `
 
 // LoadConfig decodes given HCL and validates the configuration.
@@ -133,9 +107,6 @@ func (c *component) LoadConfig(configBody *hcl.Body, evalContext *hcl.EvalContex
 		diagnostics = append(diagnostics, err...)
 	}
 
-	// Set default values in the component configuration if they are missing
-	c.setDefaults()
-
 	// Validate component's configuration
 	diagnostics = append(diagnostics, c.validate()...)
 
@@ -153,9 +124,9 @@ func (c *component) RenderManifests() (map[string]string, error) {
 		return nil, fmt.Errorf("retrieving chart from assets: %w", err)
 	}
 
-	values, err := template.Render(chartValuesTmpl, c)
+	values, err := c.values()
 	if err != nil {
-		return nil, fmt.Errorf("rendering chart values template: %w", err)
+		return nil, fmt.Errorf("getting values: %w", err)
 	}
 
 	renderedFiles, err := util.RenderChart(helmChart, name, c.Namespace, values)
@@ -166,48 +137,79 @@ func (c *component) RenderManifests() (map[string]string, error) {
 	return renderedFiles, nil
 }
 
-// setDefaults set default values for all nested blocks
-//
-// Since nested blocks in hcl2 does not support default values during DecodeBody,
-// we need to set the default value here, rather then adding diagnostics.
-// Once PR https://github.com/hashicorp/hcl2/pull/120 is released, this value can be set in
-// newComponent() and diagnostic can be added.
-func (c *component) setDefaults() {
-	if c.Metrics == nil {
-		c.Metrics = &Metrics{
-			Enabled:        false,
-			ServiceMonitor: false,
-		}
+// values renders common values for all providers, provider specific values and
+// concatenates them.
+func (c *component) values() (string, error) {
+	commonValues, err := template.Render(chartValuesTmpl, c)
+	if err != nil {
+		return "", fmt.Errorf("rendering common values template: %w", err)
+	}
+
+	p, err := c.getProvider()
+	if err != nil {
+		return "", fmt.Errorf("getting provider: %w", err)
+	}
+
+	values, err := p.Values()
+	if err != nil {
+		return "", fmt.Errorf("getting provider values: %w", err)
 	}
+
+	return commonValues + values, nil
 }
 
 // validate validates component configuration
 func (c *component) validate() hcl.Diagnostics {
 	diagnostics := hcl.Diagnostics{}
+	// Supported providers.
+	supportedProviders := c.getSupportedProviders()
 
 	// Select provider and validate it's configuration
 	p, err := c.getProvider()
 	if err != nil {
-		// Slice can't be constant, so just use a variable
-		supportedProviders := []string{"azure"}
 		return append(diagnostics, &hcl.Diagnostic{
 			Severity: hcl.DiagError,
 			Summary:  fmt.Sprintf("provider must be one of: '%s'", strings.Join(supportedProviders[:], "', '")),
-			Detail:   "Make sure to set provider to one of supported values",
+			Detail:   fmt.Sprintf("Make sure to set provider to one of supported values: %v", err.Error()),
 		})
 	}
 
 	return append(diagnostics, p.Validate()...)
 }
 
-// getProvider returns correct provider interface based on component configuration
+// getSupportedProviders returns a list of supported providers.
+func (c *component) getSupportedProviders() []string {
+	return []string{"azure", "openebs", "restic"}
+}
+
+// getProvider returns correct provider interface based on component configuration.
+//
+// If no providers are configured or there is more than one provider configured, error
+// is returned.
 func (c *component) getProvider() (provider, error) {
-	switch c.Provider {
-	case "azure":
-		return c.Azure, nil
-	default:
-		return nil, fmt.Errorf("unsupported provider '%s'", c.Provider)
+	providers := []provider{}
+
+	if c.Azure != nil {
+		providers = append(providers, c.Azure)
+	}
+
+	if c.OpenEBS != nil {
+		providers = append(providers, c.OpenEBS)
 	}
+
+	if c.Restic != nil {
+		providers = append(providers, c.Restic)
+	}
+
+	if len(providers) > 1 {
+		return nil, fmt.Errorf("more than one provider configured")
+	}
+
+	if len(providers) == 0 {
+		return nil, fmt.Errorf("no providers configured")
+	}
+
+	return providers[0], nil
 }
 
 func (c *component) Metadata() components.Metadata {
diff --git a/pkg/components/velero/openebs/openebs.go b/pkg/components/velero/openebs/openebs.go
new file mode 100644
index 00000000..2a059214
--- /dev/null
+++ b/pkg/components/velero/openebs/openebs.go
@@ -0,0 +1,219 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// 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 openebs deals with configuring Velero openebs plugin.
+package openebs
+
+import (
+	"bytes"
+	"fmt"
+	"text/template"
+
+	"github.com/hashicorp/hcl/v2"
+
+	"github.com/kinvolk/lokomotive/internal"
+)
+
+const indentation = 6
+
+// Configuration contains OpenEBS specific parameters.
+type Configuration struct {
+	Credentials            string                  `hcl:"credentials"`
+	Provider               string                  `hcl:"provider,optional"`
+	BackupStorageLocation  *BackupStorageLocation  `hcl:"backup_storage_location,block"`
+	VolumeSnapshotLocation *VolumeSnapshotLocation `hcl:"volume_snapshot_location,block"`
+}
+
+// BackupStorageLocation configures the backup storage location for OpenEBS plugin.
+type BackupStorageLocation struct {
+	Region   string `hcl:"region"`
+	Bucket   string `hcl:"bucket"`
+	Provider string `hcl:"provider,optional"`
+	Name     string `hcl:"name,optional"`
+}
+
+// validate validates BackupStorageLocation struct fields.
+func (b *BackupStorageLocation) validate(defaultProvider string) hcl.Diagnostics { // nolint:dupl
+	var diagnostics hcl.Diagnostics
+
+	if b == nil {
+		b = &BackupStorageLocation{}
+
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.backup_storage_location' block must be specified",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if b.Bucket == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.backup_storage_location.bucket' cannot be empty",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if b.Region == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.backup_storage_location.region' cannot be empty",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if b.Provider == "" && defaultProvider == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "either 'openebs.provider' or 'openebs.backup_storage_location.provider' must be set",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if !isSupportedProvider(b.Provider) {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary: fmt.Sprintf("openebs.backup_storage_location.provider must be one of: '%s'",
+				openEBSSupportedProviders()),
+			Detail: "Make sure to set provider to one of supported values",
+		})
+	}
+
+	return diagnostics
+}
+
+// VolumeSnapshotLocation configures the volume snapshot location for OpenEBS plugin.
+type VolumeSnapshotLocation struct {
+	Bucket           string `hcl:"bucket"`
+	Region           string `hcl:"region"`
+	Provider         string `hcl:"provider,optional"`
+	Name             string `hcl:"name,optional"`
+	Prefix           string `hcl:"prefix,optional"`
+	OpenEBSNamespace string `hcl:"openebs_namespace,optional"`
+	S3URL            string `hcl:"s3_url,optional"`
+	Local            bool   `hcl:"local,optional"`
+}
+
+// validate validates VolumeSnapshotLocation struct fields.
+func (v *VolumeSnapshotLocation) validate(defaultProvider string) hcl.Diagnostics { // nolint:dupl
+	var diagnostics hcl.Diagnostics
+
+	if v == nil {
+		v = &VolumeSnapshotLocation{}
+
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.volume_snapshot_location' block must be specified",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if v.Bucket == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.volume_snapshot_location.bucket' cannot be empty",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if v.Region == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'openebs.volume_snapshot_location.region' cannot be empty",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if v.Provider == "" && defaultProvider == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "either 'openebs.provider' or 'openebs.volume_snapshot_location.provider' must be set",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if !isSupportedProvider(v.Provider) {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary: fmt.Sprintf("openebs.volume_snapshot_location.provider must be one of: '%s'",
+				openEBSSupportedProviders()),
+			Detail: "Make sure to set provider to one of supported values",
+		})
+	}
+
+	return diagnostics
+}
+
+// Values returns the plugin specific values for Velero Helm chart.
+func (c *Configuration) Values() (string, error) {
+	t := template.Must(template.New("values").Parse(chartValuesTmpl))
+
+	var buf bytes.Buffer
+
+	v := struct {
+		Configuration       *Configuration
+		CredentialsIndented string
+	}{
+		Configuration:       c,
+		CredentialsIndented: internal.Indent(c.Credentials, indentation),
+	}
+
+	if err := t.Execute(&buf, v); err != nil {
+		return "", fmt.Errorf("executing values template: %w", err)
+	}
+
+	return buf.String(), nil
+}
+
+// Validate validates OpenEBS specific parts in the configuration.
+func (c *Configuration) Validate() hcl.Diagnostics {
+	var diagnostics hcl.Diagnostics
+	if c.Credentials == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'credentials' cannot be empty",
+			Detail:   "No credentials found",
+		})
+	}
+
+	if !isSupportedProvider(c.Provider) {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary: fmt.Sprintf("openebs.provider must be one of: '%s'",
+				openEBSSupportedProviders()),
+			Detail: "Make sure to set provider to one of supported values",
+		})
+	}
+
+	diagnostics = append(diagnostics, c.BackupStorageLocation.validate(c.Provider)...)
+	diagnostics = append(diagnostics, c.VolumeSnapshotLocation.validate(c.Provider)...)
+
+	return diagnostics
+}
+
+// isSupportedProvider checks if the provider is supported or not.
+func isSupportedProvider(provider string) bool {
+	for _, p := range openEBSSupportedProviders() {
+		if provider == p {
+			return true
+		}
+	}
+
+	return false
+}
+
+// openEBSSupportedProviders returns the list of supported providers.
+func openEBSSupportedProviders() []string {
+	return []string{"aws", "gcp"}
+}
diff --git a/pkg/components/velero/openebs/template.go b/pkg/components/velero/openebs/template.go
new file mode 100644
index 00000000..191ce6cc
--- /dev/null
+++ b/pkg/components/velero/openebs/template.go
@@ -0,0 +1,91 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// 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 openebs
+
+const chartValuesTmpl = `
+configuration:
+  provider: {{ .Configuration.BackupStorageLocation.Provider }}
+  backupStorageLocation:
+    {{- if .Configuration.BackupStorageLocation.Provider}}
+    provider: {{ .Configuration.BackupStorageLocation.Provider }}
+    {{- end }}
+    {{- if .Configuration.BackupStorageLocation.Name}}
+    name: {{ .Configuration.BackupStorageLocation.Name }}
+    {{- end }}
+    bucket: {{ .Configuration.BackupStorageLocation.Bucket }}
+    config:
+      region: {{ .Configuration.BackupStorageLocation.Region }}
+  volumeSnapshotLocation:
+    provider: openebs.io/cstor-blockstore
+    {{- if .Configuration.VolumeSnapshotLocation.Name }}
+    name: {{ .Configuration.VolumeSnapshotLocation.Name }}
+    {{- end }}
+    config:
+      bucket: {{ .Configuration.VolumeSnapshotLocation.Bucket }}
+      region: {{ .Configuration.VolumeSnapshotLocation.Region }}
+      {{- if .Configuration.VolumeSnapshotLocation.Provider}}
+      provider: {{ .Configuration.VolumeSnapshotLocation.Provider }}
+      {{- end }}
+      {{- if .Configuration.VolumeSnapshotLocation.Prefix }}
+      prefix: {{ .Configuration.VolumeSnapshotLocation.Prefix }}
+      {{- end }}
+      {{- if .Configuration.VolumeSnapshotLocation.OpenEBSNamespace }}
+      namespace: {{ .Configuration.VolumeSnapshotLocation.OpenEBSNamespace }}
+      {{- end }}
+      {{- if .Configuration.VolumeSnapshotLocation.S3URL }}
+      s3_url: {{ .Configuration.VolumeSnapshotLocation.S3URL }}
+      {{- end }}
+      {{- if .Configuration.VolumeSnapshotLocation.Local }}
+      local: {{ .Configuration.VolumeSnapshotLocation.Local }}
+      {{- end }}
+credentials:
+  secretContents:
+  {{- if .Configuration.Credentials }}
+    cloud: |
+{{ .CredentialsIndented }}
+  {{- end }}
+initContainers:
+- image: openebs/velero-plugin:2.0.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-openebs
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- if eq .Configuration.BackupStorageLocation.Provider "aws" }}
+- image: velero/velero-plugin-for-aws:v1.1.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-aws
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- end }}
+{{- if eq .Configuration.BackupStorageLocation.Provider "gcp" }}
+- image: velero/velero-plugin-for-gcp:v1.1.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-gcp
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- end }}
+`
diff --git a/pkg/components/velero/restic/restic.go b/pkg/components/velero/restic/restic.go
new file mode 100644
index 00000000..a7a8430d
--- /dev/null
+++ b/pkg/components/velero/restic/restic.go
@@ -0,0 +1,122 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// 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 deals with configuring restic plugin.
+package restic
+
+import (
+	"bytes"
+	"fmt"
+	"text/template"
+
+	"github.com/hashicorp/hcl/v2"
+
+	"github.com/kinvolk/lokomotive/internal"
+)
+
+const indentation = 6
+
+// Configuration contains Restic specific parameters.
+type Configuration struct {
+	Credentials           string                 `hcl:"credentials"`
+	BackupStorageLocation *BackupStorageLocation `hcl:"backup_storage_location,block"`
+}
+
+// BackupStorageLocation configures the backup storage location.
+type BackupStorageLocation struct {
+	Provider string `hcl:"provider"`
+	Bucket   string `hcl:"bucket"`
+	Name     string `hcl:"name,optional"`
+}
+
+// NewConfiguration returns the default restic configuration.
+func NewConfiguration() *Configuration {
+	return &Configuration{
+		BackupStorageLocation: &BackupStorageLocation{
+			Provider: "aws",
+			Name:     "default",
+		},
+	}
+}
+
+// Values returns plugin-specific value for Velero Helm chart.
+func (c *Configuration) Values() (string, error) {
+	t := template.Must(template.New("values").Parse(chartValuesTmpl))
+
+	var buf bytes.Buffer
+
+	v := struct {
+		Configuration       *Configuration
+		CredentialsIndented string
+	}{
+		Configuration:       c,
+		CredentialsIndented: internal.Indent(c.Credentials, indentation),
+	}
+
+	if err := t.Execute(&buf, v); err != nil {
+		return "", fmt.Errorf("executing values template: %w", err)
+	}
+
+	return buf.String(), nil
+}
+
+// Validate validates the restic configuration.
+func (c *Configuration) Validate() hcl.Diagnostics {
+	var diagnostics hcl.Diagnostics
+
+	if c.BackupStorageLocation == nil {
+		c.BackupStorageLocation = &BackupStorageLocation{}
+
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "'restic.backup_storage_location' block must be specified",
+			Detail:   "Make sure to the set the field to valid non-empty value",
+		})
+	}
+
+	if c.BackupStorageLocation.Bucket == "" {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary:  "restic.backup_storage_location.bucket must not be empty",
+			Detail:   "Make sure `bucket` value is set",
+		})
+	}
+
+	if !isSupportedProvider(c.BackupStorageLocation.Provider) {
+		diagnostics = append(diagnostics, &hcl.Diagnostic{
+			Severity: hcl.DiagError,
+			Summary: fmt.Sprintf("restic.backup_storage_location.provider must be one of: '%s'",
+				resticSupportedProviders()),
+			Detail: "Make sure to set provider to one of supported values",
+		})
+	}
+
+	return diagnostics
+}
+
+// isSupportedProvider checks if the provider is supported or not.
+func isSupportedProvider(provider string) bool {
+	for _, p := range resticSupportedProviders() {
+		if provider == p {
+			return true
+		}
+	}
+
+	return false
+}
+
+// resticSupportedProviders returns the list of supported providers.
+func resticSupportedProviders() []string {
+	return []string{"aws", "gcp", "azure"}
+}
diff --git a/pkg/components/velero/restic/template.go b/pkg/components/velero/restic/template.go
new file mode 100644
index 00000000..f7545342
--- /dev/null
+++ b/pkg/components/velero/restic/template.go
@@ -0,0 +1,74 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package restic
+
+const chartValuesTmpl = `
+configuration:
+  provider: {{ .Configuration.BackupStorageLocation.Provider }}
+  backupStorageLocation:
+    {{- if .Configuration.BackupStorageLocation.Provider}}
+    provider: {{ .Configuration.BackupStorageLocation.Provider }}
+    {{- end }}
+    {{- if .Configuration.BackupStorageLocation.Name}}
+    name: {{ .Configuration.BackupStorageLocation.Name }}
+    {{- end }}
+    bucket: {{ .Configuration.BackupStorageLocation.Bucket }}
+    config:
+      region: eu-west-1
+deployRestic: true
+snapshotsEnabled: false
+restic:
+  privileged: true
+credentials:
+  secretContents:
+  {{- if .Configuration.Credentials }}
+    cloud: |
+{{ .CredentialsIndented }}
+  {{- end }}
+initContainers:
+{{- if eq .Configuration.BackupStorageLocation.Provider "aws" }}
+- image: velero/velero-plugin-for-aws:v1.1.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-aws
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- end }}
+{{- if eq .Configuration.BackupStorageLocation.Provider "gcp" }}
+- image: velero/velero-plugin-for-gcp:v1.1.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-gcp
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- end }}
+{{- if eq .Configuration.BackupStorageLocation.Provider "azure" }}
+- image: velero/velero-plugin-for-microsoft-azure:v1.1.0
+  imagePullPolicy: IfNotPresent
+  name: velero-plugin-for-azure
+  resources: {}
+  terminationMessagePath: /dev/termination-log
+  terminationMessagePolicy: File
+  volumeMounts:
+  - mountPath: /target
+    name: plugins
+{{- end }}
+`


Test patch:
diff --git a/internal/util_test.go b/internal/util_test.go
index 97750835..dfb8a3ff 100644
--- a/internal/util_test.go
+++ b/internal/util_test.go
@@ -98,3 +98,66 @@ func TestMergeMapsNil(t *testing.T) {
 		t.Errorf("expected map to be empty but not nil, got: %v", final)
 	}
 }
+
+func TestIndent(t *testing.T) {
+	type args struct {
+		data   string
+		indent int
+	}
+
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			args: args{
+				data: `foo:
+  bar:
+  - baz`,
+				indent: 2,
+			},
+			want: `  foo:
+    bar:
+    - baz`,
+		},
+		{
+			args: args{
+				data:   "singleline",
+				indent: 3,
+			},
+			want: "   singleline",
+		},
+		{
+			args: args{
+				data: `a:
+  b: foobar
+`,
+				indent: 4,
+			},
+			want: `    a:
+      b: foobar
+`,
+		},
+		{
+			args: args{
+				data: `[default]
+aws_access_key=test_key
+aws_secret_access_key=secret_key`,
+				indent: 6,
+			},
+			want: `      [default]
+      aws_access_key=test_key
+      aws_secret_access_key=secret_key`,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			if got := internal.Indent(tt.args.data, tt.args.indent); got != tt.want {
+				t.Errorf("indent() = \n%v\nwant =\n%v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/components/linkerd/component_test.go b/pkg/components/linkerd/component_test.go
deleted file mode 100644
index 5bbb60e5..00000000
--- a/pkg/components/linkerd/component_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2020 The Lokomotive Authors
-//
-// 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.
-
-//nolint:testpackage
-package linkerd
-
-import "testing"
-
-func Test_indent(t *testing.T) {
-	type args struct {
-		data   string
-		indent int
-	}
-
-	tests := []struct {
-		name string
-		args args
-		want string
-	}{
-		{
-			args: args{
-				data: `foo:
-  bar:
-  - baz`,
-				indent: 2,
-			},
-			want: `  foo:
-    bar:
-    - baz`,
-		},
-		{
-			args: args{
-				data:   "singleline",
-				indent: 3,
-			},
-			want: "   singleline",
-		},
-		{
-			args: args{
-				data: `a:
-  b: foobar
-`,
-				indent: 4,
-			},
-			want: `    a:
-      b: foobar
-`,
-		},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-			if got := indent(tt.args.data, tt.args.indent); got != tt.want {
-				t.Errorf("indent() = \n%v\nwant =\n%v", got, tt.want)
-			}
-		})
-	}
-}
diff --git a/pkg/components/velero/component_test.go b/pkg/components/velero/component_test.go
new file mode 100644
index 00000000..fc922b67
--- /dev/null
+++ b/pkg/components/velero/component_test.go
@@ -0,0 +1,193 @@
+// Copyright 2020 The Lokomotive Authors
+//
+// 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 velero //nolint:testpackage
+
+import (
+	"testing"
+
+	"github.com/hashicorp/hcl/v2"
+
+	"github.com/kinvolk/lokomotive/pkg/components/util"
+)
+
+func TestEmptyConfig(t *testing.T) {
+	c := newComponent()
+
+	emptyConfig := hcl.EmptyBody()
+	evalContext := hcl.EvalContext{}
+	diagnostics := c.LoadConfig(&emptyConfig, &evalContext)
+
+	if !diagnostics.HasErrors() {
+		t.Errorf("Empty config should return error")
+	}
+}
+
+func TestRenderManifestAzure(t *testing.T) {
+	configHCL := `
+component "velero" {
+  azure {
+    subscription_id  = "foo"
+    tenant_id        = "foo"
+    client_id        = "foo"
+    client_secret    = "foo"
+    resource_group   = "foo"
+
+    backup_storage_location {
+      resource_group  = "foo"
+      storage_account = "foo"
+      bucket          = "foo"
+    }
+  }
+}
+`
+
+	component := newComponent()
+
+	body, diagnostics := util.GetComponentBody(configHCL, name)
+	if diagnostics != nil {
+		t.Fatalf("Error getting component body: %v", diagnostics)
+	}
+
+	diagnostics = component.LoadConfig(body, &hcl.EvalContext{})
+	if diagnostics.HasErrors() {
+		t.Fatalf("Valid config should not return error, got: %s", diagnostics)
+	}
+
+	m, err := component.RenderManifests()
+	if err != nil {
+		t.Fatalf("Rendering manifests with valid config should succeed, got: %s", err)
+	}
+
+	if len(m) == 0 {
+		t.Fatalf("Rendered manifests shouldn't be empty")
+	}
+}
+
+func TestRenderManifestOpenEBS(t *testing.T) {
+	configHCL := `
+component "velero" {
+  openebs {
+    credentials = "foo"
+    provider    = "aws"
+
+    backup_storage_location {
+      provider = "aws"
+      bucket   = "foo"
+      region   = "foo"
+    }
+
+    volume_snapshot_location {
+      provider = "aws"
+      bucket   = "foo"
+      region   = "foo"
+    }
+  }
+}
+`
+
+	component := newComponent()
+
+	body, diagnostics := util.GetComponentBody(configHCL, name)
+	if diagnostics != nil {
+		t.Fatalf("Error getting component body: %v", diagnostics)
+	}
+
+	diagnostics = component.LoadConfig(body, &hcl.EvalContext{})
+	if diagnostics.HasErrors() {
+		t.Fatalf("Valid config should not return error, got: %s", diagnostics)
+	}
+
+	m, err := component.RenderManifests()
+	if err != nil {
+		t.Fatalf("Rendering manifests with valid config should succeed, got: %s", err)
+	}
+
+	if len(m) == 0 {
+		t.Fatalf("Rendered manifests shouldn't be empty")
+	}
+}
+
+func TestRenderManifestConflictingProviders(t *testing.T) {
+	configHCL := `
+component "velero" {
+  azure {}
+  openebs {}
+}
+`
+
+	component := newComponent()
+
+	body, d := util.GetComponentBody(configHCL, name)
+	if d != nil {
+		t.Fatalf("Error getting component body: %v", d)
+	}
+
+	if d := component.LoadConfig(body, &hcl.EvalContext{}); !d.HasErrors() {
+		t.Fatalf("Loading configuration should fail if there is more than one provider configured")
+	}
+}
+
+func TestRenderManifestNoProviderConfigured(t *testing.T) {
+	configHCL := `
+component "velero" {}
+`
+
+	component := newComponent()
+
+	body, d := util.GetComponentBody(configHCL, name)
+	if d != nil {
+		t.Fatalf("Error getting component body: %v", d)
+	}
+
+	if d := component.LoadConfig(body, &hcl.EvalContext{}); !d.HasErrors() {
+		t.Fatalf("Loading configuration should fail if there is no provider configured")
+	}
+}
+
+func TestRenderManifestRestic(t *testing.T) {
+	configHCL := `
+component "velero" {
+  restic {
+    credentials = "foo"
+
+    backup_storage_location {
+      bucket   = "foo"
+      provider = "aws"
+    }
+  }
+}
+`
+
+	component := newComponent()
+
+	body, d := util.GetComponentBody(configHCL, name)
+	if d != nil {
+		t.Fatalf("Error getting component body: %v", d)
+	}
+
+	d = component.LoadConfig(body, &hcl.EvalContext{})
+	if d.HasErrors() {
+		t.Fatalf("Valid config should not return error, got: %s", d)
+	}
+
+	m, err := component.RenderManifests()
+	if err != nil {
+		t.Fatalf("Rendering manifests with valid config should succeed, got: %s", err)
+	}
+
+	if len(m) == 0 {
+		t.Fatalf("Rendered manifests shouldn't be empty")
+	}
+}
diff --git a/pkg/components/velero/velero_test.go b/pkg/components/velero/velero_test.go
deleted file mode 100644
index 1893b503..00000000
--- a/pkg/components/velero/velero_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2020 The Lokomotive Authors
-//
-// 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 velero
-
-import (
-	"testing"
-
-	"github.com/hashicorp/hcl/v2"
-
-	"github.com/kinvolk/lokomotive/pkg/components/util"
-)
-
-func TestEmptyConfig(t *testing.T) {
-	c := newComponent()
-	emptyConfig := hcl.EmptyBody()
-	evalContext := hcl.EvalContext{}
-	diagnostics := c.LoadConfig(&emptyConfig, &evalContext)
-	if !diagnostics.HasErrors() {
-		t.Errorf("Empty config should return error")
-	}
-}
-
-func TestRenderManifestAzure(t *testing.T) {
-	configHCL := `
-component "velero" {
-  azure {
-    subscription_id  = "foo"
-    tenant_id        = "foo"
-    client_id        = "foo"
-    client_secret    = "foo"
-    resource_group   = "foo"
-
-    backup_storage_location {
-      resource_group  = "foo"
-      storage_account = "foo"
-      bucket          = "foo"
-    }
-  }
-}
-`
-
-	component := newComponent()
-
-	body, diagnostics := util.GetComponentBody(configHCL, name)
-	if diagnostics != nil {
-		t.Fatalf("Error getting component body: %v", diagnostics)
-	}
-
-	diagnostics = component.LoadConfig(body, &hcl.EvalContext{})
-	if diagnostics.HasErrors() {
-		t.Fatalf("Valid config should not return error, got: %s", diagnostics)
-	}
-
-	m, err := component.RenderManifests()
-	if err != nil {
-		t.Fatalf("Rendering manifests with valid config should succeed, got: %s", err)
-	}
-	if len(m) <= 0 {
-		t.Fatalf("Rendered manifests shouldn't be empty")
-	}
-}