Skip to content

Commit

Permalink
Merge pull request #1706 from yashvardhannanavati/rpa_rule_data
Browse files Browse the repository at this point in the history
Add support for JSON and YAML files in --extra-rule-data
  • Loading branch information
zregvart authored Jun 28, 2024
2 parents 1dbd956 + 0fa91b0 commit 016598f
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 17 deletions.
12 changes: 8 additions & 4 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,12 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
policySpec := p.Spec()
sources := policySpec.Sources
for i := range sources {
source := sources[i]
src := sources[i]
var rule_data_raw []byte
unmarshaled := make(map[string]interface{})

if source.RuleData != nil {
rule_data_raw, err = source.RuleData.MarshalJSON()
if src.RuleData != nil {
rule_data_raw, err = src.RuleData.MarshalJSON()
if err != nil {
log.Errorf("Unable to parse ruledata to raw data")
}
Expand All @@ -249,7 +249,11 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
if len(parts) < 2 {
log.Errorf("Incorrect syntax for --extra-rule-data")
}
unmarshaled[parts[0]] = parts[1]
extraRuleDataPolicyConfig, err := validate_utils.GetPolicyConfig(ctx, parts[1])
if err != nil {
log.Errorf("Unable to load data from extraRuleData: %s", err.Error())
}
unmarshaled[parts[0]] = extraRuleDataPolicyConfig
}
rule_data_raw, err = json.Marshal(unmarshaled)
if err != nil {
Expand Down
133 changes: 131 additions & 2 deletions cmd/validate/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"

hd "github.com/MakeNowJust/heredoc"
"github.com/gkampitakis/go-snaps/snaps"
app "github.com/redhat-appstudio/application-api/api/v1alpha1"
"github.com/sigstore/cosign/v2/pkg/cosign"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"

Expand Down Expand Up @@ -747,6 +750,10 @@ func Test_ValidateImageCommandExtraData(t *testing.T) {
- "registry/policy:latest"
data:
- "registry/policy-data:latest"
ruleData:
custom_rule_data:
prefix_data:
- registry1
configuration:
collections:
- minimal
Expand All @@ -759,6 +766,22 @@ configuration:
panic(err)
}

testExtraRuleDataYAML := `---
kind: ReleasePlanAdmission
spec:
application: [some-app]
data:
mapping:
components:
- name: some-name
repository: quay.io/some-namespace/msd
`

err = afero.WriteFile(fs, "/value.yaml", []byte(testExtraRuleDataYAML), 0644)
if err != nil {
panic(err)
}

cmd.SetArgs(append(rootArgs, []string{
"--image",
"registry/image:tag",
Expand All @@ -767,7 +790,7 @@ configuration:
"--policy",
"/policy.json",
"--extra-rule-data",
"key=value",
"key=/value.yaml,key2=value2",
}...))

var out bytes.Buffer
Expand All @@ -794,7 +817,9 @@ configuration:
"registry/policy:latest"
],
"ruleData": {
"key":"value"
"custom_rule_data":{"prefix_data":["registry1"]},
"key": "---\nkind: ReleasePlanAdmission\nspec:\n application: [some-app]\n data:\n mapping:\n components:\n - name: some-name\n repository: quay.io/some-namespace/msd\n",
"key2": "value2"
}
}`, string(sourceSampleMarshaled))
}
Expand Down Expand Up @@ -866,6 +891,110 @@ func Test_ValidateImageCommandEmptyPolicyFile(t *testing.T) {
assert.EqualError(t, err, "1 error occurred:\n\t* file /policy.yaml is empty\n\n")
}

func Test_ValidateImageErrorLog(t *testing.T) {
// TODO: Enhance this test to cover other Error Log messages
validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) {
return &output.Output{
ImageSignatureCheck: output.VerificationStatus{
Passed: true,
},
ImageAccessibleCheck: output.VerificationStatus{
Passed: true,
},
AttestationSignatureCheck: output.VerificationStatus{
Passed: true,
},
AttestationSyntaxCheck: output.VerificationStatus{
Passed: true,
},
PolicyCheck: []evaluator.Outcome{
{
FileName: "test.json",
Namespace: "test.main",
Successes: []evaluator.Result{
{
Message: "Pass",
Metadata: map[string]interface{}{
"code": "policy.nice",
},
},
},
},
},
ImageURL: component.ContainerImage,
ExitCode: 0,
}, nil
}
logger, hook := test.NewNullLogger()
logger.SetLevel(log.DebugLevel)
log.StandardLogger().ReplaceHooks(make(log.LevelHooks))
log.AddHook(hook)

validateImageCmd := validateImageCmd(validate)
cmd := setUpCobra(validateImageCmd)

fs := afero.NewMemMapFs()

ctx := utils.WithFS(context.Background(), fs)
client := fake.FakeClient{}
commonMockClient(&client)
ctx = oci.WithClient(ctx, &client)

cmd.SetContext(ctx)

testPolicyJSON := `sources:
- policy:
- "registry/policy:latest"
data:
- "registry/policy-data:latest"
configuration:
collections:
- minimal
include:
- "*"
exclude: []
`
err := afero.WriteFile(fs, "/policy.yaml", []byte(testPolicyJSON), 0644)
if err != nil {
panic(err)
}

err = afero.WriteFile(fs, "/value.json", []byte(nil), 0644)
if err != nil {
panic(err)
}
args := append(rootArgs, []string{
"--image",
"registry/image:tag",
"--public-key",
utils.TestPublicKey,
"--policy",
"/policy.yaml",
"--extra-rule-data",
"key=/value.json",
}...)
cmd.SetArgs(args)

var out bytes.Buffer
cmd.SetOut(&out)

utils.SetTestRekorPublicKey(t)

err = cmd.Execute()
assert.NoError(t, err)

errorMsg := "Unable to load data from extraRuleData: file /value.json is empty"
found := false
for _, entry := range hook.AllEntries() {
if strings.Contains(entry.Message, errorMsg) {
found = true
break
}
}
assert.True(t, found, "Error message should have the pre-defined string", errorMsg)
log.StandardLogger().ReplaceHooks(make(log.LevelHooks))
}

func Test_ValidateErrorCommand(t *testing.T) {
cases := []struct {
name string
Expand Down
22 changes: 11 additions & 11 deletions internal/validate/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,32 @@ func GetPolicyConfig(ctx context.Context, policyConfiguration string) (string, e
if err != nil {
return "", err
}
return readPolicyConfigurationFile(ctx, configFile)
log.Debugf("Loading %s as policy configuration", configFile)
return ReadFile(ctx, configFile)
} else if source.SourceIsFile(policyConfiguration) && utils.HasJsonOrYamlExt(policyConfiguration) {
// If policyConfiguration is detected as a file and it has a json or yaml extension,
// we read its contents and return it.
return readPolicyConfigurationFile(ctx, policyConfiguration)
log.Debugf("Loading %s as policy configuration", policyConfiguration)
return ReadFile(ctx, policyConfiguration)
}

// If policyConfiguration is not a file path, git url, or https url,
// we assume it's a string and return it as is.
return policyConfiguration, nil
}

// Read policyConfiguration file and return its contents.
func readPolicyConfigurationFile(ctx context.Context, policyConfiguration string) (string, error) {
// Check if policyConfig is a file path. If so, try to read it.
// If successful we write that into the data.policyConfiguration var.
// Read file from the workspace and return its contents.
func ReadFile(ctx context.Context, fileName string) (string, error) {
fs := utils.FS(ctx)
policyBytes, err := afero.ReadFile(fs, policyConfiguration)
fileBytes, err := afero.ReadFile(fs, fileName)
if err != nil {
return "", err
}
// Check for empty file as that would cause a false "success"
if len(policyBytes) == 0 {
err := fmt.Errorf("file %s is empty", policyConfiguration)
if len(fileBytes) == 0 {
err := fmt.Errorf("file %s is empty", fileName)
return "", err
}
log.Debugf("Loaded %s as policyConfiguration", policyConfiguration)
return string(policyBytes), nil
log.Debugf("Loaded %s", fileName)
return string(fileBytes), nil
}

0 comments on commit 016598f

Please sign in to comment.