github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/orchestrator/jenkins.go (about) 1 package orchestrator 2 3 import ( 4 "encoding/json" 5 "io" 6 "strings" 7 "time" 8 9 "github.com/Jeffail/gabs/v2" 10 piperHttp "github.com/SAP/jenkins-library/pkg/http" 11 "github.com/SAP/jenkins-library/pkg/log" 12 "github.com/pkg/errors" 13 ) 14 15 type JenkinsConfigProvider struct { 16 client piperHttp.Client 17 apiInformation map[string]interface{} 18 } 19 20 // InitOrchestratorProvider initializes the Jenkins orchestrator with credentials 21 func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { 22 j.client.SetOptions(piperHttp.ClientOptions{ 23 Username: settings.JenkinsUser, 24 Password: settings.JenkinsToken, 25 MaxRetries: 3, 26 TransportTimeout: time.Second * 10, 27 }) 28 log.Entry().Debug("Successfully initialized Jenkins config provider") 29 } 30 31 // OrchestratorVersion returns the orchestrator version currently running on 32 func (j *JenkinsConfigProvider) OrchestratorVersion() string { 33 return getEnv("JENKINS_VERSION", "n/a") 34 } 35 36 // OrchestratorType returns the orchestrator type Jenkins 37 func (j *JenkinsConfigProvider) OrchestratorType() string { 38 return "Jenkins" 39 } 40 41 func (j *JenkinsConfigProvider) fetchAPIInformation() { 42 if len(j.apiInformation) == 0 { 43 log.Entry().Debugf("apiInformation is empty, getting infos from API") 44 URL := j.GetBuildURL() + "api/json" 45 log.Entry().Debugf("API URL: %s", URL) 46 response, err := j.client.GetRequest(URL, nil, nil) 47 if err != nil { 48 log.Entry().WithError(err).Error("could not get API information from Jenkins") 49 j.apiInformation = map[string]interface{}{} 50 return 51 } 52 53 if response.StatusCode != 200 { //http.StatusNoContent 54 log.Entry().Errorf("Response-Code is %v, could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode) 55 j.apiInformation = map[string]interface{}{} 56 return 57 } 58 err = piperHttp.ParseHTTPResponseBodyJSON(response, &j.apiInformation) 59 if err != nil { 60 log.Entry().WithError(err).Errorf("could not parse HTTP response body") 61 j.apiInformation = map[string]interface{}{} 62 return 63 } 64 log.Entry().Debugf("successfully retrieved apiInformation") 65 } else { 66 log.Entry().Debugf("apiInformation already set") 67 } 68 } 69 70 // GetBuildStatus returns build status of the current job 71 func (j *JenkinsConfigProvider) GetBuildStatus() string { 72 j.fetchAPIInformation() 73 if val, ok := j.apiInformation["result"]; ok { 74 // cases in ADO: succeeded, failed, canceled, none, partiallySucceeded 75 switch result := val; result { 76 case "SUCCESS": 77 return BuildStatusSuccess 78 case "ABORTED": 79 return BuildStatusAborted 80 default: 81 // FAILURE, NOT_BUILT 82 return BuildStatusFailure 83 } 84 } 85 return BuildStatusFailure 86 } 87 88 // GetChangeSet returns the commitIds and timestamp of the changeSet of the current run 89 func (j *JenkinsConfigProvider) GetChangeSet() []ChangeSet { 90 j.fetchAPIInformation() 91 92 marshal, err := json.Marshal(j.apiInformation) 93 if err != nil { 94 log.Entry().WithError(err).Debugf("could not marshal apiInformation") 95 return []ChangeSet{} 96 } 97 jsonParsed, err := gabs.ParseJSON(marshal) 98 if err != nil { 99 log.Entry().WithError(err).Debugf("could not parse apiInformation") 100 return []ChangeSet{} 101 } 102 103 var changeSetList []ChangeSet 104 for _, child := range jsonParsed.Path("changeSets").Children() { 105 if child.Path("kind").Data().(string) == "git" { 106 for _, item := range child.S("items").Children() { 107 tmpChangeSet := ChangeSet{ 108 CommitId: item.Path("commitId").Data().(string), 109 Timestamp: item.Path("timestamp").String(), 110 } 111 changeSetList = append(changeSetList, tmpChangeSet) 112 } 113 } 114 115 } 116 return changeSetList 117 } 118 119 // GetLog returns the logfile from the current job as byte object 120 func (j *JenkinsConfigProvider) GetLog() ([]byte, error) { 121 URL := j.GetBuildURL() + "consoleText" 122 123 response, err := j.client.GetRequest(URL, nil, nil) 124 if err != nil { 125 return []byte{}, errors.Wrapf(err, "could not GET Jenkins log file %v", err) 126 } else if response.StatusCode != 200 { 127 log.Entry().Error("response code !=200 could not get log information from Jenkins, returning with empty log.") 128 return []byte{}, nil 129 } 130 logFile, err := io.ReadAll(response.Body) 131 if err != nil { 132 return []byte{}, errors.Wrapf(err, "could not read Jenkins log file from request %v", err) 133 } 134 defer response.Body.Close() 135 return logFile, nil 136 } 137 138 // GetPipelineStartTime returns the pipeline start time in UTC 139 func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time { 140 URL := j.GetBuildURL() + "api/json" 141 response, err := j.client.GetRequest(URL, nil, nil) 142 if err != nil { 143 log.Entry().WithError(err).Errorf("could not getRequest to URL %s", URL) 144 return time.Time{}.UTC() 145 } 146 147 if response.StatusCode != 200 { //http.StatusNoContent -> also empty log! 148 log.Entry().Errorf("response code is %v . \n Could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode) 149 return time.Time{}.UTC() 150 } 151 var responseInterface map[string]interface{} 152 err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface) 153 if err != nil { 154 log.Entry().WithError(err).Infof("could not parse http response, returning 1970") 155 return time.Time{}.UTC() 156 } 157 158 rawTimeStamp := responseInterface["timestamp"].(float64) 159 timeStamp := time.Unix(int64(rawTimeStamp)/1000, 0) 160 161 log.Entry().Debugf("Pipeline start time: %v", timeStamp.String()) 162 defer response.Body.Close() 163 return timeStamp.UTC() 164 } 165 166 // GetJobName returns the job name of the current job e.g. foo/bar/BRANCH 167 func (j *JenkinsConfigProvider) GetJobName() string { 168 return getEnv("JOB_NAME", "n/a") 169 } 170 171 // GetJobURL returns the current job URL e.g. https://jaas.url/job/foo/job/bar/job/main 172 func (j *JenkinsConfigProvider) GetJobURL() string { 173 return getEnv("JOB_URL", "n/a") 174 } 175 176 // getJenkinsHome returns the jenkins home e.g. /var/lib/jenkins 177 func (j *JenkinsConfigProvider) getJenkinsHome() string { 178 return getEnv("JENKINS_HOME", "n/a") 179 } 180 181 // GetBuildID returns the build ID of the current job, e.g. 1234 182 func (j *JenkinsConfigProvider) GetBuildID() string { 183 return getEnv("BUILD_ID", "n/a") 184 } 185 186 // GetStageName returns the stage name the job is currently in, e.g. Promote 187 func (j *JenkinsConfigProvider) GetStageName() string { 188 return getEnv("STAGE_NAME", "n/a") 189 } 190 191 // GetBuildReason returns the build reason of the current build 192 func (j *JenkinsConfigProvider) GetBuildReason() string { 193 // BuildReasons are unified with AzureDevOps build reasons,see 194 // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services 195 // ResourceTrigger, PullRequest, Manual, IndividualCI, Schedule 196 j.fetchAPIInformation() 197 marshal, err := json.Marshal(j.apiInformation) 198 if err != nil { 199 log.Entry().WithError(err).Debugf("could not marshal apiInformation") 200 return BuildReasonUnknown 201 } 202 jsonParsed, err := gabs.ParseJSON(marshal) 203 if err != nil { 204 log.Entry().WithError(err).Debugf("could not parse apiInformation") 205 return BuildReasonUnknown 206 } 207 208 for _, child := range jsonParsed.Path("actions").Children() { 209 class := child.S("_class") 210 if class == nil { 211 continue 212 } 213 if class.Data().(string) == "hudson.model.CauseAction" { 214 for _, val := range child.Path("causes").Children() { 215 subclass := val.S("_class") 216 if subclass.Data().(string) == "hudson.model.Cause$UserIdCause" { 217 return BuildReasonManual 218 } else if subclass.Data().(string) == "hudson.triggers.TimerTrigger$TimerTriggerCause" { 219 return BuildReasonSchedule 220 } else if subclass.Data().(string) == "jenkins.branch.BranchEventCause" { 221 return BuildReasonPullRequest 222 } else if subclass.Data().(string) == "org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause" { 223 return BuildReasonResourceTrigger 224 } else { 225 return BuildReasonUnknown 226 } 227 } 228 } 229 230 } 231 return BuildReasonUnknown 232 } 233 234 // GetBranch returns the branch name, only works with the git plugin enabled 235 func (j *JenkinsConfigProvider) GetBranch() string { 236 return getEnv("BRANCH_NAME", "n/a") 237 } 238 239 // GetReference returns the git reference, only works with the git plugin enabled 240 func (j *JenkinsConfigProvider) GetReference() string { 241 ref := getEnv("BRANCH_NAME", "n/a") 242 if ref == "n/a" { 243 return ref 244 } else if strings.Contains(ref, "PR") { 245 return "refs/pull/" + strings.Split(ref, "-")[1] + "/head" 246 } else { 247 return "refs/heads/" + ref 248 } 249 } 250 251 // GetBuildURL returns the build url, e.g. https://jaas.url/job/foo/job/bar/job/main/1234/ 252 func (j *JenkinsConfigProvider) GetBuildURL() string { 253 return getEnv("BUILD_URL", "n/a") 254 } 255 256 // GetCommit returns the commit SHA from the current build, only works with the git plugin enabled 257 func (j *JenkinsConfigProvider) GetCommit() string { 258 return getEnv("GIT_COMMIT", "n/a") 259 } 260 261 // GetRepoURL returns the repo URL of the current build, only works with the git plugin enabled 262 func (j *JenkinsConfigProvider) GetRepoURL() string { 263 return getEnv("GIT_URL", "n/a") 264 } 265 266 // GetPullRequestConfig returns the pull request config 267 func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig { 268 return PullRequestConfig{ 269 Branch: getEnv("CHANGE_BRANCH", "n/a"), 270 Base: getEnv("CHANGE_TARGET", "n/a"), 271 Key: getEnv("CHANGE_ID", "n/a"), 272 } 273 } 274 275 // IsPullRequest returns boolean indicating if current job is a PR 276 func (j *JenkinsConfigProvider) IsPullRequest() bool { 277 return truthy("CHANGE_ID") 278 } 279 280 func isJenkins() bool { 281 envVars := []string{"JENKINS_HOME", "JENKINS_URL"} 282 return areIndicatingEnvVarsSet(envVars) 283 }