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

import (
	"encoding/json"
	"fmt"
6
	"go.uber.org/zap"
LinuxSuRen's avatar
LinuxSuRen 已提交
7
	"io/ioutil"
8
	"moul.io/http2curl"
LinuxSuRen's avatar
LinuxSuRen 已提交
9
	"net/http"
LinuxSuRen's avatar
LinuxSuRen 已提交
10
	"net/url"
LinuxSuRen's avatar
LinuxSuRen 已提交
11 12
	"strconv"
	"strings"
13 14

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

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

	Parent string
LinuxSuRen's avatar
LinuxSuRen 已提交
22 23 24
}

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

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

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

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

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

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

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

LinuxSuRen's avatar
LinuxSuRen 已提交
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
// DisableJob disable a job
func (q *JobClient) DisableJob(jobName string) (err error) {
	path := ParseJobPath(jobName)
	api := fmt.Sprintf("%s/disable", path)

	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
	return
}

// EnableJob disable a job
func (q *JobClient) EnableJob(jobName string) (err error) {
	path := ParseJobPath(jobName)
	api := fmt.Sprintf("%s/enable", path)

	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
	return
}

93
// StopJob stops a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
94
func (q *JobClient) StopJob(jobName string, num int) (err error) {
95
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
96 97 98 99 100 101 102

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

104
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
105 106 107
	return
}

108
// GetJob returns the job info
LinuxSuRen's avatar
LinuxSuRen 已提交
109
func (q *JobClient) GetJob(name string) (job *Job, err error) {
110
	path := ParseJobPath(name)
111
	api := fmt.Sprintf("%s/api/json", path)
LinuxSuRen's avatar
LinuxSuRen 已提交
112

LinuxSuRen's avatar
LinuxSuRen 已提交
113
	err = q.RequestWithData("GET", api, nil, nil, 200, &job)
LinuxSuRen's avatar
LinuxSuRen 已提交
114 115 116
	return
}

117
// GetJobTypeCategories returns all categories of jobs
118 119
func (q *JobClient) GetJobTypeCategories() (jobCategories []JobCategory, err error) {
	var (
120 121
		statusCode int
		data       []byte
122 123
	)

124 125
	if statusCode, data, err = q.Request("GET", "/view/all/itemCategories?depth=3", nil, nil); err == nil {
		if statusCode == 200 {
126 127 128 129 130 131 132
			type innerJobCategories struct {
				Categories []JobCategory
			}
			result := &innerJobCategories{}
			err = json.Unmarshal(data, result)
			jobCategories = result.Categories
		} else {
133
			err = fmt.Errorf("unexpected status code: %d", statusCode)
134 135 136 137 138
		}
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
139 140
// GetPipeline return the pipeline object
func (q *JobClient) GetPipeline(name string) (pipeline *Pipeline, err error) {
141
	path := ParseJobPath(name)
LinuxSuRen's avatar
LinuxSuRen 已提交
142 143
	api := fmt.Sprintf("%s/restFul", path)
	err = q.RequestWithData("GET", api, nil, nil, 200, &pipeline)
LinuxSuRen's avatar
LinuxSuRen 已提交
144 145 146
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
147 148
// UpdatePipeline updates the pipeline script
func (q *JobClient) UpdatePipeline(name, script string) (err error) {
149 150 151
	formData := url.Values{}
	formData.Add("script", script)

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

155
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
156 157 158
	return
}

159
// GetHistory returns the build history of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
160
func (q *JobClient) GetHistory(name string) (builds []*JobBuild, err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
161 162
	var job *Job
	if job, err = q.GetJob(name); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
163 164 165 166 167 168 169
		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
170
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
171
			builds = append(builds, build)
172
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
173 174 175 176
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
177
// Log get the log of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
178
func (q *JobClient) Log(jobName string, history int, start int64) (jobLog JobLog, err error) {
179
	path := ParseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
180 181
	var api string
	if history == -1 {
LinuxSuRen's avatar
LinuxSuRen 已提交
182
		api = fmt.Sprintf("%s%s/lastBuild/logText/progressiveText?start=%d", q.URL, path, start)
LinuxSuRen's avatar
LinuxSuRen 已提交
183
	} else {
LinuxSuRen's avatar
LinuxSuRen 已提交
184
		api = fmt.Sprintf("%s%s/%d/logText/progressiveText?start=%d", q.URL, path, history, start)
LinuxSuRen's avatar
LinuxSuRen 已提交
185
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
186 187 188 189 190 191 192
	var (
		req      *http.Request
		response *http.Response
	)

	req, err = http.NewRequest("GET", api, nil)
	if err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
193 194 195
		err = q.AuthHandle(req)
	}
	if err != nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
196 197 198 199 200 201 202 203 204
		return
	}

	client := q.GetClient()
	jobLog = JobLog{
		HasMore:   false,
		Text:      "",
		NextStart: int64(0),
	}
205 206 207 208 209

	if curlCmd, curlErr := http2curl.GetCurlCommand(req); curlErr == nil {
		logger.Debug("HTTP request as curl", zap.String("cmd", curlCmd.String()))
	}

LinuxSuRen's avatar
LinuxSuRen 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
	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
}

226 227 228 229 230 231
// CreateJobPayload the payload for creating a job
type CreateJobPayload struct {
	Name string `json:"name"`
	Mode string `json:"mode"`
	From string `json:"from"`
}
232

233 234 235
// Create can create a job
func (q *JobClient) Create(jobPayload CreateJobPayload) (err error) {
	playLoadData, _ := json.Marshal(jobPayload)
236 237
	formData := url.Values{
		"json": {string(playLoadData)},
238 239 240
		"name": {jobPayload.Name},
		"mode": {jobPayload.Mode},
		"from": {jobPayload.From},
241 242 243
	}
	payload := strings.NewReader(formData.Encode())

LinuxSuRen's avatar
LinuxSuRen 已提交
244
	var code int
245 246
	code, err = q.RequestWithoutData("POST", "/view/all/createItem",
		map[string]string{util.ContentType: util.ApplicationForm}, payload, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
247 248
	if code == 302 {
		err = nil
249 250 251 252
	}
	return
}

253
// Delete will delete a job by name
254 255
func (q *JobClient) Delete(jobName string) (err error) {
	var (
256
		statusCode int
257
	)
258

259 260
	jobName = ParseJobPath(jobName)
	api := fmt.Sprintf("%s/doDelete", jobName)
261
	header := map[string]string{
LinuxSuRen's avatar
LinuxSuRen 已提交
262
		util.ContentType: util.ApplicationForm,
263 264
	}

LinuxSuRen's avatar
LinuxSuRen 已提交
265
	if statusCode, _, err = q.Request("POST", api, header, nil); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
266
		if statusCode != 200 && statusCode != 302 {
267
			err = fmt.Errorf("unexpected status code: %d", statusCode)
268 269 270 271 272
		}
	}
	return
}

273 274
// GetJobInputActions returns the all pending actions
func (q *JobClient) GetJobInputActions(jobName string, buildID int) (actions []JobInputItem, err error) {
275
	path := ParseJobPath(jobName)
276 277 278 279 280 281 282 283 284 285 286
	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) {
287
	jobPath := ParseJobPath(jobName)
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
	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
}

314 315 316
// ParseJobPath leads with slash
func ParseJobPath(jobName string) (path string) {
	path = jobName
317 318
	if jobName == "" || strings.HasPrefix(jobName, "/job/") ||
		strings.HasPrefix(jobName, "job/") {
319 320 321
		return
	}

322 323 324 325 326 327 328 329
	jobItems := strings.Split(jobName, " ")
	path = ""
	for _, item := range jobItems {
		path = fmt.Sprintf("%s/job/%s", path, item)
	}
	return
}

330
// JobLog holds the log text
LinuxSuRen's avatar
LinuxSuRen 已提交
331 332 333 334 335 336
type JobLog struct {
	HasMore   bool
	NextStart int64
	Text      string
}

337 338 339 340 341 342 343
// JenkinsItem represents the item of Jenkins
type JenkinsItem struct {
	Name        string
	DisplayName string
	URL         string
	Description string
	Type        string
344 345 346 347 348 349 350 351 352

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

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

LinuxSuRen's avatar
LinuxSuRen 已提交
355
// Job represents a job
LinuxSuRen's avatar
LinuxSuRen 已提交
356
type Job struct {
357
	Type            string `json:"_class"`
LinuxSuRen's avatar
LinuxSuRen 已提交
358 359 360 361 362 363 364
	Builds          []JobBuild
	Color           string
	ConcurrentBuild bool
	Name            string
	NextBuildNumber int
	URL             string
	Buildable       bool
365 366 367 368

	Property []ParametersDefinitionProperty
}

LinuxSuRen's avatar
LinuxSuRen 已提交
369
// ParametersDefinitionProperty holds the param definition property
370 371 372 373
type ParametersDefinitionProperty struct {
	ParameterDefinitions []ParameterDefinition
}

LinuxSuRen's avatar
LinuxSuRen 已提交
374
// ParameterDefinition holds the parameter definition
375 376 377 378 379 380 381 382
type ParameterDefinition struct {
	Description           string
	Name                  string `json:"name"`
	Type                  string
	Value                 string `json:"value"`
	DefaultParameterValue DefaultParameterValue
}

LinuxSuRen's avatar
LinuxSuRen 已提交
383
// DefaultParameterValue represents the default value for param
384 385
type DefaultParameterValue struct {
	Description string
386
	Value       interface{}
LinuxSuRen's avatar
LinuxSuRen 已提交
387 388
}

LinuxSuRen's avatar
LinuxSuRen 已提交
389
// SimpleJobBuild represents a simple job build
LinuxSuRen's avatar
LinuxSuRen 已提交
390
type SimpleJobBuild struct {
LinuxSuRen's avatar
LinuxSuRen 已提交
391 392 393
	Number int
	URL    string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
394

LinuxSuRen's avatar
LinuxSuRen 已提交
395
// JobBuild represents a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
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 已提交
413
// Pipeline represents a pipeline
LinuxSuRen's avatar
LinuxSuRen 已提交
414 415 416 417
type Pipeline struct {
	Script  string
	Sandbox bool
}
418

LinuxSuRen's avatar
LinuxSuRen 已提交
419
// JobCategory represents a job category
420 421 422 423 424 425 426 427 428
type JobCategory struct {
	Description string
	ID          string
	Items       []JobCategoryItem
	MinToShow   int
	Name        string
	Order       int
}

LinuxSuRen's avatar
LinuxSuRen 已提交
429
// JobCategoryItem represents a job category item
430 431 432 433
type JobCategoryItem struct {
	Description string
	DisplayName string
	Order       int
434
	Class       string
435
}
436 437 438 439 440 441 442 443 444

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