1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-04 04:07:16 +02:00

fix(orchestrator) usage of correct env variables (#3650)

* Reorders getApiInformation, changes variables to get start time, adjusts and adds test cases
* Changes the way to get apiInformation and reduces number of requests
* Changes getting pipeline start time from correct env variable
* Refactors getApiInformation functionality
* Adds GetBuildReason() for Azure and Jenkins
* Updates JobURL for ADO
This commit is contained in:
ffeldmann 2022-03-28 09:52:15 +02:00 committed by GitHub
parent 5926aa7f77
commit ccc1c976ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1081 additions and 202 deletions

View File

@ -202,7 +202,7 @@ func addRootFlags(rootCmd *cobra.Command) {
provider = &orchestrator.UnknownOrchestratorConfigProvider{} provider = &orchestrator.UnknownOrchestratorConfigProvider{}
} }
rootCmd.PersistentFlags().StringVar(&GeneralConfig.CorrelationID, "correlationID", provider.GetBuildUrl(), "ID for unique identification of a pipeline run") rootCmd.PersistentFlags().StringVar(&GeneralConfig.CorrelationID, "correlationID", provider.GetBuildURL(), "ID for unique identification of a pipeline run")
rootCmd.PersistentFlags().StringVar(&GeneralConfig.CustomConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file") rootCmd.PersistentFlags().StringVar(&GeneralConfig.CustomConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file")
rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.GitHubTokens, "gitHubTokens", AccessTokensFromEnvJSON(os.Getenv("PIPER_gitHubTokens")), "List of entries in form of <hostname>:<token> to allow GitHub token authentication for downloading config / defaults") rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.GitHubTokens, "gitHubTokens", AccessTokensFromEnvJSON(os.Getenv("PIPER_gitHubTokens")), "List of entries in form of <hostname>:<token> to allow GitHub token authentication for downloading config / defaults")
rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.DefaultConfig, "defaultConfig", []string{".pipeline/defaults.yaml"}, "Default configurations, passed as path to yaml file") rootCmd.PersistentFlags().StringSliceVar(&GeneralConfig.DefaultConfig, "defaultConfig", []string{".pipeline/defaults.yaml"}, "Default configurations, passed as path to yaml file")

View File

@ -67,7 +67,7 @@ type ClientOptions struct {
TrustedCerts []string TrustedCerts []string
} }
// TransportWrapper is a wrapper for central roundtrip capabilities // TransportWrapper is a wrapper for central round trip capabilities
type TransportWrapper struct { type TransportWrapper struct {
Transport http.RoundTripper Transport http.RoundTripper
doLogRequestBodyOnDebug bool doLogRequestBodyOnDebug bool
@ -206,7 +206,7 @@ func (c *Client) Upload(data UploadRequestData) (*http.Response, error) {
} }
} }
// SendRequest sends an http request with a defined method // SendRequest sends a http request with a defined method
// //
// On error, any Response can be ignored and the Response.Body // On error, any Response can be ignored and the Response.Body
// does not need to be closed. // does not need to be closed.
@ -219,7 +219,7 @@ func (c *Client) SendRequest(method, url string, body io.Reader, header http.Hea
return c.Send(request) return c.Send(request)
} }
// Send sends an http request // Send sends a http request
func (c *Client) Send(request *http.Request) (*http.Response, error) { func (c *Client) Send(request *http.Request) (*http.Response, error) {
httpClient := c.initialize() httpClient := c.initialize()
response, err := httpClient.Do(request) response, err := httpClient.Do(request)
@ -355,7 +355,7 @@ var contextKeyRequestStart = &contextKey{"RequestStart"}
var authHeaderKey = "Authorization" var authHeaderKey = "Authorization"
// RoundTrip is the core part of this module and implements http.RoundTripper. // RoundTrip is the core part of this module and implements http.RoundTripper.
// Executes HTTP request with request/response logging. // Executes HTTP requests with request/response logging.
func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) { func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := context.WithValue(req.Context(), contextKeyRequestStart, time.Now()) ctx := context.WithValue(req.Context(), contextKeyRequestStart, time.Now())
req = req.WithContext(ctx) req = req.WithContext(ctx)
@ -372,7 +372,7 @@ func (t *TransportWrapper) RoundTrip(req *http.Request) (*http.Response, error)
} }
func handleAuthentication(req *http.Request, username, password, token string) { func handleAuthentication(req *http.Request, username, password, token string) {
// Handle authenticaion if not done already // Handle authentication if not done already
if (len(username) > 0 || len(password) > 0) && len(req.Header.Get(authHeaderKey)) == 0 { if (len(username) > 0 || len(password) > 0) && len(req.Header.Get(authHeaderKey)) == 0 {
req.SetBasicAuth(username, password) req.SetBasicAuth(username, password)
log.Entry().Debug("Using Basic Authentication ****/****") log.Entry().Debug("Using Basic Authentication ****/****")
@ -432,9 +432,9 @@ func transformHeaders(header http.Header) http.Header {
// Since // Since
// 1.) The auth header type itself might serve as a vector for an // 1.) The auth header type itself might serve as a vector for an
// intrusion // intrusion
// 2.) We cannot make assumtions about the structure of the auth // 2.) We cannot make assumptions about the structure of the auth
// header value since that depends on the type, e.g. several tokens // header value since that depends on the type, e.g. several tokens
// where only some of the tokens define the secret // where only some tokens define the secret
// we hide the full auth header value anyway in order to be on the // we hide the full auth header value anyway in order to be on the
// save side. // save side.
value = []string{"<set>"} value = []string{"<set>"}
@ -623,7 +623,7 @@ func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) er
return nil return nil
} }
// default truststore location // TrustStoreDirectory default truststore location
const TrustStoreDirectory = ".pipeline/trustStore" const TrustStoreDirectory = ".pipeline/trustStore"
func getWorkingDirForTrustStore() (string, error) { func getWorkingDirForTrustStore() (string, error) {
@ -637,7 +637,7 @@ func getWorkingDirForTrustStore() (string, error) {
return TrustStoreDirectory, nil return TrustStoreDirectory, nil
} }
// ParseHTTPResponseBodyXML parses a XML http response into a given interface // ParseHTTPResponseBodyXML parses an XML http response into a given interface
func ParseHTTPResponseBodyXML(resp *http.Response, response interface{}) error { func ParseHTTPResponseBodyXML(resp *http.Response, response interface{}) error {
if resp == nil { if resp == nil {
return errors.Errorf("cannot parse HTTP response with value <nil>") return errors.Errorf("cannot parse HTTP response with value <nil>")

View File

@ -1,22 +1,21 @@
package orchestrator package orchestrator
import ( import (
"fmt"
piperHttp "github.com/SAP/jenkins-library/pkg/http" piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv" "strconv"
"strings"
"time" "time"
) )
type AzureDevOpsConfigProvider struct { type AzureDevOpsConfigProvider struct {
client piperHttp.Client client piperHttp.Client
options piperHttp.ClientOptions options piperHttp.ClientOptions
apiInformation map[string]interface{}
} }
//InitOrchestratorProvider initializes http client for AzureDevopsConfigProvider // InitOrchestratorProvider initializes http client for AzureDevopsConfigProvider
func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
a.client = piperHttp.Client{} a.client = piperHttp.Client{}
a.options = piperHttp.ClientOptions{ a.options = piperHttp.ClientOptions{
@ -29,6 +28,58 @@ func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *Orchestra
log.Entry().Debug("Successfully initialized Azure config provider") log.Entry().Debug("Successfully initialized Azure config provider")
} }
// fetchAPIInformation fetches Azure API information of current build
func (a *AzureDevOpsConfigProvider) fetchAPIInformation() {
// if apiInformation is empty fill it otherwise do nothing
if len(a.apiInformation) == 0 {
log.Entry().Debugf("apiInformation is empty, getting infos from API")
URL := a.getSystemCollectionURI() + a.getTeamProjectID() + "/_apis/build/builds/" + a.getAzureBuildID() + "/"
log.Entry().Debugf("API URL: %s", URL)
response, err := a.client.GetRequest(URL, nil, nil)
if err != nil {
log.Entry().Error("failed to get HTTP response, returning empty API information", err)
a.apiInformation = map[string]interface{}{}
return
} else if response.StatusCode != 200 { //http.StatusNoContent
log.Entry().Errorf("response code is %v, could not get API information from AzureDevOps. Returning with empty interface.", response.StatusCode)
a.apiInformation = map[string]interface{}{}
return
}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &a.apiInformation)
if err != nil {
log.Entry().Error("failed to parse HTTP response, returning with empty interface", err)
a.apiInformation = map[string]interface{}{}
return
}
log.Entry().Debugf("successfully retrieved apiInformation")
} else {
log.Entry().Debugf("apiInformation already set")
}
}
// getSystemCollectionURI returns the URI of the TFS collection or Azure DevOps organization e.g. https://dev.azure.com/fabrikamfiber/
func (a *AzureDevOpsConfigProvider) getSystemCollectionURI() string {
return getEnv("SYSTEM_COLLECTIONURI", "n/a")
}
// getTeamProjectID is the name of the project that contains this build e.g. 123a4567-ab1c-12a1-1234-123456ab7890
func (a *AzureDevOpsConfigProvider) getTeamProjectID() string {
return getEnv("SYSTEM_TEAMPROJECTID", "n/a")
}
// getAzureBuildID returns the id of the build, e.g. 1234
func (a *AzureDevOpsConfigProvider) getAzureBuildID() string {
// INFO: Private function only used for API requests, buildId for e.g. reporting
// is GetBuildNumber to align with the UI of ADO
return getEnv("BUILD_BUILDID", "n/a")
}
// GetJobName returns the pipeline job name, currently org/repo
func (a *AzureDevOpsConfigProvider) GetJobName() string {
return getEnv("BUILD_REPOSITORY_NAME", "n/a")
}
// OrchestratorVersion returns the agent version on ADO // OrchestratorVersion returns the agent version on ADO
func (a *AzureDevOpsConfigProvider) OrchestratorVersion() string { func (a *AzureDevOpsConfigProvider) OrchestratorVersion() string {
return getEnv("AGENT_VERSION", "n/a") return getEnv("AGENT_VERSION", "n/a")
@ -39,145 +90,134 @@ func (a *AzureDevOpsConfigProvider) OrchestratorType() string {
return "Azure" return "Azure"
} }
//GetBuildStatus returns status of the build. Return variables are aligned with Jenkins build statuses.
func (a *AzureDevOpsConfigProvider) GetBuildStatus() string { func (a *AzureDevOpsConfigProvider) GetBuildStatus() string {
responseInterface := a.getAPIInformation() // cases to align with Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED
if _, ok := responseInterface["result"]; ok { switch buildStatus := getEnv("AGENT_JOBSTATUS", "FAILURE"); buildStatus {
// cases in Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED case "Succeeded":
switch result := responseInterface["result"]; result { return "SUCCESS"
case "SUCCESS": case "Canceled":
return "SUCCESS" return "ABORTED"
case "ABORTED": default:
return "ABORTED" // Failed, SucceededWithIssues
default: return "FAILURE"
// FAILURE, NOT_BUILT
return "FAILURE"
}
} }
return "FAILURE"
} }
func (a *AzureDevOpsConfigProvider) getAPIInformation() map[string]interface{} { // GetLog returns the whole logfile for the current pipeline run
URL := a.GetSystemCollectionURI() + a.GetTeamProjectId() + "/_apis/build/builds/" + a.GetBuildId() + "/"
response, err := a.client.GetRequest(URL, nil, nil)
if err != nil {
log.Entry().Error("failed to get http response, using default values", err)
return map[string]interface{}{}
}
if response.StatusCode != 200 { //http.StatusNoContent
log.Entry().Errorf("Response-Code is %v . \n Could not get API information from AzureDevOps. Returning with empty interface.", response.StatusCode)
return map[string]interface{}{}
}
var responseInterface map[string]interface{}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
if err != nil {
log.Entry().Error("failed to parse http response, returning with empty interface", err)
return map[string]interface{}{}
}
return responseInterface
}
// GetJobName returns the pipeline job name
func (a *AzureDevOpsConfigProvider) GetJobName() string {
responseInterface := a.getAPIInformation()
if val, ok := responseInterface["repository"]; ok {
return val.(map[string]interface{})["id"].(string)
}
return "n/a"
}
// GetLog returns the logfile of the pipeline run so far
func (a *AzureDevOpsConfigProvider) GetLog() ([]byte, error) { func (a *AzureDevOpsConfigProvider) GetLog() ([]byte, error) {
// ToDo: How to get step specific logs, not only whole log? URL := a.getSystemCollectionURI() + a.getTeamProjectID() + "/_apis/build/builds/" + a.getAzureBuildID() + "/logs"
URL := a.GetSystemCollectionURI() + a.GetTeamProjectId() + "/_apis/build/builds/" + a.GetBuildId() + "/logs"
response, err := a.client.GetRequest(URL, nil, nil) response, err := a.client.GetRequest(URL, nil, nil)
logs := []byte{}
if err != nil { if err != nil {
log.Entry().Error("failed to get http response", err) log.Entry().Error("failed to get HTTP response: ", err)
return logs, nil return []byte{}, err
} }
if response.StatusCode != 200 { //http.StatusNoContent -> also empty log! if response.StatusCode != 200 { //http.StatusNoContent -> also empty log!
log.Entry().Errorf("Response-Code is %v . \n Could not get log information from AzureDevOps. Returning with empty log.", response.StatusCode) log.Entry().Errorf("response-Code is %v, could not get log information from AzureDevOps, returning with empty log.", response.StatusCode)
return logs, nil return []byte{}, nil
} }
var responseInterface map[string]interface{} var responseInterface map[string]interface{}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface) err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
if err != nil { if err != nil {
log.Entry().Error("failed to parse http response", err) log.Entry().Error("failed to parse http response: ", err)
return logs, nil return []byte{}, err
} }
// check if response interface is empty or non-existent // check if response interface is empty or non-existent
logCount := int(responseInterface["count"].(float64)) var logCount int
if val, ok := responseInterface["count"]; ok {
logCount = int(val.(float64))
} else {
log.Entry().Error("log count variable not found, returning empty log")
return []byte{}, err
}
var logs []byte
for i := 1; i <= logCount; i++ { for i := 1; i <= logCount; i++ {
counter := strconv.Itoa(i) counter := strconv.Itoa(i)
logURL := URL + "/" + counter logURL := URL + "/" + counter
fmt.Println("logURL: ", logURL)
log.Entry().Debugf("Getting log no.: %d from %v", i, logURL) log.Entry().Debugf("Getting log no.: %d from %v", i, logURL)
response, err := a.client.GetRequest(logURL, nil, nil) response, err := a.client.GetRequest(logURL, nil, nil)
if err != nil { if err != nil {
fmt.Println(err) log.Entry().Error("failed to get log", err)
return []byte{}, err
}
if response.StatusCode != 200 { //http.StatusNoContent -> also empty log!
log.Entry().Errorf("response code is %v, could not get log information from AzureDevOps ", response.StatusCode)
return []byte{}, err
} }
content, err := ioutil.ReadAll(response.Body) content, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Entry().Error("failed to parse http response", err)
return []byte{}, err
}
logs = append(logs, content...) logs = append(logs, content...)
} }
return logs, nil return logs, nil
} }
// GetPipelineStartTime returns the pipeline start time // GetPipelineStartTime returns the pipeline start time in UTC
func (a *AzureDevOpsConfigProvider) GetPipelineStartTime() time.Time { func (a *AzureDevOpsConfigProvider) GetPipelineStartTime() time.Time {
// "2021-10-11 13:49:09+00:00" //"2022-03-18T07:30:31.1915758Z"
timestamp := getEnv("SYSTEM_PIPELINESTARTTIME", "n/a") a.fetchAPIInformation()
replaced := strings.Replace(timestamp, " ", "T", 1) if val, ok := a.apiInformation["startTime"]; ok {
parsed, err := time.Parse(time.RFC3339, replaced) parsed, err := time.Parse(time.RFC3339, val.(string))
if err != nil { if err != nil {
log.Entry().Errorf("Could not parse timestamp. %v", err) log.Entry().Errorf("could not parse timestamp, %v", err)
// Return 1970 in case parsing goes wrong parsed = time.Time{}
parsed = time.Date(1970, time.January, 01, 0, 0, 0, 0, time.UTC) }
return parsed.UTC()
} }
return parsed return time.Time{}.UTC()
} }
func (a *AzureDevOpsConfigProvider) GetSystemCollectionURI() string { // GetBuildID returns the BuildNumber displayed in the ADO UI
return getEnv("SYSTEM_COLLECTIONURI", "n/a") func (a *AzureDevOpsConfigProvider) GetBuildID() string {
} // INFO: ADO has BUILD_ID and buildNumber, as buildNumber is used in the UI we return this value
// for the buildID used only for API requests we have a private method getAzureBuildID
func (a *AzureDevOpsConfigProvider) GetTeamProjectId() string { // example: buildNumber: 20220318.16 buildId: 76443
return getEnv("SYSTEM_TEAMPROJECTID", "n/a") return getEnv("BUILD_BUILDNUMBER", "n/a")
}
func (a *AzureDevOpsConfigProvider) GetBuildId() string {
return getEnv("BUILD_BUILDID", "n/a")
} }
// GetStageName returns the human-readable name given to a stage. e.g. "Promote" or "Init"
func (a *AzureDevOpsConfigProvider) GetStageName() string { func (a *AzureDevOpsConfigProvider) GetStageName() string {
return getEnv("SYSTEM_STAGEDISPLAYNAME", "n/a") return getEnv("SYSTEM_STAGEDISPLAYNAME", "n/a")
} }
// GetBranch returns the source branch name, e.g. main
func (a *AzureDevOpsConfigProvider) GetBranch() string { func (a *AzureDevOpsConfigProvider) GetBranch() string {
tmp := getEnv("BUILD_SOURCEBRANCH", "n/a") return getEnv("BUILD_SOURCEBRANCHNAME", "n/a")
return strings.TrimPrefix(tmp, "refs/heads/")
} }
func (a *AzureDevOpsConfigProvider) GetBuildUrl() string { // GetBuildURL returns the builds URL e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build/results?buildId=1234
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") + "/_build/results?buildId=" + os.Getenv("BUILD_BUILDID") func (a *AzureDevOpsConfigProvider) GetBuildURL() string {
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") + "/" + os.Getenv("SYSTEM_DEFINITIONNAME") + "/_build/results?buildId=" + a.getAzureBuildID()
} }
func (a *AzureDevOpsConfigProvider) GetJobUrl() string { // GetJobURL returns tje current job url e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build?definitionId=1234
// TODO: Check if thi is the correct URL func (a *AzureDevOpsConfigProvider) GetJobURL() string {
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") // TODO: Check if this is the correct URL
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") + "/" + os.Getenv("SYSTEM_DEFINITIONNAME") + "/_build?definitionId=" + os.Getenv("SYSTEM_DEFINITIONID")
} }
// GetCommit returns commit SHA of current build
func (a *AzureDevOpsConfigProvider) GetCommit() string { func (a *AzureDevOpsConfigProvider) GetCommit() string {
return getEnv("BUILD_SOURCEVERSION", "n/a") return getEnv("BUILD_SOURCEVERSION", "n/a")
} }
func (a *AzureDevOpsConfigProvider) GetRepoUrl() string { // GetRepoURL returns current repo URL e.g. https://github.com/SAP/jenkins-library
func (a *AzureDevOpsConfigProvider) GetRepoURL() string {
return getEnv("BUILD_REPOSITORY_URI", "n/a") return getEnv("BUILD_REPOSITORY_URI", "n/a")
} }
// GetBuildReason returns the build reason
func (a *AzureDevOpsConfigProvider) GetBuildReason() string {
// https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services
return getEnv("BUILD_REASON", "n/a")
}
// GetPullRequestConfig returns pull request configuration
func (a *AzureDevOpsConfigProvider) GetPullRequestConfig() PullRequestConfig { func (a *AzureDevOpsConfigProvider) GetPullRequestConfig() PullRequestConfig {
prKey := getEnv("SYSTEM_PULLREQUEST_PULLREQUESTID", "n/a") prKey := getEnv("SYSTEM_PULLREQUEST_PULLREQUESTID", "n/a")
@ -196,6 +236,7 @@ func (a *AzureDevOpsConfigProvider) GetPullRequestConfig() PullRequestConfig {
} }
} }
// IsPullRequest indicates whether the current build is a PR
func (a *AzureDevOpsConfigProvider) IsPullRequest() bool { func (a *AzureDevOpsConfigProvider) IsPullRequest() bool {
return getEnv("BUILD_REASON", "n/a") == "PullRequest" return getEnv("BUILD_REASON", "n/a") == "PullRequest"
} }

View File

@ -1,8 +1,14 @@
package orchestrator package orchestrator
import ( import (
"fmt"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/jarcoal/httpmock"
"github.com/pkg/errors"
"net/http"
"os" "os"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,21 +18,23 @@ func TestAzure(t *testing.T) {
defer resetEnv(os.Environ()) defer resetEnv(os.Environ())
os.Clearenv() os.Clearenv()
os.Setenv("AZURE_HTTP_USER_AGENT", "FOO BAR BAZ") os.Setenv("AZURE_HTTP_USER_AGENT", "FOO BAR BAZ")
os.Setenv("BUILD_SOURCEBRANCH", "refs/heads/feat/test-azure") os.Setenv("BUILD_SOURCEBRANCHNAME", "feat/test-azure")
os.Setenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://pogo.foo") os.Setenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://pogo.sap/")
os.Setenv("SYSTEM_TEAMPROJECT", "bar") os.Setenv("SYSTEM_TEAMPROJECT", "foo")
os.Setenv("BUILD_BUILDID", "42") os.Setenv("BUILD_BUILDID", "42")
os.Setenv("BUILD_SOURCEVERSION", "abcdef42713") os.Setenv("BUILD_SOURCEVERSION", "abcdef42713")
os.Setenv("BUILD_REPOSITORY_URI", "github.com/foo/bar") os.Setenv("BUILD_REPOSITORY_URI", "github.com/foo/bar")
os.Setenv("SYSTEM_DEFINITIONNAME", "bar")
os.Setenv("SYSTEM_DEFINITIONID", "1234")
p, _ := NewOrchestratorSpecificConfigProvider() p, _ := NewOrchestratorSpecificConfigProvider()
assert.False(t, p.IsPullRequest()) assert.False(t, p.IsPullRequest())
assert.Equal(t, "feat/test-azure", p.GetBranch()) assert.Equal(t, "feat/test-azure", p.GetBranch())
assert.Equal(t, "https://pogo.foobar/_build/results?buildId=42", p.GetBuildUrl()) assert.Equal(t, "https://pogo.sap/foo/bar/_build/results?buildId=42", p.GetBuildURL())
assert.Equal(t, "abcdef42713", p.GetCommit()) assert.Equal(t, "abcdef42713", p.GetCommit())
assert.Equal(t, "github.com/foo/bar", p.GetRepoUrl()) assert.Equal(t, "github.com/foo/bar", p.GetRepoURL())
assert.Equal(t, "Azure", p.OrchestratorType()) assert.Equal(t, "Azure", p.OrchestratorType())
assert.Equal(t, "https://pogo.sap/foo/bar/_build?definitionId=1234", p.GetJobURL())
}) })
t.Run("PR", func(t *testing.T) { t.Run("PR", func(t *testing.T) {
@ -74,4 +82,279 @@ func TestAzure(t *testing.T) {
assert.Equal(t, Orchestrator(Unknown), o) assert.Equal(t, Orchestrator(Unknown), o)
}) })
t.Run("env variables", func(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("SYSTEM_COLLECTIONURI", "https://dev.azure.com/fabrikamfiber/")
os.Setenv("SYSTEM_TEAMPROJECTID", "123a4567-ab1c-12a1-1234-123456ab7890")
os.Setenv("BUILD_BUILDID", "42")
os.Setenv("AGENT_VERSION", "2.193.0")
os.Setenv("BUILD_BUILDNUMBER", "20220318.16")
os.Setenv("BUILD_REPOSITORY_NAME", "repo-org/repo-name")
p := AzureDevOpsConfigProvider{}
assert.Equal(t, "https://dev.azure.com/fabrikamfiber/", p.getSystemCollectionURI())
assert.Equal(t, "123a4567-ab1c-12a1-1234-123456ab7890", p.getTeamProjectID())
assert.Equal(t, "42", p.getAzureBuildID()) // Don't confuse getAzureBuildID and GetBuildID!
assert.Equal(t, "20220318.16", p.GetBuildID()) // buildNumber is used in the UI
assert.Equal(t, "2.193.0", p.OrchestratorVersion())
assert.Equal(t, "repo-org/repo-name", p.GetJobName())
})
}
func TestAzureDevOpsConfigProvider_GetPipelineStartTime(t *testing.T) {
tests := []struct {
name string
apiInformation map[string]interface{}
want time.Time
}{
{
name: "Retrieve correct time",
apiInformation: map[string]interface{}{"startTime": "2022-03-18T12:30:42.0Z"},
want: time.Date(2022, time.March, 18, 12, 30, 42, 0, time.UTC),
},
{
name: "Empty apiInformation",
apiInformation: map[string]interface{}{},
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
},
{
name: "apiInformation does not contain key",
apiInformation: map[string]interface{}{"someKey": "someValue"},
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
},
{
name: "apiInformation contains malformed date",
apiInformation: map[string]interface{}{"startTime": "2022-03/18 12:30:42.0Z"},
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AzureDevOpsConfigProvider{}
a.apiInformation = tt.apiInformation
pipelineStartTime := a.GetPipelineStartTime()
assert.Equalf(t, tt.want, pipelineStartTime, "GetPipelineStartTime()")
})
}
}
func TestAzureDevOpsConfigProvider_GetBuildStatus(t *testing.T) {
tests := []struct {
name string
want string
envVar string
}{
{
name: "Success",
envVar: "Succeeded",
want: "SUCCESS",
},
{
name: "aborted",
envVar: "Canceled",
want: "ABORTED",
},
{
name: "failure",
envVar: "failed",
want: "FAILURE",
},
{
name: "other",
envVar: "some other status",
want: "FAILURE",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("AGENT_JOBSTATUS", tt.envVar)
a := &AzureDevOpsConfigProvider{}
assert.Equalf(t, tt.want, a.GetBuildStatus(), "GetBuildStatus()")
})
}
}
func TestAzureDevOpsConfigProvider_getAPIInformation(t *testing.T) {
tests := []struct {
name string
wantHTTPErr bool
wantHTTPStatusCodeError bool
wantHTTPJSONParseError bool
apiInformation map[string]interface{}
wantAPIInformation map[string]interface{}
}{
{
name: "success case",
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{"Success": "Case"},
},
{
name: "apiInformation already set",
apiInformation: map[string]interface{}{"API info": "set"},
wantAPIInformation: map[string]interface{}{"API info": "set"},
},
{
name: "failed to get response",
apiInformation: map[string]interface{}{},
wantHTTPErr: true,
wantAPIInformation: map[string]interface{}{},
},
{
name: "response code != 200 http.StatusNoContent",
wantHTTPStatusCodeError: true,
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{},
},
{
name: "parseResponseBodyJson fails",
wantHTTPJSONParseError: true,
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AzureDevOpsConfigProvider{
apiInformation: tt.apiInformation,
}
a.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 5 * time.Second,
Token: "TOKEN",
TransportSkipVerification: true,
UseDefaultTransport: true, // need to use default transport for http mock
MaxRetries: -1,
})
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("SYSTEM_COLLECTIONURI", "https://dev.azure.com/fabrikamfiber/")
os.Setenv("SYSTEM_TEAMPROJECTID", "123a4567-ab1c-12a1-1234-123456ab7890")
os.Setenv("BUILD_BUILDID", "1234")
fakeUrl := "https://dev.azure.com/fabrikamfiber/123a4567-ab1c-12a1-1234-123456ab7890/_apis/build/builds/1234/"
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", fakeUrl,
func(req *http.Request) (*http.Response, error) {
if tt.wantHTTPErr {
return nil, errors.New("this error shows up")
}
if tt.wantHTTPStatusCodeError {
return &http.Response{
Status: "204",
StatusCode: http.StatusNoContent,
Request: req,
}, nil
}
if tt.wantHTTPJSONParseError {
// Intentionally malformed JSON response
return httpmock.NewJsonResponse(200, "timestamp:broken")
}
return httpmock.NewStringResponse(200, "{\"Success\":\"Case\"}"), nil
},
)
a.fetchAPIInformation()
assert.Equal(t, tt.wantAPIInformation, a.apiInformation)
})
}
}
func TestAzureDevOpsConfigProvider_GetLog(t *testing.T) {
tests := []struct {
name string
want []byte
wantErr assert.ErrorAssertionFunc
wantHTTPErr bool
wantHTTPStatusCodeError bool
wantLogCountError bool
}{
{
name: "Successfully got log file",
want: []byte("Success"),
wantErr: assert.NoError,
},
{
name: "Log count variable not available",
want: []byte(""),
wantErr: assert.NoError,
wantLogCountError: true,
},
{
name: "HTTP error",
want: []byte(""),
wantErr: assert.Error,
wantHTTPErr: true,
},
{
name: "Status code error",
want: []byte(""),
wantErr: assert.NoError,
wantHTTPStatusCodeError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &AzureDevOpsConfigProvider{}
a.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 5 * time.Second,
Token: "TOKEN",
TransportSkipVerification: true,
UseDefaultTransport: true, // need to use default transport for http mock
MaxRetries: -1,
})
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("SYSTEM_COLLECTIONURI", "https://dev.azure.com/fabrikamfiber/")
os.Setenv("SYSTEM_TEAMPROJECTID", "123a4567-ab1c-12a1-1234-123456ab7890")
os.Setenv("BUILD_BUILDID", "1234")
fakeUrl := "https://dev.azure.com/fabrikamfiber/123a4567-ab1c-12a1-1234-123456ab7890/_apis/build/builds/1234/logs"
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", fakeUrl+"/1",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewStringResponse(200, "Success"), nil
})
httpmock.RegisterResponder("GET", fakeUrl,
func(req *http.Request) (*http.Response, error) {
if tt.wantHTTPErr {
return nil, errors.New("this error shows up")
}
if tt.wantHTTPStatusCodeError {
return &http.Response{
Status: "204",
StatusCode: http.StatusNoContent,
Request: req,
}, nil
}
if tt.wantLogCountError {
return httpmock.NewJsonResponse(200, map[string]interface{}{
"some": "value",
})
}
return httpmock.NewJsonResponse(200, map[string]interface{}{
"count": 1,
})
},
)
got, err := a.GetLog()
if !tt.wantErr(t, err, fmt.Sprintf("GetLog()")) {
return
}
assert.Equalf(t, tt.want, got, "GetLog()")
})
}
} }

View File

@ -10,7 +10,7 @@ import (
type GitHubActionsConfigProvider struct{} type GitHubActionsConfigProvider struct{}
func (g *GitHubActionsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { func (g *GitHubActionsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
log.Entry().Debug("Successfully initalized GitHubActions config provider") log.Entry().Debug("Successfully initialized GitHubActions config provider")
} }
func (g *GitHubActionsConfigProvider) OrchestratorVersion() string { func (g *GitHubActionsConfigProvider) OrchestratorVersion() string {
@ -23,51 +23,55 @@ func (g *GitHubActionsConfigProvider) OrchestratorType() string {
func (g *GitHubActionsConfigProvider) GetBuildStatus() string { func (g *GitHubActionsConfigProvider) GetBuildStatus() string {
log.Entry().Infof("GetBuildStatus() for GitHub Actions not yet implemented.") log.Entry().Infof("GetBuildStatus() for GitHub Actions not yet implemented.")
return "SUCCESS" return "FAILURE"
} }
func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) { func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) {
log.Entry().Infof("GetLog() for GitHub Actions not yet implemented.") log.Entry().Infof("GetLog() for GitHub Actions not yet implemented.")
return nil, nil return []byte{}, nil
} }
func (g *GitHubActionsConfigProvider) GetBuildId() string { func (g *GitHubActionsConfigProvider) GetBuildID() string {
log.Entry().Infof("GetBuildId() for GitHub Actions not yet implemented.") log.Entry().Infof("GetBuildID() for GitHub Actions not yet implemented.")
return "n/a" return "n/a"
} }
func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time { func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time {
log.Entry().Infof("GetPipelineStartTime() for GitHub Actions not yet implemented.") log.Entry().Infof("GetPipelineStartTime() for GitHub Actions not yet implemented.")
timestamp, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 1970") return time.Time{}.UTC()
return timestamp
} }
func (g *GitHubActionsConfigProvider) GetStageName() string { func (g *GitHubActionsConfigProvider) GetStageName() string {
return "GITHUB_WORKFLOW" //TODO: is there something like is "stage" in GH Actions? return "GITHUB_WORKFLOW" //TODO: is there something like is "stage" in GH Actions?
} }
func (g *GitHubActionsConfigProvider) GetBuildReason() string {
log.Entry().Infof("GetBuildReason() for GitHub Actions not yet implemented.")
return "n/a"
}
func (g *GitHubActionsConfigProvider) GetBranch() string { func (g *GitHubActionsConfigProvider) GetBranch() string {
return strings.TrimPrefix(getEnv("GITHUB_REF", "n/a"), "refs/heads/") return strings.TrimPrefix(getEnv("GITHUB_REF", "n/a"), "refs/heads/")
} }
func (g *GitHubActionsConfigProvider) GetBuildUrl() string { func (g *GitHubActionsConfigProvider) GetBuildURL() string {
return g.GetRepoUrl() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a") return g.GetRepoURL() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a")
} }
func (g *GitHubActionsConfigProvider) GetJobUrl() string { func (g *GitHubActionsConfigProvider) GetJobURL() string {
log.Entry().Debugf("Not yet implemented.") log.Entry().Debugf("Not yet implemented.")
return g.GetRepoUrl() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a") return g.GetRepoURL() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a")
} }
func (g *GitHubActionsConfigProvider) GetJobName() string { func (g *GitHubActionsConfigProvider) GetJobName() string {
log.Entry().Debugf("GetJobName() for GitHubActions not yet implemented.") log.Entry().Debugf("GetJobName() for GitHubActions not yet implemented.")
return "N/A" return "n/a"
} }
func (g *GitHubActionsConfigProvider) GetCommit() string { func (g *GitHubActionsConfigProvider) GetCommit() string {
return getEnv("GITHUB_SHA", "n/a") return getEnv("GITHUB_SHA", "n/a")
} }
func (g *GitHubActionsConfigProvider) GetRepoUrl() string { func (g *GitHubActionsConfigProvider) GetRepoURL() string {
return getEnv("GITHUB_SERVER_URL", "n/a") + getEnv("GITHUB_REPOSITORY", "n/a") return getEnv("GITHUB_SERVER_URL", "n/a") + getEnv("GITHUB_REPOSITORY", "n/a")
} }

View File

@ -22,10 +22,10 @@ func TestGitHubActions(t *testing.T) {
p, _ := NewOrchestratorSpecificConfigProvider() p, _ := NewOrchestratorSpecificConfigProvider()
assert.False(t, p.IsPullRequest()) assert.False(t, p.IsPullRequest())
assert.Equal(t, "github.com/foo/bar/actions/runs/42", p.GetBuildUrl()) assert.Equal(t, "github.com/foo/bar/actions/runs/42", p.GetBuildURL())
assert.Equal(t, "feat/test-gh-actions", p.GetBranch()) assert.Equal(t, "feat/test-gh-actions", p.GetBranch())
assert.Equal(t, "abcdef42713", p.GetCommit()) assert.Equal(t, "abcdef42713", p.GetCommit())
assert.Equal(t, "github.com/foo/bar", p.GetRepoUrl()) assert.Equal(t, "github.com/foo/bar", p.GetRepoURL())
assert.Equal(t, "GitHubActions", p.OrchestratorType()) assert.Equal(t, "GitHubActions", p.OrchestratorType())
}) })

View File

@ -1,6 +1,8 @@
package orchestrator package orchestrator
import ( import (
"encoding/json"
"github.com/Jeffail/gabs/v2"
piperHttp "github.com/SAP/jenkins-library/pkg/http" piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log" "github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -9,10 +11,12 @@ import (
) )
type JenkinsConfigProvider struct { type JenkinsConfigProvider struct {
client piperHttp.Client client piperHttp.Client
options piperHttp.ClientOptions options piperHttp.ClientOptions
apiInformation map[string]interface{}
} }
// InitOrchestratorProvider initializes the Jenkins orchestrator with credentials
func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
j.client = piperHttp.Client{} j.client = piperHttp.Client{}
j.options = piperHttp.ClientOptions{ j.options = piperHttp.ClientOptions{
@ -25,43 +29,51 @@ func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorS
log.Entry().Debug("Successfully initialized Jenkins config provider") log.Entry().Debug("Successfully initialized Jenkins config provider")
} }
// OrchestratorVersion returns the orchestrator version currently running on
func (j *JenkinsConfigProvider) OrchestratorVersion() string { func (j *JenkinsConfigProvider) OrchestratorVersion() string {
return getEnv("JENKINS_VERSION", "n/a") return getEnv("JENKINS_VERSION", "n/a")
} }
// OrchestratorType returns the orchestrator type Jenkins
func (j *JenkinsConfigProvider) OrchestratorType() string { func (j *JenkinsConfigProvider) OrchestratorType() string {
return "Jenkins" return "Jenkins"
} }
func (j *JenkinsConfigProvider) getAPIInformation() map[string]interface{} { func (j *JenkinsConfigProvider) fetchAPIInformation() {
URL := j.GetBuildUrl() + "api/json" if len(j.apiInformation) == 0 {
log.Entry().Debugf("apiInformation is empty, getting infos from API")
URL := j.GetBuildURL() + "api/json"
log.Entry().Debugf("API URL: %s", URL)
response, err := j.client.GetRequest(URL, nil, nil)
if err != nil {
log.Entry().WithError(err).Error("could not get API information from Jenkins")
j.apiInformation = map[string]interface{}{}
return
}
response, err := j.client.GetRequest(URL, nil, nil) if response.StatusCode != 200 { //http.StatusNoContent
if err != nil { log.Entry().Errorf("Response-Code is %v, could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode)
log.Entry().WithError(err).Error("could not get api information from Jenkins") j.apiInformation = map[string]interface{}{}
return map[string]interface{}{} return
}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &j.apiInformation)
if err != nil {
log.Entry().WithError(err).Errorf("could not parse HTTP response body")
j.apiInformation = map[string]interface{}{}
return
}
log.Entry().Debugf("successfully retrieved apiInformation")
} else {
log.Entry().Debugf("apiInformation already set")
} }
if response.StatusCode != 200 { //http.StatusNoContent
log.Entry().Errorf("Response-Code is %v . \n Could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode)
return map[string]interface{}{}
}
var responseInterface map[string]interface{}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
if err != nil {
log.Entry().Error(err)
return map[string]interface{}{}
}
return responseInterface
} }
// GetBuildInformation // GetBuildStatus returns build status of the current job
func (j *JenkinsConfigProvider) GetBuildStatus() string { func (j *JenkinsConfigProvider) GetBuildStatus() string {
responseInterface := j.getAPIInformation() j.fetchAPIInformation()
if val, ok := j.apiInformation["result"]; ok {
if val, ok := responseInterface["result"]; ok {
// cases in ADO: succeeded, failed, canceled, none, partiallySucceeded // cases in ADO: succeeded, failed, canceled, none, partiallySucceeded
switch result := responseInterface["result"]; result { switch result := val; result {
case "SUCCESS": case "SUCCESS":
return "SUCCESS" return "SUCCESS"
case "ABORTED": case "ABORTED":
@ -70,50 +82,47 @@ func (j *JenkinsConfigProvider) GetBuildStatus() string {
// FAILURE, NOT_BUILT // FAILURE, NOT_BUILT
return "FAILURE" return "FAILURE"
} }
return val.(string)
} }
return "FAILURE" return "FAILURE"
} }
// GetLog returns the logfile from the current job as byte object
func (j *JenkinsConfigProvider) GetLog() ([]byte, error) { func (j *JenkinsConfigProvider) GetLog() ([]byte, error) {
URL := j.GetBuildUrl() + "consoleText" URL := j.GetBuildURL() + "consoleText"
response, err := j.client.GetRequest(URL, nil, nil) response, err := j.client.GetRequest(URL, nil, nil)
if err != nil { if err != nil {
return []byte{}, errors.Wrapf(err, "Could not read Jenkins logfile. %v", err) return []byte{}, errors.Wrapf(err, "could not GET Jenkins log file %v", err)
} } else if response.StatusCode != 200 {
if response.StatusCode != 200 { log.Entry().Error("response code !=200 could not get log information from Jenkins, returning with empty log.")
log.Entry().Error("Could not get log information from Jenkins. Returning with empty log.")
return []byte{}, nil return []byte{}, nil
} }
defer response.Body.Close()
logFile, err := ioutil.ReadAll(response.Body) logFile, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return []byte{}, errors.Wrapf(err, "could not read Jenkins logfile from request. %v", err) return []byte{}, errors.Wrapf(err, "could not read Jenkins log file from request %v", err)
} }
defer response.Body.Close()
return logFile, nil return logFile, nil
} }
// GetPipelineStartTime returns the pipeline start time in UTC
func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time { func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time {
URL := j.GetBuildUrl() + "api/json" URL := j.GetBuildURL() + "api/json"
response, err := j.client.GetRequest(URL, nil, nil) response, err := j.client.GetRequest(URL, nil, nil)
if err != nil { if err != nil {
log.Entry().Error(err) log.Entry().WithError(err).Errorf("could not getRequest to URL %s", URL)
return time.Time{}.UTC()
} }
if response.StatusCode != 200 { //http.StatusNoContent -> also empty log! if response.StatusCode != 200 { //http.StatusNoContent -> also empty log!
log.Entry().Errorf("Response-Code is %v . \n Could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode) log.Entry().Errorf("response code is %v . \n Could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode)
return time.Unix(1, 0) return time.Time{}.UTC()
} }
var responseInterface map[string]interface{} var responseInterface map[string]interface{}
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface) err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
if err != nil { if err != nil {
log.Entry().Error(err) log.Entry().WithError(err).Infof("could not parse http response, returning 1970")
return time.Time{}.UTC()
} }
rawTimeStamp := responseInterface["timestamp"].(float64) rawTimeStamp := responseInterface["timestamp"].(float64)
@ -121,45 +130,88 @@ func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time {
log.Entry().Debugf("Pipeline start time: %v", timeStamp.String()) log.Entry().Debugf("Pipeline start time: %v", timeStamp.String())
defer response.Body.Close() defer response.Body.Close()
return timeStamp return timeStamp.UTC()
} }
// GetJobName returns the job name of the current job e.g. foo/bar/BRANCH
func (j *JenkinsConfigProvider) GetJobName() string { func (j *JenkinsConfigProvider) GetJobName() string {
return getEnv("JOB_NAME", "n/a") return getEnv("JOB_NAME", "n/a")
} }
func (j *JenkinsConfigProvider) GetJobUrl() string { // GetJobURL returns the current job URL e.g. https://jaas.url/job/foo/job/bar/job/main
func (j *JenkinsConfigProvider) GetJobURL() string {
return getEnv("JOB_URL", "n/a") return getEnv("JOB_URL", "n/a")
} }
// getJenkinsHome returns the jenkins home e.g. /var/lib/jenkins
func (j *JenkinsConfigProvider) getJenkinsHome() string { func (j *JenkinsConfigProvider) getJenkinsHome() string {
return getEnv("JENKINS_HOME", "n/a") return getEnv("JENKINS_HOME", "n/a")
} }
func (j *JenkinsConfigProvider) GetBuildId() string { // GetBuildID returns the build ID of the current job, e.g. 1234
func (j *JenkinsConfigProvider) GetBuildID() string {
return getEnv("BUILD_ID", "n/a") return getEnv("BUILD_ID", "n/a")
} }
func (a *JenkinsConfigProvider) GetStageName() string { // GetStageName returns the stage name the job is currently in, e.g. Promote
func (j *JenkinsConfigProvider) GetStageName() string {
return getEnv("STAGE_NAME", "n/a") return getEnv("STAGE_NAME", "n/a")
} }
//GetBuildReason returns the build reason of the current build
func (j *JenkinsConfigProvider) GetBuildReason() string {
j.fetchAPIInformation()
marshal, err := json.Marshal(j.apiInformation)
if err != nil {
log.Entry().WithError(err).Debugf("could not marshal apiInformation")
return "Unknown"
}
jsonParsed, err := gabs.ParseJSON(marshal)
if err != nil {
log.Entry().WithError(err).Debugf("could not parse apiInformation")
return "Unknown"
}
for _, child := range jsonParsed.S("actions").Children() {
class := child.S("_class")
if class.String() == "\"hudson.model.CauseAction\"" {
for _, val := range child.S("causes").Children() {
subclass := val.S("_class")
if subclass.String() == "\"hudson.model.Cause$UserIdCause\"" {
return "Manual"
} else if subclass.String() == "\"hudson.triggers.TimerTrigger$TimerTriggerCause\"" {
return "Schedule"
} else {
return "Unknown"
}
}
}
}
return "Unknown"
}
// GetBranch returns the branch name, only works with the git plugin enabled
func (j *JenkinsConfigProvider) GetBranch() string { func (j *JenkinsConfigProvider) GetBranch() string {
return getEnv("BRANCH_NAME", "n/a") return getEnv("BRANCH_NAME", "n/a")
} }
func (j *JenkinsConfigProvider) GetBuildUrl() string { // GetBuildURL returns the build url, e.g. https://jaas.url/job/foo/job/bar/job/main/1234/
func (j *JenkinsConfigProvider) GetBuildURL() string {
return getEnv("BUILD_URL", "n/a") return getEnv("BUILD_URL", "n/a")
} }
// GetCommit returns the commit SHA from the current build, only works with the git plugin enabled
func (j *JenkinsConfigProvider) GetCommit() string { func (j *JenkinsConfigProvider) GetCommit() string {
return getEnv("GIT_COMMIT", "n/a") return getEnv("GIT_COMMIT", "n/a")
} }
func (j *JenkinsConfigProvider) GetRepoUrl() string { // GetRepoURL returns the repo URL of the current build, only works with the git plugin enabled
func (j *JenkinsConfigProvider) GetRepoURL() string {
return getEnv("GIT_URL", "n/a") return getEnv("GIT_URL", "n/a")
} }
// GetPullRequestConfig returns the pull request config
func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig { func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig {
return PullRequestConfig{ return PullRequestConfig{
Branch: getEnv("CHANGE_BRANCH", "n/a"), Branch: getEnv("CHANGE_BRANCH", "n/a"),
@ -168,6 +220,7 @@ func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig {
} }
} }
// IsPullRequest returns boolean indicating if current job is a PR
func (j *JenkinsConfigProvider) IsPullRequest() bool { func (j *JenkinsConfigProvider) IsPullRequest() bool {
return truthy("CHANGE_ID") return truthy("CHANGE_ID")
} }

View File

@ -1,10 +1,17 @@
package orchestrator package orchestrator
import ( import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"os" "os"
"testing" "testing"
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http"
) )
func TestJenkins(t *testing.T) { func TestJenkins(t *testing.T) {
@ -12,7 +19,7 @@ func TestJenkins(t *testing.T) {
defer resetEnv(os.Environ()) defer resetEnv(os.Environ())
os.Clearenv() os.Clearenv()
os.Setenv("JENKINS_URL", "FOO BAR BAZ") os.Setenv("JENKINS_URL", "FOO BAR BAZ")
os.Setenv("BUILD_URL", "jaas.com/foo/bar/main/42") os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/")
os.Setenv("BRANCH_NAME", "main") os.Setenv("BRANCH_NAME", "main")
os.Setenv("GIT_COMMIT", "abcdef42713") os.Setenv("GIT_COMMIT", "abcdef42713")
os.Setenv("GIT_URL", "github.com/foo/bar") os.Setenv("GIT_URL", "github.com/foo/bar")
@ -20,10 +27,10 @@ func TestJenkins(t *testing.T) {
p, _ := NewOrchestratorSpecificConfigProvider() p, _ := NewOrchestratorSpecificConfigProvider()
assert.False(t, p.IsPullRequest()) assert.False(t, p.IsPullRequest())
assert.Equal(t, "jaas.com/foo/bar/main/42", p.GetBuildUrl()) assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.GetBuildURL())
assert.Equal(t, "main", p.GetBranch()) assert.Equal(t, "main", p.GetBranch())
assert.Equal(t, "abcdef42713", p.GetCommit()) assert.Equal(t, "abcdef42713", p.GetCommit())
assert.Equal(t, "github.com/foo/bar", p.GetRepoUrl()) assert.Equal(t, "github.com/foo/bar", p.GetRepoURL())
assert.Equal(t, "Jenkins", p.OrchestratorType()) assert.Equal(t, "Jenkins", p.OrchestratorType())
}) })
@ -43,4 +50,449 @@ func TestJenkins(t *testing.T) {
assert.Equal(t, "main", c.Base) assert.Equal(t, "main", c.Base)
assert.Equal(t, "42", c.Key) assert.Equal(t, "42", c.Key)
}) })
t.Run("env variables", func(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("JENKINS_HOME", "/var/lib/jenkins")
os.Setenv("BUILD_ID", "1234")
os.Setenv("JOB_URL", "https://jaas.url/job/foo/job/bar/job/main")
os.Setenv("JENKINS_VERSION", "42")
os.Setenv("JOB_NAME", "foo/bar/BRANCH")
os.Setenv("STAGE_NAME", "Promote")
os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/")
os.Setenv("STAGE_NAME", "Promote")
p := JenkinsConfigProvider{}
assert.Equal(t, "/var/lib/jenkins", p.getJenkinsHome())
assert.Equal(t, "1234", p.GetBuildID())
assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main", p.GetJobURL())
assert.Equal(t, "42", p.OrchestratorVersion())
assert.Equal(t, "Jenkins", p.OrchestratorType())
assert.Equal(t, "foo/bar/BRANCH", p.GetJobName())
assert.Equal(t, "Promote", p.GetStageName())
assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.GetBuildURL())
})
}
func TestJenkinsConfigProvider_GetPipelineStartTime(t *testing.T) {
type fields struct {
client piperhttp.Client
options piperhttp.ClientOptions
}
tests := []struct {
name string
fields fields
want time.Time
wantHTTPErr bool
wantHTTPStatusCodeError bool
wantHTTPJSONParseError bool
}{
{
name: "Retrieve correct time",
want: time.Date(2022, time.March, 21, 22, 30, 0, 0, time.UTC),
wantHTTPErr: false,
wantHTTPStatusCodeError: false,
},
{
name: "ParseHTTPResponseBodyJSON error",
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
wantHTTPErr: false,
wantHTTPStatusCodeError: false,
},
{
name: "GetRequest fails",
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
wantHTTPErr: true,
wantHTTPStatusCodeError: false,
},
{
name: "response code != 200 http.StatusNoContent",
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
wantHTTPErr: false,
wantHTTPStatusCodeError: true,
},
{
name: "parseResponseBodyJson fails",
want: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
wantHTTPErr: false,
wantHTTPStatusCodeError: false,
wantHTTPJSONParseError: true,
},
}
j := &JenkinsConfigProvider{
client: piperhttp.Client{},
}
j.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 5 * time.Second,
Token: "TOKEN",
TransportSkipVerification: true,
UseDefaultTransport: true,
MaxRetries: -1,
})
httpmock.Activate()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
buildURl := "https://jaas.url/job/foo/job/bar/job/main/1234/"
os.Setenv("BUILD_URL", buildURl)
fakeUrl := buildURl + "api/json"
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", fakeUrl,
func(req *http.Request) (*http.Response, error) {
if tt.wantHTTPErr {
return nil, errors.New("this error shows up")
}
if tt.wantHTTPStatusCodeError {
return &http.Response{
Status: "204",
StatusCode: http.StatusNoContent,
Request: req,
}, nil
}
if tt.wantHTTPJSONParseError {
// Intentionally malformed JSON response
return httpmock.NewJsonResponse(200, "timestamp:asdffd")
}
return httpmock.NewStringResponse(200, "{\"timestamp\":1647901800932,\"url\":\"https://jaas.url/view/piperpipelines/job/foo/job/bar/job/main/3731/\"}"), nil
},
)
assert.Equalf(t, tt.want, j.GetPipelineStartTime(), "GetPipelineStartTime()")
})
}
}
func TestJenkinsConfigProvider_GetBuildStatus(t *testing.T) {
apiSuccess := []byte(`{ "queueId":376475,
"result":"SUCCESS",
"timestamp":1647946800925
}`)
apiFailure := []byte(`{ "queueId":376475,
"result":"FAILURE",
"timestamp":1647946800925
}`)
apiAborted := []byte(`{ "queueId":376475,
"result":"ABORTED",
"timestamp":1647946800925
}`)
apiOTHER := []byte(`{ "queueId":376475,
"result":"SOMETHING",
"timestamp":1647946800925
}`)
tests := []struct {
name string
want string
apiInformation []byte
}{
{
name: "SUCCESS",
apiInformation: apiSuccess,
want: "SUCCESS",
},
{
name: "ABORTED",
apiInformation: apiAborted,
want: "ABORTED",
},
{
name: "FAILURE",
apiInformation: apiFailure,
want: "FAILURE",
},
{
name: "Unknown FAILURE",
apiInformation: apiOTHER,
want: "FAILURE",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var apiInformation map[string]interface{}
err := json.Unmarshal(tt.apiInformation, &apiInformation)
if err != nil {
t.Fatal("could not parse json:", err)
}
j := &JenkinsConfigProvider{
apiInformation: apiInformation,
}
assert.Equalf(t, tt.want, j.GetBuildStatus(), "GetBuildStatus()")
})
}
}
func TestJenkinsConfigProvider_GetBuildReason(t *testing.T) {
apiJsonSchedule := []byte(`{
"_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
"actions": [{
"_class": "hudson.model.CauseAction",
"causes": [{
"_class": "hudson.triggers.TimerTrigger$TimerTriggerCause",
"shortDescription": "Started by timer"
}]
},
{
"_class": "jenkins.metrics.impl.TimeInQueueAction",
"blockedDurationMillis": "0"
}
]
}`)
apiJSONManual := []byte(`{
"_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
"actions": [{
"_class": "hudson.model.CauseAction",
"causes": [{
"_class": "hudson.model.Cause$UserIdCause",
"shortDescription": "Started by user John Doe",
"userId": "i12345",
"userName": "John Doe"
}]
},
{
"_class": "jenkins.metrics.impl.TimeInQueueAction",
"blockedDurationMillis": "0"
}
]
}`)
apiJSONUnknown := []byte(`{
"_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
"actions": [{
"_class": "hudson.model.CauseAction",
"causes": [{
"_class": "hudson.model.RandomThingHere",
"shortDescription": "Something"
}]
},
{
"_class": "jenkins.metrics.impl.TimeInQueueAction",
"blockedDurationMillis": "0"
}
]
}`)
tests := []struct {
name string
apiInformation []byte
want string
}{
{
name: "Manual trigger",
apiInformation: apiJSONManual,
want: "Manual",
},
{
name: "Scheduled trigger",
apiInformation: apiJsonSchedule,
want: "Schedule",
},
{
name: "Unknown",
apiInformation: apiJSONUnknown,
want: "Unknown",
},
{
name: "Empty api",
apiInformation: []byte(`{}`),
want: "Unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var apiInformation map[string]interface{}
err := json.Unmarshal(tt.apiInformation, &apiInformation)
if err != nil {
t.Fatal("could not parse json:", err)
}
j := &JenkinsConfigProvider{apiInformation: apiInformation}
assert.Equalf(t, tt.want, j.GetBuildReason(), "GetBuildReason()")
})
}
}
func TestJenkinsConfigProvider_getAPIInformation(t *testing.T) {
tests := []struct {
name string
wantHTTPErr bool
wantHTTPStatusCodeError bool
wantHTTPJSONParseError bool
apiInformation map[string]interface{}
wantAPIInformation map[string]interface{}
}{
{
name: "success case",
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{"Success": "Case"},
},
{
name: "apiInformation already set",
apiInformation: map[string]interface{}{"API info": "set"},
wantAPIInformation: map[string]interface{}{"API info": "set"},
},
{
name: "failed to get response",
apiInformation: map[string]interface{}{},
wantHTTPErr: true,
wantAPIInformation: map[string]interface{}{},
},
{
name: "response code != 200 http.StatusNoContent",
wantHTTPStatusCodeError: true,
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{},
},
{
name: "parseResponseBodyJson fails",
wantHTTPJSONParseError: true,
apiInformation: map[string]interface{}{},
wantAPIInformation: map[string]interface{}{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j := &JenkinsConfigProvider{
apiInformation: tt.apiInformation,
}
j.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 5 * time.Second,
Token: "TOKEN",
TransportSkipVerification: true,
UseDefaultTransport: true, // need to use default transport for http mock
MaxRetries: -1,
})
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/")
fakeUrl := "https://jaas.url/job/foo/job/bar/job/main/1234/api/json"
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", fakeUrl,
func(req *http.Request) (*http.Response, error) {
if tt.wantHTTPErr {
return nil, errors.New("this error shows up")
}
if tt.wantHTTPStatusCodeError {
return &http.Response{
Status: "204",
StatusCode: http.StatusNoContent,
Request: req,
}, nil
}
if tt.wantHTTPJSONParseError {
// Intentionally malformed JSON response
return httpmock.NewJsonResponse(200, "timestamp:broken")
}
return httpmock.NewStringResponse(200, "{\"Success\":\"Case\"}"), nil
},
)
j.fetchAPIInformation()
assert.Equal(t, tt.wantAPIInformation, j.apiInformation)
})
}
}
func TestJenkinsConfigProvider_GetLog(t *testing.T) {
tests := []struct {
name string
want []byte
wantErr assert.ErrorAssertionFunc
wantHTTPErr bool
wantHTTPStatusCodeError bool
}{
{
name: "Successfully got log file",
want: []byte("Success!"),
wantErr: assert.NoError,
},
{
name: "HTTP error",
want: []byte(""),
wantErr: assert.Error,
wantHTTPErr: true,
},
{
name: "Status code error",
want: []byte(""),
wantErr: assert.NoError,
wantHTTPStatusCodeError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j := &JenkinsConfigProvider{}
j.client.SetOptions(piperhttp.ClientOptions{
MaxRequestDuration: 5 * time.Second,
Token: "TOKEN",
TransportSkipVerification: true,
UseDefaultTransport: true, // need to use default transport for http mock
MaxRetries: -1,
})
defer resetEnv(os.Environ())
os.Clearenv()
os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/")
fakeUrl := "https://jaas.url/job/foo/job/bar/job/main/1234/consoleText"
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpmock.RegisterResponder("GET", fakeUrl,
func(req *http.Request) (*http.Response, error) {
if tt.wantHTTPErr {
return nil, errors.New("this error shows up")
}
if tt.wantHTTPStatusCodeError {
return &http.Response{
Status: "204",
StatusCode: http.StatusNoContent,
Request: req,
}, nil
}
return httpmock.NewStringResponse(200, "Success!"), nil
},
)
got, err := j.GetLog()
if !tt.wantErr(t, err, fmt.Sprintf("GetLog()")) {
return
}
assert.Equalf(t, tt.want, got, "GetLog()")
})
}
}
func TestJenkinsConfigProvider_InitOrchestratorProvider(t *testing.T) {
tests := []struct {
name string
settings *OrchestratorSettings
apiInformation map[string]interface{}
}{
{
name: "Init, test empty apiInformation",
settings: &OrchestratorSettings{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j := &JenkinsConfigProvider{}
j.InitOrchestratorProvider(tt.settings)
var expected map[string]interface{}
assert.Equal(t, j.apiInformation, expected)
})
}
} }

View File

@ -22,17 +22,18 @@ type OrchestratorSpecificConfigProviding interface {
OrchestratorVersion() string OrchestratorVersion() string
GetStageName() string GetStageName() string
GetBranch() string GetBranch() string
GetBuildUrl() string GetBuildURL() string
GetBuildId() string GetBuildID() string
GetJobUrl() string GetJobURL() string
GetJobName() string GetJobName() string
GetCommit() string GetCommit() string
GetPullRequestConfig() PullRequestConfig GetPullRequestConfig() PullRequestConfig
GetRepoUrl() string GetRepoURL() string
IsPullRequest() bool IsPullRequest() bool
GetLog() ([]byte, error) GetLog() ([]byte, error)
GetPipelineStartTime() time.Time GetPipelineStartTime() time.Time
GetBuildStatus() string GetBuildStatus() string
GetBuildReason() string
} }
type PullRequestConfig struct { type PullRequestConfig struct {
@ -41,6 +42,7 @@ type PullRequestConfig struct {
Key string Key string
} }
// OrchestratorSettings struct to set orchestrator specific settings e.g. Jenkins credentials
type OrchestratorSettings struct { type OrchestratorSettings struct {
JenkinsUser string JenkinsUser string
JenkinsToken string JenkinsToken string
@ -60,6 +62,7 @@ func NewOrchestratorSpecificConfigProvider() (OrchestratorSpecificConfigProvidin
} }
} }
// DetectOrchestrator returns the name of the current orchestrator e.g. Jenkins, Azure, Unknown
func DetectOrchestrator() Orchestrator { func DetectOrchestrator() Orchestrator {
if isAzure() { if isAzure() {
return Orchestrator(AzureDevOps) return Orchestrator(AzureDevOps)

View File

@ -7,75 +7,96 @@ import (
type UnknownOrchestratorConfigProvider struct{} type UnknownOrchestratorConfigProvider struct{}
// InitOrchestratorProvider returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { func (u *UnknownOrchestratorConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
} }
// OrchestratorVersion returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) OrchestratorVersion() string { func (u *UnknownOrchestratorConfigProvider) OrchestratorVersion() string {
log.Entry().Warning("Unknown orchestrator - returning default values.")
return "N/A"
}
func (u *UnknownOrchestratorConfigProvider) GetBuildStatus() string {
log.Entry().Warning("Unknown orchestrator - returning default values.")
return "SUCCESS"
}
func (u *UnknownOrchestratorConfigProvider) GetBuildId() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
// GetBuildStatus returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetBuildStatus() string {
log.Entry().Warning("Unknown orchestrator - returning default values.")
return "FAILURE"
}
// GetBuildReason returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetBuildReason() string {
log.Entry().Infof("Unknown orchestrator - returning default values.")
return "n/a"
}
// GetBuildID returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetBuildID() string {
log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a"
}
// GetJobName returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetJobName() string { func (u *UnknownOrchestratorConfigProvider) GetJobName() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
// OrchestratorType returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) OrchestratorType() string { func (u *UnknownOrchestratorConfigProvider) OrchestratorType() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "Unknown" return "Unknown"
} }
// GetLog returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetLog() ([]byte, error) { func (u *UnknownOrchestratorConfigProvider) GetLog() ([]byte, error) {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return nil, nil return []byte{}, nil
} }
// GetPipelineStartTime returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetPipelineStartTime() time.Time { func (u *UnknownOrchestratorConfigProvider) GetPipelineStartTime() time.Time {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
timestamp, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 1970") return time.Time{}.UTC()
return timestamp
} }
// GetStageName returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetStageName() string { func (u *UnknownOrchestratorConfigProvider) GetStageName() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
// GetBranch returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetBranch() string { func (u *UnknownOrchestratorConfigProvider) GetBranch() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
func (u *UnknownOrchestratorConfigProvider) GetBuildUrl() string { // GetBuildURL returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetBuildURL() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
func (u *UnknownOrchestratorConfigProvider) GetJobUrl() string { // GetJobURL returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetJobURL() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
// GetCommit returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetCommit() string { func (u *UnknownOrchestratorConfigProvider) GetCommit() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
func (u *UnknownOrchestratorConfigProvider) GetRepoUrl() string { // GetRepoURL returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetRepoURL() string {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return "n/a" return "n/a"
} }
// GetPullRequestConfig returns n/a for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestConfig { func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestConfig {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return PullRequestConfig{ return PullRequestConfig{
@ -85,6 +106,7 @@ func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestCo
} }
} }
// IsPullRequest returns false for the unknownOrchestrator
func (u *UnknownOrchestratorConfigProvider) IsPullRequest() bool { func (u *UnknownOrchestratorConfigProvider) IsPullRequest() bool {
log.Entry().Warning("Unknown orchestrator - returning default values.") log.Entry().Warning("Unknown orchestrator - returning default values.")
return false return false

View File

@ -3,6 +3,7 @@ package orchestrator
import ( import (
"os" "os"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -15,10 +16,10 @@ func TestUnknownOrchestrator(t *testing.T) {
p, _ := NewOrchestratorSpecificConfigProvider() p, _ := NewOrchestratorSpecificConfigProvider()
assert.False(t, p.IsPullRequest()) assert.False(t, p.IsPullRequest())
assert.Equal(t, "n/a", p.GetBuildUrl()) assert.Equal(t, "n/a", p.GetBuildURL())
assert.Equal(t, "n/a", p.GetBranch()) assert.Equal(t, "n/a", p.GetBranch())
assert.Equal(t, "n/a", p.GetCommit()) assert.Equal(t, "n/a", p.GetCommit())
assert.Equal(t, "n/a", p.GetRepoUrl()) assert.Equal(t, "n/a", p.GetRepoURL())
assert.Equal(t, "Unknown", p.OrchestratorType()) assert.Equal(t, "Unknown", p.OrchestratorType())
}) })
@ -34,4 +35,24 @@ func TestUnknownOrchestrator(t *testing.T) {
assert.Equal(t, "n/a", c.Base) assert.Equal(t, "n/a", c.Base)
assert.Equal(t, "n/a", c.Key) assert.Equal(t, "n/a", c.Key)
}) })
t.Run("env variables", func(t *testing.T) {
defer resetEnv(os.Environ())
os.Clearenv()
p := UnknownOrchestratorConfigProvider{}
assert.Equal(t, "n/a", p.OrchestratorVersion())
assert.Equal(t, "n/a", p.GetBuildID())
assert.Equal(t, "n/a", p.GetJobName())
assert.Equal(t, "Unknown", p.OrchestratorType())
assert.Equal(t, time.Time{}.UTC(), p.GetPipelineStartTime())
assert.Equal(t, "FAILURE", p.GetBuildStatus())
assert.Equal(t, "n/a", p.GetRepoURL())
assert.Equal(t, "n/a", p.GetBuildURL())
assert.Equal(t, "n/a", p.GetStageName())
log, err := p.GetLog()
assert.Equal(t, []byte{}, log)
assert.Equal(t, nil, err)
})
} }

View File

@ -14,8 +14,8 @@ type BaseData struct {
URL string `json:"url"` URL string `json:"url"`
StepName string `json:"e_3"` // set by step generator StepName string `json:"e_3"` // set by step generator
StageName string `json:"e_10"` StageName string `json:"e_10"`
PipelineURLHash string `json:"e_4"` // defaults to sha1 of provider.GetBuildUrl() PipelineURLHash string `json:"e_4"` // defaults to sha1 of provider.GetBuildURL()
BuildURLHash string `json:"e_5"` // defaults to sha1 of provider.GetJobUrl() BuildURLHash string `json:"e_5"` // defaults to sha1 of provider.GetJobURL()
Orchestrator string `json:"e_14"` // defaults to provider.OrchestratorType() Orchestrator string `json:"e_14"` // defaults to provider.OrchestratorType()
} }

View File

@ -88,13 +88,13 @@ func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) {
} }
func (t *Telemetry) getPipelineURLHash() string { func (t *Telemetry) getPipelineURLHash() string {
jobUrl := t.provider.GetJobUrl() jobURL := t.provider.GetJobURL()
return t.toSha1OrNA(jobUrl) return t.toSha1OrNA(jobURL)
} }
func (t *Telemetry) getBuildURLHash() string { func (t *Telemetry) getBuildURLHash() string {
buildUrl := t.provider.GetBuildUrl() buildURL := t.provider.GetBuildURL()
return t.toSha1OrNA(buildUrl) return t.toSha1OrNA(buildURL)
} }
func (t *Telemetry) toSha1OrNA(input string) string { func (t *Telemetry) toSha1OrNA(input string) string {
@ -161,7 +161,7 @@ func (t *Telemetry) logStepTelemetryData() {
StepDuration: t.data.CustomData.Duration, StepDuration: t.data.CustomData.Duration,
ErrorCategory: t.data.CustomData.ErrorCategory, ErrorCategory: t.data.CustomData.ErrorCategory,
ErrorDetail: fatalError, ErrorDetail: fatalError,
CorrelationID: t.provider.GetBuildUrl(), CorrelationID: t.provider.GetBuildURL(),
PiperCommitHash: t.data.CustomData.PiperCommitHash, PiperCommitHash: t.data.CustomData.PiperCommitHash,
} }
stepTelemetryJSON, err := json.Marshal(stepTelemetryData) stepTelemetryJSON, err := json.Marshal(stepTelemetryData)