From 1cb8c4eefe413473d3d7a5c6c81eeed61880b1f0 Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Tue, 14 Jan 2025 21:00:52 +0100 Subject: [PATCH] require backup resource when restore is present (#195) * begin unit testing work * add a rule requiring a Backup resource when a Restore resource exists --- .../test-data/kots/kots-kinds/backup.yaml | 33 ++++++++++ .../test-data/kots/kots-kinds/restore.yaml | 27 +++++++++ pkg/kots/lint_test.go | 60 +++++++++++++++++++ pkg/kots/rego/kots-spec-opa-nonrendered.rego | 37 ++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 pkg/handlers/test-data/kots/kots-kinds/backup.yaml create mode 100644 pkg/handlers/test-data/kots/kots-kinds/restore.yaml diff --git a/pkg/handlers/test-data/kots/kots-kinds/backup.yaml b/pkg/handlers/test-data/kots/kots-kinds/backup.yaml new file mode 100644 index 0000000..81d74ed --- /dev/null +++ b/pkg/handlers/test-data/kots/kots-kinds/backup.yaml @@ -0,0 +1,33 @@ +apiVersion: velero.io/v1 +kind: Backup +metadata: + name: backup + annotations: + preserve: me +spec: + ttl: 36h0m0s + includedNamespaces: + - kotsadm + orLabelSelectors: + - matchExpressions: + - { key: kots.io/kotsadm, operator: NotIn, values: ["true"] } + hooks: + resources: + - name: test-hook + includedResources: + - 'pods' + labelSelector: + matchLabels: + app: example + component: nginx + pre: + - exec: + container: nginx + command: + - /bin/uname + - -a + post: + - exec: + command: + - /bin/uname + - -a diff --git a/pkg/handlers/test-data/kots/kots-kinds/restore.yaml b/pkg/handlers/test-data/kots/kots-kinds/restore.yaml new file mode 100644 index 0000000..e7df352 --- /dev/null +++ b/pkg/handlers/test-data/kots/kots-kinds/restore.yaml @@ -0,0 +1,27 @@ +apiVersion: velero.io/v1 +kind: Restore +metadata: + name: restore + annotations: + preserve: me +spec: + backupName: backup + includedNamespaces: + - '*' + hooks: + resources: + - name: restore-hook-1 + includedNamespaces: + - kotsadm + labelSelector: + matchLabels: + app: example + postHooks: + - init: + initContainers: + - name: restore-hook-init1 + image: proxy.replicated.com/anonymous/nginx:1.24-alpine + command: + - /bin/ash + - -c + - echo -n "FOOBARBAZ" > /tmp/foobarbaz diff --git a/pkg/kots/lint_test.go b/pkg/kots/lint_test.go index d8b8894..f98a9ab 100644 --- a/pkg/kots/lint_test.go +++ b/pkg/kots/lint_test.go @@ -1639,6 +1639,16 @@ kind: Backup metadata: name: backup spec: {}`, + }, + { + Name: "restore.yaml", + Path: "restore.yaml", + Content: `apiVersion: velero.io/v1 +kind: Restore +metadata: + name: restore +spec: + backupName: backup`, }, { Name: "identity.yaml", @@ -4181,6 +4191,56 @@ spec: }, expect: []domain.LintExpression{}, }, + { + name: "cannot have restore without backup", + specFiles: domain.SpecFiles{ + validKotsAppSpec, + validPreflightSpec, + validSupportBundleSpec, + validRegexValidationConfigSpec, + { + Name: "restore.yaml", + Path: "restore.yaml", + Content: `apiVersion: velero.io/v1 +kind: Restore`, + }, + }, + expect: []domain.LintExpression{ + { + Rule: "backup-resource-required-when-restore-exists", + Type: "error", + Message: "A velero backup resource is required when a velero restore resource is included", + Path: "restore.yaml", + Positions: []domain.LintExpressionItemPosition{ + { + domain.LintExpressionItemLinePosition{Line: 1}, + }, + }, + }, + }, + }, + { + name: "can have restore with backup", + specFiles: domain.SpecFiles{ + validKotsAppSpec, + validPreflightSpec, + validSupportBundleSpec, + validRegexValidationConfigSpec, + { + Name: "restore.yaml", + Path: "restore.yaml", + Content: `apiVersion: velero.io/v1 +kind: Restore`, + }, + { + Name: "backup.yaml", + Path: "backup.yaml", + Content: `apiVersion: velero.io/v1 +kind: Backup`, + }, + }, + expect: []domain.LintExpression{}, + }, } err := InitOPALinting() diff --git a/pkg/kots/rego/kots-spec-opa-nonrendered.rego b/pkg/kots/rego/kots-spec-opa-nonrendered.rego index 1f8d871..a64d21a 100644 --- a/pkg/kots/rego/kots-spec-opa-nonrendered.rego +++ b/pkg/kots/rego/kots-spec-opa-nonrendered.rego @@ -130,6 +130,9 @@ is_kots_kind(file) { } else { file.content.apiVersion == "velero.io/v1" file.content.kind == "Backup" +} else { + file.content.apiVersion == "velero.io/v1" + file.content.kind == "Restore" } else { is_kubernetes_installer_api_version(file.content.apiVersion) file.content.kind == "Installer" @@ -1166,6 +1169,40 @@ lint[output] { } } +# A function to check that a backup resource exists +v1_backup_spec_exists { + file := files[_] + file.content.kind == "Backup" + file.content.apiVersion == "velero.io/v1" +} +# A function to check that a restore resource exists +v1_restore_spec_exists { + file := files[_] + file.content.kind == "Restore" + file.content.apiVersion == "velero.io/v1" +} +# A rule that returns the restore file path +restore_file_path = file.path { + file := files[_] + file.content.kind == "Restore" + file.content.apiVersion == "velero.io/v1" +} + +# Validate that a velero backup resource exists when a velero restore resource is present +lint[output] { + rule_name := "backup-resource-required-when-restore-exists" + rule_config := lint_rule_config(rule_name, "error") + not rule_config.off + not v1_backup_spec_exists + v1_restore_spec_exists + output := { + "rule": rule_name, + "type": rule_config.level, + "message": "A velero backup resource is required when a velero restore resource is included", + "path": restore_file_path, + } +} + # Check if LintConfig spec exists lintconfig_spec_exists { file := files[_]