diff --git a/github/github-accessors.go b/github/github-accessors.go index e410c695d1..42b1abc802 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -3566,6 +3566,30 @@ func (c *CreateOrgInvitationOptions) GetRole() string { return *c.Role } +// GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateCustomRoleOptions) GetBaseRole() string { + if c == nil || c.BaseRole == nil { + return "" + } + return *c.BaseRole +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateCustomRoleOptions) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (c *CreateOrUpdateCustomRoleOptions) GetName() string { + if c == nil || c.Name == nil { + return "" + } + return *c.Name +} + // GetFrom returns the From field if it's non-nil, zero value otherwise. func (c *CreateProtectedChanges) GetFrom() bool { if c == nil || c.From == nil { @@ -3622,6 +3646,22 @@ func (c *CreateUserProjectOptions) GetBody() string { return *c.Body } +// GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise. +func (c *CustomRepoRoles) GetBaseRole() string { + if c == nil || c.BaseRole == nil { + return "" + } + return *c.BaseRole +} + +// GetDescription returns the Description field if it's non-nil, zero value otherwise. +func (c *CustomRepoRoles) GetDescription() string { + if c == nil || c.Description == nil { + return "" + } + return *c.Description +} + // GetID returns the ID field if it's non-nil, zero value otherwise. func (c *CustomRepoRoles) GetID() int64 { if c == nil || c.ID == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 506db4238b..c65c98d2d2 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -4173,6 +4173,36 @@ func TestCreateOrgInvitationOptions_GetRole(tt *testing.T) { c.GetRole() } +func TestCreateOrUpdateCustomRoleOptions_GetBaseRole(tt *testing.T) { + var zeroValue string + c := &CreateOrUpdateCustomRoleOptions{BaseRole: &zeroValue} + c.GetBaseRole() + c = &CreateOrUpdateCustomRoleOptions{} + c.GetBaseRole() + c = nil + c.GetBaseRole() +} + +func TestCreateOrUpdateCustomRoleOptions_GetDescription(tt *testing.T) { + var zeroValue string + c := &CreateOrUpdateCustomRoleOptions{Description: &zeroValue} + c.GetDescription() + c = &CreateOrUpdateCustomRoleOptions{} + c.GetDescription() + c = nil + c.GetDescription() +} + +func TestCreateOrUpdateCustomRoleOptions_GetName(tt *testing.T) { + var zeroValue string + c := &CreateOrUpdateCustomRoleOptions{Name: &zeroValue} + c.GetName() + c = &CreateOrUpdateCustomRoleOptions{} + c.GetName() + c = nil + c.GetName() +} + func TestCreateProtectedChanges_GetFrom(tt *testing.T) { var zeroValue bool c := &CreateProtectedChanges{From: &zeroValue} @@ -4240,6 +4270,26 @@ func TestCreateUserProjectOptions_GetBody(tt *testing.T) { c.GetBody() } +func TestCustomRepoRoles_GetBaseRole(tt *testing.T) { + var zeroValue string + c := &CustomRepoRoles{BaseRole: &zeroValue} + c.GetBaseRole() + c = &CustomRepoRoles{} + c.GetBaseRole() + c = nil + c.GetBaseRole() +} + +func TestCustomRepoRoles_GetDescription(tt *testing.T) { + var zeroValue string + c := &CustomRepoRoles{Description: &zeroValue} + c.GetDescription() + c = &CustomRepoRoles{} + c.GetDescription() + c = nil + c.GetDescription() +} + func TestCustomRepoRoles_GetID(tt *testing.T) { var zeroValue int64 c := &CustomRepoRoles{ID: &zeroValue} diff --git a/github/orgs_custom_roles.go b/github/orgs_custom_roles.go index 9904685b94..41270b3221 100644 --- a/github/orgs_custom_roles.go +++ b/github/orgs_custom_roles.go @@ -20,8 +20,11 @@ type OrganizationCustomRepoRoles struct { // See https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization // for more information. type CustomRepoRoles struct { - ID *int64 `json:"id,omitempty"` - Name *string `json:"name,omitempty"` + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + BaseRole *string `json:"base_role,omitempty"` + Permissions []string `json:"permissions,omitempty"` } // ListCustomRepoRoles lists the custom repository roles available in this organization. @@ -44,3 +47,74 @@ func (s *OrganizationsService) ListCustomRepoRoles(ctx context.Context, org stri return customRepoRoles, resp, nil } + +// CreateOrUpdateCustomRoleOptions represents options required to create or update a custom repository role. +type CreateOrUpdateCustomRoleOptions struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + BaseRole *string `json:"base_role,omitempty"` + Permissions []string `json:"permissions,omitempty"` +} + +// CreateCustomRepoRole creates a custom repository role in this organization. +// In order to create custom repository roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/custom-roles#create-a-custom-role +func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, opts *CreateOrUpdateCustomRoleOptions) (*CustomRepoRoles, *Response, error) { + u := fmt.Sprintf("orgs/%v/custom_roles", org) + + req, err := s.client.NewRequest("POST", u, opts) + if err != nil { + return nil, nil, err + } + + resultingRole := new(CustomRepoRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return nil, resp, err + } + + return resultingRole, resp, err +} + +// UpdateCustomRepoRole updates a custom repository role in this organization. +// In order to update custom repository roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/custom-roles#update-a-custom-role +func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org, roleID string, opts *CreateOrUpdateCustomRoleOptions) (*CustomRepoRoles, *Response, error) { + u := fmt.Sprintf("orgs/%v/custom_roles/%v", org, roleID) + + req, err := s.client.NewRequest("PATCH", u, opts) + if err != nil { + return nil, nil, err + } + + resultingRole := new(CustomRepoRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return nil, resp, err + } + + return resultingRole, resp, err +} + +// DeleteCustomRepoRole deletes an existing custom repository role in this organization. +// In order to delete custom repository roles in an organization, the authenticated user must be an organization owner. +// +// GitHub API docs: https://docs.github.com/en/rest/orgs/custom-roles#delete-a-custom-role +func (s *OrganizationsService) DeleteCustomRepoRole(ctx context.Context, org, roleID string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/custom_roles/%v", org, roleID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + resultingRole := new(CustomRepoRoles) + resp, err := s.client.Do(ctx, req, resultingRole) + if err != nil { + return resp, err + } + + return resp, nil +} diff --git a/github/orgs_custom_roles_test.go b/github/orgs_custom_roles_test.go index 7042a5ec82..720190c288 100644 --- a/github/orgs_custom_roles_test.go +++ b/github/orgs_custom_roles_test.go @@ -20,7 +20,7 @@ func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) { mux.HandleFunc("/orgs/o/custom_roles", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - fmt.Fprint(w, `{"total_count": 1, "custom_roles": [{ "id": 1, "name": "Developer"}]}`) + fmt.Fprint(w, `{"total_count": 1, "custom_roles": [{ "id": 1, "name": "Developer", "base_role": "write", "permissions": ["delete_alerts_code_scanning"]}]}`) }) ctx := context.Background() @@ -29,7 +29,7 @@ func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) { t.Errorf("Organizations.ListCustomRepoRoles returned error: %v", err) } - want := &OrganizationCustomRepoRoles{TotalCount: Int(1), CustomRepoRoles: []*CustomRepoRoles{{ID: Int64(1), Name: String("Developer")}}} + want := &OrganizationCustomRepoRoles{TotalCount: Int(1), CustomRepoRoles: []*CustomRepoRoles{{ID: Int64(1), Name: String("Developer"), BaseRole: String("write"), Permissions: []string{"delete_alerts_code_scanning"}}}} if !cmp.Equal(apps, want) { t.Errorf("Organizations.ListCustomRepoRoles returned %+v, want %+v", apps, want) } @@ -48,3 +48,114 @@ func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) { return resp, err }) } + +func TestOrganizationsService_CreateCustomRepoRole(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/custom_roles", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id":8030,"name":"Labeler","description":"A role for issue and PR labelers","base_role":"read","permissions":["add_label"]}`) + }) + + ctx := context.Background() + + opts := &CreateOrUpdateCustomRoleOptions{ + Name: String("Labeler"), + Description: String("A role for issue and PR labelers"), + BaseRole: String("read"), + Permissions: []string{"add_label"}, + } + apps, _, err := client.Organizations.CreateCustomRepoRole(ctx, "o", opts) + if err != nil { + t.Errorf("Organizations.CreateCustomRepoRole returned error: %v", err) + } + + want := &CustomRepoRoles{ID: Int64(8030), Name: String("Labeler"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("A role for issue and PR labelers")} + + if !cmp.Equal(apps, want) { + t.Errorf("Organizations.CreateCustomRepoRole returned %+v, want %+v", apps, want) + } + + const methodName = "CreateCustomRepoRole" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Organizations.CreateCustomRepoRole(ctx, "\no", nil) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.CreateCustomRepoRole(ctx, "o", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestOrganizationsService_UpdateCustomRepoRole(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/custom_roles/8030", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + fmt.Fprint(w, `{"id":8030,"name":"Updated Name","description":"Updated Description","base_role":"read","permissions":["add_label"]}`) + }) + + ctx := context.Background() + + opts := &CreateOrUpdateCustomRoleOptions{ + Name: String("Updated Name"), + Description: String("Updated Description"), + } + apps, _, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", "8030", opts) + if err != nil { + t.Errorf("Organizations.UpdateCustomRepoRole returned error: %v", err) + } + + want := &CustomRepoRoles{ID: Int64(8030), Name: String("Updated Name"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("Updated Description")} + + if !cmp.Equal(apps, want) { + t.Errorf("Organizations.UpdateCustomRepoRole returned %+v, want %+v", apps, want) + } + + const methodName = "UpdateCustomRepoRole" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Organizations.UpdateCustomRepoRole(ctx, "\no", "8030", nil) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", "8030", nil) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestOrganizationsService_DeleteCustomRepoRole(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/orgs/o/custom_roles/8030", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + + resp, err := client.Organizations.DeleteCustomRepoRole(ctx, "o", "8030") + if err != nil { + t.Errorf("Organizations.DeleteCustomRepoRole returned error: %v", err) + } + + if !cmp.Equal(resp.StatusCode, 204) { + t.Errorf("Organizations.DeleteCustomRepoRole returned status code %+v, want %+v", resp.StatusCode, "204") + } + + const methodName = "DeleteCustomRepoRole" + testBadOptions(t, methodName, func() (err error) { + _, err = client.Organizations.DeleteCustomRepoRole(ctx, "\no", "8030") + return err + }) +}