-
Notifications
You must be signed in to change notification settings - Fork 2
Support for group actions ( team and repository creation) #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR updates Go modules and dependency versions, expands config value handling, and adds authenticated ResourceActions to create GitHub repositories and teams via the connector (registration + handlers, entitlements and grants post-processing). Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Handler as Action Handler
participant API as GitHub API
participant Builder as Resource Builder
participant Ent as Entitlements/Grants
Client->>Handler: Execute Create Action (args)
activate Handler
Handler->>Handler: Validate & Normalize Args
Handler->>API: Create Repository/Team
activate API
API-->>Handler: Created Resource + RateLimit Info
deactivate API
Handler->>Builder: Build Resource Object
activate Builder
Builder-->>Handler: Resource
deactivate Builder
Handler->>Ent: Generate Entitlements
activate Ent
Handler->>API: List Members/Collaborators
API-->>Ent: Members/Collaborators
Ent-->>Handler: Grants
deactivate Ent
Handler-->>Client: Return (success, resource, entitlements, grants)
deactivate Handler
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
pkg/connector/repository.go
Outdated
| } | ||
|
|
||
| func (o *repositoryResourceType) registerCreateRepositoryAction(ctx context.Context, registry actions.ResourceTypeActionRegistry) error { | ||
| return registry.Register(ctx, &v2.ResourceActionSchema{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list of create repository params is very long, not sure if we should add all of them in the schema or just the most common ones? Right now its adding the most common ones here.
6513520 to
25ba291
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@pkg/connector/repository.go`:
- Around line 654-717: The collaborators and teams fetch only the first page;
update the calls to o.client.Repositories.ListCollaborators and
o.client.Repositories.ListTeams to iterate pages until no more results:
create/reuse ListOptions (collabOpts/teamOpts) with PerPage=maxPageSize and a
for loop that sets Page, calls the list method, appends returned users/teams to
the grants logic on each iteration, breaks when resp.NextPage == 0 (or
len(results)==0), and preserve existing error logging and per-item processing
(variables: collabOpts, users, teamOpts, teams, maxPageSize,
o.client.Repositories.ListCollaborators, o.client.Repositories.ListTeams).
- Around line 601-609: The visibility check currently logs via l.Warn and leaves
newRepo.Visibility unset; replace that silent fallback by validating the value
returned from actions.GetStringArg and handling invalid inputs explicitly:
either set an explicit default (e.g., newRepo.Visibility =
github.Ptr("private")) or, preferably, return an error to the caller (e.g.,
return fmt.Errorf("invalid visibility: %q", visibility)) so the create flow
fails fast; update the branch that currently calls l.Warn (around
actions.GetStringArg / newRepo.Visibility) to perform this explicit defaulting
or error return and ensure the provided visibility is included in the error
message.
In `@pkg/connector/team.go`:
- Around line 625-653: The ListTeamMembersByID call in the loop over
teamAccessLevels can return only the first page, so implement pagination: change
the single ListTeamMembersByID call to a paginated loop that sets
opts.ListOptions.Page (starting at 1) and repeatedly calls
o.client.Teams.ListTeamMembersByID(ctx, createdTeam.GetOrganization().GetID(),
createdTeam.GetID(), opts), processing members into grants (using userResource
and grant.NewGrant as before) and advancing to resp.NextPage until resp.NextPage
== 0; keep PerPage set to maxPageSize and preserve existing error handling and
logging when each page call fails.
🧹 Nitpick comments (2)
pkg/connector/team.go (1)
536-552: Consider returning an error for invalid privacy on nested teams.When a user requests
secretprivacy for a nested team, the code logs a warning but silently changes it toclosed. This could lead to unexpected results where the user thinks they created a secret team but it's actually closed.Consider either:
- Returning an error explaining the constraint
- Including the actual privacy setting in the return values so the caller knows what was applied
Option 1: Return an error for invalid request
if isNestedTeam { // Nested teams can only be "closed" if privacy == teamPrivacySecret { - l.Warn("github-connector: secret privacy not allowed for nested teams, using closed", - zap.String("requested_privacy", privacy), - ) + return nil, nil, fmt.Errorf("nested teams cannot have 'secret' privacy, only 'closed' is allowed") } newTeam.Privacy = github.Ptr(teamPrivacyClosed)pkg/connector/repository.go (1)
506-551: Hardcoded template lists may need periodic updates.The gitignore and license template options are hardcoded. While this provides a good UX, these lists may become outdated as GitHub adds new templates.
Consider:
- Documenting that these lists are static and may need updates
- Alternatively, using GitHub's APIs (
/gitignore/templatesand/licenses) to populate these dynamicallyThis is a minor maintenance concern and acceptable for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@pkg/connector/repository.go`:
- Around line 483-606: The visibility option currently only supports "public"
and "private" which disallows creating enterprise-only "internal" repos; update
the config options for the "visibility" Field (the StringField options where
Value is "public"/"private") to include an {Value: "internal", DisplayName:
"Internal", Name: "..."} entry and modify handleCreateRepositoryAction to accept
"internal" as a valid visibility value (extend the if check that currently
allows only "public" or "private" to also allow "internal" before setting
newRepo.Visibility). Ensure the default behavior remains unchanged and no other
validation blocks the "internal" value.
- Around line 609-620: The code currently sets newRepo.GitignoreTemplate and
newRepo.LicenseTemplate without ensuring newRepo.AutoInit is true; update the
logic in the repository creation block (symbols: newRepo, AutoInit,
GitignoreTemplate, LicenseTemplate, actions.GetBoolArg, actions.GetStringArg) to
enforce GitHub's requirement: if either gitignore_template or license_template
is provided, ensure newRepo.AutoInit is true—if the user explicitly passed
add_readme=false return an error indicating templates require auto_init=true,
otherwise set newRepo.AutoInit = github.Ptr(true) before assigning the
templates.
In `@pkg/connector/team.go`:
- Around line 450-472: The argument descriptions for the fields named
"maintainers" and "repo_names" are misleading because both are
ResourceIdSliceField (parsed as numeric resource IDs) but the text implies
usernames/full names; update the DisplayName/Description strings for the fields
defined in this block (the "maintainers" and "repo_names" Field entries using
config.Field_ResourceIdSliceField / config.ResourceIdSliceField and
AllowedResourceTypeIds referencing resourceTypeUser.Id and
resourceTypeRepository.Id) to clearly state they expect resource IDs (e.g.,
"List of user resource IDs" and "List of repository resource IDs") so callers
understand these are ID selections not textual usernames/full names.
- Around line 536-548: The privacy branch currently only sets newTeam.Privacy
for allowed values and silently ignores invalid values for non-nested teams; add
an explicit validation for the "privacy" arg when isNestedTeam is false: if
privacy is provided and is not teamPrivacySecret or teamPrivacyClosed, return an
input/argument error (consistent with how invalid parent/maintainer IDs are
handled) instead of letting the GitHub API default it. Locate the block using
actions.GetStringArg(args, "privacy"), isNestedTeam, teamPrivacySecret,
teamPrivacyClosed and newTeam.Privacy and insert the validation+early return for
invalid non-nested privacy values.
| Name: "visibility", | ||
| DisplayName: "Visibility", | ||
| Description: "The visibility level of the repository", | ||
| Field: &config.Field_StringField{ | ||
| StringField: &config.StringField{ | ||
| Options: []*config.StringFieldOption{ | ||
| {Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"}, | ||
| {Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"}, | ||
| }, | ||
| DefaultValue: "private", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "add_readme", | ||
| DisplayName: "Add README.md", | ||
| Description: "Add a README.md file to the repository", | ||
| Field: &config.Field_BoolField{ | ||
| BoolField: &config.BoolField{ | ||
| DefaultValue: true, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "gitignore_template", | ||
| DisplayName: "Gitignore Template", | ||
| Description: "Gitignore template to apply", | ||
| Field: &config.Field_StringField{ | ||
| StringField: &config.StringField{ | ||
| Options: []*config.StringFieldOption{ | ||
| {Value: "", DisplayName: "No .gitignore template"}, | ||
| {Value: "Go", DisplayName: "Go"}, | ||
| {Value: "Python", DisplayName: "Python"}, | ||
| {Value: "Node", DisplayName: "Node"}, | ||
| {Value: "Java", DisplayName: "Java"}, | ||
| {Value: "Ruby", DisplayName: "Ruby"}, | ||
| {Value: "Rust", DisplayName: "Rust"}, | ||
| {Value: "C++", DisplayName: "C++"}, | ||
| {Value: "C", DisplayName: "C"}, | ||
| {Value: "Swift", DisplayName: "Swift"}, | ||
| {Value: "Kotlin", DisplayName: "Kotlin"}, | ||
| {Value: "Scala", DisplayName: "Scala"}, | ||
| {Value: "Terraform", DisplayName: "Terraform"}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "license_template", | ||
| DisplayName: "License Template", | ||
| Description: "License template to apply", | ||
| Field: &config.Field_StringField{ | ||
| StringField: &config.StringField{ | ||
| Options: []*config.StringFieldOption{ | ||
| {Value: "", DisplayName: "No license"}, | ||
| {Value: "mit", DisplayName: "MIT License"}, | ||
| {Value: "apache-2.0", DisplayName: "Apache License 2.0"}, | ||
| {Value: "gpl-3.0", DisplayName: "GNU GPLv3"}, | ||
| {Value: "gpl-2.0", DisplayName: "GNU GPLv2"}, | ||
| {Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"}, | ||
| {Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"}, | ||
| {Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"}, | ||
| {Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"}, | ||
| {Value: "unlicense", DisplayName: "The Unlicense"}, | ||
| {Value: "agpl-3.0", DisplayName: "GNU AGPLv3"}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| ReturnTypes: []*config.Field{ | ||
| {Name: "success", Field: &config.Field_BoolField{}}, | ||
| {Name: "resource", Field: &config.Field_ResourceField{}}, | ||
| {Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{ | ||
| EntitlementSliceField: &config.EntitlementSliceField{}, | ||
| }}, | ||
| {Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{ | ||
| GrantSliceField: &config.GrantSliceField{}, | ||
| }}, | ||
| }, | ||
| }, o.handleCreateRepositoryAction) | ||
| } | ||
|
|
||
| func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) { | ||
| l := ctxzap.Extract(ctx) | ||
|
|
||
| // Extract required arguments using SDK helpers | ||
| name, err := actions.RequireStringArg(args, "name") | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| parentResourceID, err := actions.RequireResourceIDArg(args, "org") | ||
| if err != nil { | ||
| return nil, nil, err | ||
| } | ||
|
|
||
| // Get the organization name from the parent resource ID | ||
| orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID) | ||
| if err != nil { | ||
| return nil, nil, fmt.Errorf("failed to get organization name: %w", err) | ||
| } | ||
|
|
||
| l.Info("github-connector: creating repository via action", | ||
| zap.String("repo_name", name), | ||
| zap.String("org_name", orgName), | ||
| ) | ||
|
|
||
| // Build the Repository request | ||
| newRepo := &github.Repository{ | ||
| Name: github.Ptr(name), | ||
| } | ||
|
|
||
| // Extract optional fields using SDK helpers | ||
| if description, ok := actions.GetStringArg(args, "description"); ok && description != "" { | ||
| newRepo.Description = github.Ptr(description) | ||
| } | ||
|
|
||
| if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" { | ||
| if visibility == "public" || visibility == "private" { | ||
| newRepo.Visibility = github.Ptr(visibility) | ||
| } else { | ||
| return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
GitHub REST API create organization repository visibility values internal
💡 Result:
When creating an organization repository via the GitHub REST API (POST /orgs/{org}/repos), the visibility field accepts:
publicprivateinternal(only if the org is part of an enterprise account on GitHub Enterprise Cloud or GitHub Enterprise Server ≥ 2.20) [1][2]
Notes:
visibility(when provided) overrides the older booleanprivatefield. [1]- Historically,
internalsupport required the previewAccept: application/vnd.github.nebula-preview+json; modern clients/docs still mention this in some references. [1][2]
Sources: [1] [2]
🏁 Script executed:
rg "visibility" pkg/connector/repository.go -B2 -A2Repository: ConductorOne/baton-github
Length of output: 600
🏁 Script executed:
rg "visibility" --type go -B1 -A1Repository: ConductorOne/baton-github
Length of output: 50381
🏁 Script executed:
grep -n "visibility" pkg/connector/repository.goRepository: ConductorOne/baton-github
Length of output: 480
Add support for internal visibility for enterprise organizations.
The schema and validation only allow public/private, but GitHub's REST API supports internal for organizations within an enterprise account (GitHub Enterprise Cloud or Server ≥ 2.20). This prevents users from creating internal repositories when using the connector on enterprise instances.
🔧 Proposed update to allow internal visibility
{
Name: "visibility",
DisplayName: "Visibility",
Description: "The visibility level of the repository",
Field: &config.Field_StringField{
StringField: &config.StringField{
Options: []*config.StringFieldOption{
{Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"},
{Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"},
+ {Value: "internal", DisplayName: "Internal", Name: "Visible to organization members"},
},
DefaultValue: "private",
},
},
}, if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" {
- if visibility == "public" || visibility == "private" {
+ if visibility == "public" || visibility == "private" || visibility == "internal" {
newRepo.Visibility = github.Ptr(visibility)
} else {
- return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility)
+ return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\", \"private\", or \"internal\")", visibility)
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Name: "visibility", | |
| DisplayName: "Visibility", | |
| Description: "The visibility level of the repository", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"}, | |
| {Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"}, | |
| }, | |
| DefaultValue: "private", | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "add_readme", | |
| DisplayName: "Add README.md", | |
| Description: "Add a README.md file to the repository", | |
| Field: &config.Field_BoolField{ | |
| BoolField: &config.BoolField{ | |
| DefaultValue: true, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "gitignore_template", | |
| DisplayName: "Gitignore Template", | |
| Description: "Gitignore template to apply", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "", DisplayName: "No .gitignore template"}, | |
| {Value: "Go", DisplayName: "Go"}, | |
| {Value: "Python", DisplayName: "Python"}, | |
| {Value: "Node", DisplayName: "Node"}, | |
| {Value: "Java", DisplayName: "Java"}, | |
| {Value: "Ruby", DisplayName: "Ruby"}, | |
| {Value: "Rust", DisplayName: "Rust"}, | |
| {Value: "C++", DisplayName: "C++"}, | |
| {Value: "C", DisplayName: "C"}, | |
| {Value: "Swift", DisplayName: "Swift"}, | |
| {Value: "Kotlin", DisplayName: "Kotlin"}, | |
| {Value: "Scala", DisplayName: "Scala"}, | |
| {Value: "Terraform", DisplayName: "Terraform"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "license_template", | |
| DisplayName: "License Template", | |
| Description: "License template to apply", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "", DisplayName: "No license"}, | |
| {Value: "mit", DisplayName: "MIT License"}, | |
| {Value: "apache-2.0", DisplayName: "Apache License 2.0"}, | |
| {Value: "gpl-3.0", DisplayName: "GNU GPLv3"}, | |
| {Value: "gpl-2.0", DisplayName: "GNU GPLv2"}, | |
| {Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"}, | |
| {Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"}, | |
| {Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"}, | |
| {Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"}, | |
| {Value: "unlicense", DisplayName: "The Unlicense"}, | |
| {Value: "agpl-3.0", DisplayName: "GNU AGPLv3"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| ReturnTypes: []*config.Field{ | |
| {Name: "success", Field: &config.Field_BoolField{}}, | |
| {Name: "resource", Field: &config.Field_ResourceField{}}, | |
| {Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{ | |
| EntitlementSliceField: &config.EntitlementSliceField{}, | |
| }}, | |
| {Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{ | |
| GrantSliceField: &config.GrantSliceField{}, | |
| }}, | |
| }, | |
| }, o.handleCreateRepositoryAction) | |
| } | |
| func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) { | |
| l := ctxzap.Extract(ctx) | |
| // Extract required arguments using SDK helpers | |
| name, err := actions.RequireStringArg(args, "name") | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| parentResourceID, err := actions.RequireResourceIDArg(args, "org") | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| // Get the organization name from the parent resource ID | |
| orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to get organization name: %w", err) | |
| } | |
| l.Info("github-connector: creating repository via action", | |
| zap.String("repo_name", name), | |
| zap.String("org_name", orgName), | |
| ) | |
| // Build the Repository request | |
| newRepo := &github.Repository{ | |
| Name: github.Ptr(name), | |
| } | |
| // Extract optional fields using SDK helpers | |
| if description, ok := actions.GetStringArg(args, "description"); ok && description != "" { | |
| newRepo.Description = github.Ptr(description) | |
| } | |
| if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" { | |
| if visibility == "public" || visibility == "private" { | |
| newRepo.Visibility = github.Ptr(visibility) | |
| } else { | |
| return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility) | |
| } | |
| { | |
| Name: "visibility", | |
| DisplayName: "Visibility", | |
| Description: "The visibility level of the repository", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"}, | |
| {Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"}, | |
| {Value: "internal", DisplayName: "Internal", Name: "Visible to organization members"}, | |
| }, | |
| DefaultValue: "private", | |
| }, | |
| }, | |
| }, |
| Name: "visibility", | |
| DisplayName: "Visibility", | |
| Description: "The visibility level of the repository", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "public", DisplayName: "Public", Name: "Anyone on the internet can view this repository"}, | |
| {Value: "private", DisplayName: "Private", Name: "You can choose who can see this repository"}, | |
| }, | |
| DefaultValue: "private", | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "add_readme", | |
| DisplayName: "Add README.md", | |
| Description: "Add a README.md file to the repository", | |
| Field: &config.Field_BoolField{ | |
| BoolField: &config.BoolField{ | |
| DefaultValue: true, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "gitignore_template", | |
| DisplayName: "Gitignore Template", | |
| Description: "Gitignore template to apply", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "", DisplayName: "No .gitignore template"}, | |
| {Value: "Go", DisplayName: "Go"}, | |
| {Value: "Python", DisplayName: "Python"}, | |
| {Value: "Node", DisplayName: "Node"}, | |
| {Value: "Java", DisplayName: "Java"}, | |
| {Value: "Ruby", DisplayName: "Ruby"}, | |
| {Value: "Rust", DisplayName: "Rust"}, | |
| {Value: "C++", DisplayName: "C++"}, | |
| {Value: "C", DisplayName: "C"}, | |
| {Value: "Swift", DisplayName: "Swift"}, | |
| {Value: "Kotlin", DisplayName: "Kotlin"}, | |
| {Value: "Scala", DisplayName: "Scala"}, | |
| {Value: "Terraform", DisplayName: "Terraform"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "license_template", | |
| DisplayName: "License Template", | |
| Description: "License template to apply", | |
| Field: &config.Field_StringField{ | |
| StringField: &config.StringField{ | |
| Options: []*config.StringFieldOption{ | |
| {Value: "", DisplayName: "No license"}, | |
| {Value: "mit", DisplayName: "MIT License"}, | |
| {Value: "apache-2.0", DisplayName: "Apache License 2.0"}, | |
| {Value: "gpl-3.0", DisplayName: "GNU GPLv3"}, | |
| {Value: "gpl-2.0", DisplayName: "GNU GPLv2"}, | |
| {Value: "lgpl-3.0", DisplayName: "GNU LGPLv3"}, | |
| {Value: "bsd-3-clause", DisplayName: "BSD 3-Clause"}, | |
| {Value: "bsd-2-clause", DisplayName: "BSD 2-Clause"}, | |
| {Value: "mpl-2.0", DisplayName: "Mozilla Public License 2.0"}, | |
| {Value: "unlicense", DisplayName: "The Unlicense"}, | |
| {Value: "agpl-3.0", DisplayName: "GNU AGPLv3"}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| ReturnTypes: []*config.Field{ | |
| {Name: "success", Field: &config.Field_BoolField{}}, | |
| {Name: "resource", Field: &config.Field_ResourceField{}}, | |
| {Name: "entitlements", DisplayName: "Entitlements", Field: &config.Field_EntitlementSliceField{ | |
| EntitlementSliceField: &config.EntitlementSliceField{}, | |
| }}, | |
| {Name: "grants", DisplayName: "Grants", Field: &config.Field_GrantSliceField{ | |
| GrantSliceField: &config.GrantSliceField{}, | |
| }}, | |
| }, | |
| }, o.handleCreateRepositoryAction) | |
| } | |
| func (o *repositoryResourceType) handleCreateRepositoryAction(ctx context.Context, args *structpb.Struct) (*structpb.Struct, annotations.Annotations, error) { | |
| l := ctxzap.Extract(ctx) | |
| // Extract required arguments using SDK helpers | |
| name, err := actions.RequireStringArg(args, "name") | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| parentResourceID, err := actions.RequireResourceIDArg(args, "org") | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| // Get the organization name from the parent resource ID | |
| orgName, err := o.orgCache.GetOrgName(ctx, parentResourceID) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to get organization name: %w", err) | |
| } | |
| l.Info("github-connector: creating repository via action", | |
| zap.String("repo_name", name), | |
| zap.String("org_name", orgName), | |
| ) | |
| // Build the Repository request | |
| newRepo := &github.Repository{ | |
| Name: github.Ptr(name), | |
| } | |
| // Extract optional fields using SDK helpers | |
| if description, ok := actions.GetStringArg(args, "description"); ok && description != "" { | |
| newRepo.Description = github.Ptr(description) | |
| } | |
| if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" { | |
| if visibility == "public" || visibility == "private" { | |
| newRepo.Visibility = github.Ptr(visibility) | |
| } else { | |
| return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\" or \"private\")", visibility) | |
| } | |
| if visibility, ok := actions.GetStringArg(args, "visibility"); ok && visibility != "" { | |
| if visibility == "public" || visibility == "private" || visibility == "internal" { | |
| newRepo.Visibility = github.Ptr(visibility) | |
| } else { | |
| return nil, nil, fmt.Errorf("invalid visibility: %q (must be \"public\", \"private\", or \"internal\")", visibility) | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@pkg/connector/repository.go` around lines 483 - 606, The visibility option
currently only supports "public" and "private" which disallows creating
enterprise-only "internal" repos; update the config options for the "visibility"
Field (the StringField options where Value is "public"/"private") to include an
{Value: "internal", DisplayName: "Internal", Name: "..."} entry and modify
handleCreateRepositoryAction to accept "internal" as a valid visibility value
(extend the if check that currently allows only "public" or "private" to also
allow "internal" before setting newRepo.Visibility). Ensure the default behavior
remains unchanged and no other validation blocks the "internal" value.
| // add_readme maps to AutoInit in GitHub API | ||
| if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok { | ||
| newRepo.AutoInit = github.Ptr(addReadme) | ||
| } | ||
|
|
||
| if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" { | ||
| newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate) | ||
| } | ||
|
|
||
| if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" { | ||
| newRepo.LicenseTemplate = github.Ptr(licenseTemplate) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
GitHub API create repository gitignore_template license_template auto_init requirement
💡 Result:
For the GitHub REST API “create repository” endpoints (POST /user/repos and POST /orgs/{org}/repos), the only required field is:
name(string) — repository name. [1]
The fields you mentioned are optional request-body parameters:
auto_init(boolean) — initialize the repo with an initial commit (minimal README). Defaultfalse. [1]gitignore_template(string) — the.gitignoretemplate name to apply at creation time. The valid names are the ones returned byGET /gitignore/templates. [1][2]license_template(string) — the license keyword to apply at creation time. Valid keywords come fromGET /licenses(commonly used licenses). [1][3]
Practical requirement/behavior:
- To initialize the repository at creation time (and thereby have GitHub apply things like a
.gitignoretemplate during creation), setauto_init: true. GitHub describesgitignore_templateas an option used when initializing a repo via the API. [4][1]
Sources: [1] GitHub Docs (REST “Repos” → “Create a repository for the authenticated user”, params include auto_init, gitignore_template, license_template). [2] GitHub Docs (REST “Gitignore” → “Get all gitignore templates”). [3] GitHub Docs (REST “Licenses” → “Get all commonly used licenses”). [4] GitHub Developer Guide (API change note describing initializing repos via API and using gitignore_template).
Enforce auto_init=true when gitignore_template or license_template is supplied.
GitHub's API applies gitignore_template and license_template only when auto_init=true. The current code allows these templates to be set independently without ensuring auto_init is enabled, which will result in the templates being silently ignored when the repo is created. Add validation to either force auto_init=true when templates are supplied, or return an error if templates are provided without auto_init=true.
🔧 Suggested guard
// add_readme maps to AutoInit in GitHub API
- if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
- newRepo.AutoInit = github.Ptr(addReadme)
- }
+ autoInit := true
+ if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok {
+ autoInit = addReadme
+ }
+ newRepo.AutoInit = github.Ptr(autoInit)
@@
if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" {
newRepo.LicenseTemplate = github.Ptr(licenseTemplate)
}
+
+ if (newRepo.GitignoreTemplate != nil || newRepo.LicenseTemplate != nil) && !autoInit {
+ return nil, nil, fmt.Errorf("gitignore_template/license_template require add_readme=true")
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // add_readme maps to AutoInit in GitHub API | |
| if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok { | |
| newRepo.AutoInit = github.Ptr(addReadme) | |
| } | |
| if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" { | |
| newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate) | |
| } | |
| if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" { | |
| newRepo.LicenseTemplate = github.Ptr(licenseTemplate) | |
| } | |
| // add_readme maps to AutoInit in GitHub API | |
| autoInit := true | |
| if addReadme, ok := actions.GetBoolArg(args, "add_readme"); ok { | |
| autoInit = addReadme | |
| } | |
| newRepo.AutoInit = github.Ptr(autoInit) | |
| if gitignoreTemplate, ok := actions.GetStringArg(args, "gitignore_template"); ok && gitignoreTemplate != "" { | |
| newRepo.GitignoreTemplate = github.Ptr(gitignoreTemplate) | |
| } | |
| if licenseTemplate, ok := actions.GetStringArg(args, "license_template"); ok && licenseTemplate != "" { | |
| newRepo.LicenseTemplate = github.Ptr(licenseTemplate) | |
| } | |
| if (newRepo.GitignoreTemplate != nil || newRepo.LicenseTemplate != nil) && !autoInit { | |
| return nil, nil, fmt.Errorf("gitignore_template/license_template require add_readme=true") | |
| } |
🤖 Prompt for AI Agents
In `@pkg/connector/repository.go` around lines 609 - 620, The code currently sets
newRepo.GitignoreTemplate and newRepo.LicenseTemplate without ensuring
newRepo.AutoInit is true; update the logic in the repository creation block
(symbols: newRepo, AutoInit, GitignoreTemplate, LicenseTemplate,
actions.GetBoolArg, actions.GetStringArg) to enforce GitHub's requirement: if
either gitignore_template or license_template is provided, ensure
newRepo.AutoInit is true—if the user explicitly passed add_readme=false return
an error indicating templates require auto_init=true, otherwise set
newRepo.AutoInit = github.Ptr(true) before assigning the templates.
| Name: "maintainers", | ||
| DisplayName: "Maintainers", | ||
| Description: "List GitHub usernames for organization members who will become team maintainers.", | ||
| Field: &config.Field_ResourceIdSliceField{ | ||
| ResourceIdSliceField: &config.ResourceIdSliceField{ | ||
| Rules: &config.RepeatedResourceIdRules{ | ||
| AllowedResourceTypeIds: []string{resourceTypeUser.Id}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "repo_names", | ||
| DisplayName: "Repository names", | ||
| Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.", | ||
| Field: &config.Field_ResourceIdSliceField{ | ||
| ResourceIdSliceField: &config.ResourceIdSliceField{ | ||
| Rules: &config.RepeatedResourceIdRules{ | ||
| AllowedResourceTypeIds: []string{resourceTypeRepository.Id}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argument descriptions don’t match the ResourceId inputs.
maintainers and repo_names are ResourceIdSliceFields and are parsed as numeric IDs, but the descriptions imply usernames/full names. This can mislead API/CLI callers. Consider updating the descriptions to reflect resource selection/IDs.
✏️ Suggested copy tweak
{
Name: "maintainers",
DisplayName: "Maintainers",
- Description: "List GitHub usernames for organization members who will become team maintainers.",
+ Description: "Select organization users (resource IDs) who will become team maintainers.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeUser.Id},
},
},
},
},
{
Name: "repo_names",
DisplayName: "Repository names",
- Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.",
+ Description: "Select repositories (resource IDs) to add the team to.",
Field: &config.Field_ResourceIdSliceField{
ResourceIdSliceField: &config.ResourceIdSliceField{
Rules: &config.RepeatedResourceIdRules{
AllowedResourceTypeIds: []string{resourceTypeRepository.Id},
},
},
},
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Name: "maintainers", | |
| DisplayName: "Maintainers", | |
| Description: "List GitHub usernames for organization members who will become team maintainers.", | |
| Field: &config.Field_ResourceIdSliceField{ | |
| ResourceIdSliceField: &config.ResourceIdSliceField{ | |
| Rules: &config.RepeatedResourceIdRules{ | |
| AllowedResourceTypeIds: []string{resourceTypeUser.Id}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "repo_names", | |
| DisplayName: "Repository names", | |
| Description: "The full name (e.g., organization-name/repository-name) of repositories to add the team to.", | |
| Field: &config.Field_ResourceIdSliceField{ | |
| ResourceIdSliceField: &config.ResourceIdSliceField{ | |
| Rules: &config.RepeatedResourceIdRules{ | |
| AllowedResourceTypeIds: []string{resourceTypeRepository.Id}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "maintainers", | |
| DisplayName: "Maintainers", | |
| Description: "Select organization users (resource IDs) who will become team maintainers.", | |
| Field: &config.Field_ResourceIdSliceField{ | |
| ResourceIdSliceField: &config.ResourceIdSliceField{ | |
| Rules: &config.RepeatedResourceIdRules{ | |
| AllowedResourceTypeIds: []string{resourceTypeUser.Id}, | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| Name: "repo_names", | |
| DisplayName: "Repository names", | |
| Description: "Select repositories (resource IDs) to add the team to.", | |
| Field: &config.Field_ResourceIdSliceField{ | |
| ResourceIdSliceField: &config.ResourceIdSliceField{ | |
| Rules: &config.RepeatedResourceIdRules{ | |
| AllowedResourceTypeIds: []string{resourceTypeRepository.Id}, | |
| }, | |
| }, | |
| }, | |
| }, |
🤖 Prompt for AI Agents
In `@pkg/connector/team.go` around lines 450 - 472, The argument descriptions for
the fields named "maintainers" and "repo_names" are misleading because both are
ResourceIdSliceField (parsed as numeric resource IDs) but the text implies
usernames/full names; update the DisplayName/Description strings for the fields
defined in this block (the "maintainers" and "repo_names" Field entries using
config.Field_ResourceIdSliceField / config.ResourceIdSliceField and
AllowedResourceTypeIds referencing resourceTypeUser.Id and
resourceTypeRepository.Id) to clearly state they expect resource IDs (e.g.,
"List of user resource IDs" and "List of repository resource IDs") so callers
understand these are ID selections not textual usernames/full names.
| if privacy, ok := actions.GetStringArg(args, "privacy"); ok && privacy != "" { | ||
| if isNestedTeam { | ||
| // Nested teams can only be "closed" | ||
| if privacy == teamPrivacySecret { | ||
| l.Warn("github-connector: secret privacy not allowed for nested teams, using closed", | ||
| zap.String("requested_privacy", privacy), | ||
| ) | ||
| } | ||
| newTeam.Privacy = github.Ptr(teamPrivacyClosed) | ||
| } else if privacy == teamPrivacySecret || privacy == teamPrivacyClosed { | ||
| // Non-nested teams can be "secret" or "closed" | ||
| newTeam.Privacy = github.Ptr(privacy) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Search for the team.go file more broadly
find . -name "team.go" -type f 2>/dev/null | head -10Repository: ConductorOne/baton-github
Length of output: 92
🏁 Script executed:
# Broader search in pkg directory
git ls-files pkg/connector/ | grep -i teamRepository: ConductorOne/baton-github
Length of output: 117
🏁 Script executed:
# Check repository structure
git ls-files | grep -E "connector|team" | head -30Repository: ConductorOne/baton-github
Length of output: 1760
🏁 Script executed:
# Search for privacy-related code patterns
rg -t go "teamPrivacy" --max-count 20Repository: ConductorOne/baton-github
Length of output: 473
🏁 Script executed:
# Search for teamPrivacy constants
rg "teamPrivacy(Secret|Closed)" -t goRepository: ConductorOne/baton-github
Length of output: 473
🏁 Script executed:
# Search for the function containing this code
rg "if isNestedTeam" -B 20 -A 30 -t goRepository: ConductorOne/baton-github
Length of output: 3972
🏁 Script executed:
# Also look for the pattern mentioned in the code
rg "Nested teams can only be" -B 10 -A 10Repository: ConductorOne/baton-github
Length of output: 1345
🏁 Script executed:
# Search for all Go files with the pattern
rg "privacy == teamPrivacy" -t go -A 2 -B 2Repository: ConductorOne/baton-github
Length of output: 748
🏁 Script executed:
# Broader search for github.Ptr
rg "github\.Ptr.*privacy" -t goRepository: ConductorOne/baton-github
Length of output: 131
🏁 Script executed:
# Search for teamPrivacy variable/constant definitions
rg "var.*teamPrivacy|const.*teamPrivacy" -t goRepository: ConductorOne/baton-github
Length of output: 51
🏁 Script executed:
# Look for error return patterns to understand how errors are returned in this file
rg "return.*error|return nil.*error" pkg/connector/team.go | head -10Repository: ConductorOne/baton-github
Length of output: 212
🏁 Script executed:
# Get the exact function containing this code
rg -B 50 "if privacy, ok := actions.GetStringArg" pkg/connector/team.go | head -60Repository: ConductorOne/baton-github
Length of output: 1769
🏁 Script executed:
# Check around line 500-560 to get context
sed -n '480,560p' pkg/connector/team.goRepository: ConductorOne/baton-github
Length of output: 2876
🏁 Script executed:
# Also check if there are any tests that show expected behavior
grep -n "privacy" pkg/connector/team_test.go | head -20Repository: ConductorOne/baton-github
Length of output: 51
🏁 Script executed:
# Get full context with line numbers
awk 'NR>=500 && NR<=570' pkg/connector/team.go | cat -nRepository: ConductorOne/baton-github
Length of output: 3176
🏁 Script executed:
# Also get earlier context to find function signature
sed -n '440,540p' pkg/connector/team.goRepository: ConductorOne/baton-github
Length of output: 3591
🏁 Script executed:
# Also check if there are any integration tests or examples
grep -rn "handleCreateTeamAction" pkg/connector/ | head -10Repository: ConductorOne/baton-github
Length of output: 297
🏁 Script executed:
# Get validation patterns from the same function to understand error handling style
sed -n '487,700p' pkg/connector/team.go | grep -n "fmt.Errorf\|return"Repository: ConductorOne/baton-github
Length of output: 1184
Add validation to reject invalid privacy values for non-nested teams.
The current code silently ignores invalid privacy values for non-nested teams, allowing the GitHub API to apply a default instead. This is inconsistent with the error validation pattern used throughout the function (e.g., invalid parent team ID, invalid maintainer ID) and with the nested-team behavior, which warns the user.
Add an upfront validation check that returns an error for invalid values:
🔧 Suggested fix
if privacy, ok := actions.GetStringArg(args, "privacy"); ok && privacy != "" {
+ if privacy != teamPrivacySecret && privacy != teamPrivacyClosed {
+ return nil, nil, fmt.Errorf("invalid privacy: %q (must be \"secret\" or \"closed\")", privacy)
+ }
if isNestedTeam {
// Nested teams can only be "closed"
if privacy == teamPrivacySecret {
l.Warn("github-connector: secret privacy not allowed for nested teams, using closed",
zap.String("requested_privacy", privacy),
)
}
newTeam.Privacy = github.Ptr(teamPrivacyClosed)
- } else if privacy == teamPrivacySecret || privacy == teamPrivacyClosed {
+ } else {
// Non-nested teams can be "secret" or "closed"
newTeam.Privacy = github.Ptr(privacy)
}🤖 Prompt for AI Agents
In `@pkg/connector/team.go` around lines 536 - 548, The privacy branch currently
only sets newTeam.Privacy for allowed values and silently ignores invalid values
for non-nested teams; add an explicit validation for the "privacy" arg when
isNestedTeam is false: if privacy is provided and is not teamPrivacySecret or
teamPrivacyClosed, return an input/argument error (consistent with how invalid
parent/maintainer IDs are handled) instead of letting the GitHub API default it.
Locate the block using actions.GetStringArg(args, "privacy"), isNestedTeam,
teamPrivacySecret, teamPrivacyClosed and newTeam.Privacy and insert the
validation+early return for invalid non-nested privacy values.
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.