diff --git a/github/event.go b/github/event.go index 4ee25603a8..4ee60e339c 100644 --- a/github/event.go +++ b/github/event.go @@ -93,6 +93,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) { payload = &PackageEvent{} case "PageBuildEvent": payload = &PageBuildEvent{} + case "PersonalAccessTokenRequestEvent": + payload = &PersonalAccessTokenRequestEvent{} case "PingEvent": payload = &PingEvent{} case "ProjectEvent": diff --git a/github/event_types.go b/github/event_types.go index 6a13b286bd..fca897af93 100644 --- a/github/event_types.go +++ b/github/event_types.go @@ -774,6 +774,74 @@ type PageBuildEvent struct { Installation *Installation `json:"installation,omitempty"` } +// PersonalAccessTokenRequestEvent occurs when there is activity relating to a +// request for a fine-grained personal access token to access resources that +// belong to a resource owner that requires approval for token access. +// The webhook event name is "personal_access_token_request". +// +// GitHub API docs: https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#personal_access_token_request +type PersonalAccessTokenRequestEvent struct { + // Action is the action that was performed. Possible values are: + // "approved", "cancelled", "created" or "denied" + Action *string `json:"action,omitempty"` + PersonalAccessTokenRequest *PersonalAccessTokenRequest `json:"personal_access_token_request,omitempty"` + Org *Organization `json:"organization,omitempty"` + Sender *User `json:"sender,omitempty"` + Installation *Installation `json:"installation,omitempty"` +} + +// PersonalAccessTokenRequest contains the details of a PersonalAccessTokenRequestEvent. +type PersonalAccessTokenRequest struct { + // Unique identifier of the request for access via fine-grained personal + // access token. Used as the pat_request_id parameter in the list and review + // API calls. + ID *int64 `json:"id,omitempty"` + Owner *User `json:"owner,omitempty"` + + // New requested permissions, categorized by type of permission. + PermissionsAdded *PersonalAccessTokenPermissions `json:"permissions_added,omitempty"` + + // Requested permissions that elevate access for a previously approved + // request for access, categorized by type of permission. + PermissionsUpgraded *PersonalAccessTokenPermissions `json:"permissions_upgraded,omitempty"` + + // Permissions requested, categorized by type of permission. + // This field incorporates permissions_added and permissions_upgraded. + PermissionsResult *PersonalAccessTokenPermissions `json:"permissions_result,omitempty"` + + // Type of repository selection requested. Possible values are: + // "none", "all" or "subset" + RepositorySelection *string `json:"repository_selection,omitempty"` + + // The number of repositories the token is requesting access to. + // This field is only populated when repository_selection is subset. + RepositoryCount *int64 `json:"repository_count,omitempty"` + + // An array of repository objects the token is requesting access to. + // This field is only populated when repository_selection is subset. + Repositories []*Repository `json:"repositories,omitempty"` + + // Date and time when the request for access was created. + CreatedAt *Timestamp `json:"created_at,omitempty"` + + // Whether the associated fine-grained personal access token has expired. + TokenExpired *bool `json:"token_expired,omitempty"` + + // Date and time when the associated fine-grained personal access token expires. + TokenExpiresAt *Timestamp `json:"token_expires_at,omitempty"` + + // Date and time when the associated fine-grained personal access token was last used for authentication. + TokenLastUsedAt *Timestamp `json:"token_last_used_at,omitempty"` +} + +// PersonalAccessTokenPermissions represents the original or newly requested +// scope of permissions for a fine-grained personal access token within a PersonalAccessTokenRequest. +type PersonalAccessTokenPermissions struct { + Org map[string]string `json:"organization,omitempty"` + Repo map[string]string `json:"repository,omitempty"` + Other map[string]string `json:"other,omitempty"` +} + // PingEvent is triggered when a Webhook is added to GitHub. // // GitHub API docs: https://developer.github.com/webhooks/#ping-event diff --git a/github/event_types_test.go b/github/event_types_test.go index db284036e3..298477fe14 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -7089,6 +7089,80 @@ func TestPackageEvent_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } +func TestPersonalAccessTokenRequestEvent_Marshal(t *testing.T) { + testJSONMarshal(t, &PersonalAccessTokenRequestEvent{}, "{}") + + event := &PersonalAccessTokenRequestEvent{ + Action: String("a"), + PersonalAccessTokenRequest: &PersonalAccessTokenRequest{ + ID: Int64(1), + Owner: &User{Login: String("l")}, + PermissionsAdded: &PersonalAccessTokenPermissions{ + Org: map[string]string{"organization_events": "read"}, + Repo: map[string]string{"security_events": "write"}, + }, + CreatedAt: &Timestamp{referenceTime}, + TokenExpired: Bool(false), + TokenExpiresAt: &Timestamp{referenceTime}, + TokenLastUsedAt: &Timestamp{referenceTime}, + RepositoryCount: Int64(1), + RepositorySelection: String("rs"), + Repositories: []*Repository{ + { + Name: String("n"), + }, + }, + }, + Org: &Organization{Name: String("n")}, + Sender: &User{ + Login: String("l"), + }, + Installation: &Installation{ + ID: Int64(1), + }, + } + + want := `{ + "action": "a", + "personal_access_token_request": { + "id": 1, + "owner": { + "login": "l" + }, + "permissions_added": { + "organization": { + "organization_events": "read" + }, + "repository": { + "security_events": "write" + } + }, + "created_at": ` + referenceTimeStr + `, + "token_expired": false, + "token_expires_at": ` + referenceTimeStr + `, + "token_last_used_at": ` + referenceTimeStr + `, + "repository_count": 1, + "repository_selection": "rs", + "repositories": [ + { + "name": "n" + } + ] + }, + "organization": { + "name": "n" + }, + "sender": { + "login": "l" + }, + "installation": { + "id": 1 + } + }` + + testJSONMarshal(t, event, want) +} + func TestPingEvent_Marshal(t *testing.T) { testJSONMarshal(t, &PingEvent{}, "{}") diff --git a/github/github-accessors.go b/github/github-accessors.go index c22073f922..1be9493a37 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -13142,6 +13142,158 @@ func (p *PagesUpdate) GetSource() *PagesSource { return p.Source } +// GetOrg returns the Org map if it's non-nil, an empty map otherwise. +func (p *PersonalAccessTokenPermissions) GetOrg() map[string]string { + if p == nil || p.Org == nil { + return map[string]string{} + } + return p.Org +} + +// GetOther returns the Other map if it's non-nil, an empty map otherwise. +func (p *PersonalAccessTokenPermissions) GetOther() map[string]string { + if p == nil || p.Other == nil { + return map[string]string{} + } + return p.Other +} + +// GetRepo returns the Repo map if it's non-nil, an empty map otherwise. +func (p *PersonalAccessTokenPermissions) GetRepo() map[string]string { + if p == nil || p.Repo == nil { + return map[string]string{} + } + return p.Repo +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetCreatedAt() Timestamp { + if p == nil || p.CreatedAt == nil { + return Timestamp{} + } + return *p.CreatedAt +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetID() int64 { + if p == nil || p.ID == nil { + return 0 + } + return *p.ID +} + +// GetOwner returns the Owner field. +func (p *PersonalAccessTokenRequest) GetOwner() *User { + if p == nil { + return nil + } + return p.Owner +} + +// GetPermissionsAdded returns the PermissionsAdded field. +func (p *PersonalAccessTokenRequest) GetPermissionsAdded() *PersonalAccessTokenPermissions { + if p == nil { + return nil + } + return p.PermissionsAdded +} + +// GetPermissionsResult returns the PermissionsResult field. +func (p *PersonalAccessTokenRequest) GetPermissionsResult() *PersonalAccessTokenPermissions { + if p == nil { + return nil + } + return p.PermissionsResult +} + +// GetPermissionsUpgraded returns the PermissionsUpgraded field. +func (p *PersonalAccessTokenRequest) GetPermissionsUpgraded() *PersonalAccessTokenPermissions { + if p == nil { + return nil + } + return p.PermissionsUpgraded +} + +// GetRepositoryCount returns the RepositoryCount field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetRepositoryCount() int64 { + if p == nil || p.RepositoryCount == nil { + return 0 + } + return *p.RepositoryCount +} + +// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetRepositorySelection() string { + if p == nil || p.RepositorySelection == nil { + return "" + } + return *p.RepositorySelection +} + +// GetTokenExpired returns the TokenExpired field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetTokenExpired() bool { + if p == nil || p.TokenExpired == nil { + return false + } + return *p.TokenExpired +} + +// GetTokenExpiresAt returns the TokenExpiresAt field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetTokenExpiresAt() Timestamp { + if p == nil || p.TokenExpiresAt == nil { + return Timestamp{} + } + return *p.TokenExpiresAt +} + +// GetTokenLastUsedAt returns the TokenLastUsedAt field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequest) GetTokenLastUsedAt() Timestamp { + if p == nil || p.TokenLastUsedAt == nil { + return Timestamp{} + } + return *p.TokenLastUsedAt +} + +// GetAction returns the Action field if it's non-nil, zero value otherwise. +func (p *PersonalAccessTokenRequestEvent) GetAction() string { + if p == nil || p.Action == nil { + return "" + } + return *p.Action +} + +// GetInstallation returns the Installation field. +func (p *PersonalAccessTokenRequestEvent) GetInstallation() *Installation { + if p == nil { + return nil + } + return p.Installation +} + +// GetOrg returns the Org field. +func (p *PersonalAccessTokenRequestEvent) GetOrg() *Organization { + if p == nil { + return nil + } + return p.Org +} + +// GetPersonalAccessTokenRequest returns the PersonalAccessTokenRequest field. +func (p *PersonalAccessTokenRequestEvent) GetPersonalAccessTokenRequest() *PersonalAccessTokenRequest { + if p == nil { + return nil + } + return p.PersonalAccessTokenRequest +} + +// GetSender returns the Sender field. +func (p *PersonalAccessTokenRequestEvent) GetSender() *User { + if p == nil { + return nil + } + return p.Sender +} + // GetHook returns the Hook field. func (p *PingEvent) GetHook() *Hook { if p == nil { @@ -18958,8 +19110,16 @@ func (r *RulesetConditions) GetRefName() *RulesetRefConditionParameters { return r.RefName } +// GetRepositoryID returns the RepositoryID field. +func (r *RulesetConditions) GetRepositoryID() *RulesetRepositoryIDsConditionParameters { + if r == nil { + return nil + } + return r.RepositoryID +} + // GetRepositoryName returns the RepositoryName field. -func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryConditionParameters { +func (r *RulesetConditions) GetRepositoryName() *RulesetRepositoryNamesConditionParameters { if r == nil { return nil } @@ -18983,7 +19143,7 @@ func (r *RulesetLinks) GetSelf() *RulesetLink { } // GetProtected returns the Protected field if it's non-nil, zero value otherwise. -func (r *RulesetRepositoryConditionParameters) GetProtected() bool { +func (r *RulesetRepositoryNamesConditionParameters) GetProtected() bool { if r == nil || r.Protected == nil { return false } diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 323708176d..2235cc5e13 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -15432,6 +15432,172 @@ func TestPagesUpdate_GetSource(tt *testing.T) { p.GetSource() } +func TestPersonalAccessTokenPermissions_GetOrg(tt *testing.T) { + zeroValue := map[string]string{} + p := &PersonalAccessTokenPermissions{Org: zeroValue} + p.GetOrg() + p = &PersonalAccessTokenPermissions{} + p.GetOrg() + p = nil + p.GetOrg() +} + +func TestPersonalAccessTokenPermissions_GetOther(tt *testing.T) { + zeroValue := map[string]string{} + p := &PersonalAccessTokenPermissions{Other: zeroValue} + p.GetOther() + p = &PersonalAccessTokenPermissions{} + p.GetOther() + p = nil + p.GetOther() +} + +func TestPersonalAccessTokenPermissions_GetRepo(tt *testing.T) { + zeroValue := map[string]string{} + p := &PersonalAccessTokenPermissions{Repo: zeroValue} + p.GetRepo() + p = &PersonalAccessTokenPermissions{} + p.GetRepo() + p = nil + p.GetRepo() +} + +func TestPersonalAccessTokenRequest_GetCreatedAt(tt *testing.T) { + var zeroValue Timestamp + p := &PersonalAccessTokenRequest{CreatedAt: &zeroValue} + p.GetCreatedAt() + p = &PersonalAccessTokenRequest{} + p.GetCreatedAt() + p = nil + p.GetCreatedAt() +} + +func TestPersonalAccessTokenRequest_GetID(tt *testing.T) { + var zeroValue int64 + p := &PersonalAccessTokenRequest{ID: &zeroValue} + p.GetID() + p = &PersonalAccessTokenRequest{} + p.GetID() + p = nil + p.GetID() +} + +func TestPersonalAccessTokenRequest_GetOwner(tt *testing.T) { + p := &PersonalAccessTokenRequest{} + p.GetOwner() + p = nil + p.GetOwner() +} + +func TestPersonalAccessTokenRequest_GetPermissionsAdded(tt *testing.T) { + p := &PersonalAccessTokenRequest{} + p.GetPermissionsAdded() + p = nil + p.GetPermissionsAdded() +} + +func TestPersonalAccessTokenRequest_GetPermissionsResult(tt *testing.T) { + p := &PersonalAccessTokenRequest{} + p.GetPermissionsResult() + p = nil + p.GetPermissionsResult() +} + +func TestPersonalAccessTokenRequest_GetPermissionsUpgraded(tt *testing.T) { + p := &PersonalAccessTokenRequest{} + p.GetPermissionsUpgraded() + p = nil + p.GetPermissionsUpgraded() +} + +func TestPersonalAccessTokenRequest_GetRepositoryCount(tt *testing.T) { + var zeroValue int64 + p := &PersonalAccessTokenRequest{RepositoryCount: &zeroValue} + p.GetRepositoryCount() + p = &PersonalAccessTokenRequest{} + p.GetRepositoryCount() + p = nil + p.GetRepositoryCount() +} + +func TestPersonalAccessTokenRequest_GetRepositorySelection(tt *testing.T) { + var zeroValue string + p := &PersonalAccessTokenRequest{RepositorySelection: &zeroValue} + p.GetRepositorySelection() + p = &PersonalAccessTokenRequest{} + p.GetRepositorySelection() + p = nil + p.GetRepositorySelection() +} + +func TestPersonalAccessTokenRequest_GetTokenExpired(tt *testing.T) { + var zeroValue bool + p := &PersonalAccessTokenRequest{TokenExpired: &zeroValue} + p.GetTokenExpired() + p = &PersonalAccessTokenRequest{} + p.GetTokenExpired() + p = nil + p.GetTokenExpired() +} + +func TestPersonalAccessTokenRequest_GetTokenExpiresAt(tt *testing.T) { + var zeroValue Timestamp + p := &PersonalAccessTokenRequest{TokenExpiresAt: &zeroValue} + p.GetTokenExpiresAt() + p = &PersonalAccessTokenRequest{} + p.GetTokenExpiresAt() + p = nil + p.GetTokenExpiresAt() +} + +func TestPersonalAccessTokenRequest_GetTokenLastUsedAt(tt *testing.T) { + var zeroValue Timestamp + p := &PersonalAccessTokenRequest{TokenLastUsedAt: &zeroValue} + p.GetTokenLastUsedAt() + p = &PersonalAccessTokenRequest{} + p.GetTokenLastUsedAt() + p = nil + p.GetTokenLastUsedAt() +} + +func TestPersonalAccessTokenRequestEvent_GetAction(tt *testing.T) { + var zeroValue string + p := &PersonalAccessTokenRequestEvent{Action: &zeroValue} + p.GetAction() + p = &PersonalAccessTokenRequestEvent{} + p.GetAction() + p = nil + p.GetAction() +} + +func TestPersonalAccessTokenRequestEvent_GetInstallation(tt *testing.T) { + p := &PersonalAccessTokenRequestEvent{} + p.GetInstallation() + p = nil + p.GetInstallation() +} + +func TestPersonalAccessTokenRequestEvent_GetOrg(tt *testing.T) { + p := &PersonalAccessTokenRequestEvent{} + p.GetOrg() + p = nil + p.GetOrg() +} + +func TestPersonalAccessTokenRequestEvent_GetPersonalAccessTokenRequest(tt *testing.T) { + p := &PersonalAccessTokenRequestEvent{} + p.GetPersonalAccessTokenRequest() + p = nil + p.GetPersonalAccessTokenRequest() +} + +func TestPersonalAccessTokenRequestEvent_GetSender(tt *testing.T) { + p := &PersonalAccessTokenRequestEvent{} + p.GetSender() + p = nil + p.GetSender() +} + func TestPingEvent_GetHook(tt *testing.T) { p := &PingEvent{} p.GetHook() @@ -22114,6 +22280,13 @@ func TestRulesetConditions_GetRefName(tt *testing.T) { r.GetRefName() } +func TestRulesetConditions_GetRepositoryID(tt *testing.T) { + r := &RulesetConditions{} + r.GetRepositoryID() + r = nil + r.GetRepositoryID() +} + func TestRulesetConditions_GetRepositoryName(tt *testing.T) { r := &RulesetConditions{} r.GetRepositoryName() @@ -22138,11 +22311,11 @@ func TestRulesetLinks_GetSelf(tt *testing.T) { r.GetSelf() } -func TestRulesetRepositoryConditionParameters_GetProtected(tt *testing.T) { +func TestRulesetRepositoryNamesConditionParameters_GetProtected(tt *testing.T) { var zeroValue bool - r := &RulesetRepositoryConditionParameters{Protected: &zeroValue} + r := &RulesetRepositoryNamesConditionParameters{Protected: &zeroValue} r.GetProtected() - r = &RulesetRepositoryConditionParameters{} + r = &RulesetRepositoryNamesConditionParameters{} r.GetProtected() r = nil r.GetProtected() diff --git a/github/messages.go b/github/messages.go index bb5ae3f389..e61ef1c07b 100644 --- a/github/messages.go +++ b/github/messages.go @@ -76,6 +76,7 @@ var ( "org_block": "OrgBlockEvent", "package": "PackageEvent", "page_build": "PageBuildEvent", + "personal_access_token_request": "PersonalAccessTokenRequestEvent", "ping": "PingEvent", "project": "ProjectEvent", "project_card": "ProjectCardEvent", diff --git a/github/messages_test.go b/github/messages_test.go index 80fb3e5ff5..5230b87a61 100644 --- a/github/messages_test.go +++ b/github/messages_test.go @@ -380,6 +380,10 @@ func TestParseWebHook(t *testing.T) { payload: &PageBuildEvent{}, messageType: "page_build", }, + { + payload: &PersonalAccessTokenRequestEvent{}, + messageType: "personal_access_token_request", + }, { payload: &PingEvent{}, messageType: "ping", diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go index 03226741e5..dfa7e2d26e 100644 --- a/github/orgs_rules_test.go +++ b/github/orgs_rules_test.go @@ -70,7 +70,7 @@ func TestOrganizationsService_GetAllOrganizationRulesets(t *testing.T) { }) } -func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) { +func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -226,7 +226,7 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) { Include: []string{"refs/heads/main", "refs/heads/master"}, Exclude: []string{"refs/heads/dev*"}, }, - RepositoryName: &RulesetRepositoryConditionParameters{ + RepositoryName: &RulesetRepositoryNamesConditionParameters{ Include: []string{"important_repository", "another_important_repository"}, Exclude: []string{"unimportant_repository"}, Protected: Bool(true), @@ -312,7 +312,7 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) { Include: []string{"refs/heads/main", "refs/heads/master"}, Exclude: []string{"refs/heads/dev*"}, }, - RepositoryName: &RulesetRepositoryConditionParameters{ + RepositoryName: &RulesetRepositoryNamesConditionParameters{ Include: []string{"important_repository", "another_important_repository"}, Exclude: []string{"unimportant_repository"}, Protected: Bool(true), @@ -391,6 +391,316 @@ func TestOrganizationsService_CreateOrganizationRuleset(t *testing.T) { }) } +func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{ + "id": 21, + "name": "ruleset", + "target": "branch", + "source_type": "Organization", + "source": "o", + "enforcement": "active", + "bypass_actors": [ + { + "actor_id": 234, + "actor_type": "Team" + } + ], + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + }, + "repository_id": { + "repository_ids": [ 123, 456 ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": true + } + }, + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "required_deployments", + "parameters": { + "required_deployment_environments": ["test"] + } + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + { + "context": "test", + "integration_id": 1 + } + ], + "strict_required_status_checks_policy": true + } + }, + { + "type": "non_fast_forward" + }, + { + "type": "commit_message_pattern", + "parameters": { + "name": "avoid test commits", + "negate": true, + "operator": "starts_with", + "pattern": "[test]" + } + }, + { + "type": "commit_author_email_pattern", + "parameters": { + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "committer_email_pattern", + "parameters": { + "name": "avoid commit emails", + "negate": true, + "operator": "ends_with", + "pattern": "abc" + } + }, + { + "type": "branch_name_pattern", + "parameters": { + "name": "avoid branch names", + "negate": true, + "operator": "regex", + "pattern": "github$" + } + }, + { + "type": "tag_name_pattern", + "parameters": { + "name": "avoid tag names", + "negate": true, + "operator": "contains", + "pattern": "github" + } + } + ] + }`) + }) + + ctx := context.Background() + ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", &Ruleset{ + ID: 21, + Name: "ruleset", + Target: String("branch"), + SourceType: String("Organization"), + Source: "o", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Int64(234), + ActorType: String("Team"), + }, + }, + Conditions: &RulesetConditions{ + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + RepositoryID: &RulesetRepositoryIDsConditionParameters{ + RepositoryIDs: []int64{123, 456}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + DismissStaleReviewsOnPush: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Int64(1), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: String("avoid test commits"), + Negate: Bool(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: String("avoid commit emails"), + Negate: Bool(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: String("avoid branch names"), + Negate: Bool(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: String("avoid tag names"), + Negate: Bool(true), + Operator: "contains", + Pattern: "github", + }), + }, + }) + if err != nil { + t.Errorf("Organizations.CreateOrganizationRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: 21, + Name: "ruleset", + Target: String("branch"), + SourceType: String("Organization"), + Source: "o", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Int64(234), + ActorType: String("Team"), + }, + }, + Conditions: &RulesetConditions{ + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + RepositoryID: &RulesetRepositoryIDsConditionParameters{ + RepositoryIDs: []int64{123, 456}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + DismissStaleReviewsOnPush: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Int64(1), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: String("avoid test commits"), + Negate: Bool(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: String("avoid commit emails"), + Negate: Bool(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: String("avoid branch names"), + Negate: Bool(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: String("avoid tag names"), + Negate: Bool(true), + Operator: "contains", + Pattern: "github", + }), + }, + } + if !cmp.Equal(ruleset, want) { + t.Errorf("Organizations.CreateOrganizationRuleset returned %+v, want %+v", ruleset, want) + } + + const methodName = "CreateOrganizationRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + func TestOrganizationsService_GetOrganizationRuleset(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -462,7 +772,7 @@ func TestOrganizationsService_GetOrganizationRuleset(t *testing.T) { Include: []string{"refs/heads/main", "refs/heads/master"}, Exclude: []string{"refs/heads/dev*"}, }, - RepositoryName: &RulesetRepositoryConditionParameters{ + RepositoryName: &RulesetRepositoryNamesConditionParameters{ Include: []string{"important_repository", "another_important_repository"}, Exclude: []string{"unimportant_repository"}, Protected: Bool(true), @@ -546,7 +856,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) { Include: []string{"refs/heads/main", "refs/heads/master"}, Exclude: []string{"refs/heads/dev*"}, }, - RepositoryName: &RulesetRepositoryConditionParameters{ + RepositoryName: &RulesetRepositoryNamesConditionParameters{ Include: []string{"important_repository", "another_important_repository"}, Exclude: []string{"unimportant_repository"}, Protected: Bool(true), @@ -577,7 +887,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) { Include: []string{"refs/heads/main", "refs/heads/master"}, Exclude: []string{"refs/heads/dev*"}, }, - RepositoryName: &RulesetRepositoryConditionParameters{ + RepositoryName: &RulesetRepositoryNamesConditionParameters{ Include: []string{"important_repository", "another_important_repository"}, Exclude: []string{"unimportant_repository"}, Protected: Bool(true), diff --git a/github/repos_rules.go b/github/repos_rules.go index b5b6e15ac1..4e6f5f1347 100644 --- a/github/repos_rules.go +++ b/github/repos_rules.go @@ -36,17 +36,24 @@ type RulesetRefConditionParameters struct { Exclude []string `json:"exclude"` } -// RulesetRepositoryConditionParameters represents the conditions object for repository_names. -type RulesetRepositoryConditionParameters struct { +// RulesetRepositoryNamesConditionParameters represents the conditions object for repository_names. +type RulesetRepositoryNamesConditionParameters struct { Include []string `json:"include,omitempty"` Exclude []string `json:"exclude,omitempty"` Protected *bool `json:"protected,omitempty"` } +// RulesetRepositoryIDsConditionParameters represents the conditions object for repository_ids. +type RulesetRepositoryIDsConditionParameters struct { + RepositoryIDs []int64 `json:"repository_ids,omitempty"` +} + // RulesetCondition represents the conditions object in a ruleset. +// Set either RepositoryName or RepositoryID, not both. type RulesetConditions struct { - RefName *RulesetRefConditionParameters `json:"ref_name,omitempty"` - RepositoryName *RulesetRepositoryConditionParameters `json:"repository_name,omitempty"` + RefName *RulesetRefConditionParameters `json:"ref_name,omitempty"` + RepositoryName *RulesetRepositoryNamesConditionParameters `json:"repository_name,omitempty"` + RepositoryID *RulesetRepositoryIDsConditionParameters `json:"repository_id,omitempty"` } // RulePatternParameters represents the rule pattern parameters.