Repository: google/go-github (go)

Issue:
Add support to create / update / delete custom roles for organisations
API can be found here - https://docs.github.com/en/rest/orgs/custom-roles#create-a-custom-role

I plan on working on this feature 

Pull request description:
Create custom role APIs
resolves #2493 

Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index e410c695..42b1abc8 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/orgs_custom_roles.go b/github/orgs_custom_roles.go
index 9904685b..41270b32 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
+}


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 506db423..c65c98d2 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_test.go b/github/orgs_custom_roles_test.go
index 7042a5ec..720190c2 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
+	})
+}