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 Coverage float64 `json:"coverage"`
40 AllowFailure bool `json:"allow_failure"`
41 CreatedAt *time.Time `json:"created_at"`
42 StartedAt *time.Time `json:"started_at"`
43 FinishedAt *time.Time `json:"finished_at"`
44 Duration float64 `json:"duration"`
45 ArtifactsExpireAt *time.Time `json:"artifacts_expire_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 Artifacts []struct {
56 FileType string `json:"file_type"`
57 Filename string `json:"filename"`
58 Size int `json:"size"`
59 FileFormat string `json:"file_format"`
60 } `json:"artifacts"`
61 ArtifactsFile struct {
62 Filename string `json:"filename"`
63 Size int `json:"size"`
64 } `json:"artifacts_file"`
65 Runner struct {
66 ID int `json:"id"`
67 Description string `json:"description"`
68 Active bool `json:"active"`
69 IsShared bool `json:"is_shared"`
70 Name string `json:"name"`
71 } `json:"runner"`
72 Stage string `json:"stage"`
73 Status string `json:"status"`
74 Tag bool `json:"tag"`
75 WebURL string `json:"web_url"`
76 User *User `json:"user"`
77}
78
79// ListJobsOptions are options for two list apis
80type ListJobsOptions struct {
81 ListOptions
82 Scope []BuildStateValue `url:"scope[],omitempty" json:"scope,omitempty"`
83}
84
85// ListProjectJobs gets a list of jobs in a project.
86//
87// The scope of jobs to show, one or array of: created, pending, running,
88// failed, success, canceled, skipped; showing all jobs if none provided
89//
90// GitLab API docs:
91// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
92func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) {
93 project, err := parseID(pid)
94 if err != nil {
95 return nil, nil, err
96 }
97 u := fmt.Sprintf("projects/%s/jobs", pathEscape(project))
98
99 req, err := s.client.NewRequest("GET", u, opts, options)
100 if err != nil {
101 return nil, nil, err
102 }
103
104 var jobs []Job
105 resp, err := s.client.Do(req, &jobs)
106 if err != nil {
107 return nil, resp, err
108 }
109
110 return jobs, resp, err
111}
112
113// ListPipelineJobs gets a list of jobs for specific pipeline in a
114// project. If the pipeline ID is not found, it will respond with 404.
115//
116// GitLab API docs:
117// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
118func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
119 project, err := parseID(pid)
120 if err != nil {
121 return nil, nil, err
122 }
123 u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", pathEscape(project), pipelineID)
124
125 req, err := s.client.NewRequest("GET", u, opts, options)
126 if err != nil {
127 return nil, nil, err
128 }
129
130 var jobs []*Job
131 resp, err := s.client.Do(req, &jobs)
132 if err != nil {
133 return nil, resp, err
134 }
135
136 return jobs, resp, err
137}
138
139// GetJob gets a single job of a project.
140//
141// GitLab API docs:
142// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
143func (s *JobsService) GetJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
144 project, err := parseID(pid)
145 if err != nil {
146 return nil, nil, err
147 }
148 u := fmt.Sprintf("projects/%s/jobs/%d", pathEscape(project), jobID)
149
150 req, err := s.client.NewRequest("GET", u, nil, options)
151 if err != nil {
152 return nil, nil, err
153 }
154
155 job := new(Job)
156 resp, err := s.client.Do(req, job)
157 if err != nil {
158 return nil, resp, err
159 }
160
161 return job, resp, err
162}
163
164// GetJobArtifacts get jobs artifacts of a project
165//
166// GitLab API docs:
167// https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts
168func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
169 project, err := parseID(pid)
170 if err != nil {
171 return nil, nil, err
172 }
173 u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)
174
175 req, err := s.client.NewRequest("GET", u, nil, options)
176 if err != nil {
177 return nil, nil, err
178 }
179
180 artifactsBuf := new(bytes.Buffer)
181 resp, err := s.client.Do(req, artifactsBuf)
182 if err != nil {
183 return nil, resp, err
184 }
185
186 return artifactsBuf, resp, err
187}
188
189// DownloadArtifactsFileOptions represents the available DownloadArtifactsFile()
190// options.
191//
192// GitLab API docs:
193// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
194type DownloadArtifactsFileOptions struct {
195 Job *string `url:"job" json:"job"`
196}
197
198// DownloadArtifactsFile download the artifacts file from the given
199// reference name and job provided the job finished successfully.
200//
201// GitLab API docs:
202// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
203func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, opt *DownloadArtifactsFileOptions, options ...OptionFunc) (io.Reader, *Response, error) {
204 project, err := parseID(pid)
205 if err != nil {
206 return nil, nil, err
207 }
208 u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download", pathEscape(project), refName)
209
210 req, err := s.client.NewRequest("GET", u, opt, options)
211 if err != nil {
212 return nil, nil, err
213 }
214
215 artifactsBuf := new(bytes.Buffer)
216 resp, err := s.client.Do(req, artifactsBuf)
217 if err != nil {
218 return nil, resp, err
219 }
220
221 return artifactsBuf, resp, err
222}
223
224// DownloadSingleArtifactsFile download a file from the artifacts from the
225// given reference name and job provided the job finished successfully.
226// Only a single file is going to be extracted from the archive and streamed
227// to a client.
228//
229// GitLab API docs:
230// https://docs.gitlab.com/ce/api/jobs.html#download-a-single-artifact-file
231func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, artifactPath string, options ...OptionFunc) (io.Reader, *Response, error) {
232 project, err := parseID(pid)
233 if err != nil {
234 return nil, nil, err
235 }
236
237 u := fmt.Sprintf(
238 "projects/%s/jobs/%d/artifacts/%s",
239 pathEscape(project),
240 jobID,
241 artifactPath,
242 )
243
244 req, err := s.client.NewRequest("GET", u, nil, options)
245 if err != nil {
246 return nil, nil, err
247 }
248
249 artifactBuf := new(bytes.Buffer)
250 resp, err := s.client.Do(req, artifactBuf)
251 if err != nil {
252 return nil, resp, err
253 }
254
255 return artifactBuf, resp, err
256}
257
258// GetTraceFile gets a trace of a specific job of a project
259//
260// GitLab API docs:
261// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
262func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
263 project, err := parseID(pid)
264 if err != nil {
265 return nil, nil, err
266 }
267 u := fmt.Sprintf("projects/%s/jobs/%d/trace", pathEscape(project), jobID)
268
269 req, err := s.client.NewRequest("GET", u, nil, options)
270 if err != nil {
271 return nil, nil, err
272 }
273
274 traceBuf := new(bytes.Buffer)
275 resp, err := s.client.Do(req, traceBuf)
276 if err != nil {
277 return nil, resp, err
278 }
279
280 return traceBuf, resp, err
281}
282
283// CancelJob cancels a single job of a project.
284//
285// GitLab API docs:
286// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
287func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
288 project, err := parseID(pid)
289 if err != nil {
290 return nil, nil, err
291 }
292 u := fmt.Sprintf("projects/%s/jobs/%d/cancel", pathEscape(project), jobID)
293
294 req, err := s.client.NewRequest("POST", u, nil, options)
295 if err != nil {
296 return nil, nil, err
297 }
298
299 job := new(Job)
300 resp, err := s.client.Do(req, job)
301 if err != nil {
302 return nil, resp, err
303 }
304
305 return job, resp, err
306}
307
308// RetryJob retries a single job of a project
309//
310// GitLab API docs:
311// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
312func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
313 project, err := parseID(pid)
314 if err != nil {
315 return nil, nil, err
316 }
317 u := fmt.Sprintf("projects/%s/jobs/%d/retry", pathEscape(project), jobID)
318
319 req, err := s.client.NewRequest("POST", u, nil, options)
320 if err != nil {
321 return nil, nil, err
322 }
323
324 job := new(Job)
325 resp, err := s.client.Do(req, job)
326 if err != nil {
327 return nil, resp, err
328 }
329
330 return job, resp, err
331}
332
333// EraseJob erases a single job of a project, removes a job
334// artifacts and a job trace.
335//
336// GitLab API docs:
337// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
338func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
339 project, err := parseID(pid)
340 if err != nil {
341 return nil, nil, err
342 }
343 u := fmt.Sprintf("projects/%s/jobs/%d/erase", pathEscape(project), jobID)
344
345 req, err := s.client.NewRequest("POST", u, nil, options)
346 if err != nil {
347 return nil, nil, err
348 }
349
350 job := new(Job)
351 resp, err := s.client.Do(req, job)
352 if err != nil {
353 return nil, resp, err
354 }
355
356 return job, resp, err
357}
358
359// KeepArtifacts prevents artifacts from being deleted when
360// expiration is set.
361//
362// GitLab API docs:
363// https://docs.gitlab.com/ce/api/jobs.html#keep-artifacts
364func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
365 project, err := parseID(pid)
366 if err != nil {
367 return nil, nil, err
368 }
369 u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", pathEscape(project), jobID)
370
371 req, err := s.client.NewRequest("POST", u, nil, options)
372 if err != nil {
373 return nil, nil, err
374 }
375
376 job := new(Job)
377 resp, err := s.client.Do(req, job)
378 if err != nil {
379 return nil, resp, err
380 }
381
382 return job, resp, err
383}
384
385// PlayJob triggers a manual action to start a job.
386//
387// GitLab API docs:
388// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
389func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
390 project, err := parseID(pid)
391 if err != nil {
392 return nil, nil, err
393 }
394 u := fmt.Sprintf("projects/%s/jobs/%d/play", pathEscape(project), jobID)
395
396 req, err := s.client.NewRequest("POST", u, nil, options)
397 if err != nil {
398 return nil, nil, err
399 }
400
401 job := new(Job)
402 resp, err := s.client.Do(req, job)
403 if err != nil {
404 return nil, resp, err
405 }
406
407 return job, resp, err
408}