Skip to content

Commit

Permalink
Introduce ResourceMeta and general resource parsing (#4783)
Browse files Browse the repository at this point in the history
This introduces the concept of resource metadata, which includes three
things:

* `type`: the type of resource
* `version`: The version of a resource
* `name`: The name of a resource

Parsing these three is mandatory for any resource in Minder's domain
model.

This also introduces a general parser which replaces the
rule-type-specific parser.

Finally, the rule type `create`/`apply` commands were refactored to use
this new parser, and will skip files that are effectively not rule
types. Thus allowing the aforementioned sub commands to run on file
directories with objects that aren't necessarily rule types.

Signed-off-by: Juan Antonio Osorio <[email protected]>
  • Loading branch information
JAORMX authored Oct 22, 2024
1 parent 8d36fc5 commit 503e9bc
Show file tree
Hide file tree
Showing 13 changed files with 1,706 additions and 1,519 deletions.
5 changes: 3 additions & 2 deletions cmd/cli/app/quickstart/quickstart.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ func quickstartCommand(
return cli.MessageAndError("error opening rule type", err)
}

rt, err := minderv1.ParseRuleType(reader)
if err != nil {
rt := &minderv1.RuleType{}

if err := minderv1.ParseResource(reader, rt); err != nil {
return cli.MessageAndError("error parsing rule type", err)
}

Expand Down
8 changes: 6 additions & 2 deletions cmd/cli/app/ruletype/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ func execOnOneRuleType(
}
defer closer()

r, err := minderv1.ParseRuleType(reader)
if err != nil {
r := &minderv1.RuleType{}
if err := minderv1.ParseResource(reader, r); err != nil {
if minderv1.YouMayHaveTheWrongResource(err) {
// We'll skip the file if it's not a rule type
return nil
}
return fmt.Errorf("error parsing rule type: %w", err)
}

Expand Down
8 changes: 7 additions & 1 deletion cmd/dev/app/rule_type/rttst.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,13 @@ func readRuleTypeFromFile(fpath string) (*minderv1.RuleType, error) {
return nil, fmt.Errorf("error opening file: %w", err)
}

return minderv1.ParseRuleType(f)
rt := &minderv1.RuleType{}
err = minderv1.ParseResource(f, rt)
if err != nil {
return nil, fmt.Errorf("error parsing rule type: %w", err)
}

return rt, nil
}

func readEntityWithPropertiesFromFile(
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/ref/proto.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/profiles/rule_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func TestExampleRulesAreValidatedCorrectly(t *testing.T) {
defer f.Close()

t.Log("parsing rule type", path)
rt, err := minderv1.ParseRuleType(f)
require.NoError(t, err, "failed to parse rule type %s", path)
rt := &minderv1.RuleType{}
require.NoError(t, minderv1.ParseResource(f, rt), "failed to parse rule type %s", path)
require.NotNil(t, rt, "failed to parse rule type %s", path)

require.NoError(t, rt.Validate(), "failed to validate rule type %s", path)
Expand Down
4 changes: 2 additions & 2 deletions internal/profiles/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ func loadRawRuleTypeDef() (json.RawMessage, error) {
}
defer f.Close()

ruleType, err := minderv1.ParseRuleType(f)
if err != nil {
ruleType := &minderv1.RuleType{}
if err := minderv1.ParseResource(f, ruleType); err != nil {
return nil, err
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/openapi/minder/v1/minder.swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 153 additions & 0 deletions pkg/api/protobuf/go/minder/v1/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
// SPDX-License-Identifier: Apache-2.0

package v1

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"

"google.golang.org/protobuf/reflect/protoreflect"

"github.com/mindersec/minder/internal/util/jsonyaml"
)

const (
// VersionV1 is the v1 version of most API resources
VersionV1 = "v1"
)

var (
// ErrInvalidResource is returned when an invalid resource is provided
ErrInvalidResource = fmt.Errorf("invalid resource")
// ErrInvalidResourceType is returned when an invalid resource type is provided
ErrInvalidResourceType = fmt.Errorf("invalid resource type")
// ErrNotAResource is returned when the provided resource is not a Minder resource
ErrNotAResource = fmt.Errorf("not a Minder resource")
// ErrResourceTypeMismatch is returned when the resource type does not match the provided resource
ErrResourceTypeMismatch = fmt.Errorf("resource type mismatch")
)

// ResourceType is the type of resource. Minder resources are all the objects that Minder manages.
// They include policy resources (rule types, profiles, etc.), entity resources (repositories, artifacts, etc.),
// and other resources like projects.
type ResourceType string

const (
// RuleTypeResource is a rule type resource
RuleTypeResource ResourceType = "rule-type"
// ProfileResource is a profile resource
ProfileResource ResourceType = "profile"
// EntityInstanceResource is an entity instance resource
EntityInstanceResource ResourceType = "entity-instance"
// RepositoryResource is a repository resource. Note that this
// will be deprecated in the future. In favor of EntityInstanceResource.
RepositoryResource ResourceType = "repository"
// ArtifactResource is an artifact resource. Note that this
// will be deprecated in the future. In favor of EntityInstanceResource.
ArtifactResource ResourceType = "artifact"
// ProjectResource is a project resource
ProjectResource ResourceType = "project"
)

// ResourceTypeIsValid checks if the resource type is valid
func ResourceTypeIsValid(rt ResourceType) bool {
switch rt {
case RuleTypeResource, ProfileResource, EntityInstanceResource, RepositoryResource, ArtifactResource,
ProjectResource:
return true
}
return false
}

var (
resourceMatchers = map[ResourceType]protoreflect.ProtoMessage{
RuleTypeResource: &RuleType{},
ProfileResource: &Profile{},
EntityInstanceResource: &EntityInstance{},
RepositoryResource: &Repository{},
ArtifactResource: &Artifact{},
}
)

// ResourceMeta defines the basic metadata for a resource within
// Minder. This is used as a common interface and to determine
// the type of resource.
type ResourceMeta interface {
protoreflect.ProtoMessage

GetVersion() string
GetType() string
GetName() string
}

var _ ResourceMeta = (*RuleType)(nil)
var _ ResourceMeta = (*Profile)(nil)

// ParseResource is a generic parser for Minder resources. It will
// attempt to parse the resource into the correct type based on the
// version and type of the resource.
func ParseResource(r io.Reader, rm ResourceMeta) error {
// We transcode to JSON so we can decode it straight to the protobuf structure
w := &bytes.Buffer{}

if err := jsonyaml.TranscodeYAMLToJSON(r, w); err != nil {
return fmt.Errorf("error converting yaml to json: %w", err)
}

if err := json.NewDecoder(w).Decode(rm); err != nil {
return fmt.Errorf("error decoding json: %w", err)
}

if err := Validate(rm); err != nil {
return fmt.Errorf("error validating resource meta: %w", err)
}

// Attempt to match resource type before trying to decode
if !ResourceMatches(ResourceType(rm.GetType()), rm) {
return fmt.Errorf("resource type does not match: %w", ErrResourceTypeMismatch)
}

return nil
}

// Validate is a utility function which allows for the validation of a struct.
func Validate(r ResourceMeta) error {
if r == nil {
return fmt.Errorf("resource meta is nil")
}

if err := validate.Struct(r); err != nil {
return errors.Join(ErrInvalidResource, err)
}

if valid := ResourceTypeIsValid(ResourceType(r.GetType())); !valid {
return fmt.Errorf("%w: invalid resource type: %s", ErrInvalidResourceType, r.GetType())
}

return nil
}

// ResourceMatches checks if the resource type matches the provided resource.
func ResourceMatches(rt ResourceType, r protoreflect.ProtoMessage) bool {
if r == nil {
return false
}

matcher, ok := resourceMatchers[rt]
if !ok {
return false
}

return r.ProtoReflect().Descriptor().FullName() == matcher.ProtoReflect().Descriptor().FullName()
}

// YouMayHaveTheWrongResource is a utility function to verify if the given
// error is due to the resource type mismatch.
func YouMayHaveTheWrongResource(err error) bool {
return err != nil && (errors.Is(err, ErrResourceTypeMismatch) || errors.Is(err, ErrNotAResource) ||
errors.Is(err, ErrInvalidResource) || errors.Is(err, ErrInvalidResourceType))
}
Loading

0 comments on commit 503e9bc

Please sign in to comment.