1//
2// Copyright 2017, Arkbriar
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17package gitlab
18
19import (
20 "bytes"
21 "fmt"
22 "io"
23 "time"
24)
25
26// JobsService handles communication with the ci builds related methods
27// of the GitLab API.
28//
29// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
30type JobsService struct {
31 client *Client
32}
33
34// Job represents a ci build.
35//
36// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
37type Job struct {
38 Commit *Commit `json:"commit"`
39 CreatedAt *time.Time `json:"created_at"`
40 Coverage float64 `json:"coverage"`
41 ArtifactsFile struct {
42 Filename string `json:"filename"`
43 Size int `json:"size"`
44 } `json:"artifacts_file"`
45 FinishedAt *time.Time `json:"finished_at"`
46 ID int `json:"id"`
47 Name string `json:"name"`
48 Pipeline struct {
49 ID int `json:"id"`
50 Ref string `json:"ref"`
51 Sha string `json:"sha"`
52 Status string `json:"status"`
53 } `json:"pipeline"`
54 Ref string `json:"ref"`
55 Runner struct {
56 ID int `json:"id"`
57 Description string `json:"description"`
58 Active bool `json:"active"`
59 IsShared bool `json:"is_shared"`
60 Name string `json:"name"`
61 } `json:"runner"`
62 Stage string `json:"stage"`
63 StartedAt *time.Time `json:"started_at"`
64 Status string `json:"status"`
65 Tag bool `json:"tag"`
66 User *User `json:"user"`
67 WebURL string `json:"web_url"`
68}
69
70// ListJobsOptions are options for two list apis
71type ListJobsOptions struct {
72 ListOptions
73 Scope []BuildStateValue `url:"scope[],omitempty" json:"scope,omitempty"`
74}
75
76// ListProjectJobs gets a list of jobs in a project.
77//
78// The scope of jobs to show, one or array of: created, pending, running,
79// failed, success, canceled, skipped; showing all jobs if none provided
80//
81// GitLab API docs:
82// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
83func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) {
84 project, err := parseID(pid)
85 if err != nil {
86 return nil, nil, err
87 }
88 u := fmt.Sprintf("projects/%s/jobs", pathEscape(project))
89
90 req, err := s.client.NewRequest("GET", u, opts, options)
91 if err != nil {
92 return nil, nil, err
93 }
94
95 var jobs []Job
96 resp, err := s.client.Do(req, &jobs)
97 if err != nil {
98 return nil, resp, err
99 }
100
101 return jobs, resp, err
102}
103
104// ListPipelineJobs gets a list of jobs for specific pipeline in a
105// project. If the pipeline ID is not found, it will respond with 404.
106//
107// GitLab API docs:
108// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
109func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
110 project, err := parseID(pid)
111 if err != nil {
112 return nil, nil, err
113 }
114 u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", pathEscape(project), pipelineID)
115
116 req, err := s.client.NewRequest("GET", u, opts, options)
117 if err != nil {
118 return nil, nil, err
119 }
120
121 var jobs []*Job
122 resp, err := s.client.Do(req, &jobs)
123 if err != nil {
124 return nil, resp, err
125 }
126
127 return jobs, resp, err
128}
129
130// GetJob gets a single job of a project.
131//
132// GitLab API docs:
133// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
134func (s *JobsService) GetJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
135 project, err := parseID(pid)
136 if err != nil {
137 return nil, nil, err
138 }
139 u := fmt.Sprintf("projects/%s/jobs/%d", pathEscape(project), jobID)
140
141 req, err := s.client.NewRequest("GET", u, nil, options)
142 if err != nil {
143 return nil, nil, err
144 }
145
146 job := new(Job)
147 resp, err := s.client.Do(req, job)
148 if err != nil {
149 return nil, resp, err
150 }
151
152 return job, resp, err
153}
154
155// GetJobArtifacts get jobs artifacts of a project
156//
157// GitLab API docs:
158// https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts
159func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
160 project, err := parseID(pid)
161 if err != nil {
162 return nil, nil, err
163 }
164 u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)
165
166 req, err := s.client.NewRequest("GET", u, nil, options)
167 if err != nil {
168 return nil, nil, err
169 }
170
171 artifactsBuf := new(bytes.Buffer)
172 resp, err := s.client.Do(req, artifactsBuf)
173 if err != nil {
174 return nil, resp, err
175 }
176
177 return artifactsBuf, resp, err
178}
179
180// DownloadArtifactsFileOptions represents the available DownloadArtifactsFile()
181// options.
182//
183// GitLab API docs:
184// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
185type DownloadArtifactsFileOptions struct {
186 Job *string `url:"job" json:"job"`
187}
188
189// DownloadArtifactsFile download the artifacts file from the given
190// reference name and job provided the job finished successfully.
191//
192// GitLab API docs:
193// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
194func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, opt *DownloadArtifactsFileOptions, options ...OptionFunc) (io.Reader, *Response, error) {
195 project, err := parseID(pid)
196 if err != nil {
197 return nil, nil, err
198 }
199 u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download", pathEscape(project), refName)
200
201 req, err := s.client.NewRequest("GET", u, opt, options)
202 if err != nil {
203 return nil, nil, err
204 }
205
206 artifactsBuf := new(bytes.Buffer)
207 resp, err := s.client.Do(req, artifactsBuf)
208 if err != nil {
209 return nil, resp, err
210 }
211
212 return artifactsBuf, resp, err
213}
214
215// DownloadSingleArtifactsFile download a file from the artifacts from the
216// given reference name and job provided the job finished successfully.
217// Only a single file is going to be extracted from the archive and streamed
218// to a client.
219//
220// GitLab API docs:
221// https://docs.gitlab.com/ce/api/jobs.html#download-a-single-artifact-file
222func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, artifactPath string, options ...OptionFunc) (io.Reader, *Response, error) {
223 project, err := parseID(pid)
224 if err != nil {
225 return nil, nil, err
226 }
227
228 u := fmt.Sprintf(
229 "projects/%s/jobs/%d/artifacts/%s",
230 pathEscape(project),
231 jobID,
232 artifactPath,
233 )
234
235 req, err := s.client.NewRequest("GET", u, nil, options)
236 if err != nil {
237 return nil, nil, err
238 }
239
240 artifactBuf := new(bytes.Buffer)
241 resp, err := s.client.Do(req, artifactBuf)
242 if err != nil {
243 return nil, resp, err
244 }
245
246 return artifactBuf, resp, err
247}
248
249// GetTraceFile gets a trace of a specific job of a project
250//
251// GitLab API docs:
252// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
253func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
254 project, err := parseID(pid)
255 if err != nil {
256 return nil, nil, err
257 }
258 u := fmt.Sprintf("projects/%s/jobs/%d/trace", pathEscape(project), jobID)
259
260 req, err := s.client.NewRequest("GET", u, nil, options)
261 if err != nil {
262 return nil, nil, err
263 }
264
265 traceBuf := new(bytes.Buffer)
266 resp, err := s.client.Do(req, traceBuf)
267 if err != nil {
268 return nil, resp, err
269 }
270
271 return traceBuf, resp, err
272}
273
274// CancelJob cancels a single job of a project.
275//
276// GitLab API docs:
277// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
278func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
279 project, err := parseID(pid)
280 if err != nil {
281 return nil, nil, err
282 }
283 u := fmt.Sprintf("projects/%s/jobs/%d/cancel", pathEscape(project), jobID)
284
285 req, err := s.client.NewRequest("POST", u, nil, options)
286 if err != nil {
287 return nil, nil, err
288 }
289
290 job := new(Job)
291 resp, err := s.client.Do(req, job)
292 if err != nil {
293 return nil, resp, err
294 }
295
296 return job, resp, err
297}
298
299// RetryJob retries a single job of a project
300//
301// GitLab API docs:
302// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
303func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
304 project, err := parseID(pid)
305 if err != nil {
306 return nil, nil, err
307 }
308 u := fmt.Sprintf("projects/%s/jobs/%d/retry", pathEscape(project), jobID)
309
310 req, err := s.client.NewRequest("POST", u, nil, options)
311 if err != nil {
312 return nil, nil, err
313 }
314
315 job := new(Job)
316 resp, err := s.client.Do(req, job)
317 if err != nil {
318 return nil, resp, err
319 }
320
321 return job, resp, err
322}
323
324// EraseJob erases a single job of a project, removes a job
325// artifacts and a job trace.
326//
327// GitLab API docs:
328// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
329func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
330 project, err := parseID(pid)
331 if err != nil {
332 return nil, nil, err
333 }
334 u := fmt.Sprintf("projects/%s/jobs/%d/erase", pathEscape(project), jobID)
335
336 req, err := s.client.NewRequest("POST", u, nil, options)
337 if err != nil {
338 return nil, nil, err
339 }
340
341 job := new(Job)
342 resp, err := s.client.Do(req, job)
343 if err != nil {
344 return nil, resp, err
345 }
346
347 return job, resp, err
348}
349
350// KeepArtifacts prevents artifacts from being deleted when
351// expiration is set.
352//
353// GitLab API docs:
354// https://docs.gitlab.com/ce/api/jobs.html#keep-artifacts
355func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
356 project, err := parseID(pid)
357 if err != nil {
358 return nil, nil, err
359 }
360 u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", pathEscape(project), jobID)
361
362 req, err := s.client.NewRequest("POST", u, nil, options)
363 if err != nil {
364 return nil, nil, err
365 }
366
367 job := new(Job)
368 resp, err := s.client.Do(req, job)
369 if err != nil {
370 return nil, resp, err
371 }
372
373 return job, resp, err
374}
375
376// PlayJob triggers a manual action to start a job.
377//
378// GitLab API docs:
379// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
380func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
381 project, err := parseID(pid)
382 if err != nil {
383 return nil, nil, err
384 }
385 u := fmt.Sprintf("projects/%s/jobs/%d/play", pathEscape(project), jobID)
386
387 req, err := s.client.NewRequest("POST", u, nil, options)
388 if err != nil {
389 return nil, nil, err
390 }
391
392 job := new(Job)
393 resp, err := s.client.Do(req, job)
394 if err != nil {
395 return nil, resp, err
396 }
397
398 return job, resp, err
399}