Repository: Azure/draft
Pull request #261 (2024-04-02 09:49:02)

Added helper loadData to facilitate Unique Service Selectors validation between manifest objects
# Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Adding this helper and new for loop preprocesses the manifest objects for any violations concerning duplicate naming of selectors.

Please delete options that are not relevant.

- [x] 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).


# 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: 
@bacongobbler,
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_
codecov-io: # [Codecov](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=h1) Report
> Merging [#261](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=desc) into [master](https://codecov.io/gh/Azure/draft/commit/7c39ff8786a486b626bcee97991dbecbd20a28ec?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/Azure/draft/pull/261/graphs/tree.svg?height=150&src=pr&token=PF70pQI5TU&width=650)](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #261   +/-   ##
=======================================
  Coverage   31.06%   31.06%           
=======================================
  Files          27       27           
  Lines        1732     1732           
=======================================
  Hits          538      538           
  Misses       1116     1116           
  Partials       78       78
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=footer). Last update [7c39ff8...9ae1616](https://codecov.io/gh/Azure/draft/pull/261?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cmd/validate.go b/cmd/validate.go
index 0ff087f..24d26d9 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io/fs"
 	"os"
+	"path"
 	"path/filepath"
 
 	"github.com/Azure/draft/pkg/safeguards"
@@ -53,11 +54,12 @@ func isDirectory(path string) (bool, error) {
 	return fileInfo.IsDir(), nil
 }
 
-// getManifests uses fs.WalkDir to retrieve a list of the manifest files within the given manifest path
-func getManifestFiles(p string) ([]string, error) {
-	var manifestFiles []string
+// getManifestFiles uses filepath.Walk to retrieve a list of the manifest files within the given manifest path
+func getManifestFiles(p string) ([]safeguards.ManifestFile, error) {
+	var manifestFiles []safeguards.ManifestFile
 
 	err := filepath.Walk(p, func(filepath string, info fs.FileInfo, err error) error {
+		manifest := safeguards.ManifestFile{}
 		if err != nil {
 			return fmt.Errorf("error walking path %s with error: %w", filepath, err)
 		}
@@ -65,7 +67,9 @@ func getManifestFiles(p string) ([]string, error) {
 		if !info.IsDir() && info.Name() != "" {
 			log.Debugf("%s is not a directory, appending to manifestFiles", info.Name())
 
-			manifestFiles = append(manifestFiles, filepath)
+			manifest.Name = info.Name()
+			manifest.Path = filepath
+			manifestFiles = append(manifestFiles, manifest)
 		} else {
 			log.Debugf("%s is a directory, skipping...", info.Name())
 		}
@@ -91,14 +95,17 @@ func (vc *validateCmd) run(c *cobra.Command) error {
 		return fmt.Errorf("not a valid file or directory: %w", err)
 	}
 
-	var manifestFiles []string
+	var manifestFiles []safeguards.ManifestFile
 	if isDir {
 		manifestFiles, err = getManifestFiles(vc.manifestPath)
 		if err != nil {
 			return err
 		}
 	} else {
-		manifestFiles = append(manifestFiles, vc.manifestPath)
+		manifestFiles = append(manifestFiles, safeguards.ManifestFile{
+			Name: path.Base(vc.manifestPath),
+			Path: vc.manifestPath,
+		})
 	}
 
 	if err != nil {
@@ -119,18 +126,19 @@ func (vc *validateCmd) run(c *cobra.Command) error {
 		for file, violations := range v.ObjectViolations {
 			log.Printf("  %s:", file)
 			for _, violation := range violations {
-				log.Printf("    %s", violation)
+				log.Printf("    ❌ %s", violation)
 				manifestViolationsFound = true
 			}
 		}
 		if !manifestViolationsFound {
-			log.Printf("    no violations found.")
+			log.Printf("    ✅ no violations found.")
 		}
 	}
 
 	if len(manifestViolations) > 0 {
 		c.SilenceUsage = true
-		return fmt.Errorf("violations found in manifests")
+	} else {
+		log.Printf("✅ No violations found in \"%s\".", vc.manifestPath)
 	}
 
 	return nil
diff --git a/go.mod b/go.mod
index 37b11c7..28924ed 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,6 @@ module github.com/Azure/draft
 go 1.21
 
 require (
-	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy v0.9.0
 	github.com/briandowns/spinner v1.23.0
 	github.com/cenkalti/backoff/v4 v4.2.1
 	github.com/fatih/color v1.16.0
@@ -32,8 +31,6 @@ require (
 
 require (
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect
-	github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
 	github.com/Masterminds/semver/v3 v3.2.1 // indirect
 	github.com/Microsoft/hcsshim v0.11.4 // indirect
diff --git a/go.sum b/go.sum
index 6fbaa6f..8924b36 100644
--- a/go.sum
+++ b/go.sum
@@ -13,22 +13,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy v0.9.0 h1:YA31g14FJRqNW6nsG/L1OTr4K238uR1yB9QS/rfpLUQ=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy v0.9.0/go.mod h1:oV/CiaEI6/PiHdtOBhAov1Gdk9dt32WsFpj+3NSL8SI=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
@@ -128,8 +114,6 @@ github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aB
 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
 github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
 github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
-github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284=
 github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
@@ -200,8 +184,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
-github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
 github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
@@ -326,8 +308,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
-github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -394,8 +374,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
-github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
-github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
diff --git a/pkg/safeguards/safeguards.go b/pkg/safeguards/safeguards.go
index ec5f7f0..0903ef2 100644
--- a/pkg/safeguards/safeguards.go
+++ b/pkg/safeguards/safeguards.go
@@ -3,10 +3,12 @@ package safeguards
 import (
 	"context"
 	"embed"
+	"fmt"
 
 	api "github.com/open-policy-agent/gatekeeper/v3/apis"
 	log "github.com/sirupsen/logrus"
 
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 )
@@ -33,14 +35,13 @@ func init() {
 	}
 }
 
-type ManifestViolation struct {
-	Name             string              // the name of the manifest
-	ObjectViolations map[string][]string // a map of string object names to slice of string objectViolations
-}
-
 // GetManifestViolations takes in a list of manifest files and returns a slice of ManifestViolation structs
-func GetManifestViolations(ctx context.Context, manifestFiles []string) ([]ManifestViolation, error) {
-	var manifestViolations = make([]ManifestViolation, 0)
+func GetManifestViolations(ctx context.Context, manifestFiles []ManifestFile) ([]ManifestViolation, error) {
+	if len(manifestFiles) == 0 {
+		return nil, fmt.Errorf("path cannot be empty")
+	}
+
+	manifestViolations := make([]ManifestViolation, 0)
 
 	// constraint client instantiation
 	c, err := getConstraintClient()
@@ -68,26 +69,38 @@ func GetManifestViolations(ctx context.Context, manifestFiles []string) ([]Manif
 		return manifestViolations, err
 	}
 
+	// organized map of manifest object by file name
+	manifestMap := make(map[string][]*unstructured.Unstructured, 0)
+	// aggregate of every manifest object into one list
+	allManifestObjects := []*unstructured.Unstructured{}
 	for _, m := range manifestFiles {
-		var objectViolations map[string][]string
-		objects, err := fc.ReadManifests(m) // read all the objects stored in a single file
+		manifestObjects, err := fc.ReadManifests(m.Path) // read all the objects stored in a single file
 		if err != nil {
 			log.Errorf("reading objects %s", err.Error())
 			return manifestViolations, err
 		}
 
+		allManifestObjects = append(allManifestObjects, manifestObjects...)
+		manifestMap[m.Name] = manifestObjects
+	}
+
+	if len(allManifestObjects) > 0 {
+		err = loadManifestObjects(ctx, c, allManifestObjects)
+	}
+
+	for _, m := range manifestFiles {
+		var objectViolations map[string][]string
+
 		// validation of deployment manifest with constraints, templates loaded
-		objectViolations, err = getObjectViolations(ctx, c, objects)
+		objectViolations, err = getObjectViolations(ctx, c, manifestMap[m.Name])
 		if err != nil {
 			log.Errorf("validating objects: %s", err.Error())
 			return manifestViolations, err
 		}
-		if len(objectViolations) > 0 {
-			manifestViolations = append(manifestViolations, ManifestViolation{
-				Name:             m,
-				ObjectViolations: objectViolations,
-			})
-		}
+		manifestViolations = append(manifestViolations, ManifestViolation{
+			Name:             m.Name,
+			ObjectViolations: objectViolations,
+		})
 	}
 
 	return manifestViolations, nil
diff --git a/pkg/safeguards/safeguards_constants.go b/pkg/safeguards/safeguards_constants.go
index e446b53..6f7c755 100644
--- a/pkg/safeguards/safeguards_constants.go
+++ b/pkg/safeguards/safeguards_constants.go
@@ -2,7 +2,6 @@ package safeguards
 
 import (
 	"fmt"
-	"io/fs"
 )
 
 const (
@@ -16,17 +15,6 @@ const (
 	Constraint_USS   = "unique-service-selectors"
 )
 
-type FileCrawler struct {
-	Safeguards   []Safeguard
-	constraintFS fs.FS
-}
-
-type Safeguard struct {
-	name           string
-	templatePath   string
-	constraintPath string
-}
-
 var selectedVersion = "v1.0.0"
 
 // TODO: consider getting this from a text file we can bump
diff --git a/pkg/safeguards/safeguards_helpers.go b/pkg/safeguards/safeguards_helpers.go
index 342d2a0..c5d4962 100644
--- a/pkg/safeguards/safeguards_helpers.go
+++ b/pkg/safeguards/safeguards_helpers.go
@@ -158,6 +158,20 @@ func loadConstraints(ctx context.Context, c *constraintclient.Client, constraint
 	return nil
 }
 
+func loadManifestObjects(ctx context.Context, c *constraintclient.Client, objects []*unstructured.Unstructured) error {
+	// AddData inserts the provided data into OPA for every target that can handle the data.
+	// On error, the responses return value will still be populated so that
+	// partial results can be analyzed.
+	for _, o := range objects {
+		_, err := c.AddData(ctx, o)
+		if err != nil {
+			return fmt.Errorf("could not add data: %w", err)
+		}
+	}
+
+	return nil
+}
+
 // getObjectViolations executes validation on manifests based on loaded constraint templates and returns a map of manifest name to list of objectViolations
 func getObjectViolations(ctx context.Context, c *constraintclient.Client, objects []*unstructured.Unstructured) (map[string][]string, error) {
 	// Review makes sure the provided object satisfies all stored constraints.
diff --git a/pkg/safeguards/safeguards_types.go b/pkg/safeguards/safeguards_types.go
new file mode 100644
index 0000000..2414ee2
--- /dev/null
+++ b/pkg/safeguards/safeguards_types.go
@@ -0,0 +1,24 @@
+package safeguards
+
+import "io/fs"
+
+type FileCrawler struct {
+	Safeguards   []Safeguard
+	constraintFS fs.FS
+}
+
+type Safeguard struct {
+	name           string
+	templatePath   string
+	constraintPath string
+}
+
+type ManifestFile struct {
+	Name string
+	Path string
+}
+
+type ManifestViolation struct {
+	Name             string              // the name of the manifest
+	ObjectViolations map[string][]string // a map of string object names to slice of string objectViolations
+}
diff --git a/pkg/workflows/charts/production.yaml b/pkg/workflows/charts/production.yaml
new file mode 100644
index 0000000..5e55a61
--- /dev/null
+++ b/pkg/workflows/charts/production.yaml
@@ -0,0 +1,8 @@
+image:
+    repository: testAcr.azurecr.io/testContainer
+    pullPolicy: Always
+    tag: latest
+service:
+    annotations: {}
+    type: LoadBalancer
+    port: "80"


Test patch:
diff --git a/cmd/validate_test.go b/cmd/validate_test.go
index 5ed22b4..067aae8 100644
--- a/cmd/validate_test.go
+++ b/cmd/validate_test.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+
 	"testing"
 
 	"github.com/Azure/draft/pkg/safeguards"
@@ -34,16 +35,15 @@ func TestIsDirectory(t *testing.T) {
 // TestRunValidate tests the run command for `draft validate` for proper returns
 func TestRunValidate(t *testing.T) {
 	ctx := context.TODO()
-	manifestPathEmpty := ""
+	manifestFilesEmpty := []safeguards.ManifestFile{}
 	manifestPathDirectorySuccess, _ := filepath.Abs("../pkg/safeguards/tests/all/success")
 	manifestPathDirectoryError, _ := filepath.Abs("../pkg/safeguards/tests/all/error")
 	manifestPathFileSuccess, _ := filepath.Abs("../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml")
 	manifestPathFileError, _ := filepath.Abs("../pkg/safeguards/tests/all/error/all-error-manifest-1.yaml")
-	var manifestFiles []string
+	var manifestFiles []safeguards.ManifestFile
 
 	// Scenario 1: empty manifest path should error
-	manifestFiles = append(manifestFiles, manifestPathEmpty)
-	_, err := safeguards.GetManifestViolations(ctx, manifestFiles)
+	_, err := safeguards.GetManifestViolations(ctx, manifestFilesEmpty)
 	assert.NotNil(t, err)
 
 	// Scenario 2a: manifest path leads to a directory of manifestFiles - expect success
@@ -51,26 +51,28 @@ func TestRunValidate(t *testing.T) {
 	assert.Nil(t, err)
 	v, err := safeguards.GetManifestViolations(ctx, manifestFiles)
 	assert.Nil(t, err)
-	assert.Equal(t, len(v), 0)
+	numViolations := countTestViolations(v)
+	assert.Equal(t, numViolations, 0)
 
 	// Scenario 2b: manifest path leads to a directory of manifestFiles - expect failure
 	manifestFiles, err = getManifestFiles(manifestPathDirectoryError)
 	assert.Nil(t, err)
 	v, err = safeguards.GetManifestViolations(ctx, manifestFiles)
 	assert.Nil(t, err)
-	assert.Greater(t, len(v), 0)
+	numViolations = countTestViolations(v)
+	assert.Greater(t, numViolations, 0)
 
 	// Scenario 3a: manifest path leads to one manifest file - expect success
-	manifestFiles = []string{}
-	manifestFiles = append(manifestFiles, manifestPathFileSuccess)
+	manifestFiles, err = getManifestFiles(manifestPathFileSuccess)
 	v, err = safeguards.GetManifestViolations(ctx, manifestFiles)
 	assert.Nil(t, err)
-	assert.Equal(t, len(v), 0)
+	numViolations = countTestViolations(v)
+	assert.Equal(t, numViolations, 0)
 
 	// Scenario 3b: manifest path leads to one manifest file - expect failure
-	manifestFiles = []string{}
-	manifestFiles = append(manifestFiles, manifestPathFileError)
+	manifestFiles, err = getManifestFiles(manifestPathFileError)
 	v, err = safeguards.GetManifestViolations(ctx, manifestFiles)
 	assert.Nil(t, err)
-	assert.Greater(t, len(v), 0)
+	numViolations = countTestViolations(v)
+	assert.Greater(t, numViolations, 0)
 }
diff --git a/cmd/validate_test_helpers.go b/cmd/validate_test_helpers.go
new file mode 100644
index 0000000..7c68562
--- /dev/null
+++ b/cmd/validate_test_helpers.go
@@ -0,0 +1,12 @@
+package cmd
+
+import "github.com/Azure/draft/pkg/safeguards"
+
+func countTestViolations(violations []safeguards.ManifestViolation) int {
+	numViolations := 0
+	for _, v := range violations {
+		numViolations += len(v.ObjectViolations)
+	}
+
+	return numViolations
+}
diff --git a/pkg/safeguards/safeguards_test.go b/pkg/safeguards/safeguards_test.go
index 5d6aecf..200ac5f 100644
--- a/pkg/safeguards/safeguards_test.go
+++ b/pkg/safeguards/safeguards_test.go
@@ -183,30 +183,30 @@ func TestValidateDeployment_ContainerLimits(t *testing.T) {
 //	validateTestManifests_Fail(ctx, t, c, testFc, testManifest_RT.ErrorPaths)
 //	validateTestManifests_Success(ctx, t, c, testFc, testManifest_RT.SuccessPaths)
 //}
-//
-//// TODO: rich description here
-//func TestValidateDeployment_UniqueServiceSelectors(t *testing.T) {
-//	// instantiate constraint client
-//	c, err := getConstraintClient()
-//	assert.Nil(t, err)
-//
-//	// retrieving template, constraint, and deployments
-//	constraintTemplate, err := testFc.ReadConstraintTemplate(testManifest_USS.Name)
-//	assert.Nil(t, err)
-//	constraint, err := testFc.ReadConstraint(testManifest_USS.Name)
-//	assert.Nil(t, err)
-//
-//	// load template, constraint into constraint client
-//	err = loadConstraintTemplates(ctx, c, []*templates.ConstraintTemplate{constraintTemplate})
-//	assert.Nil(t, err)
-//	err = loadConstraints(ctx, c, []*unstructured.Unstructured{constraint})
-//	assert.Nil(t, err)
-//
-//	// validating deployment manifests
-//	validateTestManifests_Fail(ctx, t, c, testFc, testManifest_USS.ErrorPaths)
-//	validateTestManifests_Success(ctx, t, c, testFc, testManifest_USS.SuccessPaths)
-//}
-//
+
+// TODO: rich description here
+func TestValidateDeployment_UniqueServiceSelectors(t *testing.T) {
+	// instantiate constraint client
+	c, err := getConstraintClient()
+	assert.Nil(t, err)
+
+	// retrieving template, constraint, and deployments
+	constraintTemplate, err := testFc.ReadConstraintTemplate(testManifest_USS.Name)
+	assert.Nil(t, err)
+	constraint, err := testFc.ReadConstraint(testManifest_USS.Name)
+	assert.Nil(t, err)
+
+	// load template, constraint into constraint client
+	err = loadConstraintTemplates(ctx, c, []*templates.ConstraintTemplate{constraintTemplate})
+	assert.Nil(t, err)
+	err = loadConstraints(ctx, c, []*unstructured.Unstructured{constraint})
+	assert.Nil(t, err)
+
+	// validating deployment manifests
+	validateTestManifestsFail(ctx, t, c, testFc, testManifest_USS.ErrorPaths)
+	validateTestManifestsSuccess(ctx, t, c, testFc, testManifest_USS.SuccessPaths)
+}
+
 //// TODO: rich description here
 //func TestValidateDeployment_All(t *testing.T) {
 //	// instantiate constraint client
diff --git a/pkg/safeguards/safeguards_test_helpers.go b/pkg/safeguards/safeguards_test_helpers.go
index 3f255f7..598c53a 100644
--- a/pkg/safeguards/safeguards_test_helpers.go
+++ b/pkg/safeguards/safeguards_test_helpers.go
@@ -13,6 +13,9 @@ func validateTestManifestsFail(ctx context.Context, t *testing.T, c *constraintc
 		errManifests, err := testFc.ReadManifests(path)
 		assert.Nil(t, err)
 
+		err = loadManifestObjects(ctx, c, errManifests)
+		assert.Nil(t, err)
+
 		// error case - should throw error
 		violations, err := getObjectViolations(ctx, c, errManifests)
 		assert.Nil(t, err)
@@ -25,6 +28,9 @@ func validateTestManifestsSuccess(ctx context.Context, t *testing.T, c *constrai
 		successManifests, err := testFc.ReadManifests(path)
 		assert.Nil(t, err)
 
+		err = loadManifestObjects(ctx, c, successManifests)
+		assert.Nil(t, err)
+
 		// success case - should not throw error
 		violations, err := getObjectViolations(ctx, c, successManifests)
 		assert.Nil(t, err)
diff --git a/pkg/safeguards/tests/all/success/all-success-manifest-1.yaml b/pkg/safeguards/tests/all/success/all-success-manifest-1.yaml
index 6c5843d..97c1f0d 100644
--- a/pkg/safeguards/tests/all/success/all-success-manifest-1.yaml
+++ b/pkg/safeguards/tests/all/success/all-success-manifest-1.yaml
@@ -29,9 +29,9 @@ spec:
       - name: my-container
         image: nginx:latest
         resources:
-        limits:
-          cpu: "200m"
-          memory: "1Gi"
+          limits:
+            cpu: "200m"
+            memory: "1Gi"
         livenessProbe:
           httpGet:
             path: /healthz
diff --git a/pkg/safeguards/tests/all/success/all-success-manifest-2.yaml b/pkg/safeguards/tests/all/success/all-success-manifest-2.yaml
index 8aa4771..fb80b3a 100644
--- a/pkg/safeguards/tests/all/success/all-success-manifest-2.yaml
+++ b/pkg/safeguards/tests/all/success/all-success-manifest-2.yaml
@@ -29,9 +29,9 @@ spec:
       - name: my-container
         image: nginx:latest
         resources:
-        limits:
-          cpu: "200m"
-          memory: "1Gi"
+          limits:
+            cpu: "200m"
+            memory: "1Gi"
         livenessProbe:
           httpGet:
             path: /healthz
diff --git a/pkg/safeguards/tests/unique-service-selectors/USS-error-manifest.yaml b/pkg/safeguards/tests/unique-service-selectors/USS-error-manifest.yaml
index b6f9e8b..0a7f83c 100644
--- a/pkg/safeguards/tests/unique-service-selectors/USS-error-manifest.yaml
+++ b/pkg/safeguards/tests/unique-service-selectors/USS-error-manifest.yaml
@@ -1,21 +1,25 @@
-apiVersion: apps/v1
-kind: Deployment
+apiVersion: v1
+kind: Service
 metadata:
-  name: my-deployment
-  labels:
-    app: my-app
-    environment: production
-    testLabel3: randomlabel
+  name: my-service-1
+  namespace: prod
 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
+    app.kubernetes.io/name: MyApp
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 9376
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-service-2
+  namespace: prod
+spec:
+  selector:
+    app.kubernetes.io/name: MyApp
+  ports:
+    - protocol: TCP
+      port: 220
+      targetPort: 9423
\ No newline at end of file
diff --git a/pkg/safeguards/tests/unique-service-selectors/USS-success-manifest.yaml b/pkg/safeguards/tests/unique-service-selectors/USS-success-manifest.yaml
index b6f9e8b..e2611e6 100644
--- a/pkg/safeguards/tests/unique-service-selectors/USS-success-manifest.yaml
+++ b/pkg/safeguards/tests/unique-service-selectors/USS-success-manifest.yaml
@@ -1,21 +1,25 @@
-apiVersion: apps/v1
-kind: Deployment
+apiVersion: v1
+kind: Service
 metadata:
-  name: my-deployment
-  labels:
-    app: my-app
-    environment: production
-    testLabel3: randomlabel
+  name: my-service-1
+  namespace: prod
 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
+    app.kubernetes.io/name: MyApp1
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 9376
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: my-service-2
+  namespace: prod
+spec:
+  selector:
+    app.kubernetes.io/name: MyApp2
+  ports:
+    - protocol: TCP
+      port: 220
+      targetPort: 9423
\ No newline at end of file