job.go 10.0 KB
Newer Older
LinuxSuRen's avatar
LinuxSuRen 已提交
1 2 3 4 5 6 7
package client

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
LinuxSuRen's avatar
LinuxSuRen 已提交
8
	"net/url"
LinuxSuRen's avatar
LinuxSuRen 已提交
9 10
	"strconv"
	"strings"
11 12

	"github.com/jenkins-zh/jenkins-cli/util"
LinuxSuRen's avatar
LinuxSuRen 已提交
13 14
)

15
// JobClient is client for operate jobs
LinuxSuRen's avatar
LinuxSuRen 已提交
16 17 18 19 20
type JobClient struct {
	JenkinsCore
}

// Search find a set of jobs by name
21 22 23 24
func (q *JobClient) Search(name, kind string, start, limit int) (items []JenkinsItem, err error) {
	err = q.RequestWithData("GET", fmt.Sprintf("/items/list?name=%s&type=%s&start=%d&limit=%d",
		name, kind, start, limit),
		nil, nil, 200, &items)
LinuxSuRen's avatar
LinuxSuRen 已提交
25 26
	return
}
27 28

// Build trigger a job
LinuxSuRen's avatar
LinuxSuRen 已提交
29
func (q *JobClient) Build(jobName string) (err error) {
30
	path := ParseJobPath(jobName)
31
	_, err = q.RequestWithoutData("POST", fmt.Sprintf("%s/build", path), nil, nil, 201)
LinuxSuRen's avatar
LinuxSuRen 已提交
32 33 34
	return
}

35
// GetBuild get build information of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
36
func (q *JobClient) GetBuild(jobName string, id int) (job *JobBuild, err error) {
37
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
38 39
	var api string
	if id == -1 {
40
		api = fmt.Sprintf("%s/lastBuild/api/json", path)
LinuxSuRen's avatar
LinuxSuRen 已提交
41
	} else {
42
		api = fmt.Sprintf("%s/%d/api/json", path, id)
LinuxSuRen's avatar
LinuxSuRen 已提交
43
	}
44

LinuxSuRen's avatar
LinuxSuRen 已提交
45
	err = q.RequestWithData("GET", api, nil, nil, 200, &job)
LinuxSuRen's avatar
LinuxSuRen 已提交
46 47 48
	return
}

49
// BuildWithParams build a job which has params
50
func (q *JobClient) BuildWithParams(jobName string, parameters []ParameterDefinition) (err error) {
51
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
52
	api := fmt.Sprintf("%s/build", path)
53 54 55 56 57 58 59 60 61

	var paramJSON []byte
	if len(parameters) == 1 {
		paramJSON, err = json.Marshal(parameters[0])
	} else {
		paramJSON, err = json.Marshal(parameters)
	}

	if err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
62 63
		formData := url.Values{"json": {fmt.Sprintf("{\"parameter\": %s}", string(paramJSON))}}
		payload := strings.NewReader(formData.Encode())
64

LinuxSuRen's avatar
LinuxSuRen 已提交
65 66
		_, err = q.RequestWithoutData("POST", api,
			map[string]string{util.ContentType: util.ApplicationForm}, payload, 201)
67 68 69 70
	}
	return
}

71
// StopJob stops a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
72
func (q *JobClient) StopJob(jobName string, num int) (err error) {
73
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
74 75 76 77 78 79 80

	var api string
	if num <= 0 {
		api = fmt.Sprintf("%s/lastBuild/stop", path)
	} else {
		api = fmt.Sprintf("%s/%d/stop", path, num)
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
81

82
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
83 84 85
	return
}

86
// GetJob returns the job info
LinuxSuRen's avatar
LinuxSuRen 已提交
87
func (q *JobClient) GetJob(name string) (job *Job, err error) {
88
	path := ParseJobPath(name)
89
	api := fmt.Sprintf("%s/api/json", path)
LinuxSuRen's avatar
LinuxSuRen 已提交
90

LinuxSuRen's avatar
LinuxSuRen 已提交
91
	err = q.RequestWithData("GET", api, nil, nil, 200, &job)
LinuxSuRen's avatar
LinuxSuRen 已提交
92 93 94
	return
}

95
// GetJobTypeCategories returns all categories of jobs
96 97
func (q *JobClient) GetJobTypeCategories() (jobCategories []JobCategory, err error) {
	var (
98 99
		statusCode int
		data       []byte
100 101
	)

102 103
	if statusCode, data, err = q.Request("GET", "/view/all/itemCategories?depth=3", nil, nil); err == nil {
		if statusCode == 200 {
104 105 106 107 108 109 110
			type innerJobCategories struct {
				Categories []JobCategory
			}
			result := &innerJobCategories{}
			err = json.Unmarshal(data, result)
			jobCategories = result.Categories
		} else {
111
			err = fmt.Errorf("unexpected status code: %d", statusCode)
112 113 114 115 116
		}
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
117 118
// GetPipeline return the pipeline object
func (q *JobClient) GetPipeline(name string) (pipeline *Pipeline, err error) {
119
	path := ParseJobPath(name)
LinuxSuRen's avatar
LinuxSuRen 已提交
120 121
	api := fmt.Sprintf("%s/restFul", path)
	err = q.RequestWithData("GET", api, nil, nil, 200, &pipeline)
LinuxSuRen's avatar
LinuxSuRen 已提交
122 123 124
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
125 126
// UpdatePipeline updates the pipeline script
func (q *JobClient) UpdatePipeline(name, script string) (err error) {
127 128 129
	formData := url.Values{}
	formData.Add("script", script)

130
	path := ParseJobPath(name)
131
	api := fmt.Sprintf("%s/restFul/update?%s", path, formData.Encode())
LinuxSuRen's avatar
LinuxSuRen 已提交
132

133
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
134 135 136
	return
}

137
// GetHistory returns the build history of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
138
func (q *JobClient) GetHistory(name string) (builds []*JobBuild, err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
139 140
	var job *Job
	if job, err = q.GetJob(name); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
141 142 143 144 145 146 147
		buildList := job.Builds // only contains basic info

		var build *JobBuild
		for _, buildItem := range buildList {
			build, err = q.GetBuild(name, buildItem.Number)
			if err != nil {
				break
148
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
149
			builds = append(builds, build)
150
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
151 152 153 154
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
155
// Log get the log of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
156
func (q *JobClient) Log(jobName string, history int, start int64) (jobLog JobLog, err error) {
157
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
158 159
	var api string
	if history == -1 {
LinuxSuRen's avatar
LinuxSuRen 已提交
160
		api = fmt.Sprintf("%s%s/lastBuild/logText/progressiveText?start=%d", q.URL, path, start)
LinuxSuRen's avatar
LinuxSuRen 已提交
161
	} else {
LinuxSuRen's avatar
LinuxSuRen 已提交
162
		api = fmt.Sprintf("%s%s/%d/logText/progressiveText?start=%d", q.URL, path, history, start)
LinuxSuRen's avatar
LinuxSuRen 已提交
163
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
164 165 166 167 168 169 170
	var (
		req      *http.Request
		response *http.Response
	)

	req, err = http.NewRequest("GET", api, nil)
	if err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
171 172 173
		err = q.AuthHandle(req)
	}
	if err != nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
		return
	}

	client := q.GetClient()
	jobLog = JobLog{
		HasMore:   false,
		Text:      "",
		NextStart: int64(0),
	}
	if response, err = client.Do(req); err == nil {
		code := response.StatusCode
		var data []byte
		data, err = ioutil.ReadAll(response.Body)
		if code == 200 {
			jobLog.Text = string(data)

			if response.Header != nil {
				jobLog.HasMore = strings.ToLower(response.Header.Get("X-More-Data")) == "true"
				jobLog.NextStart, _ = strconv.ParseInt(response.Header.Get("X-Text-Size"), 10, 64)
			}
		}
	}
	return
}

199 200 201 202 203 204
// CreateJobPayload the payload for creating a job
type CreateJobPayload struct {
	Name string `json:"name"`
	Mode string `json:"mode"`
	From string `json:"from"`
}
205

206 207 208
// Create can create a job
func (q *JobClient) Create(jobPayload CreateJobPayload) (err error) {
	playLoadData, _ := json.Marshal(jobPayload)
209 210
	formData := url.Values{
		"json": {string(playLoadData)},
211 212 213
		"name": {jobPayload.Name},
		"mode": {jobPayload.Mode},
		"from": {jobPayload.From},
214 215 216
	}
	payload := strings.NewReader(formData.Encode())

LinuxSuRen's avatar
LinuxSuRen 已提交
217
	var code int
218 219
	code, err = q.RequestWithoutData("POST", "/view/all/createItem",
		map[string]string{util.ContentType: util.ApplicationForm}, payload, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
220 221
	if code == 302 {
		err = nil
222 223 224 225
	}
	return
}

226
// Delete will delete a job by name
227 228
func (q *JobClient) Delete(jobName string) (err error) {
	var (
229
		statusCode int
230
	)
231

232 233
	jobName = ParseJobPath(jobName)
	api := fmt.Sprintf("%s/doDelete", jobName)
234
	header := map[string]string{
LinuxSuRen's avatar
LinuxSuRen 已提交
235
		util.ContentType: util.ApplicationForm,
236 237
	}

LinuxSuRen's avatar
LinuxSuRen 已提交
238
	if statusCode, _, err = q.Request("POST", api, header, nil); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
239
		if statusCode != 200 && statusCode != 302 {
240
			err = fmt.Errorf("unexpected status code: %d", statusCode)
241 242 243 244 245
		}
	}
	return
}

246 247
// GetJobInputActions returns the all pending actions
func (q *JobClient) GetJobInputActions(jobName string, buildID int) (actions []JobInputItem, err error) {
248
	path := ParseJobPath(jobName)
249 250 251 252 253 254 255 256 257 258 259
	err = q.RequestWithData("GET", fmt.Sprintf("%s/%d/wfapi/pendingInputActions", path, buildID), nil, nil, 200, &actions)
	return
}

// JenkinsInputParametersRequest represents the parameters for the Jenkins input request
type JenkinsInputParametersRequest struct {
	Parameter []ParameterDefinition `json:"parameter"`
}

// JobInputSubmit submit the pending input request
func (q *JobClient) JobInputSubmit(jobName, inputID string, buildID int, abort bool, params map[string]string) (err error) {
260
	jobPath := ParseJobPath(jobName)
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
	var api string
	if abort {
		api = fmt.Sprintf("%s/%d/input/%s/abort", jobPath, buildID, inputID)
	} else {
		api = fmt.Sprintf("%s/%d/input/%s/proceed", jobPath, buildID, inputID)
	}

	request := JenkinsInputParametersRequest{
		Parameter: make([]ParameterDefinition, 0),
	}

	for k, v := range params {
		request.Parameter = append(request.Parameter, ParameterDefinition{
			Name:  k,
			Value: v,
		})
	}

	paramData, _ := json.Marshal(request)

	api = fmt.Sprintf("%s?json=%s", api, string(paramData))
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)

	return
}

287 288 289 290 291 292 293
// ParseJobPath leads with slash
func ParseJobPath(jobName string) (path string) {
	path = jobName
	if jobName == "" || strings.HasPrefix(jobName, "/job") {
		return
	}

294 295 296 297 298 299 300 301
	jobItems := strings.Split(jobName, " ")
	path = ""
	for _, item := range jobItems {
		path = fmt.Sprintf("%s/job/%s", path, item)
	}
	return
}

302
// JobLog holds the log text
LinuxSuRen's avatar
LinuxSuRen 已提交
303 304 305 306 307 308
type JobLog struct {
	HasMore   bool
	NextStart int64
	Text      string
}

309 310 311 312 313 314 315
// JenkinsItem represents the item of Jenkins
type JenkinsItem struct {
	Name        string
	DisplayName string
	URL         string
	Description string
	Type        string
LinuxSuRen's avatar
LinuxSuRen 已提交
316
}
LinuxSuRen's avatar
LinuxSuRen 已提交
317

LinuxSuRen's avatar
LinuxSuRen 已提交
318
// Job represents a job
LinuxSuRen's avatar
LinuxSuRen 已提交
319
type Job struct {
320
	Type            string `json:"_class"`
LinuxSuRen's avatar
LinuxSuRen 已提交
321 322 323 324 325 326 327
	Builds          []JobBuild
	Color           string
	ConcurrentBuild bool
	Name            string
	NextBuildNumber int
	URL             string
	Buildable       bool
328 329 330 331

	Property []ParametersDefinitionProperty
}

LinuxSuRen's avatar
LinuxSuRen 已提交
332
// ParametersDefinitionProperty holds the param definition property
333 334 335 336
type ParametersDefinitionProperty struct {
	ParameterDefinitions []ParameterDefinition
}

LinuxSuRen's avatar
LinuxSuRen 已提交
337
// ParameterDefinition holds the parameter definition
338 339 340 341 342 343 344 345
type ParameterDefinition struct {
	Description           string
	Name                  string `json:"name"`
	Type                  string
	Value                 string `json:"value"`
	DefaultParameterValue DefaultParameterValue
}

LinuxSuRen's avatar
LinuxSuRen 已提交
346
// DefaultParameterValue represents the default value for param
347 348
type DefaultParameterValue struct {
	Description string
349
	Value       interface{}
LinuxSuRen's avatar
LinuxSuRen 已提交
350 351
}

LinuxSuRen's avatar
LinuxSuRen 已提交
352
// SimpleJobBuild represents a simple job build
LinuxSuRen's avatar
LinuxSuRen 已提交
353
type SimpleJobBuild struct {
LinuxSuRen's avatar
LinuxSuRen 已提交
354 355 356
	Number int
	URL    string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
357

LinuxSuRen's avatar
LinuxSuRen 已提交
358
// JobBuild represents a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
type JobBuild struct {
	SimpleJobBuild
	Building          bool
	Description       string
	DisplayName       string
	Duration          int64
	EstimatedDuration int64
	FullDisplayName   string
	ID                string
	KeepLog           bool
	QueueID           int
	Result            string
	Timestamp         int64
	PreviousBuild     SimpleJobBuild
	NextBuild         SimpleJobBuild
}

LinuxSuRen's avatar
LinuxSuRen 已提交
376
// Pipeline represents a pipeline
LinuxSuRen's avatar
LinuxSuRen 已提交
377 378 379 380
type Pipeline struct {
	Script  string
	Sandbox bool
}
381

LinuxSuRen's avatar
LinuxSuRen 已提交
382
// JobCategory represents a job category
383 384 385 386 387 388 389 390 391
type JobCategory struct {
	Description string
	ID          string
	Items       []JobCategoryItem
	MinToShow   int
	Name        string
	Order       int
}

LinuxSuRen's avatar
LinuxSuRen 已提交
392
// JobCategoryItem represents a job category item
393 394 395 396
type JobCategoryItem struct {
	Description string
	DisplayName string
	Order       int
397
	Class       string
398
}
399 400 401 402 403 404 405 406 407

// JobInputItem represents a job input action
type JobInputItem struct {
	ID                  string
	AbortURL            string
	Message             string
	ProceedText         string
	ProceedURL          string
	RedirectApprovalURL string
408
	Inputs              []ParameterDefinition
409
}