Skip to content

Commit

Permalink
Add alias to data source reference (#5292)
Browse files Browse the repository at this point in the history
  • Loading branch information
eleftherias authored Jan 13, 2025
1 parent 2caea05 commit 9b2ecaa
Show file tree
Hide file tree
Showing 7 changed files with 1,061 additions and 849 deletions.
3 changes: 2 additions & 1 deletion docs/docs/ref/proto.md

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

12 changes: 11 additions & 1 deletion internal/datasources/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,14 +414,24 @@ func (d *dataSourceService) BuildDataSourceRegistry(
return nil, fmt.Errorf("failed to build data source from protobuf: %w", err)
}

if err := reg.RegisterDataSource(inst.GetName(), impl); err != nil {
if err := reg.RegisterDataSource(getDataSourceReferenceAlias(ref), impl); err != nil {
return nil, fmt.Errorf("failed to register data source: %w", err)
}
}

return reg, nil
}

// getDataSourceReferenceKey gets the alias that the data source will be referred to by
// in the registry.
func getDataSourceReferenceAlias(dsr *minderv1.DataSourceReference) string {
key := dsr.GetAlias()
if key == "" {
return dsr.GetName()
}
return key
}

// addDataSourceFunctions adds functions to a data source based on its driver type.
func addDataSourceFunctions(
ctx context.Context,
Expand Down
6 changes: 5 additions & 1 deletion 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.

1,695 changes: 855 additions & 840 deletions pkg/api/protobuf/go/minder/v1/minder.pb.go

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions pkg/api/protobuf/go/minder/v1/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-playground/validator/v10"
"github.com/itchyny/gojq"
"github.com/open-policy-agent/opa/v1/ast"
"k8s.io/apimachinery/pkg/util/sets"

"github.com/mindersec/minder/internal/util"
)
Expand All @@ -29,6 +30,8 @@ var (
ErrBadDNSStyleName = errors.New(
"name may only contain letters, numbers, hyphens and underscores, and is limited to a maximum of 63 characters",
)

dataSourceAliasRegex = regexp.MustCompile(`^[a-z][-_[:word:]]*$`)
)

var (
Expand Down Expand Up @@ -211,6 +214,22 @@ func (ev *RuleType_Definition_Eval) Validate() error {
}
}
}

if ev.GetDataSources() != nil {
aliases := sets.New[string]()

for i, ds := range ev.GetDataSources() {
if err := ds.Validate(); err != nil {
return fmt.Errorf("%w: data source reference %d is invalid: %s", ErrInvalidRuleTypeDefinition, i, err)
}

alias := ds.getAliasWithDefault()
if aliases.Has(alias) {
return fmt.Errorf("cannot have multiple data sources with the same alias: %s", alias)
}
aliases.Insert(alias)
}
}
return nil
}

Expand Down Expand Up @@ -280,6 +299,32 @@ func (op *RuleType_Definition_Eval_JQComparison_Operator) Validate() error {
return nil
}

// Validate validates a data source reference
func (dsr *DataSourceReference) Validate() interface{} {
if dsr == nil {
return fmt.Errorf("%w: data source reference is nil", ErrInvalidRuleTypeDefinition)
}

if dsr.GetName() == "" {
return fmt.Errorf("%w: data source reference name cannot be empty", ErrInvalidRuleTypeDefinition)
}

alias := dsr.getAliasWithDefault()
if !dataSourceAliasRegex.MatchString(alias) {
return fmt.Errorf("%w: data source reference alias %q is invalid", ErrInvalidRuleTypeDefinition, alias)
}

return nil
}

func (dsr *DataSourceReference) getAliasWithDefault() string {
alias := dsr.GetAlias()
if alias == "" {
return dsr.GetName()
}
return alias
}

// Validate validates a rule type definition ingest
func (ing *RuleType_Definition_Ingest) Validate() error {
if ing == nil {
Expand Down Expand Up @@ -542,6 +587,9 @@ func (ds *DataSource) Validate() error {
if ds.GetName() == "" {
return fmt.Errorf("%w: data source name cannot be empty", ErrValidationFailed)
}
if err := validateNamespacedName(ds.GetName()); err != nil {
return fmt.Errorf("%w: %w", ErrValidationFailed, err)
}

if ds.GetDriver() == nil {
return fmt.Errorf("%w: data source driver cannot be nil", ErrValidationFailed)
Expand Down
124 changes: 124 additions & 0 deletions pkg/api/protobuf/go/minder/v1/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,81 @@ func TestRuleType_Definition_Ingest_Validate(t *testing.T) {
}
}

func TestRuleType_Definition_Eval_Validate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
eval *RuleType_Definition_Eval
wantErr bool
}{
{
name: "valid eval definition",
eval: &RuleType_Definition_Eval{
Type: "rego",
Rego: &RuleType_Definition_Eval_Rego{
Def: "package example.policy\n\nallow { true }",
},
DataSources: []*DataSourceReference{
{
Name: "osv",
Alias: "osv_data",
},
},
},
wantErr: false,
},
{
name: "eval definition with duplicate alias",
eval: &RuleType_Definition_Eval{
Type: "rego",
Rego: &RuleType_Definition_Eval_Rego{
Def: "package example.policy\n\nallow { true }",
},
DataSources: []*DataSourceReference{
{
Name: "osv1",
Alias: "osv_data",
},
{
Name: "osv2",
Alias: "osv_data",
},
},
},
wantErr: true,
},
{
name: "eval definition with same name and alias",
eval: &RuleType_Definition_Eval{
Rego: &RuleType_Definition_Eval_Rego{
Def: "package example.policy\n\nallow { true }",
},
DataSources: []*DataSourceReference{
{
Name: "osv_data",
},
{
Name: "osv",
Alias: "osv_data",
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := tt.eval.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestRuleType_Definition_Eval_JQComparison_Validate(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -258,6 +333,55 @@ func TestRuleType_Definition_Eval_Rego_Validate(t *testing.T) {
}
}

func TestDataSourceReference_Validate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ds *DataSourceReference
wantErr bool
}{
{
name: "valid data source reference with alias",
ds: &DataSourceReference{
Name: "namespace/name",
Alias: "my_data_source",
},
wantErr: false,
},
{
name: "valid data source reference without alias",
ds: &DataSourceReference{
Name: "osv",
},
wantErr: false,
},
{
name: "no name",
ds: &DataSourceReference{},
wantErr: true,
},
{
name: "no alias and name with invalid characters",
ds: &DataSourceReference{
Name: "invalid/name",
},
wantErr: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := tt.ds.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestRuleType_Definition_Alert_Validate(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
22 changes: 16 additions & 6 deletions proto/minder/v1/minder.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,7 @@ message StoreProviderTokenRequest {
reserved 2; // deprecated project_id
}

message StoreProviderTokenResponse {
message StoreProviderTokenResponse {
}

// Project API Objects. This is only used in responses.
Expand Down Expand Up @@ -1732,7 +1732,7 @@ message ListProfilesRequest {
// context is the context which contains the profiles
Context context = 1;
// Filter profiles to only those matching the specified labels.
//
//
// The default is to return all user-created profiles; the string "*" can
// be used to select all profiles, including system profiles. This syntax
// may be expanded in the future.
Expand Down Expand Up @@ -1863,7 +1863,7 @@ message RuleEvaluationStatus {
string rule_evaluation_id = 17;
// remediation_url is a url to get more data about a remediation, for PRs is the link to the PR
string remediation_url = 18;
// rule_display_name captures the display name of the rule
// rule_display_name captures the display name of the rule
string rule_display_name = 19;
// release_phase is the phase of the release
RuleTypeReleasePhase release_phase = 20 [
Expand Down Expand Up @@ -2249,7 +2249,7 @@ message ListEvaluationResultsRequest {
(buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE
];
// Filter profiles to only those matching the specified labels.
//
//
// The default is to return all user-created profiles; the string "*" can
// be used to select all profiles, including system profiles. This syntax
// may be expanded in the future.
Expand Down Expand Up @@ -4218,11 +4218,21 @@ message RestDataSource {
// Note that for a resource to refer to a data source the data source must
// be available in the same project hierarchy.
message DataSourceReference {
// refer to a data source by name.
// name is the name of the data source within the project hierarchy.
string name = 1 [
(buf.validate.field).string = {
pattern: "^[a-z][-_[:word:]]*$",
pattern: "^[a-z][-_/[:word:]]*$",
max_len: 200,
}
];
// alias is the alias used to refer to the data source in the rule
// definition.
// If left unset, it will default to the name of the data source.
string alias = 2 [
(buf.validate.field).string = {
pattern: "^[a-z][-_[:word:]]*$",
max_len: 200,
},
(buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE
];
}

0 comments on commit 9b2ecaa

Please sign in to comment.