feat(control-plane): resolve repos by name with --project scope · Entire
feat(control-plane): resolve repos by name with --project scope
7dc26c0·
toothbrush·3w ago·4 files·+132 added/-14 removed
repo get/delete and grant repo add/list/remove now accept a repo name (not just a ULID) when --project <name|ULID> is given to scope the lookup — repo names are unique only within a project. A ULID still passes through unchanged and ignores --project.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
ab8e423ff72fView transcript
Changes
4
cmd/entire/cli
Mgrant.go+24/-7
Mrepo.go+24/-7
Mresolveref.go+47
Mresolveref_test.go+37
347 unmodified lines
348
349
350
351
351
352
353
354
4 unmodified lines
359
360
361
362
363
364
365
366
367
368
2 unmodified lines
371
372
373
370
374
375
376
377
378
379
380
381
382
383
384
385
386
382
387
388
389
390
391
392
393
388
394
395
396
397
398
399
400
401
1 unmodified line
403
404
405
406
407
408
409
410
399
411
412
413
414
8 unmodified lines
423
424
425
426
427
428
429
430
431
416
432
433
434
435
3 unmodified lines
439
440
441
426
442
443
444
445
8 unmodified lines
454
455
456
457
458
459
460
347 unmodified lines
}
func newGrantRepoAddCmd() *cobra.Command {
var provider, providerUserID, role, granteeType string
var provider, providerUserID, role, granteeType, project string
cmd := &cobra.Command{
Use: "add <repo>",
Short: "Grant access to a repo",
return err
}
return runCoreJSON(cmd, func(ctx context.Context, c *coreapi.Client) (any, error) {
repoID, err := resolveRepoRef(ctx, c, args[0], project)
if err != nil {
return nil, err
}
body := &coreapi.GrantRepoAccessInputBody{
Provider: provider,
ProviderUserId: providerUserID,
}\n
if granteeType != "" {
body.GranteeType = coreapi.NewOptGrantRepoAccessInputBodyGranteeType(coreapi.GrantRepoAccessInputBodyGranteeType(granteeType))
}
return c.GrantRepoAccess(ctx, body, coreapi.GrantRepoAccessParams{RepoId: args[0]})
return c.GrantRepoAccess(ctx, body, coreapi.GrantRepoAccessParams{RepoId: repoID})
})
},
}
bindGranteeFlags(cmd, &provider, &providerUserID)
cmd.Flags().StringVar(&role, "role", "", "repo role (required)")
cmd.Flags().StringVar(&granteeType, "grantee-type", "", "grantee kind: account, org, or team (default account)")
bindRepoProjectFlag(cmd, &project)
markRequired(cmd, "role")
return cmd
}
func newGrantRepoListCmd() *cobra.Command {
return &cobra.Command{
var project string
cmd := &cobra.Command{
Use: "list <repo>",
Short: "List repo grants",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runCoreList(cmd, projectGrantColumns, repoGrantRow, func(ctx context.Context, c *coreapi.Client) ([]coreapi.RepoGrant, error) {
out, err := c.ListRepoGrants(ctx, coreapi.ListRepoGrantsParams{RepoId: args[0]})
repoID, err := resolveRepoRef(ctx, c, args[0], project)
if err != nil {
return nil, err
}
out, err := c.ListRepoGrants(ctx, coreapi.ListRepoGrantsParams{RepoId: repoID})
if err != nil {
return nil, err
}
1 unmodified line
})
},
}
bindRepoProjectFlag(cmd, &project)
return cmd
}
func newGrantRepoRemoveCmd() *cobra.Command {
var granteeType, granteeID, provider, providerUserID string
var granteeType, granteeID, provider, providerUserID, project string
cmd := &cobra.Command{
Use: "remove <repo>",
Short: "Revoke repo access from a grantee",
8 unmodified lines
return err
}
return runCore(cmd, func(ctx context.Context, c *coreapi.Client) error {
repoID, err := resolveRepoRef(ctx, c, args[0], project)
if err != nil {
return err
}
if mode == granteeModeProvider {
if err := c.RevokeRepoAccessByProvider(ctx, coreapi.RevokeRepoAccessByProviderParams{
RepoId: args[0],
RepoId: repoID,
Provider: provider,
ProviderUserId: providerUserID,
}); err != nil {
3 unmodified lines
return nil
}
if err := c.RevokeRepoAccess(ctx, coreapi.RevokeRepoAccessParams{
RepoId: args[0],
RepoId: repoID,
GranteeType: granteeType,
GranteeId: granteeID,
}); err != nil {
8 unmodified lines
cmd.Flags().StringVar(&granteeID, "grantee-id", "", "grantee ULID (with --grantee-type)")
cmd.Flags().StringVar(&provider, "provider", "", "identity provider, e.g. github (with --provider-user-id)")
cmd.Flags().StringVar(&providerUserID, "provider-user-id", "", "provider-specific user id (with --provider)")
bindRepoProjectFlag(cmd, &project)
return cmd
}
func newRepoGetCmd() *cobra.Command {
return &cobra.Command{
var project string
cmd := &cobra.Command{
Use: "get <repo>",
Short: "Show a repository by ULID",
Short: "Show a repository by name or ULID",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runCoreObject(cmd, repoColumns, repoRow, func(ctx context.Context, c *coreapi.Client) (*coreapi.Repo, error) {
sc, err := c.GetRepo(ctx, coreapi.GetRepoParams{RepoId: args[0]})
repoID, err := resolveRepoRef(ctx, c, args[0], project)
if err != nil {
return nil, err
}
return sc, nil
return c.GetRepo(ctx, coreapi.GetRepoParams{RepoId: repoID})
})
},
}
bindRepoProjectFlag(cmd, &project)
return cmd
}
func newRepoDeleteCmd() *cobra.Command {
return &cobra.Command{
var project string
cmd := &cobra.Command{
Use: "delete <repo>",
Short: "Delete a repository by ULID",
Short: "Delete a repository by name or ULID",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runCore(cmd, func(ctx context.Context, c *coreapi.Client) error {
if err := c.DeleteRepo(ctx, coreapi.DeleteRepoParams{RepoId: args[0]}); err != nil {
repoID, err := resolveRepoRef(ctx, c, args[0], project)
if err != nil {
return err
}
if err := c.DeleteRepo(ctx, coreapi.DeleteRepoParams{RepoId: repoID}); err != nil {
return err
}
cmd.Printf("Deleted repo %s\n", args[0])
1 unmodified line
})
},
}
bindRepoProjectFlag(cmd, &project)
return cmd
}
// bindRepoProjectFlag wires the shared --project scope used to resolve a repo addressed by name (a repo name is unique only within its project). Ignored when the repo arg is already a ULID.
func bindRepoProjectFlag(cmd *cobra.Command, project *string) {
cmd.Flags().StringVar(project, "project", "", "owning project (name or ULID); required when <repo> is a name")
}
// resolveRepoRef turns a repo reference into its ULID. A ULID passes through. A name requires a project scope (projectRef, itself a name or ULID) because repo names are unique only within a project: we list that project's repos and match by name.
func resolveRepoRef(ctx context.Context, c *coreapi.Client, ref, projectRef string) (string, error) {
if looksLikeULID(ref) {
return ref, nil
}
if projectRef == "" {
return "", fmt.Errorf("repo %q is a name; pass --project <name|ULID> to resolve it, or use a repo ULID", ref)
}
projID, err := resolveProjectRef(ctx, c, projectRef)
if err != nil {
return "", err
}
out, err := c.ListProjectRepos(ctx, coreapi.ListProjectReposParams{ProjectId: projID})
if err != nil {
return "", err
}
return pickRepo(out.Repos, ref)
}
// pickRepo selects the single repo named name within an already-scoped project listing. Repo names are unique per project, so a name matches at most one; zero is an error pointing at `repo list`, and (defensively) multiple lists the colliding ids.
func pickRepo(repos []coreapi.Repo, name string) (string, error) {
var matches []coreapi.Repo
for _, r := range repos {
if r.Name == name {
matches = append(matches, r)
}
}
switch len(matches) {
case 1:
return matches[0].ID, nil
case 0:
return "", fmt.Errorf("no repo named %q in that project (run `entire repo list <project>` to see names, or pass a ULID)", name)
default:
ids := make([]string, len(matches))
for i, r := range matches {
ids[i] = r.ID
}
return "", fmt.Errorf("repo name %q is ambiguous (%s); pass a ULID instead", name, strings.Join(ids, ", "))
}
}
// filterProjectsByName narrows projects to exact name matches, returning all of them when name is empty. Used by `project list --org` to apply --name client-side, since the org-scoped list endpoint has no name parameter.