Skip to content

Commit

Permalink
Add GetProfileByName RPC (#4029)
Browse files Browse the repository at this point in the history
This adds a new RPC to get a profile by name, this is taken into use by
the CLI already.

Signed-off-by: Juan Antonio Osorio <ozz@stacklok.com>
  • Loading branch information
JAORMX authored Jul 29, 2024
1 parent eec1a30 commit 6c46b59
Show file tree
Hide file tree
Showing 13 changed files with 3,360 additions and 2,739 deletions.
47 changes: 30 additions & 17 deletions cmd/cli/app/profile/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package profile
import (
"context"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
Expand All @@ -45,6 +44,7 @@ func getCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.
project := viper.GetString("project")
format := viper.GetString("output")
id := viper.GetString("id")
name := viper.GetString("name")

// Ensure the output format is supported
if !app.IsOutputFormatSupported(format) {
Expand All @@ -55,33 +55,50 @@ func getCommand(ctx context.Context, cmd *cobra.Command, _ []string, conn *grpc.
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413
cmd.SilenceUsage = true

p, err := client.GetProfileById(ctx, &minderv1.GetProfileByIdRequest{
Context: &minderv1.Context{Project: &project},
Id: id,
})
if err != nil {
return cli.MessageAndError("Error getting profile", err)
var prof *minderv1.Profile
if id != "" {
p, err := client.GetProfileById(ctx, &minderv1.GetProfileByIdRequest{
Context: &minderv1.Context{Project: &project},
Id: id,
})
if err != nil {
return cli.MessageAndError("Error getting profile", err)
}

prof = p.GetProfile()
} else if name != "" {
p, err := client.GetProfileByName(ctx, &minderv1.GetProfileByNameRequest{
Context: &minderv1.Context{Project: &project},
Name: name,
})
if err != nil {
return cli.MessageAndError("Error getting profile", err)
}

prof = p.GetProfile()
} else {
return cli.MessageAndError("Error getting profile", fmt.Errorf("id or name required"))
}

switch format {
case app.YAML:
out, err := util.GetYamlFromProto(p)
out, err := util.GetYamlFromProto(prof)
if err != nil {
return cli.MessageAndError("Error getting yaml from proto", err)
}
cmd.Println(out)
case app.JSON:
out, err := util.GetJsonFromProto(p)
out, err := util.GetJsonFromProto(prof)
if err != nil {
return cli.MessageAndError("Error getting json from proto", err)
}
cmd.Println(out)
case app.Table:
settable := NewProfileSettingsTable()
RenderProfileSettingsTable(p.GetProfile(), settable)
RenderProfileSettingsTable(prof, settable)
settable.Render()
table := NewProfileTable()
RenderProfileTable(p.GetProfile(), table)
RenderProfileTable(prof, table)
table.Render()
}
return nil
Expand All @@ -91,12 +108,8 @@ func init() {
ProfileCmd.AddCommand(getCmd)
// Flags
getCmd.Flags().StringP("id", "i", "", "ID for the profile to query")
getCmd.Flags().StringP("name", "n", "", "Name for the profile to query")
getCmd.Flags().StringP("output", "o", app.Table,
fmt.Sprintf("Output format (one of %s)", strings.Join(app.SupportedOutputFormats(), ",")))
// Required
if err := getCmd.MarkFlagRequired("id"); err != nil {
getCmd.Printf("Error marking flag required: %s", err)
os.Exit(1)
}

getCmd.MarkFlagsMutuallyExclusive("id", "name")
}
15 changes: 15 additions & 0 deletions database/mock/store.go

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

19 changes: 19 additions & 0 deletions database/query/profiles.sql
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ JOIN profiles_with_entity_profiles ON profiles.id = profiles_with_entity_profile
LEFT JOIN helper ON profiles.id = helper.profid
WHERE profiles.project_id = $1 AND profiles.id = $2;

-- name: GetProfileByProjectAndName :many
WITH helper AS(
SELECT pr.id as profid,
ARRAY_AGG(ROW(ps.id, ps.profile_id, ps.entity, ps.selector, ps.comment)::profile_selector) AS selectors
FROM profiles pr
JOIN profile_selectors ps
ON pr.id = ps.profile_id
WHERE pr.project_id = $1
GROUP BY pr.id
)
SELECT
sqlc.embed(profiles),
sqlc.embed(profiles_with_entity_profiles),
helper.selectors::profile_selector[] AS profiles_with_selectors
FROM profiles
JOIN profiles_with_entity_profiles ON profiles.id = profiles_with_entity_profiles.profid
LEFT JOIN helper ON profiles.id = helper.profid
WHERE profiles.project_id = $1 AND lower(profiles.name) = lower(sqlc.arg(name));

-- name: GetProfileByID :one
SELECT * FROM profiles WHERE id = $1 AND project_id = $2;

Expand Down
24 changes: 24 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.

46 changes: 46 additions & 0 deletions internal/controlplane/handlers_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,52 @@ func (s *Server) GetProfileById(ctx context.Context,
}, nil
}

// GetProfileByName implements the RPC method for getting a profile by name
func (s *Server) GetProfileByName(ctx context.Context,
in *minderv1.GetProfileByNameRequest) (*minderv1.GetProfileByNameResponse, error) {
entityCtx := engcontext.EntityFromContext(ctx)

err := entityCtx.ValidateProject(ctx, s.store)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error in entity context: %v", err)
}

if in.Name == "" {
return nil, util.UserVisibleError(codes.InvalidArgument, "profile name must be specified")
}

profiles, err := s.store.GetProfileByProjectAndName(ctx, db.GetProfileByProjectAndNameParams{
ProjectID: entityCtx.Project.ID,
Name: in.Name,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, util.UserVisibleError(codes.NotFound, "profile %q not found", in.Name)
}
return nil, err
}

pols := prof.MergeDatabaseGetByNameIntoProfiles(profiles)

// Telemetry logging
logger.BusinessRecord(ctx).Project = entityCtx.Project.ID

if len(pols) == 0 {
return nil, util.UserVisibleError(codes.NotFound, "profile %q not found", in.Name)
} else if len(pols) > 1 {
return nil, fmt.Errorf("expected only one profile, got %d", len(pols))
}

// This should be only one profile
for _, profile := range pols {
return &minderv1.GetProfileByNameResponse{
Profile: profile,
}, nil
}

return nil, util.UserVisibleError(codes.NotFound, "profile %q not found", in.Name)
}

func getProfilePBFromDB(
ctx context.Context,
id uuid.UUID,
Expand Down
75 changes: 75 additions & 0 deletions internal/db/profiles.sql.go

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

1 change: 1 addition & 0 deletions internal/db/querier.go

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

Loading

0 comments on commit 6c46b59

Please sign in to comment.