Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a generic version of JQ accessor, called JQGetTypedFromAccessor to parse GitHub payload #801

Merged
merged 1 commit into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions internal/engine/eval/jq/jq.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package jq
import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"

Expand Down Expand Up @@ -65,14 +66,18 @@ func NewJQEvaluator(assertions []*pb.RuleType_Definition_Eval_JQComparison) (*Ev
// Eval calls the jq library to evaluate the rule
func (jqe *Evaluator) Eval(ctx context.Context, pol map[string]any, obj any) error {
for idx := range jqe.assertions {
var policyVal, dataVal any

a := jqe.assertions[idx]
policyVal, err := util.JQGetValuesFromAccessor(ctx, a.Policy.Def, pol)
if err != nil {
policyVal, err := util.JQReadFrom[any](ctx, a.Policy.Def, pol)
// we ignore util.ErrNoValueFound because we want to allow the JQ accessor to return the default value
// which is fine for DeepEqual
if err != nil && !errors.Is(err, util.ErrNoValueFound) {
return fmt.Errorf("cannot get values from policy accessor: %w", err)
}

dataVal, err := util.JQGetValuesFromAccessor(ctx, a.Ingested.Def, obj)
if err != nil {
dataVal, err = util.JQReadFrom[any](ctx, a.Ingested.Def, obj)
if err != nil && !errors.Is(err, util.ErrNoValueFound) {
return fmt.Errorf("cannot get values from data accessor: %w", err)
}

Expand Down
45 changes: 20 additions & 25 deletions internal/engine/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,62 +281,62 @@ func (e *Executor) handleWebhookEvent(msg *message.Message) error {
}

func extractArtifactFromPayload(ctx context.Context, payload map[string]any) (*pb.Artifact, error) {
artifactId, err := util.JQGetValuesFromAccessor(ctx, ".package.id", payload)
artifactId, err := util.JQReadFrom[float64](ctx, ".package.id", payload)
if err != nil {
return nil, err
}
artifactName, err := util.JQGetValuesFromAccessor(ctx, ".package.name", payload)
artifactName, err := util.JQReadFrom[string](ctx, ".package.name", payload)
if err != nil {
return nil, err
}
artifactType, err := util.JQGetValuesFromAccessor(ctx, ".package.package_type", payload)
artifactType, err := util.JQReadFrom[string](ctx, ".package.package_type", payload)
if err != nil {
return nil, err
}
ownerLogin, err := util.JQGetValuesFromAccessor(ctx, ".package.owner.login", payload)
ownerLogin, err := util.JQReadFrom[string](ctx, ".package.owner.login", payload)
if err != nil {
return nil, err
}
repoName, err := util.JQGetValuesFromAccessor(ctx, ".repository.full_name", payload)
repoName, err := util.JQReadFrom[string](ctx, ".repository.full_name", payload)
if err != nil {
return nil, err
}
packageUrl, err := util.JQGetValuesFromAccessor(ctx, ".package.package_version.package_url", payload)
packageUrl, err := util.JQReadFrom[string](ctx, ".package.package_version.package_url", payload)
if err != nil {
return nil, err
}

artifact := &pb.Artifact{
ArtifactId: int64(artifactId.(float64)),
Owner: ownerLogin.(string),
Name: artifactName.(string),
Type: artifactType.(string),
Repository: repoName.(string),
PackageUrl: packageUrl.(string),
ArtifactId: int64(artifactId),
Owner: ownerLogin,
Name: artifactName,
Type: artifactType,
Repository: repoName,
PackageUrl: packageUrl,
// visibility and createdAt are not in the payload, we need to get it with a REST call
}

return artifact, nil
}

func extractArtifactVersionFromPayload(ctx context.Context, payload map[string]any) (*pb.ArtifactVersion, error) {
packageVersionId, err := util.JQGetValuesFromAccessor(ctx, ".package.package_version.id", payload)
packageVersionId, err := util.JQReadFrom[float64](ctx, ".package.package_version.id", payload)
if err != nil {
return nil, err
}
packageVersionSha, err := util.JQGetValuesFromAccessor(ctx, ".package.package_version.version", payload)
packageVersionSha, err := util.JQReadFrom[string](ctx, ".package.package_version.version", payload)
if err != nil {
return nil, err
}
tag, err := util.JQGetValuesFromAccessor(ctx, ".package.package_version.container_metadata.tag.name", payload)
tag, err := util.JQReadFrom[string](ctx, ".package.package_version.container_metadata.tag.name", payload)
if err != nil {
return nil, err
}

version := &pb.ArtifactVersion{
VersionId: int64(packageVersionId.(float64)),
Tags: []string{tag.(string)},
Sha: packageVersionSha.(string),
VersionId: int64(packageVersionId),
Tags: []string{tag},
Sha: packageVersionSha,
SignatureVerification: nil, // will be filled later by a call to the container registry
GithubWorkflow: nil, // will be filled later by a call to the container registry
}
Expand All @@ -351,7 +351,7 @@ func updateArtifactVersionFromRegistry(
artifactOwnerLogin, artifactName string,
version *pb.ArtifactVersion,
) error {
packageVersionName, err := util.JQGetValuesFromAccessor(ctx, ".package.package_version.name", payload)
packageVersionName, err := util.JQReadFrom[string](ctx, ".package.package_version.name", payload)
if err != nil {
return fmt.Errorf("error getting package version name: %w", err)
}
Expand All @@ -372,13 +372,8 @@ func updateArtifactVersionFromRegistry(
sort.Strings(tags)

// now get information for signature and workflow
packageVersionNameStr, ok := packageVersionName.(string)
if !ok {
return fmt.Errorf("package version name is not a string")
}

sigInfo, workflowInfo, err := container.GetArtifactSignatureAndWorkflowInfo(
ctx, client, artifactOwnerLogin, artifactName, packageVersionNameStr)
ctx, client, artifactOwnerLogin, artifactName, packageVersionName)
if errors.Is(err, container.ErrSigValidation) || errors.Is(err, container.ErrProtoParse) {
return err
} else if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/engine/ingester/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package builtin
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"reflect"
Expand Down Expand Up @@ -117,8 +118,8 @@ func entityMatchesParams(ctx context.Context, ent protoreflect.ProtoMessage, par
if !strings.HasPrefix(key, ".") {
key = "." + key
}
expectedVal, err := util.JQGetValuesFromAccessor(ctx, key, jsonData)
if err != nil {
expectedVal, err := util.JQReadFrom[any](ctx, key, jsonData)
if err != nil && !errors.Is(err, util.ErrNoValueFound) {
return false, fmt.Errorf("cannot get values from data accessor: %w", err)
}
if !reflect.DeepEqual(expectedVal, val) {
Expand Down
38 changes: 36 additions & 2 deletions internal/util/jq.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ package util

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/itchyny/gojq"
)

// JQGetValuesFromAccessor gets the values from the given accessor
// jQReadAsAny gets the values from the given accessor
// the path is the accessor path in jq format.
// the obj is the object to be evaluated using the accessor.
func JQGetValuesFromAccessor(ctx context.Context, path string, obj any) (any, error) {
func jQReadAsAny(ctx context.Context, path string, obj any) (any, error) {
out := []any{}
accessor, err := gojq.Parse(path)
if err != nil {
Expand Down Expand Up @@ -56,3 +58,35 @@ func JQGetValuesFromAccessor(ctx context.Context, path string, obj any) (any, er

return out, nil
}

// ErrNoValueFound is an error that is returned when the accessor doesn't find anything
var ErrNoValueFound = errors.New("evaluation error")

func newErrNoValueFound(sfmt string, args ...any) error {
msg := fmt.Sprintf(sfmt, args...)
return fmt.Errorf("%w: %s", ErrNoValueFound, msg)
}

// JQReadFrom gets the typed value from the given accessor. Returns an error when the accessor
// doesn't find anything or when the type assertion fails. Useful for when you know the type you're expecting
// AND the accessor must return a value (IOW, the value is required by the caller)
func JQReadFrom[T any](ctx context.Context, path string, obj any) (T, error) {
var out T

outAny, err := jQReadAsAny(ctx, path, obj)
if err != nil {
return out, err
}

if outAny == nil {
return out, newErrNoValueFound("no value found for path %s", path)
}

// test for nil to cover the case where T is any and the accessor doesn't match - we'd attempt to type assert nil to any
out, ok := outAny.(T)
if !ok {
return out, fmt.Errorf("could not type assert %v to %v", outAny, reflect.TypeOf(out))
}

return out, nil
}
Loading