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

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.