// 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" "github.com/emicklei/go-restful" "kubesphere.io/kubesphere/pkg/simple/client/devops" "net/http" "net/url" "strconv" "time" ) 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(pipelineId, projectId, branch) if err != nil { return nil, restful.NewError(devops.GetDevOpsStatusCode(err), err.Error()) } build, err := job.getBuildByType(status) return build.Raw, nil }