diff --git a/src/pkg/common/common_test.go b/src/pkg/common/common_test.go index 0ffb5f91..e1ed9e65 100644 --- a/src/pkg/common/common_test.go +++ b/src/pkg/common/common_test.go @@ -18,6 +18,7 @@ import ( const multiValidationPath = "../../test/e2e/scenarios/remote-validations/multi-validations.yaml" const singleValidationPath = "../../test/e2e/scenarios/remote-validations/validation.opa.yaml" +const whitespaceValidationPath = "../../test/e2e/scenarios/remote-validations/validation.whitespace.yaml" // Helper function to load test data func loadTestData(t *testing.T, path string) []byte { @@ -392,6 +393,29 @@ func TestValidationToResource(t *testing.T) { t.Errorf("ToResource() description = \"\", want a valid UUID") } }) + + t.Run("It trims whitespace from the validation", func(t *testing.T) { + t.Parallel() + validationBytes := loadTestData(t, whitespaceValidationPath) + + validation, err := common.ReadValidationsFromYaml(validationBytes) + if err != nil { + t.Fatalf("yaml.Unmarshal failed: %v", err) + } + + if len(validation) > 1 { + t.Errorf("Expected 1 validation, got %d", len(validation)) + } + + resource, err := validation[0].ToResource() + if err != nil { + t.Errorf("ToResource() error = %v", err) + } + strings.Contains(resource.Description, " \n") + if strings.Contains(resource.Description, " \n") { + t.Errorf("ToResource() description = should not contain whitespace followed by newline") + } + }) } func TestReadValidationsFromYaml(t *testing.T) { diff --git a/src/pkg/common/types.go b/src/pkg/common/types.go index 161f0bef..f454f5c8 100644 --- a/src/pkg/common/types.go +++ b/src/pkg/common/types.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "strings" "github.com/defenseunicorns/go-oscal/src/pkg/uuid" @@ -55,6 +56,12 @@ func (v *Validation) ToResource() (resource *oscalTypes_1_1_2.Resource, err erro } else { resource.UUID = uuid.NewUUID() } + // If the provider is opa, trim whitespace from the rego + if v.Provider != nil && v.Provider.OpaSpec != nil { + re := regexp.MustCompile(`[ \t]+\r?\n`) + v.Provider.OpaSpec.Rego = re.ReplaceAllString(v.Provider.OpaSpec.Rego, "\n") + } + validationBytes, err := v.MarshalYaml() if err != nil { return nil, err diff --git a/src/test/e2e/scenarios/remote-validations/validation.whitespace.yaml b/src/test/e2e/scenarios/remote-validations/validation.whitespace.yaml new file mode 100644 index 00000000..310c86a7 --- /dev/null +++ b/src/test/e2e/scenarios/remote-validations/validation.whitespace.yaml @@ -0,0 +1,27 @@ +lula-version: ">=v0.2.0" +metadata: + name: Validate pods with label foo=bar + uuid: 7f4c3b2a-1c3d-4a2b-8b64-3b1f76a8e36f +domain: + type: kubernetes + kubernetes-spec: + resources: + - name: podsvt + resource-rule: + version: v1 + resource: pods + namespaces: [validation-test] +provider: + type: opa + opa-spec: + rego: | + package validate + + import future.keywords.every + + validate { + every pod in input.podsvt { + podLabel := pod.metadata.labels.foo + podLabel == "bar" + } + }