diff --git a/docs/configuration-reference/components/velero.md b/docs/configuration-reference/components/velero.md index 151c4d724..ada418f85 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 9c03354b4..6d4ce6b17 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/internal/util_test.go b/internal/util_test.go index 977508354..dfb8a3ff1 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/gangway/component.go b/pkg/components/gangway/component.go index 2c4aa9e36..2071ebda9 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 d095a00b8..de36c8803 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/linkerd/component_test.go b/pkg/components/linkerd/component_test.go deleted file mode 100644 index 5bbb60e55..000000000 --- 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/azure/azure.go b/pkg/components/velero/azure/azure.go index cf3711b54..b5f8e836f 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 000000000..2a0260e3e --- /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 1784cbc78..c7afe9a02 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/component_test.go b/pkg/components/velero/component_test.go new file mode 100644 index 000000000..fc922b67b --- /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/openebs/openebs.go b/pkg/components/velero/openebs/openebs.go new file mode 100644 index 000000000..2a0592149 --- /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 000000000..191ce6cca --- /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 000000000..a7a8430dd --- /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 000000000..f75453423 --- /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 }} +` diff --git a/pkg/components/velero/velero_test.go b/pkg/components/velero/velero_test.go deleted file mode 100644 index 1893b5038..000000000 --- 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") - } -}