Skip to content

Commit

Permalink
Artifact list: filter per repo (#1967)
Browse files Browse the repository at this point in the history
With more repositories enrolled, it's not trivial to get an overview of
what artifacts did minder download. This commit adds a `--from` switch
to artifact list that allows to simply filter the artifacts by a
repository.
  • Loading branch information
jhrozek authored Dec 19, 2023
1 parent bcc4981 commit 6913677
Show file tree
Hide file tree
Showing 6 changed files with 1,423 additions and 1,306 deletions.
2 changes: 2 additions & 0 deletions cmd/cli/app/artifact/artifact_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var artifact_listCmd = &cobra.Command{
&pb.ListArtifactsRequest{
Provider: provider,
ProjectId: projectID,
From: viper.GetString("from"),
},
)

Expand Down Expand Up @@ -106,4 +107,5 @@ func init() {
artifact_listCmd.Flags().StringP("output", "f", "", "Output format (json or yaml)")
artifact_listCmd.Flags().StringP("provider", "p", "github", "Name for the provider to enroll")
artifact_listCmd.Flags().StringP("project-id", "g", "", "ID of the project for repo registration")
artifact_listCmd.Flags().String("from", "", "Filter artifacts from a source, example: from=repository=owner/repo")
}
1 change: 1 addition & 0 deletions docs/docs/ref/proto.md

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

148 changes: 123 additions & 25 deletions internal/controlplane/handlers_artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"slices"
"strings"

"github.com/google/uuid"
Expand Down Expand Up @@ -49,35 +51,16 @@ func (s *Server) ListArtifacts(ctx context.Context, in *pb.ListArtifactsRequest)
return nil, providerError(err)
}

// first read all the repositories for provider and project
repositories, err := s.store.ListRegisteredRepositoriesByProjectIDAndProvider(ctx,
db.ListRegisteredRepositoriesByProjectIDAndProviderParams{Provider: provider.Name, ProjectID: projectID})
artifactFilter, err := parseArtifactListFrom(s.store, in.From)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.NotFound, "repositories not found")
}
return nil, status.Errorf(codes.Unknown, "failed to get repositories: %s", err)
return nil, fmt.Errorf("failed to parse artifact list from: %w", err)
}

results := []*pb.Artifact{}
for _, repository := range repositories {
artifacts, err := s.store.ListArtifactsByRepoID(ctx, repository.ID)
if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to get artifacts: %s", err)
}

for _, artifact := range artifacts {
results = append(results, &pb.Artifact{
ArtifactPk: artifact.ID.String(),
Owner: repository.RepoOwner,
Name: artifact.ArtifactName,
Type: artifact.ArtifactType,
Visibility: artifact.ArtifactVisibility,
Repository: repository.RepoName,
CreatedAt: timestamppb.New(artifact.CreatedAt),
})
}
results, err := artifactFilter.listArtifacts(ctx, provider.Name, projectID)
if err != nil {
return nil, fmt.Errorf("failed to list artifacts: %w", err)
}

return &pb.ListArtifactsResponse{Results: results}, nil
}

Expand Down Expand Up @@ -178,3 +161,118 @@ func (s *Server) GetArtifactById(ctx context.Context, in *pb.GetArtifactByIdRequ
Versions: final_versions,
}, nil
}

type artifactSource string

const (
artifactSourceRepo artifactSource = "repository"
)

type artifactListFilter struct {
store db.Store

repoSlubList []string
source artifactSource
filter string
}

func parseArtifactListFrom(store db.Store, from string) (*artifactListFilter, error) {
if from == "" {
return &artifactListFilter{
store: store,
source: artifactSourceRepo,
}, nil
}

parts := strings.Split(from, "=")
if len(parts) != 2 {
return nil, util.UserVisibleError(codes.InvalidArgument, "invalid filter, use format: <source>=<filter>")
}

source := parts[0]
filter := parts[1]

var repoSlubList []string

switch source {
case string(artifactSourceRepo):
repoSlubList = strings.Split(filter, ",")
default:
return nil, util.UserVisibleError(codes.InvalidArgument, "invalid filter source, only repository is supported")
}

return &artifactListFilter{
store: store,
source: artifactSource(source),
filter: filter,
repoSlubList: repoSlubList,
}, nil
}

func (filter *artifactListFilter) listArtifacts(ctx context.Context, provider string, project uuid.UUID) ([]*pb.Artifact, error) {
if filter.source != artifactSourceRepo {
// just repos are supported now and we should never get here
// when we support more, we turn this into an if-else or a switch
return []*pb.Artifact{}, nil
}

repositories, err := artifactListRepoFilter(ctx, filter.store, provider, project, filter.repoSlubList)
if err != nil {
return nil, fmt.Errorf("failed to get repositories: %w", err)
}

results := []*pb.Artifact{}
for _, repository := range repositories {
artifacts, err := filter.store.ListArtifactsByRepoID(ctx, repository.ID)
if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to get artifacts: %s", err)
}

for _, artifact := range artifacts {
results = append(results, &pb.Artifact{
ArtifactPk: artifact.ID.String(),
Owner: repository.RepoOwner,
Name: artifact.ArtifactName,
Type: artifact.ArtifactType,
Visibility: artifact.ArtifactVisibility,
Repository: repository.RepoName,
CreatedAt: timestamppb.New(artifact.CreatedAt),
})
}
}

return results, nil
}

func artifactListRepoFilter(
ctx context.Context, store db.Store, provider string, projectID uuid.UUID, repoSlubList []string,
) ([]*db.Repository, error) {
repositories, err := store.ListRegisteredRepositoriesByProjectIDAndProvider(ctx,
db.ListRegisteredRepositoriesByProjectIDAndProviderParams{Provider: provider, ProjectID: projectID})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Errorf(codes.NotFound, "repositories not found")
}
return nil, status.Errorf(codes.Unknown, "failed to get repositories: %s", err)
}

var filterRepositories []*db.Repository
for _, repo := range repositories {
repo := repo
if repoInSlubList(&repo, repoSlubList) {
filterRepositories = append(filterRepositories, &repo)
}
}

return filterRepositories, nil
}

func repoInSlubList(repo *db.Repository, slubList []string) bool {
if len(slubList) == 0 {
return true
}

// we might want to save the repoSlub in the future into the db..
repoSlub := fmt.Sprintf("%s/%s", repo.RepoOwner, repo.RepoName)
return slices.Contains(slubList, repoSlub)
}
6 changes: 6 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.

Loading

0 comments on commit 6913677

Please sign in to comment.