diff --git a/job_token_scope.go b/job_token_scope.go index ff96ba698..35525b76d 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) +} diff --git a/job_token_scope_test.go b/job_token_scope_test.go index 4300f21cd..4c3404712 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) +}