diff --git a/cmd/validate.go b/cmd/validate.go index c2abe0e8..396db901 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/Azure/draft/pkg/safeguards" - "github.com/Azure/draft/pkg/safeguards/preprocessing" "github.com/Azure/draft/pkg/safeguards/types" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -71,7 +70,7 @@ func (vc *validateCmd) run(c *cobra.Command) error { ctx := context.Background() var manifestFiles []types.ManifestFile - manifestFiles, err := preprocessing.GetManifestFiles(vc.manifestPath, opt) + manifestFiles, err := safeguards.GetManifestFiles(vc.manifestPath, opt) if err != nil { return fmt.Errorf("error retrieving manifest files: %w", err) } diff --git a/cmd/validate_test.go b/cmd/validate_test.go index f7dd0b72..d323c7e7 100644 --- a/cmd/validate_test.go +++ b/cmd/validate_test.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "path/filepath" "testing" "github.com/Azure/draft/pkg/safeguards" @@ -13,14 +12,21 @@ import ( "helm.sh/helm/v3/pkg/chartutil" ) +const ( + manifestPathDirectorySuccess = "../pkg/safeguards/tests/all/success" + manifestPathDirectoryError = "../pkg/safeguards/tests/all/error" + manifestPathFileSuccess = "../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml" + manifestPathFileError = "../pkg/safeguards/tests/all/error/all-error-manifest-1.yaml" + kustomizationPath = "../pkg/safeguards/tests/kustomize/overlays/production" + chartPath = "../pkg/safeguards/tests/testmanifests/validchart" + kustomizationFilePath = "../pkg/safeguards/tests/kustomize/overlays/production/kustomization.yaml" +) + // TestRunValidate tests the run command for `draft validate` for proper returns func TestRunValidate(t *testing.T) { ctx := context.TODO() manifestFilesEmpty := []types.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 []types.ManifestFile var opt chartutil.ReleaseOptions @@ -29,7 +35,7 @@ func TestRunValidate(t *testing.T) { assert.NotNil(t, err) // Scenario 2a: manifest path leads to a directory of manifestFiles - expect success - manifestFiles, err = preprocessing.GetManifestFiles(manifestPathDirectorySuccess, opt) + manifestFiles, err = safeguards.GetManifestFiles(manifestPathDirectorySuccess, opt) assert.Nil(t, err) v, err := safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -37,7 +43,7 @@ func TestRunValidate(t *testing.T) { assert.Equal(t, numViolations, 0) // Scenario 2b: manifest path leads to a directory of manifestFiles - expect failure - manifestFiles, err = preprocessing.GetManifestFiles(manifestPathDirectoryError, opt) + manifestFiles, err = safeguards.GetManifestFiles(manifestPathDirectoryError, opt) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -45,7 +51,7 @@ func TestRunValidate(t *testing.T) { assert.Greater(t, numViolations, 0) // Scenario 3a: manifest path leads to one manifest file - expect success - manifestFiles, err = preprocessing.GetManifestFiles(manifestPathFileSuccess, opt) + manifestFiles, err = safeguards.GetManifestFiles(manifestPathFileSuccess, opt) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -53,7 +59,7 @@ func TestRunValidate(t *testing.T) { assert.Equal(t, numViolations, 0) // Scenario 3b: manifest path leads to one manifest file - expect failure - manifestFiles, err = preprocessing.GetManifestFiles(manifestPathFileError, opt) + manifestFiles, err = safeguards.GetManifestFiles(manifestPathFileError, opt) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -61,10 +67,7 @@ func TestRunValidate(t *testing.T) { assert.Greater(t, numViolations, 0) //Scenario 4: Test Kustomize - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) - - manifestFiles, err = preprocessing.GetManifestFiles(kustomizationPath, opt) + manifestFiles, err = safeguards.GetManifestFiles(kustomizationPath, opt) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -74,10 +77,8 @@ func TestRunValidate(t *testing.T) { // Scenario 5: Test Helm opt.Name = "test-release-name" opt.Namespace = "test-release-namespace" - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) - manifestFiles, err = preprocessing.GetManifestFiles(chartPath, opt) + manifestFiles, err = safeguards.GetManifestFiles(chartPath, opt) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -88,17 +89,11 @@ func TestRunValidate(t *testing.T) { // TestRunValidate_Kustomize tests the run command for `draft validate` for proper returns when given a kustomize project func TestRunValidate_Kustomize(t *testing.T) { ctx := context.TODO() - kustomizationPath, _ := filepath.Abs("../pkg/safeguards/tests/kustomize/overlays/production") - kustomizationFilePath, _ := filepath.Abs("../pkg/safeguards/tests/kustomize/overlays/production/kustomization.yaml") - - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) - var manifestFiles []types.ManifestFile var err error // Scenario 1a: kustomizationPath leads to a directory containing kustomization.yaml - expect success - manifestFiles, err = preprocessing.RenderKustomizeManifest(kustomizationPath, tempDir) + manifestFiles, err = preprocessing.RenderKustomizeManifest(kustomizationPath) assert.Nil(t, err) v, err := safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) @@ -106,7 +101,7 @@ func TestRunValidate_Kustomize(t *testing.T) { assert.Equal(t, numViolations, 1) // Scenario 1b: kustomizationFilePath path leads to a specific kustomization.yaml - expect success - manifestFiles, err = preprocessing.RenderKustomizeManifest(kustomizationFilePath, tempDir) + manifestFiles, err = preprocessing.RenderKustomizeManifest(kustomizationFilePath) assert.Nil(t, err) v, err = safeguards.GetManifestResults(ctx, manifestFiles) assert.Nil(t, err) diff --git a/cmd/validate_test_helpers.go b/cmd/validate_test_helpers.go index d728886e..a47d37d4 100644 --- a/cmd/validate_test_helpers.go +++ b/cmd/validate_test_helpers.go @@ -1,20 +1,9 @@ package cmd import ( - "os" - "path/filepath" - "testing" - types "github.com/Azure/draft/pkg/safeguards/types" ) -var tempDir, _ = filepath.Abs("./testdata") - -const ( - chartPath = "../pkg/safeguards/tests/testmanifests/validchart" - kustomizationPath = "../pkg/safeguards/tests/kustomize/overlays/production" -) - func countTestViolations(results []types.ManifestResult) int { numViolations := 0 for _, r := range results { @@ -23,16 +12,3 @@ func countTestViolations(results []types.ManifestResult) int { return numViolations } - -func makeTempDir(t *testing.T) { - if err := os.MkdirAll(tempDir, 0755); err != nil { - t.Fatalf("failed to create temporary output directory: %s", err) - } -} - -func cleanupDir(t *testing.T, dir string) { - err := os.RemoveAll(dir) - if err != nil { - t.Fatalf("Failed to clean directory: %s", err) - } -} diff --git a/pkg/safeguards/helpers.go b/pkg/safeguards/helpers.go index 89059a9b..a9009e6c 100644 --- a/pkg/safeguards/helpers.go +++ b/pkg/safeguards/helpers.go @@ -5,19 +5,106 @@ import ( "fmt" "io/fs" "os" + "path" "path/filepath" + "strings" + + "helm.sh/helm/v3/pkg/chartutil" - sgTypes "github.com/Azure/draft/pkg/safeguards/types" constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client" "github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego" "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" "github.com/open-policy-agent/gatekeeper/v3/pkg/target" log "github.com/sirupsen/logrus" - "golang.org/x/mod/semver" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/Azure/draft/pkg/safeguards/preprocessing" + "github.com/Azure/draft/pkg/safeguards/types" ) +// Given a path, will determine if it's Kustomize, Helm, a directory of manifests, or a single manifest +func GetManifestFiles(manifestsPath string, opt chartutil.ReleaseOptions) ([]types.ManifestFile, error) { + isDir, err := IsDirectory(manifestsPath) + if err != nil { + return nil, fmt.Errorf("not a valid file or directory: %w", err) + } + + var manifestFiles []types.ManifestFile + if isDir { + // check if Helm or Kustomize dir + if isHelm(true, manifestsPath) { + return preprocessing.RenderHelmChart(false, manifestsPath, opt) + } else if isKustomize(true, manifestsPath) { + return preprocessing.RenderKustomizeManifest(manifestsPath) + } else { + manifestFiles, err = GetManifestFilesFromDir(manifestsPath) + return manifestFiles, err + } + } else if IsYAML(manifestsPath) { // path points to a file + if isHelm(false, manifestsPath) { + return preprocessing.RenderHelmChart(true, manifestsPath, opt) + } else if isKustomize(false, manifestsPath) { + return preprocessing.RenderKustomizeManifest(manifestsPath) + } else { + byteContent, err := os.ReadFile(manifestsPath) + if err != nil { + return nil, fmt.Errorf("could not read file %s: %s", manifestsPath, err) + } + manifestFiles = append(manifestFiles, types.ManifestFile{ + Name: path.Base(manifestsPath), + ManifestContent: byteContent, + }) + } + return manifestFiles, nil + } else { + return nil, fmt.Errorf("expected at least one .yaml or .yml file within given path") + } +} + +// GetManifestFilesFromDir uses filepath.Walk to retrieve a list of the manifest files within a directory of .yaml files +func GetManifestFilesFromDir(p string) ([]types.ManifestFile, error) { + var manifestFiles []types.ManifestFile + + err := filepath.Walk(p, func(walkPath string, info fs.FileInfo, err error) error { + manifest := types.ManifestFile{} + // skip when walkPath is just given path and also a directory + if p == walkPath && info.IsDir() { + return nil + } + + if err != nil { + return fmt.Errorf("error walking path %s with error: %w", walkPath, err) + } + + if !info.IsDir() && info.Name() != "" && IsYAML(walkPath) { + log.Debugf("%s is not a directory, appending to manifestFiles", info.Name()) + + byteContent, err := os.ReadFile(walkPath) + if err != nil { + return fmt.Errorf("could not read file %s: %s", walkPath, err) + } + manifest.Name = info.Name() + manifest.ManifestContent = byteContent + manifestFiles = append(manifestFiles, manifest) + } else if !IsYAML(p) { + log.Debugf("%s is not a manifest file, skipping...", info.Name()) + } else { + log.Debugf("%s is a directory, skipping...", info.Name()) + } + + return nil + }) + if err != nil { + return nil, fmt.Errorf("could not walk directory: %w", err) + } + if len(manifestFiles) == 0 { + return nil, fmt.Errorf("no manifest files found within given path") + } + + return manifestFiles, nil +} + // retrieves the constraint client that does all rego code related operations func getConstraintClient() (*constraintclient.Client, error) { driver, err := rego.New() @@ -35,20 +122,20 @@ func getConstraintClient() (*constraintclient.Client, error) { // sorts the list of supported safeguards versions and returns the last item in the list func getLatestSafeguardsVersion() string { - semver.Sort(sgTypes.SupportedVersions) - return sgTypes.SupportedVersions[len(sgTypes.SupportedVersions)-1] + semver.Sort(types.SupportedVersions) + return types.SupportedVersions[len(types.SupportedVersions)-1] } -func updateSafeguardPaths(safeguardList *[]sgTypes.Safeguard) { +func updateSafeguardPaths(safeguardList *[]types.Safeguard) { for _, sg := range *safeguardList { - sg.TemplatePath = fmt.Sprintf("%s/%s/%s", sgTypes.SelectedVersion, sg.Name, sgTypes.TemplateFileName) - sg.ConstraintPath = fmt.Sprintf("%s/%s/%s", sgTypes.SelectedVersion, sg.Name, sgTypes.ConstraintFileName) + sg.TemplatePath = fmt.Sprintf("%s/%s/%s", types.SelectedVersion, sg.Name, types.TemplateFileName) + sg.ConstraintPath = fmt.Sprintf("%s/%s/%s", types.SelectedVersion, sg.Name, types.ConstraintFileName) } } // adds Safeguard_CRIP to full list of Safeguards func AddSafeguardCRIP() { - fc.Safeguards = append(fc.Safeguards, sgTypes.Safeguard_CRIP) + fc.Safeguards = append(fc.Safeguards, types.Safeguard_CRIP) } // loads constraint templates, constraints into constraint client @@ -109,45 +196,6 @@ func IsYAML(path string) bool { return filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" } -// GetManifestFiles uses filepath.Walk to retrieve a list of the manifest files within the given manifest path -func GetManifestFiles(p string) ([]sgTypes.ManifestFile, error) { - var manifestFiles []sgTypes.ManifestFile - - err := filepath.Walk(p, func(walkPath string, info fs.FileInfo, err error) error { - manifest := sgTypes.ManifestFile{} - // skip when walkPath is just given path and also a directory - if p == walkPath && info.IsDir() { - return nil - } - - if err != nil { - return fmt.Errorf("error walking path %s with error: %w", walkPath, err) - } - - if !info.IsDir() && info.Name() != "" && IsYAML(walkPath) { - log.Debugf("%s is not a directory, appending to manifestFiles", info.Name()) - - manifest.Name = info.Name() - manifest.Path = walkPath - manifestFiles = append(manifestFiles, manifest) - } else if !IsYAML(p) { - log.Debugf("%s is not a manifest file, skipping...", info.Name()) - } else { - log.Debugf("%s is a directory, skipping...", info.Name()) - } - - return nil - }) - if err != nil { - return nil, fmt.Errorf("could not walk directory: %w", err) - } - if len(manifestFiles) == 0 { - return nil, fmt.Errorf("no manifest files found within given path") - } - - return manifestFiles, 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. @@ -179,3 +227,43 @@ func getObjectViolations(ctx context.Context, c *constraintclient.Client, object return results, nil } + +// Checks whether a given path is a helm directory or a path to a Helm Chart (contains/is Chart.yaml) +func isHelm(isDir bool, path string) bool { + var chartPaths []string // Used to define what a valid helm chart looks like. Currently, presence of Chart.yaml/.yml. + + if isDir { + chartPaths = []string{filepath.Join(path, "Chart.yaml")} + chartPaths = append(chartPaths, filepath.Join(path, "Chart.yml")) + } else { + if filepath.Base(path) != "Chart.yaml" && filepath.Base(path) != "Chart.yml" { + return false + } + chartPaths = []string{path} + } + + for _, path := range chartPaths { + _, err := os.Stat(path) + if err == nil { //Found the file, it's a valid helm chart + return true + } + } + + return false +} + +// IsKustomize checks whether a given path should be treated as a kustomize project +func isKustomize(isDir bool, p string) bool { + var err error + if isDir { + if _, err = os.Stat(filepath.Join(p, "kustomization.yaml")); err == nil { + return true + } else if _, err = os.Stat(filepath.Join(p, "kustomization.yml")); err == nil { + return true + } else { + return false + } + } else { + return strings.Contains(p, "kustomization.yaml") + } +} diff --git a/pkg/safeguards/helpers_test.go b/pkg/safeguards/helpers_test.go index 6cd18c4b..9822fb20 100644 --- a/pkg/safeguards/helpers_test.go +++ b/pkg/safeguards/helpers_test.go @@ -2,19 +2,53 @@ package safeguards import ( "context" + "os" + "path/filepath" "testing" - "github.com/Azure/draft/pkg/safeguards/preprocessing" - c "github.com/Azure/draft/pkg/safeguards/types" + "github.com/Azure/draft/pkg/safeguards/types" constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client" "github.com/stretchr/testify/assert" "helm.sh/helm/v3/pkg/chartutil" ) -func validateOneTestManifestFail(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc c.FileCrawler, testManifestPaths []string) { +const ( + chartPath = "tests/testmanifests/validchart" + kustomizationPath = "tests/kustomize/overlays/production" + kustomizationFilePath = "tests/kustomize/overlays/production/kustomization.yaml" + directPath_ToValidChart = "tests/testmanifests/validchart/Chart.yaml" +) + +func TestGetManifestFiles(t *testing.T) { + var opt chartutil.ReleaseOptions + // Test Helm + _, err := GetManifestFiles(chartPath, opt) + assert.Nil(t, err) + + // Test Kustomize + _, err = GetManifestFiles(kustomizationPath, opt) + assert.Nil(t, err) + + // Test Normal Directory with manifest files + absPath, err := filepath.Abs("tests/all/success") + assert.Nil(t, err) + _, err = GetManifestFiles(absPath, opt) + assert.Nil(t, err) + + // test single manifest file + manifestPathFileSuccess, err := filepath.Abs("tests/all/success/all-success-manifest-1.yaml") + assert.Nil(t, err) + _, err = GetManifestFiles(manifestPathFileSuccess, opt) + assert.Nil(t, err) +} + +func validateOneTestManifestFail(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc types.FileCrawler, testManifestPaths []string) { for _, path := range testManifestPaths { - errManifests, err := testFc.ReadManifests(path) + byteContent, err := os.ReadFile(path) + assert.Nil(t, err) + + errManifests, err := testFc.ReadManifests(byteContent) assert.Nil(t, err) err = loadManifestObjects(ctx, c, errManifests) @@ -27,9 +61,12 @@ func validateOneTestManifestFail(ctx context.Context, t *testing.T, c *constrain } } -func validateOneTestManifestSuccess(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc c.FileCrawler, testManifestPaths []string) { +func validateOneTestManifestSuccess(ctx context.Context, t *testing.T, c *constraintclient.Client, testFc types.FileCrawler, testManifestPaths []string) { for _, path := range testManifestPaths { - successManifests, err := testFc.ReadManifests(path) + byteContent, err := os.ReadFile(path) + assert.Nil(t, err) + + successManifests, err := testFc.ReadManifests(byteContent) assert.Nil(t, err) err = loadManifestObjects(ctx, c, successManifests) @@ -45,7 +82,7 @@ func validateOneTestManifestSuccess(ctx context.Context, t *testing.T, c *constr func validateAllTestManifestsFail(ctx context.Context, t *testing.T, testManifestPaths []string) { var opt chartutil.ReleaseOptions for _, path := range testManifestPaths { - manifestFiles, err := preprocessing.GetManifestFiles(path, opt) + manifestFiles, err := GetManifestFiles(path, opt) assert.Nil(t, err) // error case - should throw error @@ -59,7 +96,7 @@ func validateAllTestManifestsFail(ctx context.Context, t *testing.T, testManifes func validateAllTestManifestsSuccess(ctx context.Context, t *testing.T, testManifestPaths []string) { for _, path := range testManifestPaths { - manifestFiles, err := preprocessing.GetManifestFilesFromDir(path) + manifestFiles, err := GetManifestFilesFromDir(path) assert.Nil(t, err) // success case - should not throw error @@ -70,3 +107,44 @@ func validateAllTestManifestsSuccess(ctx context.Context, t *testing.T, testMani } } } + +// TestIsKustomize checks whether the given path contains a kustomize project +func TestIsKustomize(t *testing.T) { + kustomizationPath := "tests/kustomize/overlays/production" + + // path contains a kustomization.yaml file + iskustomize := isKustomize(true, kustomizationPath) + assert.True(t, iskustomize) + // path is a kustomization.yaml file + iskustomize = isKustomize(false, kustomizationFilePath) + assert.True(t, iskustomize) + // not a kustomize project + iskustomize = isKustomize(true, chartPath) + assert.False(t, iskustomize) +} + +func TestIsHelm(t *testing.T) { + // path is a directory + ishelm := isHelm(true, chartPath) + assert.True(t, ishelm) + + // path is a Chart.yaml file + ishelm = isHelm(false, directPath_ToValidChart) + assert.True(t, ishelm) + + // Is a directory but does not contain Chart.yaml + ishelm = isHelm(true, kustomizationPath) + assert.False(t, ishelm) + + // Is a directory of manifest files, not a helm chart + ishelm = isHelm(false, "../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml") + assert.False(t, ishelm) + + // Is a directory of manifest files, not a helm chart + ishelm = isHelm(false, "../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml") + assert.False(t, ishelm) + + // invalid path + ishelm = isHelm(false, "invalid/path") + assert.False(t, ishelm) +} diff --git a/pkg/safeguards/manifestresults.go b/pkg/safeguards/manifestresults.go index 2aa595ed..091a0631 100644 --- a/pkg/safeguards/manifestresults.go +++ b/pkg/safeguards/manifestresults.go @@ -73,7 +73,7 @@ func GetManifestResults(ctx context.Context, manifestFiles []types.ManifestFile) // aggregate of every manifest object into one list allManifestObjects := []*unstructured.Unstructured{} for _, m := range manifestFiles { - manifestObjects, err := fc.ReadManifests(m.Path) // read all the objects stored in a single file + manifestObjects, err := fc.ReadManifests(m.ManifestContent) // read all the objects stored in a single file if err != nil { log.Errorf("reading objects %s", err.Error()) return manifestResults, err diff --git a/pkg/safeguards/preprocessing/preprocessing.go b/pkg/safeguards/preprocessing/preprocessing.go index e2f04498..37d5bad4 100644 --- a/pkg/safeguards/preprocessing/preprocessing.go +++ b/pkg/safeguards/preprocessing/preprocessing.go @@ -2,10 +2,8 @@ package preprocessing import ( "fmt" - "io/fs" "os" "path/filepath" - "strings" sgTypes "github.com/Azure/draft/pkg/safeguards/types" log "github.com/sirupsen/logrus" @@ -19,82 +17,8 @@ import ( "sigs.k8s.io/kustomize/kyaml/filesys" ) -// Given a path, will determine if it's Kustomize, Helm, a directory of manifests, or a single manifest -func GetManifestFiles(manifestsPath string, opt chartutil.ReleaseOptions) ([]sgTypes.ManifestFile, error) { - isDir, err := IsDirectory(manifestsPath) - if err != nil { - return nil, fmt.Errorf("not a valid file or directory: %w", err) - } - - var manifestFiles []sgTypes.ManifestFile - if isDir { - // check if Helm or Kustomize dir - if isHelm(true, manifestsPath) { - return RenderHelmChart(false, manifestsPath, tempDir, opt) - } else if isKustomize(true, manifestsPath) { - return RenderKustomizeManifest(manifestsPath, tempDir) - } else { - manifestFiles, err = GetManifestFilesFromDir(manifestsPath) - return manifestFiles, err - } - } else if IsYAML(manifestsPath) { // path points to a file - if isHelm(false, manifestsPath) { - return RenderHelmChart(true, manifestsPath, tempDir, opt) - } else if isKustomize(false, manifestsPath) { - return RenderKustomizeManifest(manifestsPath, tempDir) - } else { - manifestFiles = append(manifestFiles, sgTypes.ManifestFile{ - Name: filepath.Base(manifestsPath), - Path: manifestsPath, - }) - } - return manifestFiles, nil - } else { - return nil, fmt.Errorf("expected at least one .yaml or .yml file within given path") - } -} - -// getManifestFiles uses filepath.Walk to retrieve a list of the manifest files within a directory of .yaml files -func GetManifestFilesFromDir(p string) ([]sgTypes.ManifestFile, error) { - var manifestFiles []sgTypes.ManifestFile - - err := filepath.Walk(p, func(walkPath string, info fs.FileInfo, err error) error { - manifest := sgTypes.ManifestFile{} - // skip when walkPath is just given path and also a directory - if p == walkPath && info.IsDir() { - return nil - } - - if err != nil { - return fmt.Errorf("error walking path %s with error: %w", walkPath, err) - } - - if !info.IsDir() && info.Name() != "" && IsYAML(walkPath) { - log.Debugf("%s is not a directory, appending to manifestFiles", info.Name()) - - manifest.Name = info.Name() - manifest.Path = walkPath - manifestFiles = append(manifestFiles, manifest) - } else if !IsYAML(p) { - log.Debugf("%s is not a manifest file, skipping...", info.Name()) - } else { - log.Debugf("%s is a directory, skipping...", info.Name()) - } - - return nil - }) - if err != nil { - return nil, fmt.Errorf("could not walk directory: %w", err) - } - if len(manifestFiles) == 0 { - return nil, fmt.Errorf("no manifest files found within given path") - } - - return manifestFiles, nil -} - // Given a Helm chart directory or file, renders all templates and writes them to the specified directory -func RenderHelmChart(isFile bool, mainChartPath, tempDir string, opt chartutil.ReleaseOptions) ([]sgTypes.ManifestFile, error) { +func RenderHelmChart(isFile bool, mainChartPath string, opt chartutil.ReleaseOptions) ([]sgTypes.ManifestFile, error) { if isFile { // Get the directory that the Chart.yaml lives in mainChartPath = filepath.Dir(mainChartPath) } @@ -133,13 +57,10 @@ func RenderHelmChart(isFile bool, mainChartPath, tempDir string, opt chartutil.R return nil, fmt.Errorf("failed to render chart: %s", err) } - // Write each rendered file to the output directory with the same name as in templates/ + // Convert renderd files to []byte for renderedPath, content := range renderedFiles { - outputFilePath := filepath.Join(tempDir, filepath.Base(renderedPath)) - if err := os.WriteFile(outputFilePath, []byte(content), 0644); err != nil { - return nil, fmt.Errorf("failed to write manifest file: %s", err) - } - manifestFiles = append(manifestFiles, sgTypes.ManifestFile{Name: filepath.Base(renderedPath), Path: outputFilePath}) + byteContent := []byte(content) + manifestFiles = append(manifestFiles, sgTypes.ManifestFile{Name: filepath.Base(renderedPath), ManifestContent: byteContent}) } } @@ -156,8 +77,8 @@ func CreateTempDir(p string) error { return err } -// Given a kustomization manifest file within kustomizationPath, RenderKustomizeManifest will render templates out to tempDir -func RenderKustomizeManifest(kustomizationPath, tempDir string) ([]sgTypes.ManifestFile, error) { +// Given a kustomization manifest file within kustomizationPath, RenderKustomizeManifest will return render templates +func RenderKustomizeManifest(kustomizationPath string) ([]sgTypes.ManifestFile, error) { log.Debugf("Rendering kustomization.yaml...") if IsYAML(kustomizationPath) { kustomizationPath = filepath.Dir(kustomizationPath) @@ -179,26 +100,16 @@ func RenderKustomizeManifest(kustomizationPath, tempDir string) ([]sgTypes.Manif // Output the manifests var manifestFiles []sgTypes.ManifestFile - kindMap := make(map[string]int) for _, res := range resMap.Resources() { yamlRes, err := res.AsYAML() if err != nil { return nil, fmt.Errorf("error converting resource to YAML: %s", err.Error()) } - // index of every kind of manifest for outputRenderPath - kindMap[res.GetKind()] += 1 - outputRenderPath := filepath.Join(tempDir, strings.ToLower(res.GetKind())) + fmt.Sprintf("-%d.yaml", kindMap[res.GetKind()]) - - err = kustomizeFS.WriteFile(outputRenderPath, yamlRes) - if err != nil { - return nil, fmt.Errorf("error writing yaml resource: %s", err.Error()) - } - // write yamlRes to dir manifestFiles = append(manifestFiles, sgTypes.ManifestFile{ - Name: res.GetName(), - Path: outputRenderPath, + Name: res.GetName(), + ManifestContent: yamlRes, }) } diff --git a/pkg/safeguards/preprocessing/preprocessing_helpers.go b/pkg/safeguards/preprocessing/preprocessing_helpers.go index 5cf51946..0ae79946 100644 --- a/pkg/safeguards/preprocessing/preprocessing_helpers.go +++ b/pkg/safeguards/preprocessing/preprocessing_helpers.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/chart" @@ -73,46 +72,6 @@ func getReleaseOptions(chart *chart.Chart, vals map[string]interface{}, opt char return mergedValues, nil } -// IsKustomize checks whether a given path should be treated as a kustomize project -func isKustomize(isDir bool, p string) bool { - var err error - if isDir { - if _, err = os.Stat(filepath.Join(p, "kustomization.yaml")); err == nil { - return true - } else if _, err = os.Stat(filepath.Join(p, "kustomization.yml")); err == nil { - return true - } else { - return false - } - } else { - return strings.Contains(p, "kustomization.yaml") - } -} - -// Checks whether a given path is a helm directory or a path to a Helm Chart (contains/is Chart.yaml) -func isHelm(isDir bool, path string) bool { - var chartPaths []string // Used to define what a valid helm chart looks like. Currently, presence of Chart.yaml/.yml. - - if isDir { - chartPaths = []string{filepath.Join(path, "Chart.yaml")} - chartPaths = append(chartPaths, filepath.Join(path, "Chart.yml")) - } else { - if filepath.Base(path) != "Chart.yaml" && filepath.Base(path) != "Chart.yml" { - return false - } - chartPaths = []string{path} - } - - for _, path := range chartPaths { - _, err := os.Stat(path) - if err == nil { //Found the file, it's a valid helm chart - return true - } - } - - return false -} - // IsYAML determines if a file is of the YAML extension or not func IsYAML(path string) bool { return filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" diff --git a/pkg/safeguards/preprocessing/preprocessing_test.go b/pkg/safeguards/preprocessing/preprocessing_test.go index 7d551c0b..c0ba8943 100644 --- a/pkg/safeguards/preprocessing/preprocessing_test.go +++ b/pkg/safeguards/preprocessing/preprocessing_test.go @@ -1,148 +1,119 @@ package preprocessing import ( - "os" - "path" - "path/filepath" + "bytes" "testing" + consts "github.com/Azure/draft/pkg/safeguards/types" "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chartutil" ) // Test rendering a valid Helm chart with no subcharts and three templates func TestRenderHelmChart_Valid(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) var opt chartutil.ReleaseOptions - manifestFiles, err := RenderHelmChart(false, chartPath, tempDir, opt) + manifestFiles, err := RenderHelmChart(false, consts.ChartPath, opt) assert.Nil(t, err) // Check that the output directory exists and contains expected files - expectedFiles := make(map[string]string) - expectedFiles["deployment.yaml"] = getManifestAsString(t, "../tests/testmanifests/expecteddeployment.yaml") - expectedFiles["service.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedservice.yaml") - expectedFiles["ingress.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedingress.yaml") - - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + expectedFiles := make(map[string][]byte) + expectedFiles["deployment.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expecteddeployment.yaml") + expectedFiles["service.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expectedservice.yaml") + expectedFiles["ingress.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expectedingress.yaml") + + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) } - cleanupDir(t, tempDir) - makeTempDir(t) - // Test by giving file directly - manifestFiles, err = RenderHelmChart(true, directPath_ToValidChart, tempDir, opt) + manifestFiles, err = RenderHelmChart(true, consts.DirectPath_ToValidChart, opt) assert.Nil(t, err) - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) } } // Test rendering a valid Helm chart with no subcharts and three templates, using command line flags func TestRenderHelmChartWithFlags_Valid(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) // user defined release name and namespace from cli flags opt := chartutil.ReleaseOptions{ Name: "test-flags-name", Namespace: "test-flags-namespace", } - manifestFiles, err := RenderHelmChart(false, chartPath, tempDir, opt) + manifestFiles, err := RenderHelmChart(false, consts.ChartPath, opt) assert.Nil(t, err) // Check that the output directory exists and contains expected files - expectedFiles := make(map[string]string) - expectedFiles["deployment.yaml"] = getManifestAsString(t, "../tests/testmanifests/expecteddeployment_flags.yaml") - expectedFiles["service.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedservice_flags.yaml") - expectedFiles["ingress.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedingress_flags.yaml") - - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + expectedFiles := make(map[string][]byte) + expectedFiles["deployment.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expecteddeployment_flags.yaml") + expectedFiles["service.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expectedservice_flags.yaml") + expectedFiles["ingress.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expectedingress_flags.yaml") + + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) } - cleanupDir(t, tempDir) - makeTempDir(t) - // Test by giving file directly - manifestFiles, err = RenderHelmChart(true, directPath_ToValidChart, tempDir, opt) + manifestFiles, err = RenderHelmChart(true, consts.DirectPath_ToValidChart, opt) assert.Nil(t, err) - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) } } // Should successfully render a Helm chart with sub charts and be able to render subchart separately within a helm chart func TestSubCharts(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) var opt chartutil.ReleaseOptions - manifestFiles, err := RenderHelmChart(false, subcharts, tempDir, opt) + manifestFiles, err := RenderHelmChart(false, consts.Subcharts, opt) assert.Nil(t, err) - // Assert that 3 files were created in temp dir: 1 from main chart, 2 from subcharts - files, _ := os.ReadDir(tempDir) - assert.Equal(t, len(files), 3) + expectedFiles := make(map[string][]byte) + expectedFiles["maindeployment.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expected-mainchart.yaml") + expectedFiles["deployment1.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expected-subchart1.yaml") + expectedFiles["deployment2.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expected-subchart2.yaml") - expectedFiles := make(map[string]string) - expectedFiles["maindeployment.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-mainchart.yaml") - expectedFiles["deployment1.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-subchart1.yaml") - expectedFiles["deployment2.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-subchart2.yaml") - - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) } - cleanupDir(t, tempDir) - makeTempDir(t) - // Given a sub-chart dir, that specific sub chart only should be evaluated and rendered - _, err = RenderHelmChart(false, subchartDir, tempDir, opt) + _, err = RenderHelmChart(false, consts.SubchartDir, opt) assert.Nil(t, err) - cleanupDir(t, tempDir) - makeTempDir(t) - // Given a Chart.yaml in the main directory, main chart and subcharts should be evaluated - _, err = RenderHelmChart(true, directPath_ToMainChartYaml, tempDir, opt) + manifestFiles, err = RenderHelmChart(true, consts.DirectPath_ToSubchartYaml, opt) assert.Nil(t, err) - cleanupDir(t, tempDir) - makeTempDir(t) - // Given path to a sub-Chart.yaml with a dependency on another subchart, should render both subcharts, but not the main chart - manifestFiles, err = RenderHelmChart(true, directPath_ToSubchartYaml, tempDir, opt) + manifestFiles, err = RenderHelmChart(true, consts.DirectPath_ToSubchartYaml, opt) assert.Nil(t, err) - expectedFiles = make(map[string]string) - expectedFiles["deployment1.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-subchart1.yaml") - expectedFiles["deployment2.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-subchart2.yaml") + expectedFiles = make(map[string][]byte) + expectedFiles["deployment1.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expected-subchart1.yaml") + expectedFiles["deployment2.yaml"] = getManifestAsBytes(t, "../tests/testmanifests/expected-subchart2.yaml") - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + assert.Equal(t, len(manifestFiles), 2) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + assert.Equal(t, bytes.TrimSpace(writtenManifestFile.ManifestContent), expectedYaml) + assert.NoFileExists(t, "maindeployment.yaml", "Unexpected file was created: maindeployment.yaml") } - - //expect mainchart.yaml to not exist - outputFilePath := filepath.Join(tempDir, "maindeployment.yaml") - assert.NoFileExists(t, outputFilePath, "Unexpected file was created: %s", outputFilePath) } /** @@ -151,158 +122,62 @@ func TestSubCharts(t *testing.T) { // Should fail if the Chart.yaml is invalid func TestInvalidChartAndValues(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) var opt chartutil.ReleaseOptions - _, err := RenderHelmChart(false, invalidChartPath, tempDir, opt) + _, err := RenderHelmChart(false, consts.InvalidChartPath, opt) assert.NotNil(t, err) assert.Contains(t, err.Error(), "failed to load main chart: validation: chart.metadata.name is required") - _, err = RenderHelmChart(true, directPath_ToValidChart, tempDir, opt) - assert.Nil(t, err) - - // Should fail if values.yaml doesn't contain all values necessary for templating - cleanupDir(t, tempDir) - makeTempDir(t) - - _, err = RenderHelmChart(false, invalidValuesChart, tempDir, opt) + _, err = RenderHelmChart(false, consts.InvalidValuesChart, opt) assert.NotNil(t, err) } func TestInvalidDeployments(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) var opt chartutil.ReleaseOptions - _, err := RenderHelmChart(false, invalidDeploymentSyntax, tempDir, opt) + _, err := RenderHelmChart(false, consts.InvalidDeploymentSyntax, opt) assert.NotNil(t, err) assert.Contains(t, err.Error(), "parse error") assert.Contains(t, err.Error(), "function \"selector\" not defined") - _, err = RenderHelmChart(false, invalidDeploymentValues, tempDir, opt) + _, err = RenderHelmChart(false, consts.InvalidDeploymentValues, opt) assert.NotNil(t, err) assert.Contains(t, err.Error(), "map has no entry for key") } // Test different helm folder structures func TestDifferentFolderStructures(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) var opt chartutil.ReleaseOptions - - manifestFiles, err := RenderHelmChart(false, folderwithHelpersTmpl, tempDir, opt) // includes _helpers.tpl + manifestFiles, err := RenderHelmChart(false, consts.FolderwithHelpersTmpl, opt) // includes _helpers.tpl assert.Nil(t, err) - expectedFiles := make(map[string]string) - expectedFiles["deployment.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-helpers-deployment.yaml") - expectedFiles["service.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-helpers-service.yaml") - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + expectedFiles := make(map[string][]byte) + expectedFiles["deployment.yaml"] = normalizeNewlines(getManifestAsBytes(t, "../tests/testmanifests/expected-helpers-deployment.yaml")) + expectedFiles["service.yaml"] = normalizeNewlines(getManifestAsBytes(t, "../tests/testmanifests/expected-helpers-service.yaml")) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + resFile := bytes.TrimSpace(normalizeNewlines(writtenManifestFile.ManifestContent)) + assert.Equal(t, resFile, expectedYaml) } - cleanupDir(t, tempDir) - makeTempDir(t) - manifestFiles, err = RenderHelmChart(false, multipleTemplateDirs, tempDir, opt) // all manifests defined in one file + manifestFiles, err = RenderHelmChart(false, consts.MultipleTemplateDirs, opt) // all manifests defined in one file assert.Nil(t, err) - expectedFiles = make(map[string]string) - expectedFiles["resources.yaml"] = getManifestAsString(t, "../tests/testmanifests/expected-resources.yaml") - expectedFiles["service-1.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedservice.yaml") - expectedFiles["service-2.yaml"] = getManifestAsString(t, "../tests/testmanifests/expectedservice2.yaml") - for _, writtenFile := range manifestFiles { - expectedYaml := expectedFiles[writtenFile.Name] - writtenYaml := parseYAML(t, getManifestAsString(t, writtenFile.Path)) - assert.Equal(t, writtenYaml, parseYAML(t, expectedYaml)) + expectedFiles = make(map[string][]byte) + expectedFiles["resources.yaml"] = normalizeNewlines(getManifestAsBytes(t, "../tests/testmanifests/expected-resources.yaml")) + expectedFiles["service-1.yaml"] = normalizeNewlines(getManifestAsBytes(t, "../tests/testmanifests/expectedservice.yaml")) + expectedFiles["service-2.yaml"] = normalizeNewlines(getManifestAsBytes(t, "../tests/testmanifests/expectedservice2.yaml")) + for i, writtenManifestFile := range manifestFiles { + writtenFileName := manifestFiles[i].Name + expectedYaml := bytes.TrimSpace(expectedFiles[writtenFileName]) + resFile := bytes.TrimSpace(normalizeNewlines(writtenManifestFile.ManifestContent)) + assert.Equal(t, string(resFile), string(expectedYaml)) } } // Test rendering a valid kustomization.yaml func TestRenderKustomizeManifest_Valid(t *testing.T) { - makeTempDir(t) - t.Cleanup(func() { cleanupDir(t, tempDir) }) - - _, err := RenderKustomizeManifest(kustomizationPath, tempDir) - assert.Nil(t, err) -} - -// TestIsKustomize checks whether the given path contains a kustomize project -func TestIsKustomize(t *testing.T) { - // path contains a kustomization.yaml file - iskustomize := isKustomize(true, kustomizationPath) - assert.True(t, iskustomize) - // path is a kustomization.yaml file - iskustomize = isKustomize(false, kustomizationFilePath) - assert.True(t, iskustomize) - // not a kustomize project - iskustomize = isKustomize(true, chartPath) - assert.False(t, iskustomize) -} - -func TestIsHelm(t *testing.T) { - // path is a directory - ishelm := isHelm(true, chartPath) - assert.True(t, ishelm) - - // path is a Chart.yaml file - ishelm = isHelm(false, directPath_ToValidChart) - assert.True(t, ishelm) - - // Is a directory but does not contain Chart.yaml - ishelm = isHelm(true, kustomizationPath) - assert.False(t, ishelm) - - // Is a directory of manifest files, not a helm chart - ishelm = isHelm(false, "../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml") - assert.False(t, ishelm) - - // Is a directory of manifest files, not a helm chart - ishelm = isHelm(false, "../pkg/safeguards/tests/all/success/all-success-manifest-1.yaml") - assert.False(t, ishelm) - - // invalid path - ishelm = isHelm(false, "invalid/path") - assert.False(t, ishelm) -} - -// TestIsYAML tests the IsYAML function for proper returns -func TestIsYAML(t *testing.T) { - dirNotYaml, _ := filepath.Abs("../tests/not-yaml") - dirYaml, _ := filepath.Abs("../tests/all/success") - fileNotYaml, _ := filepath.Abs("../tests/not-yaml/readme.md") - fileYaml, _ := filepath.Abs("/tests/all/success/all-success-manifest-1.yaml") - var opt chartutil.ReleaseOptions - - assert.False(t, IsYAML(fileNotYaml)) - assert.True(t, IsYAML(fileYaml)) - - manifestFiles, err := GetManifestFiles(dirNotYaml, opt) - assert.Nil(t, manifestFiles) - assert.NotNil(t, err) - - manifestFiles, err = GetManifestFiles(dirYaml, opt) - assert.NotNil(t, manifestFiles) - assert.Nil(t, err) -} - -// TestIsDirectory tests the isDirectory function for proper returns -func TestIsDirectory(t *testing.T) { - testWd, _ := os.Getwd() - pathTrue := testWd - pathFalse := path.Join(testWd, "preprocessing.go") - pathError := "" - - isDir, err := IsDirectory(pathTrue) - assert.True(t, isDir) + _, err := RenderKustomizeManifest(consts.KustomizationPath) 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) } diff --git a/pkg/safeguards/preprocessing/preprocessing_test_helpers.go b/pkg/safeguards/preprocessing/preprocessing_test_helpers.go index ff8061bd..ad81425c 100644 --- a/pkg/safeguards/preprocessing/preprocessing_test_helpers.go +++ b/pkg/safeguards/preprocessing/preprocessing_test_helpers.go @@ -2,63 +2,38 @@ package preprocessing import ( "os" + "regexp" + "strings" "testing" - - "gopkg.in/yaml.v3" -) - -const ( - tempDir = "testdata" // Rendered files are stored here before they are read for comparison - chartPath = "../tests/testmanifests/validchart" - invalidChartPath = "../tests/testmanifests/invalidchart" - invalidValuesChart = "../tests/testmanifests/invalidvalues" - invalidDeploymentsChart = "../tests/testmanifests/invaliddeployment" - invalidDeploymentSyntax = "../tests/testmanifests/invaliddeployment-syntax" - invalidDeploymentValues = "../tests/testmanifests/invaliddeployment-values" - folderwithHelpersTmpl = "../tests/testmanifests/different-structure" - multipleTemplateDirs = "../tests/testmanifests/multiple-templates" - multipleValuesFile = "../tests/testmanifests/multiple-values-files" - - subcharts = "../tests/testmanifests/multiple-charts" - subchartDir = "../tests/testmanifests/multiple-charts/charts/subchart2" - directPath_ToSubchartYaml = "../tests/testmanifests/multiple-charts/charts/subchart1/Chart.yaml" - directPath_ToMainChartYaml = "../tests/testmanifests/multiple-charts/Chart.yaml" - - directPath_ToValidChart = "../tests/testmanifests/validchart/Chart.yaml" - directPath_ToInvalidChart = "../tests/testmanifests/invalidchart/Chart.yaml" - - kustomizationPath = "../tests/kustomize/overlays/production" - kustomizationFilePath = "../tests/kustomize/overlays/production/kustomization.yaml" ) -func makeTempDir(t *testing.T) { - if err := CreateTempDir(tempDir); err != nil { - t.Fatalf("failed to create temporary output directory: %s", err) - } -} +const () -func cleanupDir(t *testing.T, dir string) { - err := os.RemoveAll(dir) +// Returns the content of a manifest file as bytes +func getManifestAsBytes(t *testing.T, filePath string) []byte { + yamlFileContent, err := os.ReadFile(filePath) if err != nil { - t.Fatalf("Failed to clean directory: %s", err) + t.Fatalf("Failed to read YAML file: %s", err) } -} -func parseYAML(t *testing.T, content string) map[string]interface{} { - var result map[string]interface{} - err := yaml.Unmarshal([]byte(content), &result) - if err != nil { - t.Fatalf("Failed to parse YAML: %s", err) - } - return result + return yamlFileContent } -func getManifestAsString(t *testing.T, filePath string) string { - yamlFileContent, err := os.ReadFile(filePath) - if err != nil { - t.Fatalf("Failed to read YAML file: %s", err) - } +// Normalize returns, newlines, extra characters with strings for easy .yaml byte comparison +func normalizeNewlines(data []byte) []byte { + str := string(data) + + // Replace various newline characters with a single newline + str = strings.ReplaceAll(str, "\r\n", "\n") + str = strings.ReplaceAll(str, "\r", "\n") + + // Replace YAML block scalars' indicators and multiple spaces + str = regexp.MustCompile(`(\s*\|\s*)`).ReplaceAllString(str, " ") + str = strings.Join(strings.Fields(str), " ") + + // Normalize empty mappings and fields + str = regexp.MustCompile(`\{\s*\}`).ReplaceAllString(str, "{}") + str = regexp.MustCompile(`\s*:\s*`).ReplaceAllString(str, ": ") - yamlContentString := string(yamlFileContent) - return yamlContentString + return []byte(str) } diff --git a/pkg/safeguards/tests/testmanifests/expected-resources.yaml b/pkg/safeguards/tests/testmanifests/expected-resources.yaml index 995a77d6..396d6264 100644 --- a/pkg/safeguards/tests/testmanifests/expected-resources.yaml +++ b/pkg/safeguards/tests/testmanifests/expected-resources.yaml @@ -35,11 +35,6 @@ data: ingress: enabled: true hostname: example.com - annotations: {} + annotations: tls: false - tlsSecret: "" - - - - - + tlsSecret: \ No newline at end of file diff --git a/pkg/safeguards/types/constants.go b/pkg/safeguards/types/constants.go index 54973ee8..c9f35398 100644 --- a/pkg/safeguards/types/constants.go +++ b/pkg/safeguards/types/constants.go @@ -14,6 +14,25 @@ const ( Constraint_RT = "restricted-taints" Constraint_USS = "unique-service-selectors" Constraint_all = "all" + + KustomizationPath = "../tests/kustomize/overlays/production" + KustomizationFilePath = "../tests/kustomize/overlays/production/kustomization.yaml" + DirectPath_ToValidChart = "../tests/testmanifests/validchart/Chart.yaml" + ChartPath = "../tests/testmanifests/validchart" + InvalidChartPath = "../tests/testmanifests/invalidchart" + InvalidValuesChart = "../tests/testmanifests/invalidvalues" + InvalidDeploymentsChart = "../tests/testmanifests/invaliddeployment" + InvalidDeploymentSyntax = "../tests/testmanifests/invaliddeployment-syntax" + InvalidDeploymentValues = "../tests/testmanifests/invaliddeployment-values" + FolderwithHelpersTmpl = "../tests/testmanifests/different-structure" + MultipleTemplateDirs = "../tests/testmanifests/multiple-templates" + MultipleValuesFile = "../tests/testmanifests/multiple-values-files" + + Subcharts = "../tests/testmanifests/multiple-charts" + SubchartDir = "../tests/testmanifests/multiple-charts/charts/subchart2" + DirectPath_ToSubchartYaml = "../tests/testmanifests/multiple-charts/charts/subchart1/Chart.yaml" + irectPath_ToMainChartYaml = "../tests/testmanifests/multiple-charts/Chart.yaml" + directPath_ToInvalidChart = "../tests/testmanifests/invalidchart/Chart.yaml" ) var SelectedVersion = "v1.0.0" diff --git a/pkg/safeguards/types/types.go b/pkg/safeguards/types/types.go index 9d0d8d5c..ecdfe977 100644 --- a/pkg/safeguards/types/types.go +++ b/pkg/safeguards/types/types.go @@ -2,9 +2,9 @@ package types import ( "bufio" + "bytes" "fmt" "io/fs" - "os" "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates" api "github.com/open-policy-agent/gatekeeper/v3/apis" @@ -26,8 +26,8 @@ type Safeguard struct { } type ManifestFile struct { - Name string - Path string + Name string + ManifestContent []byte } type ManifestResult struct { @@ -37,16 +37,14 @@ type ManifestResult struct { } // methods for retrieval of manifest, constraint templates, and constraints -func (fc FileCrawler) ReadManifests(path string) ([]*unstructured.Unstructured, error) { - file, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("opening file %q: %w", path, err) - } - defer file.Close() +func (fc FileCrawler) ReadManifests(manifestBytes []byte) ([]*unstructured.Unstructured, error) { + // Create a new bytes.Reader from the byte slice + bufReader := bufio.NewReader(bytes.NewReader(manifestBytes)) - manifests, err := reader.ReadK8sResources(bufio.NewReader(file)) + // Read the Kubernetes resources using the reader + manifests, err := reader.ReadK8sResources(bufReader) if err != nil { - return nil, fmt.Errorf("reading file %q: %w", path, err) + return nil, fmt.Errorf("reading manifests: %w", err) } return manifests, nil