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  }