job.go 10.3 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
type JobClient struct {
	JenkinsCore
LinuxSuRen's avatar
LinuxSuRen 已提交
18 19

	Parent string
LinuxSuRen's avatar
LinuxSuRen 已提交
20 21 22
}

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

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

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

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

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

	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 已提交
64 65
		formData := url.Values{"json": {fmt.Sprintf("{\"parameter\": %s}", string(paramJSON))}}
		payload := strings.NewReader(formData.Encode())
66

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

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

	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 已提交
83

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

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

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

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

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

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

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

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

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

139
// GetHistory returns the build history of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
140
func (q *JobClient) GetHistory(name string) (builds []*JobBuild, err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
141 142
	var job *Job
	if job, err = q.GetJob(name); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
143 144 145 146 147 148 149
		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
150
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
151
			builds = append(builds, build)
152
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
153 154 155 156
	}
	return
}

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

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

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

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

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

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

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

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

248 249
// GetJobInputActions returns the all pending actions
func (q *JobClient) GetJobInputActions(jobName string, buildID int) (actions []JobInputItem, err error) {
250
	path := ParseJobPath(jobName)
251 252 253 254 255 256 257 258 259 260 261
	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) {
262
	jobPath := ParseJobPath(jobName)
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
	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
}

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

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

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

312 313 314 315 316 317 318
// JenkinsItem represents the item of Jenkins
type JenkinsItem struct {
	Name        string
	DisplayName string
	URL         string
	Description string
	Type        string
319 320 321 322 323 324 325 326 327

	/** comes from Job */
	Buildable bool
	Building  bool
	InQueue   bool

	/** comes from ParameterizedJob */
	Parameterized bool
	Disabled      bool
LinuxSuRen's avatar
LinuxSuRen 已提交
328
}
LinuxSuRen's avatar
LinuxSuRen 已提交
329

LinuxSuRen's avatar
LinuxSuRen 已提交
330
// Job represents a job
LinuxSuRen's avatar
LinuxSuRen 已提交
331
type Job struct {
332
	Type            string `json:"_class"`
LinuxSuRen's avatar
LinuxSuRen 已提交
333 334 335 336 337 338 339
	Builds          []JobBuild
	Color           string
	ConcurrentBuild bool
	Name            string
	NextBuildNumber int
	URL             string
	Buildable       bool
340 341 342 343

	Property []ParametersDefinitionProperty
}

LinuxSuRen's avatar
LinuxSuRen 已提交
344
// ParametersDefinitionProperty holds the param definition property
345 346 347 348
type ParametersDefinitionProperty struct {
	ParameterDefinitions []ParameterDefinition
}

LinuxSuRen's avatar
LinuxSuRen 已提交
349
// ParameterDefinition holds the parameter definition
350 351 352 353 354 355 356 357
type ParameterDefinition struct {
	Description           string
	Name                  string `json:"name"`
	Type                  string
	Value                 string `json:"value"`
	DefaultParameterValue DefaultParameterValue
}

LinuxSuRen's avatar
LinuxSuRen 已提交
358
// DefaultParameterValue represents the default value for param
359 360
type DefaultParameterValue struct {
	Description string
361
	Value       interface{}
LinuxSuRen's avatar
LinuxSuRen 已提交
362 363
}

LinuxSuRen's avatar
LinuxSuRen 已提交
364
// SimpleJobBuild represents a simple job build
LinuxSuRen's avatar
LinuxSuRen 已提交
365
type SimpleJobBuild struct {
LinuxSuRen's avatar
LinuxSuRen 已提交
366 367 368
	Number int
	URL    string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
369

LinuxSuRen's avatar
LinuxSuRen 已提交
370
// JobBuild represents a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
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 已提交
388
// Pipeline represents a pipeline
LinuxSuRen's avatar
LinuxSuRen 已提交
389 390 391 392
type Pipeline struct {
	Script  string
	Sandbox bool
}
393

LinuxSuRen's avatar
LinuxSuRen 已提交
394
// JobCategory represents a job category
395 396 397 398 399 400 401 402 403
type JobCategory struct {
	Description string
	ID          string
	Items       []JobCategoryItem
	MinToShow   int
	Name        string
	Order       int
}

LinuxSuRen's avatar
LinuxSuRen 已提交
404
// JobCategoryItem represents a job category item
405 406 407 408
type JobCategoryItem struct {
	Description string
	DisplayName string
	Order       int
409
	Class       string
410
}
411 412 413 414 415 416 417 418 419

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