417 lines
11 KiB
Go
417 lines
11 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"
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/emicklei/go-restful"
|
|
|
|
"kubesphere.io/kubesphere/pkg/simple/client/devops"
|
|
)
|
|
|
|
const (
|
|
Git = "git"
|
|
Hg = "hg"
|
|
Svn = "svc"
|
|
)
|
|
|
|
type Build struct {
|
|
Raw *devops.Build
|
|
Job *Job
|
|
Jenkins *Jenkins
|
|
Base string
|
|
Depth int
|
|
}
|
|
|
|
type Parameter struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
type Branch struct {
|
|
SHA1 string `json:",omitempty"`
|
|
Name string `json:",omitempty"`
|
|
}
|
|
|
|
type BuildRevision struct {
|
|
SHA1 string `json:"SHA1,omitempty"`
|
|
Branch []Branch `json:"Branch,omitempty"`
|
|
}
|
|
|
|
type Builds struct {
|
|
BuildNumber int64 `json:"buildNumber"`
|
|
BuildResult interface{} `json:"buildResult"`
|
|
Marked BuildRevision `json:"marked"`
|
|
Revision BuildRevision `json:"revision"`
|
|
}
|
|
|
|
type Culprit struct {
|
|
AbsoluteUrl string
|
|
FullName string
|
|
}
|
|
|
|
type GeneralObj struct {
|
|
Parameters []Parameter `json:"parameters,omitempty"`
|
|
Causes []map[string]interface{} `json:"causes,omitempty"`
|
|
BuildsByBranchName map[string]Builds `json:"buildsByBranchName,omitempty"`
|
|
LastBuiltRevision *BuildRevision `json:"lastBuiltRevision,omitempty"`
|
|
RemoteUrls []string `json:"remoteUrls,omitempty"`
|
|
ScmName string `json:"scmName,omitempty"`
|
|
MercurialNodeName string `json:"mercurialNodeName,omitempty"`
|
|
MercurialRevisionNumber string `json:"mercurialRevisionNumber,omitempty"`
|
|
Subdir interface{} `json:"subdir,omitempty"`
|
|
ClassName string `json:"_class,omitempty"`
|
|
SonarTaskId string `json:"ceTaskId,omitempty"`
|
|
SonarServerUrl string `json:"serverUrl,omitempty"`
|
|
SonarDashboardUrl string `json:"sonarqubeDashboardUrl,omitempty"`
|
|
TotalCount int64 `json:",omitempty"`
|
|
UrlName string `json:",omitempty"`
|
|
}
|
|
|
|
type TestResult struct {
|
|
Duration int64 `json:"duration"`
|
|
Empty bool `json:"empty"`
|
|
FailCount int64 `json:"failCount"`
|
|
PassCount int64 `json:"passCount"`
|
|
SkipCount int64 `json:"skipCount"`
|
|
Suites []struct {
|
|
Cases []struct {
|
|
Age int64 `json:"age"`
|
|
ClassName string `json:"className"`
|
|
Duration int64 `json:"duration"`
|
|
ErrorDetails interface{} `json:"errorDetails"`
|
|
ErrorStackTrace interface{} `json:"errorStackTrace"`
|
|
FailedSince int64 `json:"failedSince"`
|
|
Name string `json:"name"`
|
|
Skipped bool `json:"skipped"`
|
|
SkippedMessage interface{} `json:"skippedMessage"`
|
|
Status string `json:"status"`
|
|
Stderr interface{} `json:"stderr"`
|
|
Stdout interface{} `json:"stdout"`
|
|
} `json:"cases"`
|
|
Duration int64 `json:"duration"`
|
|
ID interface{} `json:"id"`
|
|
Name string `json:"name"`
|
|
Stderr interface{} `json:"stderr"`
|
|
Stdout interface{} `json:"stdout"`
|
|
Timestamp interface{} `json:"timestamp"`
|
|
} `json:"suites"`
|
|
}
|
|
|
|
type BuildResponse struct {
|
|
Actions []devops.GeneralAction
|
|
Artifacts []struct {
|
|
DisplayPath string `json:"displayPath"`
|
|
FileName string `json:"fileName"`
|
|
RelativePath string `json:"relativePath"`
|
|
} `json:"artifacts"`
|
|
Building bool `json:"building"`
|
|
BuiltOn string `json:"builtOn"`
|
|
ChangeSet struct {
|
|
Items []struct {
|
|
AffectedPaths []string `json:"affectedPaths"`
|
|
Author struct {
|
|
AbsoluteUrl string `json:"absoluteUrl"`
|
|
FullName string `json:"fullName"`
|
|
} `json:"author"`
|
|
Comment string `json:"comment"`
|
|
CommitID string `json:"commitId"`
|
|
Date string `json:"date"`
|
|
ID string `json:"id"`
|
|
Msg string `json:"msg"`
|
|
Paths []struct {
|
|
EditType string `json:"editType"`
|
|
File string `json:"file"`
|
|
} `json:"paths"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
} `json:"items"`
|
|
Kind string `json:"kind"`
|
|
Revisions []struct {
|
|
Module string
|
|
Revision int
|
|
} `json:"revision"`
|
|
} `json:"changeSet"`
|
|
Culprits []devops.Culprit `json:"culprits"`
|
|
Description interface{} `json:"description"`
|
|
Duration int64 `json:"duration"`
|
|
EstimatedDuration int64 `json:"estimatedDuration"`
|
|
Executor interface{} `json:"executor"`
|
|
FullDisplayName string `json:"fullDisplayName"`
|
|
ID string `json:"id"`
|
|
KeepLog bool `json:"keepLog"`
|
|
Number int64 `json:"number"`
|
|
QueueID int64 `json:"queueId"`
|
|
Result string `json:"result"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
URL string `json:"url"`
|
|
MavenArtifacts interface{} `json:"mavenArtifacts"`
|
|
MavenVersionUsed string `json:"mavenVersionUsed"`
|
|
Runs []struct {
|
|
Number int64
|
|
URL string
|
|
} `json:"runs"`
|
|
}
|
|
|
|
// Builds
|
|
func (b *Build) Info() *devops.Build {
|
|
return b.Raw
|
|
}
|
|
|
|
func (b *Build) GetUrl() string {
|
|
return b.Raw.URL
|
|
}
|
|
|
|
func (b *Build) GetBuildNumber() int64 {
|
|
return b.Raw.Number
|
|
}
|
|
func (b *Build) GetResult() string {
|
|
return b.Raw.Result
|
|
}
|
|
|
|
func (b *Build) Stop() (bool, error) {
|
|
if b.IsRunning() {
|
|
response, err := b.Jenkins.Requester.Post(b.Base+"/stop", nil, nil, nil)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if response.StatusCode != http.StatusOK {
|
|
return false, errors.New(strconv.Itoa(response.StatusCode))
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (b *Build) GetConsoleOutput() string {
|
|
url := b.Base + "/consoleText"
|
|
var content string
|
|
b.Jenkins.Requester.GetXML(url, &content, nil)
|
|
return content
|
|
}
|
|
|
|
func (b *Build) GetCauses() ([]map[string]interface{}, error) {
|
|
_, err := b.Poll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, a := range b.Raw.Actions {
|
|
if a.Causes != nil {
|
|
return a.Causes, nil
|
|
}
|
|
}
|
|
return nil, errors.New("No Causes")
|
|
}
|
|
|
|
func (b *Build) GetInjectedEnvVars() (map[string]string, error) {
|
|
var envVars struct {
|
|
EnvMap map[string]string `json:"envMap"`
|
|
}
|
|
endpoint := b.Base + "/injectedEnvVars"
|
|
_, err := b.Jenkins.Requester.GetJSON(endpoint, &envVars, nil)
|
|
if err != nil {
|
|
return envVars.EnvMap, err
|
|
}
|
|
return envVars.EnvMap, nil
|
|
}
|
|
|
|
func (b *Build) GetDownstreamBuilds() ([]*Build, error) {
|
|
result := make([]*Build, 0)
|
|
downstreamJobs, err := b.Job.GetDownstreamJobs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, job := range downstreamJobs {
|
|
allBuildIDs, err := job.GetAllBuildIds()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, buildID := range allBuildIDs {
|
|
build, err := job.GetBuild(buildID.Number)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
upstreamBuild, _ := build.GetUpstreamBuild()
|
|
// cannot compare only id, it can be from different job
|
|
if b.GetUrl() == upstreamBuild.GetUrl() {
|
|
result = append(result, build)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *Build) GetUpstreamJob() (*Job, error) {
|
|
causes, err := b.GetCauses()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(causes) > 0 {
|
|
if job, ok := causes[0]["upstreamProject"]; ok {
|
|
return b.Jenkins.GetJob(job.(string))
|
|
}
|
|
}
|
|
return nil, errors.New("Unable to get Upstream Job")
|
|
}
|
|
|
|
func (b *Build) GetUpstreamBuildNumber() (int64, error) {
|
|
causes, err := b.GetCauses()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(causes) > 0 {
|
|
if build, ok := causes[0]["upstreamBuild"]; ok {
|
|
switch t := build.(type) {
|
|
default:
|
|
return t.(int64), nil
|
|
case float64:
|
|
return int64(t), nil
|
|
}
|
|
}
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func (b *Build) GetUpstreamBuild() (*Build, error) {
|
|
job, err := b.GetUpstreamJob()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if job != nil {
|
|
buildNumber, err := b.GetUpstreamBuildNumber()
|
|
if err == nil {
|
|
return job.GetBuild(buildNumber)
|
|
}
|
|
}
|
|
return nil, errors.New("Build not found")
|
|
}
|
|
|
|
func (b *Build) GetResultSet() (*TestResult, error) {
|
|
|
|
url := b.Base + "/testReport"
|
|
var report TestResult
|
|
|
|
_, err := b.Jenkins.Requester.GetJSON(url, &report, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &report, nil
|
|
|
|
}
|
|
|
|
func (b *Build) GetTimestamp() time.Time {
|
|
msInt := int64(b.Raw.Timestamp)
|
|
return time.Unix(0, msInt*int64(time.Millisecond))
|
|
}
|
|
|
|
func (b *Build) GetDuration() int64 {
|
|
return b.Raw.Duration
|
|
}
|
|
|
|
func (b *Build) GetRevisionBranch() string {
|
|
vcs := b.Raw.ChangeSet.Kind
|
|
if vcs == Git {
|
|
for _, a := range b.Raw.Actions {
|
|
if len(a.LastBuiltRevision.Branch) > 0 && a.LastBuiltRevision.Branch[0].SHA1 != "" {
|
|
return a.LastBuiltRevision.Branch[0].SHA1
|
|
}
|
|
}
|
|
} else {
|
|
panic("Not implemented")
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b *Build) IsGood() bool {
|
|
return !b.IsRunning() && b.Raw.Result == STATUS_SUCCESS
|
|
}
|
|
|
|
func (b *Build) IsRunning() bool {
|
|
_, err := b.Poll()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return b.Raw.Building
|
|
}
|
|
|
|
func (b *Build) SetDescription(description string) error {
|
|
data := url.Values{}
|
|
data.Set("description", description)
|
|
_, err := b.Jenkins.Requester.Post(b.Base+"/submitDescription", bytes.NewBufferString(data.Encode()), nil, nil)
|
|
return err
|
|
}
|
|
func (b *Build) PauseToggle() error {
|
|
response, err := b.Jenkins.Requester.Post(b.Base+"/pause/toggle", nil, nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if response.StatusCode != http.StatusOK {
|
|
return errors.New(strconv.Itoa(response.StatusCode))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Poll for current data. Optional Parameter - depth.
|
|
// More about depth here: https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API
|
|
func (b *Build) Poll(options ...interface{}) (int, error) {
|
|
depth := "-1"
|
|
|
|
for _, o := range options {
|
|
switch v := o.(type) {
|
|
case string:
|
|
depth = v
|
|
case int:
|
|
depth = strconv.Itoa(v)
|
|
case int64:
|
|
depth = strconv.FormatInt(v, 10)
|
|
}
|
|
}
|
|
if depth == "-1" {
|
|
depth = strconv.Itoa(b.Depth)
|
|
}
|
|
|
|
qr := map[string]string{
|
|
"depth": depth,
|
|
}
|
|
response, err := b.Jenkins.Requester.GetJSON(b.Base, b.Raw, qr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return response.StatusCode, nil
|
|
}
|
|
|
|
func (j *Jenkins) GetProjectPipelineBuildByType(projectId, pipelineId string, status string) (*devops.Build, error) {
|
|
job, err := j.GetJob(pipelineId, projectId)
|
|
if err != nil {
|
|
return nil, restful.NewError(devops.GetDevOpsStatusCode(err), err.Error())
|
|
}
|
|
build, err := job.getBuildByType(status)
|
|
return build.Raw, nil
|
|
}
|
|
func (j *Jenkins) GetMultiBranchPipelineBuildByType(projectId, pipelineId, branch string, status string) (*devops.Build, error) {
|
|
job, err := j.GetJob(branch, projectId, pipelineId)
|
|
if err != nil {
|
|
return nil, restful.NewError(devops.GetDevOpsStatusCode(err), err.Error())
|
|
}
|
|
build, err := job.getBuildByType(status)
|
|
return build.Raw, nil
|
|
}
|