Skip to content
Open
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
31 changes: 19 additions & 12 deletions cmd/cli/app/profile/status/status_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"os"

"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"
Expand Down Expand Up @@ -44,14 +45,24 @@ func getCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.
return cli.MessageAndError(fmt.Sprintf("Output format %s not supported", format), fmt.Errorf("invalid argument"))
}

entity := &minderv1.EntityTypedId{
Type: minderv1.EntityFromString(entityType),
}
// If entityId is a UUID, fill the `id` field, otherwise fill the name field.
if _, err := uuid.Parse(entityId); err == nil {
entity.Id = entityId
} else {
entity.Name = entityId
}

if profileId != "" {
resp, err := getProfileStatusById(ctx, client, project, profileId, entityId, entityType)
resp, err := getProfileStatusById(ctx, client, project, profileId, entity)
if err != nil {
return cli.MessageAndError("Error getting profile status", err)
}
return formatAndDisplayOutput(cmd, format, resp, viper.GetBool("emoji"))
} else if profileName != "" {
resp, err := getProfileStatusByName(ctx, client, project, profileName, entityId, entityType)
resp, err := getProfileStatusByName(ctx, client, project, profileName, entity)
if err != nil {
return cli.MessageAndError("Error getting profile status", err)
}
Expand All @@ -64,7 +75,8 @@ func getCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.
func getProfileStatusById(
ctx context.Context,
client minderv1.ProfileServiceClient,
project, profileId, entityId, entityType string,
project, profileId string,
entity *minderv1.EntityTypedId,
) (*minderv1.GetProfileStatusByIdResponse, error) {
if profileId == "" {
return nil, cli.MessageAndError("Error getting profile status", fmt.Errorf("profile id required"))
Expand All @@ -73,10 +85,7 @@ func getProfileStatusById(
resp, err := client.GetProfileStatusById(ctx, &minderv1.GetProfileStatusByIdRequest{
Context: &minderv1.Context{Project: &project},
Id: profileId,
Entity: &minderv1.EntityTypedId{
Id: entityId,
Type: minderv1.EntityFromString(entityType),
},
Entity: entity,
})
if err != nil {
return nil, err
Expand All @@ -91,7 +100,8 @@ func getProfileStatusById(
func getProfileStatusByName(
ctx context.Context,
client minderv1.ProfileServiceClient,
project, profileName, entityId, entityType string,
project, profileName string,
entity *minderv1.EntityTypedId,
) (*minderv1.GetProfileStatusByNameResponse, error) {
if profileName == "" {
return nil, cli.MessageAndError("Error getting profile status", fmt.Errorf("profile name required"))
Expand All @@ -100,10 +110,7 @@ func getProfileStatusByName(
resp, err := client.GetProfileStatusByName(ctx, &minderv1.GetProfileStatusByNameRequest{
Context: &minderv1.Context{Project: &project},
Name: profileName,
Entity: &minderv1.EntityTypedId{
Id: entityId,
Type: minderv1.EntityFromString(entityType),
},
Entity: entity,
})
if err != nil {
return nil, err
Expand Down
28 changes: 9 additions & 19 deletions cmd/cli/app/repo/repo_reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,19 @@ func reconcileCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

if id == "" {
repoClient := minderv1.NewRepositoryServiceClient(conn)

repo, err := repoClient.GetRepositoryByName(ctx, &minderv1.GetRepositoryByNameRequest{
Name: name,
Context: &minderv1.Context{
Provider: &provider,
Project: &project,
},
})
if err != nil {
return cli.MessageAndError("Failed to get repository", err)
}

id = repo.GetRepository().GetId()
entity := &minderv1.EntityTypedId{
Type: minderv1.Entity_ENTITY_REPOSITORIES,
}
if id != "" {
entity.Id = id
}
if name != "" {
entity.Name = name
}

projectsClient := minderv1.NewProjectsServiceClient(conn)
_, err := projectsClient.CreateEntityReconciliationTask(ctx, &minderv1.CreateEntityReconciliationTaskRequest{
Entity: &minderv1.EntityTypedId{
Id: id,
Type: minderv1.Entity_ENTITY_REPOSITORIES,
},
Entity: entity,
Context: &minderv1.Context{
Provider: &provider,
Project: &project,
Expand Down
6 changes: 4 additions & 2 deletions database/query/profile_status.sql
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ SELECT
rt.guidance as rule_type_guidance,
rt.display_name as rule_type_display_name,
ere.entity_instance_id as entity_id,
ei.name as entity_name,
ei.project_id as project_id,
rt.release_phase as rule_type_release_phase
FROM latest_evaluation_statuses les
Expand All @@ -99,8 +100,9 @@ FROM latest_evaluation_statuses les
INNER JOIN rule_type rt ON rt.id = ri.rule_type_id
INNER JOIN entity_instances ei ON ei.id = ere.entity_instance_id
INNER JOIN providers prov ON prov.id = ei.provider_id
WHERE les.profile_id = $1 AND
(ere.entity_instance_id = sqlc.narg(entity_id)::UUID OR sqlc.narg(entity_id)::UUID IS NULL)
WHERE les.profile_id = $1
AND (ere.entity_instance_id = sqlc.narg(entity_id)::UUID OR sqlc.narg(entity_id)::UUID IS NULL)
AND (ei.name = sqlc.narg(entity_name) OR sqlc.narg(entity_name) IS NULL)
AND (rt.name = sqlc.narg(rule_type_name) OR sqlc.narg(rule_type_name) IS NULL)
AND (lower(ri.name) = lower(sqlc.narg(rule_name)) OR sqlc.narg(rule_name) IS NULL)
;
7 changes: 5 additions & 2 deletions docs/docs/ref/proto.mdx

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

5 changes: 2 additions & 3 deletions internal/controlplane/handlers_evalstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,11 +666,10 @@ func (s *Server) buildRuleEvaluationStatusFromDBEvaluation(
func buildEntityFromEvaluation(efp *entmodels.EntityWithProperties) *minderv1.EntityTypedId {
ent := &minderv1.EntityTypedId{
Type: efp.Entity.Type,
Id: efp.Entity.ID.String(),
Name: efp.Entity.Name,
}

if ent.Type == minderv1.Entity_ENTITY_REPOSITORIES {
ent.Id = efp.Entity.ID.String()
}
return ent
}

Expand Down
144 changes: 55 additions & 89 deletions internal/controlplane/handlers_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,17 +617,6 @@ func (s *Server) GetProfileStatusById(
}, nil
}

func extractEntitySelector(entity *minderv1.EntityTypedId) *uuid.NullUUID {
if entity == nil {
return nil
}
var selector uuid.NullUUID
if err := selector.Scan(entity.GetId()); err != nil {
return nil
}
return &selector
}

func (s *Server) processProfileStatusByName(
ctx context.Context,
profileName string,
Expand All @@ -638,21 +627,29 @@ func (s *Server) processProfileStatusByName(
) (*minderv1.GetProfileStatusByNameResponse, error) {
var ruleEvaluationStatuses []*minderv1.RuleEvaluationStatus

selector, ruleType, ruleName, err := extractFiltersFromNameRequest(req)
if err != nil {
// Telemetry logging
entityCtx := engcontext.EntityFromContext(ctx)
logger.BusinessRecord(ctx).Project = entityCtx.Project.ID
logger.BusinessRecord(ctx).Profile = logger.Profile{Name: profileName, ID: profileID}

if err := validateEntityType(req.GetEntity()); err != nil {
return nil, err
}
maybeEntityID, err := maybeNullUUID(req.GetEntity().GetId())
if err != nil {
return nil, util.UserVisibleError(codes.InvalidArgument, "Unable to parse entity id: %q", req.GetEntity().GetId())
}
maybeEntityName := maybeNullString(req.GetEntity().GetName())
ruleType := maybeNullString(req.GetRuleType())
ruleName := maybeNullString(req.GetRuleName())

if selector != nil || req.GetAll() {
var entityID uuid.NullUUID
if selector != nil {
entityID = *selector
}
if req.GetAll() || maybeEntityID.Valid || maybeEntityName.Valid {
dbRuleEvaluationStatuses, err := s.store.ListRuleEvaluationsByProfileId(ctx, db.ListRuleEvaluationsByProfileIdParams{
ProfileID: profileID,
EntityID: entityID,
RuleTypeName: *ruleType,
RuleName: *ruleName,
EntityID: maybeEntityID,
EntityName: maybeEntityName,
RuleTypeName: ruleType,
RuleName: ruleName,
})
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.Unknown, "failed to list rule evaluation status: %s", err)
Expand All @@ -663,11 +660,6 @@ func (s *Server) processProfileStatusByName(
)
}

// Telemetry logging
entityCtx := engcontext.EntityFromContext(ctx)
logger.BusinessRecord(ctx).Project = entityCtx.Project.ID
logger.BusinessRecord(ctx).Profile = logger.Profile{Name: profileName, ID: profileID}

return &minderv1.GetProfileStatusByNameResponse{
ProfileStatus: &minderv1.ProfileStatus{
ProfileId: profileID.String(),
Expand All @@ -689,22 +681,30 @@ func (s *Server) processProfileStatusById(
) (*minderv1.GetProfileStatusByIdResponse, error) {
var ruleEvaluationStatuses []*minderv1.RuleEvaluationStatus

selector, ruleType, ruleName, err := extractFiltersFromIdRequest(req)
if err != nil {
// Telemetry logging
entityCtx := engcontext.EntityFromContext(ctx)
logger.BusinessRecord(ctx).Project = entityCtx.Project.ID
logger.BusinessRecord(ctx).Profile = logger.Profile{Name: profileName, ID: profileID}

// selector, ruleType, ruleName, err := extractFiltersFromIdRequest(req)
if err := validateEntityType(req.GetEntity()); err != nil {
return nil, err
}
maybeEntityID, err := maybeNullUUID(req.GetEntity().GetId())
if err != nil {
return nil, util.UserVisibleError(codes.InvalidArgument, "Unable to parse entity id: %q", req.GetEntity().GetId())
}
maybeEntityName := maybeNullString(req.GetEntity().GetName())
ruleType := maybeNullString(req.GetRuleType())
ruleName := maybeNullString(req.GetRuleName())

// Only fetch rule evaluations if selector is present or all is requested
if selector != nil || req.GetAll() {
var entityID uuid.NullUUID
if selector != nil {
entityID = *selector
}
if req.GetAll() || maybeEntityID.Valid || maybeEntityName.Valid {
dbRuleEvaluationStatuses, err := s.store.ListRuleEvaluationsByProfileId(ctx, db.ListRuleEvaluationsByProfileIdParams{
ProfileID: profileID,
EntityID: entityID,
RuleTypeName: *ruleType,
RuleName: *ruleName,
EntityID: maybeEntityID,
EntityName: maybeEntityName,
RuleTypeName: ruleType,
RuleName: ruleName,
})
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.Unknown, "failed to list rule evaluation status: %s", err)
Expand All @@ -715,11 +715,6 @@ func (s *Server) processProfileStatusById(
)
}

// Telemetry logging
entityCtx := engcontext.EntityFromContext(ctx)
logger.BusinessRecord(ctx).Project = entityCtx.Project.ID
logger.BusinessRecord(ctx).Profile = logger.Profile{Name: profileName, ID: profileID}

return &minderv1.GetProfileStatusByIdResponse{
ProfileStatus: &minderv1.ProfileStatus{
ProfileId: profileID.String(),
Expand All @@ -731,61 +726,32 @@ func (s *Server) processProfileStatusById(
}, nil
}

func extractFiltersFromNameRequest(
req *minderv1.GetProfileStatusByNameRequest) (
*uuid.NullUUID, *sql.NullString, *sql.NullString, error) {
if e := req.GetEntity(); e != nil {
func validateEntityType(e *minderv1.EntityTypedId) error {
if e != nil {
if !e.GetType().IsValid() {
return nil, nil, nil, util.UserVisibleError(codes.InvalidArgument,
return util.UserVisibleError(codes.InvalidArgument,
"invalid entity type %s, please use one of %s",
e.GetType(), entities.KnownTypesCSV())
}
}

selector := extractEntitySelector(req.GetEntity())

ruleType := &sql.NullString{
String: req.GetRuleType(),
Valid: req.GetRuleType() != "",
}
if !ruleType.Valid {
//nolint:staticcheck // ignore SA1019: Deprecated field supported for backward compatibility
ruleType = &sql.NullString{
String: req.GetRule(),
Valid: req.GetRule() != "",
}
}

ruleName := &sql.NullString{
String: req.GetRuleName(),
Valid: req.GetRuleName() != "",
}

return selector, ruleType, ruleName, nil
return nil
}

func extractFiltersFromIdRequest(
req *minderv1.GetProfileStatusByIdRequest) (
*uuid.NullUUID, *sql.NullString, *sql.NullString, error) {
if e := req.GetEntity(); e != nil {
if !e.GetType().IsValid() {
return nil, nil, nil, util.UserVisibleError(codes.InvalidArgument,
"invalid entity type %s, please use one of %s",
e.GetType(), entities.KnownTypesCSV())
}
}

selector := extractEntitySelector(req.GetEntity())

ruleType := &sql.NullString{
String: req.GetRuleType(),
Valid: req.GetRuleType() != "",
func maybeNullString(s string) sql.NullString {
return sql.NullString{
String: s,
Valid: s != "",
}
}

ruleName := &sql.NullString{
String: req.GetRuleName(),
Valid: req.GetRuleName() != "",
func maybeNullUUID(s string) (uuid.NullUUID, error) {
var id uuid.UUID
var err error
if s != "" {
id, err = uuid.Parse(s)
}

return selector, ruleType, ruleName, nil
return uuid.NullUUID{
UUID: id,
Valid: s != "",
}, err
}
4 changes: 4 additions & 0 deletions internal/controlplane/handlers_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,8 @@ func TestGetProfileStatusByName(t *testing.T) {
require.Equal(t, dbProfile.ID.String(), resp.ProfileStatus.ProfileId, "Profile ID should match")
require.Equal(t, expectedProfileName, resp.ProfileStatus.ProfileName, "Profile name should match")
})

// TODO: add test case for requesting evaluation details
}

func TestGetProfileStatusById(t *testing.T) {
Expand Down Expand Up @@ -1255,6 +1257,8 @@ func TestGetProfileStatusById(t *testing.T) {
require.Equal(t, dbProfile.ID.String(), resp.ProfileStatus.ProfileId, "Profile ID should match")
require.Equal(t, expectedProfileName, resp.ProfileStatus.ProfileName, "Profile name should match")
})

// TODO: add test case for requesting evaluation details
}

type deleteProfileTestCase struct {
Expand Down
Loading