job.go 10.0 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
func (q *JobClient) GetHistory(name string) (builds []*JobBuild, err error) {
LinuxSuRen's avatar
LinuxSuRen 已提交
135 136
	var job *Job
	if job, err = q.GetJob(name); err == nil {
LinuxSuRen's avatar
LinuxSuRen 已提交
137 138 139 140 141 142 143
		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
144
			}
LinuxSuRen's avatar
LinuxSuRen 已提交
145
			builds = append(builds, build)
146
		}
LinuxSuRen's avatar
LinuxSuRen 已提交
147 148 149 150
	}
	return
}

LinuxSuRen's avatar
LinuxSuRen 已提交
151
// Log get the log of a job
LinuxSuRen's avatar
LinuxSuRen 已提交
152
func (q *JobClient) Log(jobName string, history int, start int64) (jobLog JobLog, err error) {
153
	path := parseJobPath(jobName)
LinuxSuRen's avatar
LinuxSuRen 已提交
154 155 156 157 158 159
	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 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	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
}

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

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

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

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

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

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

248 249 250 251 252 253 254 255 256 257 258 259 260 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 287 288
// 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
}

289
// parseJobPath leads with slash
290 291 292 293 294 295 296 297 298
func parseJobPath(jobName string) (path string) {
	jobItems := strings.Split(jobName, " ")
	path = ""
	for _, item := range jobItems {
		path = fmt.Sprintf("%s/job/%s", path, item)
	}
	return
}

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

306
// SearchResult holds the result items
LinuxSuRen's avatar
LinuxSuRen 已提交
307 308 309 310
type SearchResult struct {
	Suggestions []SearchResultItem
}

LinuxSuRen's avatar
LinuxSuRen 已提交
311
// SearchResultItem hold the result item
LinuxSuRen's avatar
LinuxSuRen 已提交
312 313 314
type SearchResultItem struct {
	Name string
}
LinuxSuRen's avatar
LinuxSuRen 已提交
315

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

	Property []ParametersDefinitionProperty
}

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

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

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

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

LinuxSuRen's avatar
LinuxSuRen 已提交
356
// JobBuild represents a job build
LinuxSuRen's avatar
LinuxSuRen 已提交
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
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 已提交
374
// Pipeline represents a pipeline
LinuxSuRen's avatar
LinuxSuRen 已提交
375 376 377 378
type Pipeline struct {
	Script  string
	Sandbox bool
}
379

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

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

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