Repository: Azure/draft
Pull request #251 (2024-03-07 11:44:21)

Manifest -m flag support added with test suite for validate command
# Description
Added support for user-defined paths containing manifest files.  Also put the plumbing in for the validate_test.go suite.

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Is it a breaking change which will impact consuming tool(s).

- [x] TestRunValidate
- [x] TestIsDirectory


# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules



msftclas: 
@michelleN,
Thanks for your contribution as a Microsoft full-time employee or intern. You do not need to sign a CLA.
_Thanks,
Microsoft Pull Request Bot_
NickKeller: failing tests are not safeguards related
tbarnes94: Merging in CLI Experience.

Patch:
diff --git a/.github/workflows/integration-linux.yml b/.github/workflows/integration-linux.yml
index b31b1b3..643f1c8 100644
--- a/.github/workflows/integration-linux.yml
+++ b/.github/workflows/integration-linux.yml
@@ -7,11 +7,6 @@ on:
 jobs:
   build:
     runs-on: ubuntu-latest
-    permissions:
-      actions: read
-      contents: read
-      deployments: read
-      packages: none
     steps:
       - uses: actions/checkout@v3
       - name: Set up Go
diff --git a/.github/workflows/integration-windows.yml b/.github/workflows/integration-windows.yml
index 322ce29..397e9a0 100644
--- a/.github/workflows/integration-windows.yml
+++ b/.github/workflows/integration-windows.yml
@@ -6,11 +6,6 @@ on:
 jobs:
   build:
     runs-on: windows-latest
-    permissions:
-      actions: read
-      contents: read
-      deployments: read
-      packages: none
     steps:
       - uses: actions/checkout@v3
       - name: Set up Go
diff --git a/cmd/validate.go b/cmd/validate.go
index 9ec3fc8..2431c75 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -2,10 +2,12 @@ package cmd
 
 import (
 	"context"
-
+	"fmt"
 	"github.com/Azure/draft/pkg/safeguards"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"io/fs"
+	"os"
 )
 
 type validateCmd struct {
@@ -34,19 +36,78 @@ func newValidateCmd() *cobra.Command {
 
 	f := cmd.Flags()
 
-	// TODO: add validation to the path
 	f.StringVarP(&vc.manifestPath, "manifest", "m", "", "'manifest' asks for the path to the manifest")
 
 	return cmd
 }
 
+// isDirectory determines if a file represented by path is a directory or not
+func isDirectory(path string) (bool, error) {
+	fileInfo, err := os.Stat(path)
+	if err != nil {
+		return false, err
+	}
+
+	return fileInfo.IsDir(), nil
+}
+
+// getManifests uses fs.WalkDir to retrieve a list of the manifest files within the given manifest path
+func getManifests(f fs.FS, path string) ([]string, error) {
+	var manifests []string
+
+	err := fs.WalkDir(f, path, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return fmt.Errorf("error walking path %s with error: %w", path, err)
+		}
+
+		if !d.IsDir() {
+			log.Debugf("%s is not a directory, appending to manifests", path)
+			manifests = append(manifests, path)
+		} else {
+			log.Debugf("%s is a directory, skipping...", path)
+		}
+
+		return nil
+	})
+	if err != nil {
+		return nil, fmt.Errorf("could not walk directory: %w", err)
+	}
+
+	return manifests, nil
+}
+
+// run is our entry point to ValidateManifests
 func (vc *validateCmd) run() error {
+	if vc.manifestPath == "" {
+		return fmt.Errorf("path to the manifests cannot be empty")
+	}
+
 	ctx := context.Background()
+	isDir, err := isDirectory(vc.manifestPath)
+	if err != nil {
+		return fmt.Errorf("could not determine if given path is a directory: %w", err)
+	}
+
+	var manifests []string
+	var manifestFS = os.DirFS(vc.manifestPath)
+	if isDir {
+		manifests, err = getManifests(manifestFS, vc.manifestPath)
+		if err != nil {
+			return err
+		}
+	} else {
+		manifests = append(manifests, vc.manifestPath)
+	}
+
+	if err != nil {
+		return fmt.Errorf("reading directory: %w", err)
+	}
 
-	log.Debugf("validating manifest")
-	err := safeguards.ValidateManifest(ctx, vc.manifestPath)
+	log.Debugf("validating manifests")
+	err = safeguards.ValidateManifests(ctx, manifestFS, manifests)
 	if err != nil {
-		log.Errorf("validating safeguards: %s", err)
+		log.Errorf("validating safeguards: %s", err.Error())
+		return err
 	}
 
 	return nil
diff --git a/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml b/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml
index e795ccd..1ac015a 100644
--- a/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml
+++ b/pkg/safeguards/lib/v1.0.0/restricted-taints/constraint.yaml
@@ -8,4 +8,4 @@ spec:
       - apiGroups: [""]
         kinds: ["Node"]
   parameters:
-    reservedTaints: {{ .Values.reservedTaints }}
\ No newline at end of file
+    reservedTaints: ["CriticalAddonsOnly"]
\ No newline at end of file
diff --git a/pkg/safeguards/safeguards.go b/pkg/safeguards/safeguards.go
index 17d60a6..cf7446b 100644
--- a/pkg/safeguards/safeguards.go
+++ b/pkg/safeguards/safeguards.go
@@ -2,9 +2,10 @@ package safeguards
 
 import (
 	"context"
-	"os"
-
+	"embed"
+	"fmt"
 	api "github.com/open-policy-agent/gatekeeper/v3/apis"
+	"io/fs"
 
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -12,8 +13,10 @@ import (
 
 // Globals
 var s = runtime.NewScheme()
-var wd, _ = os.Getwd()
-var f = os.DirFS(wd)
+
+//go:embed lib
+var embedFS embed.FS
+
 var fc FileCrawler
 
 // primes the scheme to be able to interpret beta templates
@@ -25,13 +28,14 @@ func init() {
 	updateSafeguardPaths()
 
 	fc = FileCrawler{
-		Safeguards: safeguards,
+		Safeguards:   safeguards,
+		constraintFS: embedFS,
 	}
 }
 
-// ValidatemManifest is what will be called by `draft validate` to validate the user's manifest
+// ValidateManifests is what will be called by `draft validate` to validate the user's manifests
 // against each safeguards constraint
-func ValidateManifest(ctx context.Context, manifestPath string) error {
+func ValidateManifests(ctx context.Context, manifestFS fs.FS, manifests []string) error {
 	// constraint client instantiation
 	c, err := getConstraintClient()
 	if err != nil {
@@ -47,10 +51,6 @@ func ValidateManifest(ctx context.Context, manifestPath string) error {
 	if err != nil {
 		return err
 	}
-	manifest, err := fc.ReadManifest(manifestPath)
-	if err != nil {
-		return err
-	}
 
 	// loading of templates, constraints into constraint client
 	err = loadConstraintTemplates(ctx, c, constraintTemplates)
@@ -62,6 +62,24 @@ func ValidateManifest(ctx context.Context, manifestPath string) error {
 		return err
 	}
 
-	// validation of deployment manifest with constraints, templates loaded
-	return validateManifest(ctx, c, manifest)
+	var violations []string
+	for _, m := range manifests {
+		manifest, err := fc.ReadManifest(m, manifestFS)
+		if err != nil {
+			return err
+		}
+
+		// validation of deployment manifest with constraints, templates loaded
+		err = validateManifest(ctx, c, manifest)
+		if err != nil {
+			violations = append(violations, err.Error())
+		}
+	}
+
+	// returning the full list of violations after each manifest is checked
+	if len(violations) > 0 {
+		return fmt.Errorf("violations have occurred: %s", violations)
+	}
+
+	return nil
 }
diff --git a/pkg/safeguards/safeguards_constants.go b/pkg/safeguards/safeguards_constants.go
index 1201a3e..aed566a 100644
--- a/pkg/safeguards/safeguards_constants.go
+++ b/pkg/safeguards/safeguards_constants.go
@@ -1,6 +1,9 @@
 package safeguards
 
-import "fmt"
+import (
+	"fmt"
+	"io/fs"
+)
 
 const (
 	Constraint_CAI   = "container-allowed-images"
@@ -14,7 +17,8 @@ const (
 )
 
 type FileCrawler struct {
-	Safeguards []Safeguard
+	Safeguards   []Safeguard
+	constraintFS fs.FS
 }
 
 type Safeguard struct {
@@ -28,49 +32,48 @@ var selectedVersion = "v1.0.0"
 // TODO: consider getting this from a text file we can bump
 var supportedVersions = []string{selectedVersion}
 
-const safeguardDirectory = "lib"
 const templateFileName = "template.yaml"
 const constraintFileName = "constraint.yaml"
 
 var safeguards = []Safeguard{
 	{
 		name:           Constraint_CAI,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CAI, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CAI, constraintFileName),
+		templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CAI, templateFileName),
+		constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CAI, constraintFileName),
 	},
 	{
 		name:           Constraint_CEP,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CEP, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CEP, constraintFileName),
+		templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CEP, templateFileName),
+		constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CEP, constraintFileName),
 	},
 	{
 		name:           Constraint_CL,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CL, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CL, constraintFileName),
-	},
-	{
-		name:           Constraint_CRIP,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CRIP, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_CRIP, constraintFileName),
-	},
-	{
-		name:           Constraint_DBPDB,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_DBPDB, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_DBPDB, constraintFileName),
-	},
-	{
-		name:           Constraint_PEA,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_PEA, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_PEA, constraintFileName),
-	},
-	{
-		name:           Constraint_RT,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_RT, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_RT, constraintFileName),
-	},
-	{
-		name:           Constraint_USS,
-		templatePath:   fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_USS, templateFileName),
-		constraintPath: fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, Constraint_USS, constraintFileName),
+		templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CL, templateFileName),
+		constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CL, constraintFileName),
 	},
+	//{
+	//	name:           Constraint_CRIP,
+	//	templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CRIP, templateFileName),
+	//	constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_CRIP, constraintFileName),
+	//},
+	//{
+	//	name:           Constraint_DBPDB,
+	//	templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_DBPDB, templateFileName),
+	//	constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_DBPDB, constraintFileName),
+	//},
+	//{
+	//	name:           Constraint_PEA,
+	//	templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_PEA, templateFileName),
+	//	constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_PEA, constraintFileName),
+	//},
+	//{
+	//	name:           Constraint_RT,
+	//	templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_RT, templateFileName),
+	//	constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_RT, constraintFileName),
+	//},
+	//{
+	//	name:           Constraint_USS,
+	//	templatePath:   fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_USS, templateFileName),
+	//	constraintPath: fmt.Sprintf("lib/%s/%s/%s", selectedVersion, Constraint_USS, constraintFileName),
+	//},
 }
diff --git a/pkg/safeguards/safeguards_helpers.go b/pkg/safeguards/safeguards_helpers.go
index 9cbae98..3f365d9 100644
--- a/pkg/safeguards/safeguards_helpers.go
+++ b/pkg/safeguards/safeguards_helpers.go
@@ -3,6 +3,7 @@ package safeguards
 import (
 	"context"
 	"fmt"
+	"io/fs"
 
 	constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client"
 	"github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego"
@@ -37,14 +38,15 @@ func getLatestSafeguardsVersion() string {
 
 func updateSafeguardPaths() {
 	for _, sg := range safeguards {
-		sg.templatePath = fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, sg.name, templateFileName)
-		sg.constraintPath = fmt.Sprintf("%s/%s/%s/%s", safeguardDirectory, selectedVersion, sg.name, constraintFileName)
+		sg.templatePath = fmt.Sprintf("%s/%s/%s", selectedVersion, sg.name, templateFileName)
+		sg.constraintPath = fmt.Sprintf("%s/%s/%s", selectedVersion, sg.name, constraintFileName)
 	}
 }
 
 // methods for retrieval of manifest, constraint templates, and constraints
-func (fc FileCrawler) ReadManifest(path string) (*unstructured.Unstructured, error) {
-	deployment, err := reader.ReadObject(f, path)
+func (fc FileCrawler) ReadManifest(path string, manifestFS fs.FS) (*unstructured.Unstructured, error) {
+	// if david prefers not having this in binary, we can change the fs object later
+	deployment, err := reader.ReadObject(manifestFS, path)
 	if err != nil {
 		return nil, fmt.Errorf("could not read manifest: %w", err)
 	}
@@ -56,7 +58,7 @@ func (fc FileCrawler) ReadConstraintTemplates() ([]*templates.ConstraintTemplate
 	var constraintTemplates []*templates.ConstraintTemplate
 
 	for _, sg := range fc.Safeguards {
-		ct, err := reader.ReadTemplate(s, f, sg.templatePath)
+		ct, err := reader.ReadTemplate(s, fc.constraintFS, sg.templatePath)
 		if err != nil {
 			return nil, fmt.Errorf("could not read template: %w", err)
 		}
@@ -71,7 +73,7 @@ func (fc FileCrawler) ReadConstraintTemplate(name string) (*templates.Constraint
 
 	for _, sg := range fc.Safeguards {
 		if sg.name == name {
-			ct, err := reader.ReadTemplate(s, f, sg.templatePath)
+			ct, err := reader.ReadTemplate(s, fc.constraintFS, sg.templatePath)
 			if err != nil {
 				return nil, fmt.Errorf("could not read template: %w", err)
 			}
@@ -89,7 +91,7 @@ func (fc FileCrawler) ReadConstraints() ([]*unstructured.Unstructured, error) {
 	var constraints []*unstructured.Unstructured
 
 	for _, sg := range fc.Safeguards {
-		u, err := reader.ReadConstraint(f, sg.constraintPath)
+		u, err := reader.ReadConstraint(fc.constraintFS, sg.constraintPath)
 		if err != nil {
 			return nil, fmt.Errorf("could not add constraint: %w", err)
 		}
@@ -105,7 +107,7 @@ func (fc FileCrawler) ReadConstraint(name string) (*unstructured.Unstructured, e
 
 	for _, sg := range fc.Safeguards {
 		if sg.name == name {
-			c, err := reader.ReadConstraint(f, sg.constraintPath)
+			c, err := reader.ReadConstraint(fc.constraintFS, sg.constraintPath)
 			if err != nil {
 				return nil, fmt.Errorf("could not add constraint: %w", err)
 			}


Test patch:
diff --git a/cmd/validate_test.go b/cmd/validate_test.go
index 1d619dd..fe0079c 100644
--- a/cmd/validate_test.go
+++ b/cmd/validate_test.go
@@ -1,1 +1,75 @@
 package cmd
+
+import (
+	"context"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/Azure/draft/pkg/safeguards"
+	"github.com/stretchr/testify/assert"
+)
+
+var wd, _ = os.Getwd()
+var testFS = os.DirFS(path.Join(wd, "../pkg/safeguards/tests"))
+var emptyFS = os.DirFS("")
+
+// TestIsDirectory tests the isDirectory function for proper returns
+func TestIsDirectory(t *testing.T) {
+	testWd, _ := os.Getwd()
+	pathTrue := testWd
+	pathFalse := path.Join(testWd, "validate.go")
+	pathError := ""
+
+	isDir, err := isDirectory(pathTrue)
+	assert.True(t, isDir)
+	assert.Nil(t, err)
+
+	isDir, err = isDirectory(pathFalse)
+	assert.False(t, isDir)
+	assert.Nil(t, err)
+
+	isDir, err = isDirectory(pathError)
+	assert.False(t, isDir)
+	assert.NotNil(t, err)
+}
+
+// TestRunValidate tests the run command for `draft validate` for proper returns
+func TestRunValidate(t *testing.T) {
+	ctx := context.TODO()
+	manifestPathEmpty := ""
+	manifestPathDirectorySuccess := "all/success"
+	manifestPathDirectoryError := "all/error"
+	manifestPathFileSuccess := "all/success/all-success-manifest.yaml"
+	manifestPathFileError := "all/error/all-error-manifest.yaml"
+	var manifests []string
+
+	// Scenario 1: empty manifest path should error
+	manifests = append(manifests, manifestPathEmpty)
+	err := safeguards.ValidateManifests(ctx, emptyFS, manifests)
+	assert.NotNil(t, err)
+
+	// Scenario 2a: manifest path leads to a directory of manifests - expect success
+	manifests, err = getManifests(testFS, manifestPathDirectorySuccess)
+	assert.Nil(t, err)
+	err = safeguards.ValidateManifests(ctx, testFS, manifests)
+	assert.Nil(t, err)
+
+	// Scenario 2b: manifest path leads to a directory of manifests - expect failure
+	manifests, err = getManifests(testFS, manifestPathDirectoryError)
+	assert.Nil(t, err)
+	err = safeguards.ValidateManifests(ctx, testFS, manifests)
+	assert.NotNil(t, err)
+
+	// Scenario 3a: manifest path leads to one manifest file - expect success
+	manifests = []string{}
+	manifests = append(manifests, manifestPathFileSuccess)
+	err = safeguards.ValidateManifests(ctx, testFS, manifests)
+	assert.Nil(t, err)
+
+	// Scenario 3b: manifest path leads to one manifest file - expect failure
+	manifests = []string{}
+	manifests = append(manifests, manifestPathFileError)
+	err = safeguards.ValidateManifests(ctx, testFS, manifests)
+	assert.NotNil(t, err)
+}
diff --git a/pkg/safeguards/safeguards_test.go b/pkg/safeguards/safeguards_test.go
index 62a534d..3362ba3 100644
--- a/pkg/safeguards/safeguards_test.go
+++ b/pkg/safeguards/safeguards_test.go
@@ -18,7 +18,8 @@ func init() {
 	updateSafeguardPaths()
 
 	testFc = FileCrawler{
-		Safeguards: safeguards,
+		Safeguards:   safeguards,
+		constraintFS: embedFS,
 	}
 }
 
diff --git a/pkg/safeguards/safeguards_test_helpers.go b/pkg/safeguards/safeguards_test_helpers.go
index 5d8d880..02b55cc 100644
--- a/pkg/safeguards/safeguards_test_helpers.go
+++ b/pkg/safeguards/safeguards_test_helpers.go
@@ -2,15 +2,19 @@ package safeguards
 
 import (
 	"context"
+	"embed"
 	"testing"
 
 	constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client"
 	"github.com/stretchr/testify/assert"
 )
 
+//go:embed tests
+var testFS embed.FS
+
 func validateTestManifests_Error(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc FileCrawler, testManifestPaths []string) {
 	for _, path := range testManifestPaths {
-		errManifest, err := testFc.ReadManifest(path)
+		errManifest, err := testFc.ReadManifest(path, testFS)
 		assert.Nil(t, err)
 
 		// error case - should throw error
@@ -21,7 +25,7 @@ func validateTestManifests_Error(ctx context.Context, t *testing.T, c *constrain
 
 func validateTestManifests_Success(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc FileCrawler, testManifestPaths []string) {
 	for _, path := range testManifestPaths {
-		successManifest, err := testFc.ReadManifest(path)
+		successManifest, err := testFc.ReadManifest(path, testFS)
 		assert.Nil(t, err)
 
 		// success case - should not throw error
diff --git a/pkg/safeguards/tests/all/all-error-manifest.yaml b/pkg/safeguards/tests/all/error/all-error-manifest-copy.yaml
similarity index 100%
rename from pkg/safeguards/tests/all/all-error-manifest.yaml
rename to pkg/safeguards/tests/all/error/all-error-manifest-copy.yaml
diff --git a/pkg/safeguards/tests/all/error/all-error-manifest.yaml b/pkg/safeguards/tests/all/error/all-error-manifest.yaml
new file mode 100644
index 0000000..2e04317
--- /dev/null
+++ b/pkg/safeguards/tests/all/error/all-error-manifest.yaml
@@ -0,0 +1,21 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-deployment
+  labels:
+    app: my-app
+    environment: production
+    testLabel3: randomlabel
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: my-app
+  template:
+    metadata:
+      labels:
+        app: my-app
+    spec:
+      containers:
+        - name: badcontainer
+          image: badimage
\ No newline at end of file
diff --git a/pkg/safeguards/tests/all/all-success-manifest.yaml b/pkg/safeguards/tests/all/success/all-success-manifest-copy.yaml
similarity index 100%
rename from pkg/safeguards/tests/all/all-success-manifest.yaml
rename to pkg/safeguards/tests/all/success/all-success-manifest-copy.yaml
diff --git a/pkg/safeguards/tests/all/success/all-success-manifest.yaml b/pkg/safeguards/tests/all/success/all-success-manifest.yaml
new file mode 100644
index 0000000..b6f9e8b
--- /dev/null
+++ b/pkg/safeguards/tests/all/success/all-success-manifest.yaml
@@ -0,0 +1,21 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-deployment
+  labels:
+    app: my-app
+    environment: production
+    testLabel3: randomlabel
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: my-app
+  template:
+    metadata:
+      labels:
+        app: my-app
+    spec:
+      containers:
+        - name: my-container
+          image: nginx:latest
\ No newline at end of file