506 lines
13 KiB
Go
506 lines
13 KiB
Go
// Copyright 2015 Vadim Kravcenko
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package jenkins
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
|
)
|
|
|
|
type Job struct {
|
|
Raw *JobResponse
|
|
Jenkins *Jenkins
|
|
Base string
|
|
}
|
|
|
|
type JobBuild struct {
|
|
Number int64
|
|
URL string
|
|
}
|
|
|
|
type JobBuildStatus struct {
|
|
Number int64
|
|
Building bool
|
|
Result string
|
|
}
|
|
|
|
type InnerJob struct {
|
|
Name string `json:"name"`
|
|
Url string `json:"url"`
|
|
Color string `json:"color"`
|
|
}
|
|
|
|
type ParameterDefinition struct {
|
|
DefaultParameterValue struct {
|
|
Name string `json:"name"`
|
|
Value interface{} `json:"value"`
|
|
} `json:"defaultParameterValue"`
|
|
Description string `json:"description"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type JobResponse struct {
|
|
Class string `json:"_class"`
|
|
Actions []devops.GeneralAction
|
|
Buildable bool `json:"buildable"`
|
|
Builds []JobBuild
|
|
Color string `json:"color"`
|
|
ConcurrentBuild bool `json:"concurrentBuild"`
|
|
Description string `json:"description"`
|
|
DisplayName string `json:"displayName"`
|
|
DisplayNameOrNull interface{} `json:"displayNameOrNull"`
|
|
DownstreamProjects []InnerJob `json:"downstreamProjects"`
|
|
FirstBuild JobBuild
|
|
HealthReport []struct {
|
|
Description string `json:"description"`
|
|
IconClassName string `json:"iconClassName"`
|
|
IconUrl string `json:"iconUrl"`
|
|
Score int64 `json:"score"`
|
|
} `json:"healthReport"`
|
|
InQueue bool `json:"inQueue"`
|
|
KeepDependencies bool `json:"keepDependencies"`
|
|
LastBuild JobBuild `json:"lastBuild"`
|
|
LastCompletedBuild JobBuild `json:"lastCompletedBuild"`
|
|
LastFailedBuild JobBuild `json:"lastFailedBuild"`
|
|
LastStableBuild JobBuild `json:"lastStableBuild"`
|
|
LastSuccessfulBuild JobBuild `json:"lastSuccessfulBuild"`
|
|
LastUnstableBuild JobBuild `json:"lastUnstableBuild"`
|
|
LastUnsuccessfulBuild JobBuild `json:"lastUnsuccessfulBuild"`
|
|
Name string `json:"name"`
|
|
SubJobs []InnerJob `json:"subJobs"`
|
|
NextBuildNumber int64 `json:"nextBuildNumber"`
|
|
Property []struct {
|
|
ParameterDefinitions []ParameterDefinition `json:"parameterDefinitions"`
|
|
} `json:"property"`
|
|
QueueItem interface{} `json:"queueItem"`
|
|
Scm struct{} `json:"scm"`
|
|
UpstreamProjects []InnerJob `json:"upstreamProjects"`
|
|
URL string `json:"url"`
|
|
Jobs []InnerJob `json:"jobs"`
|
|
}
|
|
|
|
func (j *Job) parentBase() string {
|
|
return j.Base[:strings.LastIndex(j.Base, "/job/")]
|
|
}
|
|
|
|
type History struct {
|
|
BuildNumber int
|
|
BuildStatus string
|
|
BuildTimestamp int64
|
|
}
|
|
|
|
func (j *Job) GetName() string {
|
|
return j.Raw.Name
|
|
}
|
|
|
|
func (j *Job) GetDescription() string {
|
|
return j.Raw.Description
|
|
}
|
|
|
|
func (j *Job) GetDetails() *JobResponse {
|
|
return j.Raw
|
|
}
|
|
|
|
func (j *Job) GetBuild(id int64) (*Build, error) {
|
|
build := Build{Jenkins: j.Jenkins, Job: j, Raw: new(devops.Build), Depth: 1, Base: "/job/" + j.GetName() + "/" + strconv.FormatInt(id, 10)}
|
|
status, err := build.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if status == 200 {
|
|
return &build, nil
|
|
}
|
|
return nil, errors.New(strconv.Itoa(status))
|
|
}
|
|
|
|
func (j *Job) getBuildByType(buildType string) (*Build, error) {
|
|
allowed := map[string]JobBuild{
|
|
"lastStableBuild": j.Raw.LastStableBuild,
|
|
"lastSuccessfulBuild": j.Raw.LastSuccessfulBuild,
|
|
"lastBuild": j.Raw.LastBuild,
|
|
"lastCompletedBuild": j.Raw.LastCompletedBuild,
|
|
"firstBuild": j.Raw.FirstBuild,
|
|
"lastFailedBuild": j.Raw.LastFailedBuild,
|
|
}
|
|
number := ""
|
|
if val, ok := allowed[buildType]; ok {
|
|
number = strconv.FormatInt(val.Number, 10)
|
|
} else {
|
|
panic("No Such Build")
|
|
}
|
|
build := Build{
|
|
Jenkins: j.Jenkins,
|
|
Depth: 1,
|
|
Job: j,
|
|
Raw: new(devops.Build),
|
|
Base: j.Base + "/" + number}
|
|
status, err := build.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if status == 200 {
|
|
return &build, nil
|
|
}
|
|
return nil, errors.New(strconv.Itoa(status))
|
|
}
|
|
|
|
func (j *Job) GetLastSuccessfulBuild() (*Build, error) {
|
|
return j.getBuildByType("lastSuccessfulBuild")
|
|
}
|
|
|
|
func (j *Job) GetFirstBuild() (*Build, error) {
|
|
return j.getBuildByType("firstBuild")
|
|
}
|
|
|
|
func (j *Job) GetLastBuild() (*Build, error) {
|
|
return j.getBuildByType("lastBuild")
|
|
}
|
|
|
|
func (j *Job) GetLastStableBuild() (*Build, error) {
|
|
return j.getBuildByType("lastStableBuild")
|
|
}
|
|
|
|
func (j *Job) GetLastFailedBuild() (*Build, error) {
|
|
return j.getBuildByType("lastFailedBuild")
|
|
}
|
|
|
|
func (j *Job) GetLastCompletedBuild() (*Build, error) {
|
|
return j.getBuildByType("lastCompletedBuild")
|
|
}
|
|
|
|
// Returns All Builds with Number and URL
|
|
func (j *Job) GetAllBuildIds() ([]JobBuild, error) {
|
|
var buildsResp struct {
|
|
Builds []JobBuild `json:"allBuilds"`
|
|
}
|
|
_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,url]"})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildsResp.Builds, nil
|
|
}
|
|
|
|
func (j *Job) GetAllBuildStatus() ([]JobBuildStatus, error) {
|
|
var buildsResp struct {
|
|
Builds []JobBuildStatus `json:"allBuilds"`
|
|
}
|
|
_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,building,result]"})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buildsResp.Builds, nil
|
|
}
|
|
|
|
func (j *Job) GetUpstreamJobsMetadata() []InnerJob {
|
|
return j.Raw.UpstreamProjects
|
|
}
|
|
|
|
func (j *Job) GetDownstreamJobsMetadata() []InnerJob {
|
|
return j.Raw.DownstreamProjects
|
|
}
|
|
|
|
func (j *Job) GetInnerJobsMetadata() []InnerJob {
|
|
return j.Raw.Jobs
|
|
}
|
|
|
|
func (j *Job) GetUpstreamJobs() ([]*Job, error) {
|
|
jobs := make([]*Job, len(j.Raw.UpstreamProjects))
|
|
for i, job := range j.Raw.UpstreamProjects {
|
|
ji, err := j.Jenkins.GetJob(job.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
jobs[i] = ji
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (j *Job) GetDownstreamJobs() ([]*Job, error) {
|
|
jobs := make([]*Job, len(j.Raw.DownstreamProjects))
|
|
for i, job := range j.Raw.DownstreamProjects {
|
|
ji, err := j.Jenkins.GetJob(job.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
jobs[i] = ji
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (j *Job) GetInnerJob(id string) (*Job, error) {
|
|
job := Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: j.Base + "/job/" + id}
|
|
status, err := job.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if status == 200 {
|
|
return &job, nil
|
|
}
|
|
return nil, errors.New(strconv.Itoa(status))
|
|
}
|
|
|
|
func (j *Job) GetInnerJobs() ([]*Job, error) {
|
|
jobs := make([]*Job, len(j.Raw.Jobs))
|
|
for i, job := range j.Raw.Jobs {
|
|
ji, err := j.GetInnerJob(job.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
jobs[i] = ji
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (j *Job) Enable() (bool, error) {
|
|
resp, err := j.Jenkins.Requester.Post(j.Base+"/enable", nil, nil, nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return false, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (j *Job) Disable() (bool, error) {
|
|
resp, err := j.Jenkins.Requester.Post(j.Base+"/disable", nil, nil, nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return false, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (j *Job) Delete() (bool, error) {
|
|
resp, err := j.Jenkins.Requester.Post(j.Base+"/doDelete", nil, nil, nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
return false, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (j *Job) Rename(name string) (bool, error) {
|
|
data := url.Values{}
|
|
data.Set("newName", name)
|
|
_, err := j.Jenkins.Requester.Post(j.Base+"/doRename", bytes.NewBufferString(data.Encode()), nil, nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
j.Base = "/job/" + name
|
|
j.Poll()
|
|
return true, nil
|
|
}
|
|
|
|
func (j *Job) Create(config string, qr ...interface{}) (*Job, error) {
|
|
var querystring map[string]string
|
|
if len(qr) > 0 {
|
|
querystring = qr[0].(map[string]string)
|
|
}
|
|
resp, err := j.Jenkins.Requester.PostXML(j.parentBase()+"/createItem", config, j.Raw, querystring)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode == 200 {
|
|
j.Poll()
|
|
return j, nil
|
|
}
|
|
return nil, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
|
|
func (j *Job) Copy(destinationName string) (*Job, error) {
|
|
qr := map[string]string{"name": destinationName, "from": j.GetName(), "mode": "copy"}
|
|
resp, err := j.Jenkins.Requester.Post(j.parentBase()+"/createItem", nil, nil, qr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode == 200 {
|
|
newJob := &Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: "/job/" + destinationName}
|
|
_, err := newJob.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newJob, nil
|
|
}
|
|
return nil, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
|
|
func (j *Job) UpdateConfig(config string) error {
|
|
|
|
var querystring map[string]string
|
|
|
|
resp, err := j.Jenkins.Requester.PostXML(j.Base+"/config.xml", config, nil, querystring)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.StatusCode == 200 {
|
|
j.Poll()
|
|
return nil
|
|
}
|
|
return errors.New(strconv.Itoa(resp.StatusCode))
|
|
|
|
}
|
|
|
|
func (j *Job) GetConfig() (string, error) {
|
|
var data string
|
|
_, err := j.Jenkins.Requester.GetXML(j.Base+"/config.xml", &data, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (j *Job) GetParameters() ([]ParameterDefinition, error) {
|
|
_, err := j.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var parameters []ParameterDefinition
|
|
for _, property := range j.Raw.Property {
|
|
parameters = append(parameters, property.ParameterDefinitions...)
|
|
}
|
|
return parameters, nil
|
|
}
|
|
|
|
func (j *Job) IsQueued() (bool, error) {
|
|
if _, err := j.Poll(); err != nil {
|
|
return false, err
|
|
}
|
|
return j.Raw.InQueue, nil
|
|
}
|
|
|
|
func (j *Job) IsRunning() (bool, error) {
|
|
if _, err := j.Poll(); err != nil {
|
|
return false, err
|
|
}
|
|
lastBuild, err := j.GetLastBuild()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return lastBuild.IsRunning(), nil
|
|
}
|
|
|
|
func (j *Job) IsEnabled() (bool, error) {
|
|
if _, err := j.Poll(); err != nil {
|
|
return false, err
|
|
}
|
|
return j.Raw.Color != "disabled", nil
|
|
}
|
|
|
|
func (j *Job) HasQueuedBuild() {
|
|
panic("Not Implemented yet")
|
|
}
|
|
|
|
func (j *Job) InvokeSimple(params map[string]string) (int64, error) {
|
|
endpoint := "/build"
|
|
parameters, err := j.GetParameters()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(parameters) > 0 {
|
|
endpoint = "/buildWithParameters"
|
|
}
|
|
data := url.Values{}
|
|
for k, v := range params {
|
|
data.Set(k, v)
|
|
}
|
|
resp, err := j.Jenkins.Requester.Post(j.Base+endpoint, bytes.NewBufferString(data.Encode()), nil, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if resp.StatusCode != 200 && resp.StatusCode != 201 {
|
|
return 0, errors.New("Could not invoke job " + j.GetName())
|
|
}
|
|
|
|
location := resp.Header.Get("Location")
|
|
if location == "" {
|
|
return 0, errors.New("Don't have key \"Location\" in response of header")
|
|
}
|
|
|
|
u, err := url.Parse(location)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
number, err := strconv.ParseInt(path.Base(u.Path), 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return number, nil
|
|
}
|
|
|
|
func (j *Job) Invoke(files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) {
|
|
isRunning, err := j.IsRunning()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if isRunning && skipIfRunning {
|
|
return false, fmt.Errorf("Will not request new build because %s is already running", j.GetName())
|
|
}
|
|
|
|
base := "/build"
|
|
|
|
// If parameters are specified - url is /builWithParameters
|
|
if params != nil {
|
|
base = "/buildWithParameters"
|
|
} else {
|
|
params = make(map[string]string)
|
|
}
|
|
|
|
// If files are specified - url is /build
|
|
if files != nil {
|
|
base = "/build"
|
|
}
|
|
reqParams := map[string]string{}
|
|
buildParams := map[string]string{}
|
|
if securityToken != "" {
|
|
reqParams["token"] = securityToken
|
|
}
|
|
|
|
buildParams["json"] = string(makeJson(params))
|
|
b, _ := json.Marshal(buildParams)
|
|
resp, err := j.Jenkins.Requester.PostFiles(j.Base+base, bytes.NewBuffer(b), nil, reqParams, files)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if resp.StatusCode == 200 || resp.StatusCode == 201 {
|
|
return true, nil
|
|
}
|
|
return false, errors.New(strconv.Itoa(resp.StatusCode))
|
|
}
|
|
|
|
func (j *Job) Poll() (int, error) {
|
|
response, err := j.Jenkins.Requester.GetJSON(j.Base, j.Raw, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return response.StatusCode, nil
|
|
}
|