Repository: xanzy/go-gitlab
Pull request #1894 (2024-03-09 08:20:15)

Add support for groups job token
This MR aims to add support for new support of groups in job token scopes : https://docs.gitlab.com/ee/api/project_job_token_scopes.html#add-a-group-to-a-cicd-job-token-allowlist

Thanks.


Patch:
diff --git a/job_token_scope.go b/job_token_scope.go
index ff96ba6..35525b7 100644
--- a/job_token_scope.go
+++ b/job_token_scope.go
@@ -184,3 +184,101 @@ func (j *JobTokenScopeService) RemoveProjectFromJobScopeAllowList(pid interface{
 
 	return j.client.Do(req, nil)
 }
+
+// JobTokenAllowlistItem represents a single job token allowlist item.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/project_job_token_scopes.html
+type JobTokenAllowlistItem struct {
+	SourceProjectID int `json:"source_project_id"`
+	TargetGroupID   int `json:"target_group_id"`
+}
+
+// GetJobTokenAllowlistGroupsOptions represents the available
+// GetJobTokenAllowlistGroups() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_job_token_scopes.html#get-a-projects-cicd-job-token-allowlist-of-groups
+type GetJobTokenAllowlistGroupsOptions struct {
+	ListOptions
+}
+
+// GetJobTokenAllowListGroups fetches the CI/CD job token allowlist groups
+// (job token scopes) of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_job_token_scopes.html#get-a-projects-cicd-job-token-allowlist-of-groups
+func (j *JobTokenScopeService) GetJobTokenAllowlistGroups(pid interface{}, opt *GetJobTokenAllowlistGroupsOptions, options ...RequestOptionFunc) ([]*Group, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(`projects/%s/job_token_scope/groups_allowlist`, PathEscape(project))
+
+	req, err := j.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ps []*Group
+	resp, err := j.client.Do(req, &ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, nil
+}
+
+// AddGroupToJobTokenAllowlistOptions represents the available
+// AddGroupToJobTokenAllowlist() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_job_token_scopes.html#add-a-group-to-a-cicd-job-token-allowlist
+type AddGroupToJobTokenAllowlistOptions struct {
+	TargetGroupID *int `url:"target_group_id,omitempty" json:"target_group_id,omitempty"`
+}
+
+// AddProjectToJobScopeGroupsAllowList adds a new group to a project's job token
+// inbound groups allow list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_job_token_scopes.html#add-a-group-to-a-cicd-job-token-allowlist
+func (j *JobTokenScopeService) AddGroupToJobTokenAllowlist(pid interface{}, opt *AddGroupToJobTokenAllowlistOptions, options ...RequestOptionFunc) (*JobTokenAllowlistItem, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(`projects/%s/job_token_scope/groups_allowlist`, PathEscape(project))
+
+	req, err := j.client.NewRequest(http.MethodPost, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	jt := new(JobTokenAllowlistItem)
+	resp, err := j.client.Do(req, jt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return jt, resp, nil
+}
+
+// RemoveGroupFromJopTokenAllowlist removes a group from a project's job
+// token inbound groups allow list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_job_token_scopes.html#remove-a-group-from-a-cicd-job-token-allowlist
+func (j *JobTokenScopeService) RemoveGroupFromJobTokenAllowlist(pid interface{}, targetGroup int, options ...RequestOptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf(`projects/%s/job_token_scope/groups_allowlist/%d`, PathEscape(project), targetGroup)
+
+	req, err := j.client.NewRequest(http.MethodDelete, u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return j.client.Do(req, nil)
+}


Test patch:
diff --git a/job_token_scope_test.go b/job_token_scope_test.go
index 4300f21..4c34047 100644
--- a/job_token_scope_test.go
+++ b/job_token_scope_test.go
@@ -177,3 +177,100 @@ func TestRemoveProjectFromJobScopeAllowList(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, 204, resp.StatusCode)
 }
+
+// This tests that when calling the GetJobTokenAllowlistGroups, we get a list
+// of groups back. There isn't a "deep" test with every attribute specified,
+// because the object returned is a *Group object, which is already tested in
+// groups.go.
+func TestGetJobTokenAllowlistGroups(t *testing.T) {
+	mux, client := setup(t)
+
+	// Handle project ID 1, and print a result of two groups
+	mux.HandleFunc("/api/v4/projects/1/job_token_scope/groups_allowlist", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+
+		// Print on the response
+		fmt.Fprint(w, `[{"id":1},{"id":2}]`)
+	})
+
+	want := []*Group{{ID: 1}, {ID: 2}}
+	groups, _, err := client.JobTokenScope.GetJobTokenAllowlistGroups(
+		1,
+		&GetJobTokenAllowlistGroupsOptions{},
+	)
+
+	assert.NoError(t, err)
+	assert.Equal(t, want, groups)
+}
+
+func TestAddGroupToJobTokenAllowlist(t *testing.T) {
+	mux, client := setup(t)
+
+	mux.HandleFunc("/api/v4/projects/1/job_token_scope/groups_allowlist", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPost)
+
+		// Read the request to determine which target group is passed in
+		body, err := io.ReadAll(r.Body)
+		if err != nil {
+			t.Fatalf("JobTokenScope.AddGroupToJobTokenAllowlist failed to read body")
+		}
+
+		// Parse to object to ensure it's sent on the request appropriately.
+		var createTokenRequest AddGroupToJobTokenAllowlistOptions
+		err = json.Unmarshal(body, &createTokenRequest)
+		if err != nil {
+			t.Fatalf("JobTokenScope.AddGroupToJobTokenAllowlist failed to unmarshal body: %v", err)
+		}
+
+		// Ensure we provide the proper response
+		w.WriteHeader(http.StatusCreated)
+
+		// Print on the response with the proper target group
+		fmt.Fprintf(w, `{
+			"source_project_id": 1,
+			"target_group_id": %d
+		}`, *createTokenRequest.TargetGroupID)
+	})
+
+	want := &JobTokenAllowlistItem{
+		SourceProjectID: 1,
+		TargetGroupID:   2,
+	}
+
+	addTokenResponse, resp, err := client.JobTokenScope.AddGroupToJobTokenAllowlist(
+		1,
+		&AddGroupToJobTokenAllowlistOptions{TargetGroupID: Ptr(2)},
+	)
+	assert.NoError(t, err)
+	assert.Equal(t, want, addTokenResponse)
+	assert.Equal(t, 201, resp.StatusCode)
+}
+
+func TestRemoveGroupFromJobTokenAllowlist(t *testing.T) {
+	mux, client := setup(t)
+
+	mux.HandleFunc("/api/v4/projects/1/job_token_scope/groups_allowlist/2", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodDelete)
+
+		// Read the request to determine which target group is passed in
+		body, err := io.ReadAll(r.Body)
+		if err != nil {
+			t.Fatalf("JobTokenScope.RemoveGroupFromJobTokenAllowlist failed to read body")
+		}
+
+		// The body should be empty since all attributes are passed in the path
+		if body != nil && string(body) != "" {
+			t.Fatalf("JobTokenScope.RemoveGroupFromJobTokenAllowlist failed to unmarshal body: %v", err)
+		}
+
+		// Ensure we provide the proper response
+		w.WriteHeader(http.StatusNoContent)
+
+		// Print an empty body, since that's what the API provides.
+		fmt.Fprint(w, "")
+	})
+
+	resp, err := client.JobTokenScope.RemoveGroupFromJobTokenAllowlist(1, 2)
+	assert.NoError(t, err)
+	assert.Equal(t, 204, resp.StatusCode)
+}