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

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

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

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

// Search find a set of jobs by name
LinuxSuRen's avatar
LinuxSuRen 已提交
22 23
func (q *JobClient) Search(keyword string, max int) (status *SearchResult, err error) {
	err = q.RequestWithData("GET", fmt.Sprintf("/search/suggest?query=%s&max=%d", keyword, max), nil, nil, 200, &status)
LinuxSuRen's avatar
LinuxSuRen 已提交
24 25
	return
}
26 27

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

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

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

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

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

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

70
// StopJob stops a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
71
func (q *JobClient) StopJob(jobName string, num int) (err error) {
72
	path := parseJobPath(jobName)
73
	api := fmt.Sprintf("%s/%d/stop", path, num)
LinuxSuRen's avatar
LinuxSuRen 已提交
74

75
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
76 77 78
	return
}

79
// GetJob returns the job info
LinuxSuRen's avatar
LinuxSuRen 已提交
80
func (q *JobClient) GetJob(name string) (job *Job, err error) {
81
	path := parseJobPath(name)
82
	api := fmt.Sprintf("%s/api/json", path)
LinuxSuRen's avatar
LinuxSuRen 已提交
83

LinuxSuRen's avatar
LinuxSuRen 已提交
84
	err = q.RequestWithData("GET", api, nil, nil, 200, &job)
LinuxSuRen's avatar
LinuxSuRen 已提交
85 86 87
	return
}

88
// GetJobTypeCategories returns all categories of jobs
89 90
func (q *JobClient) GetJobTypeCategories() (jobCategories []JobCategory, err error) {
	var (
91 92
		statusCode int
		data       []byte
93 94
	)

95 96
	if statusCode, data, err = q.Request("GET", "/view/all/itemCategories?depth=3", nil, nil); err == nil {
		if statusCode == 200 {
97 98 99 100 101 102 103
			type innerJobCategories struct {
				Categories []JobCategory
			}
			result := &innerJobCategories{}
			err = json.Unmarshal(data, result)
			jobCategories = result.Categories
		} else {
104 105 106 107
			err = fmt.Errorf("unexpected status code: %d", statusCode)
			if q.Debug {
				ioutil.WriteFile("debug.html", data, 0664)
			}
108 109 110 111 112
		}
	}
	return
}

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

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

126
	path := parseJobPath(name)
127
	api := fmt.Sprintf("%s/restFul/update?%s", path, formData.Encode())
LinuxSuRen's avatar
LinuxSuRen 已提交
128

129
	_, err = q.RequestWithoutData("POST", api, nil, nil, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
130 131 132
	return
}

133
// GetHistory returns the build history of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
134 135 136 137
func (q *JobClient) GetHistory(name string) (builds []JobBuild, err error) {
	var job *Job
	if job, err = q.GetJob(name); err == nil {
		builds = job.Builds
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

		for i, build := range builds {
			api := fmt.Sprintf("%sapi/json", build.URL)
			var (
				req      *http.Request
				response *http.Response
			)

			req, err = http.NewRequest("GET", api, nil)
			if err == nil {
				q.AuthHandle(req)
			} else {
				return
			}
			client := q.GetClient()
			if response, err = client.Do(req); err == nil {
				code := response.StatusCode
				var data []byte
				data, err = ioutil.ReadAll(response.Body)
				if code == 200 {
					err = json.Unmarshal(data, &build)
					builds[i] = build
				} else {
					log.Fatal(string(data))
				}
			} else {
				log.Fatal(err)
			}
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
167 168 169 170
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
171
// Log get the log of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
172
func (q *JobClient) Log(jobName string, history int, start int64) (jobLog JobLog, err error) {
173
	path := parseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
174 175 176 177 178 179
	var api string
	if history == -1 {
		api = fmt.Sprintf("%s/%s/lastBuild/logText/progressiveText?start=%d", q.URL, path, start)
	} else {
		api = fmt.Sprintf("%s/%s/%d/logText/progressiveText?start=%d", q.URL, path, history, start)
	}
LinuxSuRen's avatar
LinuxSuRen 已提交
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	var (
		req      *http.Request
		response *http.Response
	)

	req, err = http.NewRequest("GET", api, nil)
	if err == nil {
		q.AuthHandle(req)
	} else {
		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)
			}
		} else {
			log.Fatal(string(data))
		}
	} else {
		log.Fatal(err)
	}
	return
}

218 219 220 221 222 223
// CreateJobPayload the payload for creating a job
type CreateJobPayload struct {
	Name string `json:"name"`
	Mode string `json:"mode"`
	From string `json:"from"`
}
224

225 226 227
// Create can create a job
func (q *JobClient) Create(jobPayload CreateJobPayload) (err error) {
	playLoadData, _ := json.Marshal(jobPayload)
228 229
	formData := url.Values{
		"json": {string(playLoadData)},
230 231 232
		"name": {jobPayload.Name},
		"mode": {jobPayload.Mode},
		"from": {jobPayload.From},
233 234 235
	}
	payload := strings.NewReader(formData.Encode())

LinuxSuRen's avatar
LinuxSuRen 已提交
236
	var code int
237 238
	code, err = q.RequestWithoutData("POST", "/view/all/createItem",
		map[string]string{util.ContentType: util.ApplicationForm}, payload, 200)
LinuxSuRen's avatar
LinuxSuRen 已提交
239 240
	if code == 302 {
		err = nil
241 242 243 244
	}
	return
}

245
// Delete will delete a job by name
246 247
func (q *JobClient) Delete(jobName string) (err error) {
	var (
248 249
		statusCode int
		data       []byte
250
	)
251 252 253

	api := fmt.Sprintf("/job/%s/doDelete", jobName)
	header := map[string]string{
LinuxSuRen's avatar
LinuxSuRen 已提交
254
		util.ContentType: util.ApplicationForm,
255 256
	}

257
	if statusCode, data, err = q.Request("POST", api, header, nil); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
258
		if statusCode != 200 && statusCode != 302 {
259 260 261 262
			err = fmt.Errorf("unexpected status code: %d", statusCode)
			if q.Debug {
				ioutil.WriteFile("debug.html", data, 0664)
			}
263 264 265 266 267
		}
	}
	return
}

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

309
// parseJobPath leads with slash
310 311 312 313 314 315 316 317 318
func parseJobPath(jobName string) (path string) {
	jobItems := strings.Split(jobName, " ")
	path = ""
	for _, item := range jobItems {
		path = fmt.Sprintf("%s/job/%s", path, item)
	}
	return
}

319
// JobLog holds the log text
LinuxSuRen's avatar
LinuxSuRen 已提交
320 321 322 323 324 325
type JobLog struct {
	HasMore   bool
	NextStart int64
	Text      string
}

326
// SearchResult holds the result items
LinuxSuRen's avatar
LinuxSuRen 已提交
327 328 329 330
type SearchResult struct {
	Suggestions []SearchResultItem
}

LinuxSuRen's avatar
LinuxSuRen 已提交
331
// SearchResultItem hold the result item
LinuxSuRen's avatar
LinuxSuRen 已提交
332 333 334
type SearchResultItem struct {
	Name string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
335

LinuxSuRen's avatar
LinuxSuRen 已提交
336
// Job represents a job
LinuxSuRen's avatar
LinuxSuRen 已提交
337
type Job struct {
338
	Type            string `json:"_class"`
LinuxSuRen's avatar
LinuxSuRen 已提交
339 340 341 342 343 344 345
	Builds          []JobBuild
	Color           string
	ConcurrentBuild bool
	Name            string
	NextBuildNumber int
	URL             string
	Buildable       bool
346 347 348 349

	Property []ParametersDefinitionProperty
}

LinuxSuRen's avatar
LinuxSuRen 已提交
350
// ParametersDefinitionProperty holds the param definition property
351 352 353 354
type ParametersDefinitionProperty struct {
	ParameterDefinitions []ParameterDefinition
}

LinuxSuRen's avatar
LinuxSuRen 已提交
355
// ParameterDefinition holds the parameter definition
356 357 358 359 360 361 362 363
type ParameterDefinition struct {
	Description           string
	Name                  string `json:"name"`
	Type                  string
	Value                 string `json:"value"`
	DefaultParameterValue DefaultParameterValue
}

LinuxSuRen's avatar
LinuxSuRen 已提交
364
// DefaultParameterValue represents the default value for param
365 366
type DefaultParameterValue struct {
	Description string
367
	Value       interface{}
LinuxSuRen's avatar
LinuxSuRen 已提交
368 369
}

LinuxSuRen's avatar
LinuxSuRen 已提交
370
// SimpleJobBuild represents a simple job build
LinuxSuRen's avatar
LinuxSuRen 已提交
371
type SimpleJobBuild struct {
LinuxSuRen's avatar
LinuxSuRen 已提交
372 373 374
	Number int
	URL    string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
375

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

LinuxSuRen's avatar
LinuxSuRen 已提交
400
// JobCategory represents a job category
401 402 403 404 405 406 407 408 409
type JobCategory struct {
	Description string
	ID          string
	Items       []JobCategoryItem
	MinToShow   int
	Name        string
	Order       int
}

LinuxSuRen's avatar
LinuxSuRen 已提交
410
// JobCategoryItem represents a job category item
411 412 413 414
type JobCategoryItem struct {
	Description string
	DisplayName string
	Order       int
415
	Class       string
416
}
417 418 419 420 421 422 423 424 425

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