Repository: xanzy/go-gitlab
Pull request #1870 (2024-01-22 04:24:22)

Add support for ci_restrict_pipeline_cancellation_role attribute to Projects
With GitLab 16.8, a new "ci_restrict_pipeline_cancellation_role" attribute was added to the List and Edit projects APIs (though, oddly, not Create according to the API documentation: https://docs.gitlab.com/ee/api/projects.html#create-project).

This PR adds support to EditProject and ListProject for that attribute, and adds several tests to verify that the parsing works properly.


Patch:
diff --git a/projects.go b/projects.go
index 2d46685..3ec346d 100644
--- a/projects.go
+++ b/projects.go
@@ -139,6 +139,7 @@ type Project struct {
 	CIJobTokenScopeEnabled                   bool               `json:"ci_job_token_scope_enabled"`
 	CIOptInJWT                               bool               `json:"ci_opt_in_jwt"`
 	CIAllowForkPipelinesToRunInParentProject bool               `json:"ci_allow_fork_pipelines_to_run_in_parent_project"`
+	CIRestrictPipelineCancellationRole       AccessControlValue `json:"ci_restrict_pipeline_cancellation_role"`
 	PublicJobs                               bool               `json:"public_jobs"`
 	BuildTimeout                             int                `json:"build_timeout"`
 	AutoCancelPendingPipelines               string             `json:"auto_cancel_pending_pipelines"`
@@ -842,6 +843,7 @@ type EditProjectOptions struct {
 	CIDefaultGitDepth                         *int                                 `url:"ci_default_git_depth,omitempty" json:"ci_default_git_depth,omitempty"`
 	CIForwardDeploymentEnabled                *bool                                `url:"ci_forward_deployment_enabled,omitempty" json:"ci_forward_deployment_enabled,omitempty"`
 	CISeperateCache                           *bool                                `url:"ci_separated_caches,omitempty" json:"ci_separated_caches,omitempty"`
+	CIRestrictPipelineCancellationRole        *AccessControlValue                  `url:"ci_restrict_pipeline_cancellation_role,omitempty" json:"ci_restrict_pipeline_cancellation_role,omitempty"`
 	ContainerExpirationPolicyAttributes       *ContainerExpirationPolicyAttributes `url:"container_expiration_policy_attributes,omitempty" json:"container_expiration_policy_attributes,omitempty"`
 	ContainerRegistryAccessLevel              *AccessControlValue                  `url:"container_registry_access_level,omitempty" json:"container_registry_access_level,omitempty"`
 	DefaultBranch                             *string                              `url:"default_branch,omitempty" json:"default_branch,omitempty"`


Test patch:
diff --git a/projects_test.go b/projects_test.go
index e905986..c9cb819 100644
--- a/projects_test.go
+++ b/projects_test.go
@@ -20,6 +20,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"io"
 	"net/http"
 	"os"
 	"reflect"
@@ -276,6 +277,53 @@ func TestListOwnedProjects(t *testing.T) {
 	}
 }
 
+func TestEditProject(t *testing.T) {
+	mux, client := setup(t)
+
+	var developerAccessLevel AccessControlValue = "developer"
+	opt := &EditProjectOptions{
+		CIRestrictPipelineCancellationRole: Ptr(developerAccessLevel),
+	}
+
+	// Store whether we've set the restrict value in our edit properly
+	restrictValueSet := false
+
+	mux.HandleFunc("/api/v4/projects/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+
+		// Check that our request properly included ci_restrict_pipeline_cancellation_role
+		body, err := io.ReadAll(r.Body)
+		if err != nil {
+			t.Fatalf("Unable to read body properly. Error: %v", err)
+		}
+
+		// Set the value to check if our value is included
+		restrictValueSet = strings.Contains(string(body), "ci_restrict_pipeline_cancellation_role")
+
+		// Print the start of the mock example from https://docs.gitlab.com/ee/api/projects.html#edit-project
+		// including the attribute we edited
+		fmt.Fprint(w, `
+		{
+			"id": 1,
+			"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+			"description_html": "<p data-sourcepos=\"1:1-1:56\" dir=\"auto\">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>",
+			"default_branch": "main",
+			"visibility": "private",
+			"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
+			"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+			"web_url": "http://example.com/diaspora/diaspora-project-site",
+			"readme_url": "http://example.com/diaspora/diaspora-project-site/blob/main/README.md",
+			"ci_restrict_pipeline_cancellation_role": "developer"
+		}`)
+	})
+
+	project, resp, err := client.Projects.EditProject(1, opt)
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusOK, resp.StatusCode)
+	assert.Equal(t, true, restrictValueSet)
+	assert.Equal(t, developerAccessLevel, project.CIRestrictPipelineCancellationRole)
+}
+
 func TestListStarredProjects(t *testing.T) {
 	mux, client := setup(t)
 
@@ -323,6 +371,7 @@ func TestGetProjectByID(t *testing.T) {
 			  "name_regex_keep": null,
 			  "next_run_at": "2020-01-07T21:42:58.658Z"
 			},
+			"ci_restrict_pipeline_cancellation_role": "developer",
 			"packages_enabled": false,
 			"build_coverage_regex": "Total.*([0-9]{1,3})%"
 		  }`)
@@ -336,8 +385,9 @@ func TestGetProjectByID(t *testing.T) {
 			Cadence:   "7d",
 			NextRunAt: &wantTimestamp,
 		},
-		PackagesEnabled:    false,
-		BuildCoverageRegex: `Total.*([0-9]{1,3})%`,
+		PackagesEnabled:                    false,
+		BuildCoverageRegex:                 `Total.*([0-9]{1,3})%`,
+		CIRestrictPipelineCancellationRole: "developer",
 	}
 
 	project, _, err := client.Projects.GetProject(1, nil)