Repository: xanzy/go-gitlab
Pull request #1344 (2022-01-19 20:29:27)

Add support for runner registration and authentication reset API
I couldn't find any contribution guidelines so I just tried to keep as close to the existing code as possible. Let me know if I can improve it at all.


Patch:
diff --git a/jobs.go b/jobs.go
index a68abcf..d50a43c 100644
--- a/jobs.go
+++ b/jobs.go
@@ -333,6 +333,40 @@ func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, ar
 	return bytes.NewReader(artifactBuf.Bytes()), resp, err
 }
 
+// DownloadSingleArtifactsFile download a single artifact file for a specific
+// job of the latest successful pipeline for the given reference name from
+// inside the job’s artifacts archive. The file is extracted from the archive
+// and streamed to the client.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/job_artifacts.html#download-a-single-artifact-file-from-specific-tag-or-branch
+func (s *JobsService) DownloadSingleArtifactsFileByTagOrBranch(pid interface{}, refName string, artifactPath string, opt *DownloadArtifactsFileOptions, options ...RequestOptionFunc) (*bytes.Reader, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	u := fmt.Sprintf(
+		"projects/%s/jobs/artifacts/%s/raw/%s",
+		PathEscape(project),
+		PathEscape(refName),
+		artifactPath,
+	)
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	artifactBuf := new(bytes.Buffer)
+	resp, err := s.client.Do(req, artifactBuf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bytes.NewReader(artifactBuf.Bytes()), resp, err
+}
+
 // GetTraceFile gets a trace of a specific job of a project
 //
 // GitLab API docs:
diff --git a/runners.go b/runners.go
index 60c483a..bd8fc5d 100644
--- a/runners.go
+++ b/runners.go
@@ -151,7 +151,7 @@ func (s *RunnersService) GetRunnerDetails(rid interface{}, options ...RequestOpt
 		return nil, nil, err
 	}
 
-	var rs *RunnerDetails
+	rs := new(RunnerDetails)
 	resp, err := s.client.Do(req, &rs)
 	if err != nil {
 		return nil, resp, err
@@ -190,7 +190,7 @@ func (s *RunnersService) UpdateRunnerDetails(rid interface{}, opt *UpdateRunnerD
 		return nil, nil, err
 	}
 
-	var rs *RunnerDetails
+	rs := new(RunnerDetails)
 	resp, err := s.client.Do(req, &rs)
 	if err != nil {
 		return nil, resp, err
@@ -312,7 +312,7 @@ func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProject
 		return nil, nil, err
 	}
 
-	var r *Runner
+	r := new(Runner)
 	resp, err := s.client.Do(req, &r)
 	if err != nil {
 		return nil, resp, err
@@ -417,7 +417,7 @@ func (s *RunnersService) RegisterNewRunner(opt *RegisterNewRunnerOptions, option
 		return nil, nil, err
 	}
 
-	var r *Runner
+	r := new(Runner)
 	resp, err := s.client.Do(req, &r)
 	if err != nil {
 		return nil, resp, err
@@ -448,7 +448,7 @@ func (s *RunnersService) DeleteRegisteredRunner(opt *DeleteRegisteredRunnerOptio
 	return s.client.Do(req, nil)
 }
 
-// DeleteRegisteredRunnerByID deletes a Runner by ID.
+// DeleteRegisteredRunnerByID deletes a runner by ID.
 //
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/runners.html#delete-a-runner-by-id
@@ -470,7 +470,7 @@ type VerifyRegisteredRunnerOptions struct {
 	Token *string `url:"token" json:"token"`
 }
 
-// VerifyRegisteredRunner registers a new Runner for the instance.
+// VerifyRegisteredRunner registers a new runner for the instance.
 //
 // GitLab API docs:
 // https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
@@ -482,3 +482,100 @@ func (s *RunnersService) VerifyRegisteredRunner(opt *VerifyRegisteredRunnerOptio
 
 	return s.client.Do(req, nil)
 }
+
+type RunnerRegistrationToken struct {
+	Token *string `url:"token" json:"token"`
+}
+
+// ResetInstanceRunnerRegistrationToken resets the instance runner registration
+// token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/runners.html#reset-instances-runner-registration-token
+func (s *RunnersService) ResetInstanceRunnerRegistrationToken(options ...RequestOptionFunc) (*RunnerRegistrationToken, *Response, error) {
+	req, err := s.client.NewRequest(http.MethodPost, "runners/reset_registration_token", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(RunnerRegistrationToken)
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// ResetGroupRunnerRegistrationToken resets a group's runner registration token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/runners.html#reset-groups-runner-registration-token
+func (s *RunnersService) ResetGroupRunnerRegistrationToken(gid interface{}, options ...RequestOptionFunc) (*RunnerRegistrationToken, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/runners/reset_registration_token", PathEscape(group))
+
+	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(RunnerRegistrationToken)
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// ResetGroupRunnerRegistrationToken resets a projects's runner registration token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/runners.html#reset-projects-runner-registration-token
+func (s *RunnersService) ResetProjectRunnerRegistrationToken(pid interface{}, options ...RequestOptionFunc) (*RunnerRegistrationToken, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/runners/reset_registration_token", PathEscape(project))
+	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(RunnerRegistrationToken)
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+type RunnerAuthenticationToken struct {
+	Token *string `url:"token" json:"token"`
+}
+
+// ResetRunnerAuthenticationToken resets a runner's authentication token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/runners.html#reset-runners-authentication-token
+func (s *RunnersService) ResetRunnerAuthenticationToken(rid int, options ...RequestOptionFunc) (*RunnerAuthenticationToken, *Response, error) {
+	u := fmt.Sprintf("runners/%d/reset_authentication_token", rid)
+	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(RunnerAuthenticationToken)
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}


Test patch:
diff --git a/jobs_test.go b/jobs_test.go
index 3244eb1..8eb816a 100644
--- a/jobs_test.go
+++ b/jobs_test.go
@@ -18,6 +18,7 @@ package gitlab
 
 import (
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"reflect"
 	"testing"
@@ -179,3 +180,34 @@ func TestJobsService_ListProjectJobs(t *testing.T) {
 		}}
 	assert.Equal(t, want, jobs)
 }
+
+func TestDownloadSingleArtifactsFileByTagOrBranch(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	wantContent := []byte("This is the file content")
+	mux.HandleFunc("/api/v4/projects/9/jobs/artifacts/abranch/raw/foo/bar.pdf", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		w.WriteHeader(http.StatusOK)
+		w.Write(wantContent)
+	})
+
+	opt := &DownloadArtifactsFileOptions{Job: String("publish")}
+	reader, resp, err := client.Jobs.DownloadSingleArtifactsFileByTagOrBranch(9, "abranch", "foo/bar.pdf", opt)
+	if err != nil {
+		t.Fatalf("Jobs.DownloadSingleArtifactsFileByTagOrBranch returns an error: %v", err)
+	}
+
+	content, err := ioutil.ReadAll(reader)
+	if err != nil {
+		t.Fatalf("Jobs.DownloadSingleArtifactsFileByTagOrBranch error reading: %v", err)
+	}
+	if !reflect.DeepEqual(content, wantContent) {
+		t.Errorf("Jobs.DownloadSingleArtifactsFileByTagOrBranch returned %+v, want %+v", content, wantContent)
+	}
+
+	wantCode := 200
+	if !reflect.DeepEqual(wantCode, resp.StatusCode) {
+		t.Errorf("Jobs.DownloadSingleArtifactsFileByTagOrBranch returned returned status code  %+v, want %+v", resp.StatusCode, wantCode)
+	}
+}
diff --git a/runners_test.go b/runners_test.go
index 606c586..8eb59b3 100644
--- a/runners_test.go
+++ b/runners_test.go
@@ -395,3 +395,123 @@ func TestVerifyRegisteredRunner(t *testing.T) {
 		t.Errorf("Runners.VerifyRegisteredRunner returned returned status code  %+v, want %+v", resp.StatusCode, want)
 	}
 }
+
+func TestResetInstanceRunnerRegistrationToken(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/runners/reset_registration_token", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPost)
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprint(w, `{
+			"token": "6337ff461c94fd3fa32ba3b1ff4125"
+		}`)
+	})
+
+	token, resp, err := client.Runners.ResetInstanceRunnerRegistrationToken(nil)
+	if err != nil {
+		t.Fatalf("Runners.ResetInstanceRunnerRegistrationToken returns an error: %v", err)
+	}
+
+	want := &RunnerRegistrationToken{
+		Token: String("6337ff461c94fd3fa32ba3b1ff4125"),
+	}
+	if !reflect.DeepEqual(want, token) {
+		t.Errorf("Runners.ResetInstanceRunnerRegistrationToken returned %+v, want %+v", token, want)
+	}
+
+	wantCode := 201
+	if !reflect.DeepEqual(wantCode, resp.StatusCode) {
+		t.Errorf("Runners.ResetInstanceRunnerRegistrationToken returned returned status code  %+v, want %+v", resp.StatusCode, wantCode)
+	}
+}
+
+func TestResetGroupRunnerRegistrationToken(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/groups/foobar/runners/reset_registration_token", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPost)
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprint(w, `{
+			"token": "6337ff461c94fd3fa32ba3b1ff4125"
+		}`)
+	})
+
+	token, resp, err := client.Runners.ResetGroupRunnerRegistrationToken("foobar", nil)
+	if err != nil {
+		t.Fatalf("Runners.ResetGroupRunnerRegistrationToken returns an error: %v", err)
+	}
+
+	want := &RunnerRegistrationToken{
+		Token: String("6337ff461c94fd3fa32ba3b1ff4125"),
+	}
+	if !reflect.DeepEqual(want, token) {
+		t.Errorf("Runners.ResetGroupRunnerRegistrationToken returned %+v, want %+v", token, want)
+	}
+
+	wantCode := 201
+	if !reflect.DeepEqual(wantCode, resp.StatusCode) {
+		t.Errorf("Runners.ResetGroupRunnerRegistrationToken returned returned status code  %+v, want %+v", resp.StatusCode, wantCode)
+	}
+}
+
+func TestResetProjectRunnerRegistrationToken(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/projects/9/runners/reset_registration_token", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPost)
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprint(w, `{
+			"token": "6337ff461c94fd3fa32ba3b1ff4125"
+		}`)
+	})
+
+	token, resp, err := client.Runners.ResetProjectRunnerRegistrationToken("9", nil)
+	if err != nil {
+		t.Fatalf("Runners.ResetProjectRunnerRegistrationToken returns an error: %v", err)
+	}
+
+	want := &RunnerRegistrationToken{
+		Token: String("6337ff461c94fd3fa32ba3b1ff4125"),
+	}
+	if !reflect.DeepEqual(want, token) {
+		t.Errorf("Runners.ResetProjectRunnerRegistrationToken returned %+v, want %+v", token, want)
+	}
+
+	wantCode := 201
+	if !reflect.DeepEqual(wantCode, resp.StatusCode) {
+		t.Errorf("Runners.ResetProjectRunnerRegistrationToken returned returned status code  %+v, want %+v", resp.StatusCode, wantCode)
+	}
+}
+
+func TestResetRunnerAuthenticationToken(t *testing.T) {
+	mux, server, client := setup(t)
+	defer teardown(server)
+
+	mux.HandleFunc("/api/v4/runners/42/reset_authentication_token", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPost)
+		w.WriteHeader(http.StatusCreated)
+		fmt.Fprint(w, `{
+			"token": "6337ff461c94fd3fa32ba3b1ff4125"
+		}`)
+	})
+
+	token, resp, err := client.Runners.ResetRunnerAuthenticationToken(42, nil)
+	if err != nil {
+		t.Fatalf("Runners.ResetRunnerAuthenticationToken returns an error: %v", err)
+	}
+
+	want := &RunnerAuthenticationToken{
+		Token: String("6337ff461c94fd3fa32ba3b1ff4125"),
+	}
+	if !reflect.DeepEqual(want, token) {
+		t.Errorf("Runners.ResetRunnerAuthenticationToken returned %+v, want %+v", token, want)
+	}
+
+	wantCode := 201
+	if !reflect.DeepEqual(wantCode, resp.StatusCode) {
+		t.Errorf("Runners.ResetRunnerAuthenticationToken returned returned status code  %+v, want %+v", resp.StatusCode, wantCode)
+	}
+}