mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +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:
parent
5926aa7f77
commit
ccc1c976ee
@ -202,7 +202,7 @@ func addRootFlags(rootCmd *cobra.Command) {
|
||||
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().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")
|
||||
|
@ -67,7 +67,7 @@ type ClientOptions struct {
|
||||
TrustedCerts []string
|
||||
}
|
||||
|
||||
// TransportWrapper is a wrapper for central roundtrip capabilities
|
||||
// TransportWrapper is a wrapper for central round trip capabilities
|
||||
type TransportWrapper struct {
|
||||
Transport http.RoundTripper
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Send sends an http request
|
||||
// Send sends a http request
|
||||
func (c *Client) Send(request *http.Request) (*http.Response, error) {
|
||||
httpClient := c.initialize()
|
||||
response, err := httpClient.Do(request)
|
||||
@ -355,7 +355,7 @@ var contextKeyRequestStart = &contextKey{"RequestStart"}
|
||||
var authHeaderKey = "Authorization"
|
||||
|
||||
// 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) {
|
||||
ctx := context.WithValue(req.Context(), contextKeyRequestStart, time.Now())
|
||||
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) {
|
||||
// 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 {
|
||||
req.SetBasicAuth(username, password)
|
||||
log.Entry().Debug("Using Basic Authentication ****/****")
|
||||
@ -432,9 +432,9 @@ func transformHeaders(header http.Header) http.Header {
|
||||
// Since
|
||||
// 1.) The auth header type itself might serve as a vector for an
|
||||
// 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
|
||||
// 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
|
||||
// save side.
|
||||
value = []string{"<set>"}
|
||||
@ -623,7 +623,7 @@ func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// default truststore location
|
||||
// TrustStoreDirectory default truststore location
|
||||
const TrustStoreDirectory = ".pipeline/trustStore"
|
||||
|
||||
func getWorkingDirForTrustStore() (string, error) {
|
||||
@ -637,7 +637,7 @@ func getWorkingDirForTrustStore() (string, error) {
|
||||
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 {
|
||||
if resp == nil {
|
||||
return errors.Errorf("cannot parse HTTP response with value <nil>")
|
||||
|
@ -1,22 +1,21 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
piperHttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AzureDevOpsConfigProvider struct {
|
||||
client piperHttp.Client
|
||||
options piperHttp.ClientOptions
|
||||
client piperHttp.Client
|
||||
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) {
|
||||
a.client = piperHttp.Client{}
|
||||
a.options = piperHttp.ClientOptions{
|
||||
@ -29,6 +28,58 @@ func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *Orchestra
|
||||
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
|
||||
func (a *AzureDevOpsConfigProvider) OrchestratorVersion() string {
|
||||
return getEnv("AGENT_VERSION", "n/a")
|
||||
@ -39,145 +90,134 @@ func (a *AzureDevOpsConfigProvider) OrchestratorType() string {
|
||||
return "Azure"
|
||||
}
|
||||
|
||||
//GetBuildStatus returns status of the build. Return variables are aligned with Jenkins build statuses.
|
||||
func (a *AzureDevOpsConfigProvider) GetBuildStatus() string {
|
||||
responseInterface := a.getAPIInformation()
|
||||
if _, ok := responseInterface["result"]; ok {
|
||||
// cases in Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED
|
||||
switch result := responseInterface["result"]; result {
|
||||
case "SUCCESS":
|
||||
return "SUCCESS"
|
||||
case "ABORTED":
|
||||
return "ABORTED"
|
||||
default:
|
||||
// FAILURE, NOT_BUILT
|
||||
return "FAILURE"
|
||||
}
|
||||
// cases to align with Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED
|
||||
switch buildStatus := getEnv("AGENT_JOBSTATUS", "FAILURE"); buildStatus {
|
||||
case "Succeeded":
|
||||
return "SUCCESS"
|
||||
case "Canceled":
|
||||
return "ABORTED"
|
||||
default:
|
||||
// Failed, SucceededWithIssues
|
||||
return "FAILURE"
|
||||
}
|
||||
return "FAILURE"
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsConfigProvider) getAPIInformation() map[string]interface{} {
|
||||
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
|
||||
// GetLog returns the whole logfile for the current pipeline run
|
||||
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.GetBuildId() + "/logs"
|
||||
URL := a.getSystemCollectionURI() + a.getTeamProjectID() + "/_apis/build/builds/" + a.getAzureBuildID() + "/logs"
|
||||
|
||||
response, err := a.client.GetRequest(URL, nil, nil)
|
||||
logs := []byte{}
|
||||
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to get http response", err)
|
||||
return logs, nil
|
||||
log.Entry().Error("failed to get HTTP response: ", err)
|
||||
return []byte{}, err
|
||||
}
|
||||
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)
|
||||
return logs, nil
|
||||
log.Entry().Errorf("response-Code is %v, could not get log information from AzureDevOps, returning with empty log.", response.StatusCode)
|
||||
return []byte{}, nil
|
||||
}
|
||||
var responseInterface map[string]interface{}
|
||||
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to parse http response", err)
|
||||
return logs, nil
|
||||
log.Entry().Error("failed to parse http response: ", err)
|
||||
return []byte{}, err
|
||||
}
|
||||
// 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++ {
|
||||
counter := strconv.Itoa(i)
|
||||
logURL := URL + "/" + counter
|
||||
fmt.Println("logURL: ", logURL)
|
||||
log.Entry().Debugf("Getting log no.: %d from %v", i, logURL)
|
||||
response, err := a.client.GetRequest(logURL, nil, 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)
|
||||
if err != nil {
|
||||
log.Entry().Error("failed to parse http response", err)
|
||||
return []byte{}, err
|
||||
}
|
||||
logs = append(logs, content...)
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// GetPipelineStartTime returns the pipeline start time
|
||||
// GetPipelineStartTime returns the pipeline start time in UTC
|
||||
func (a *AzureDevOpsConfigProvider) GetPipelineStartTime() time.Time {
|
||||
// "2021-10-11 13:49:09+00:00"
|
||||
timestamp := getEnv("SYSTEM_PIPELINESTARTTIME", "n/a")
|
||||
replaced := strings.Replace(timestamp, " ", "T", 1)
|
||||
parsed, err := time.Parse(time.RFC3339, replaced)
|
||||
if err != nil {
|
||||
log.Entry().Errorf("Could not parse timestamp. %v", err)
|
||||
// Return 1970 in case parsing goes wrong
|
||||
parsed = time.Date(1970, time.January, 01, 0, 0, 0, 0, time.UTC)
|
||||
//"2022-03-18T07:30:31.1915758Z"
|
||||
a.fetchAPIInformation()
|
||||
if val, ok := a.apiInformation["startTime"]; ok {
|
||||
parsed, err := time.Parse(time.RFC3339, val.(string))
|
||||
if err != nil {
|
||||
log.Entry().Errorf("could not parse timestamp, %v", err)
|
||||
parsed = time.Time{}
|
||||
}
|
||||
return parsed.UTC()
|
||||
}
|
||||
return parsed
|
||||
return time.Time{}.UTC()
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsConfigProvider) GetSystemCollectionURI() string {
|
||||
return getEnv("SYSTEM_COLLECTIONURI", "n/a")
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsConfigProvider) GetTeamProjectId() string {
|
||||
return getEnv("SYSTEM_TEAMPROJECTID", "n/a")
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsConfigProvider) GetBuildId() string {
|
||||
return getEnv("BUILD_BUILDID", "n/a")
|
||||
// GetBuildID returns the BuildNumber displayed in the ADO UI
|
||||
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
|
||||
// example: buildNumber: 20220318.16 buildId: 76443
|
||||
return getEnv("BUILD_BUILDNUMBER", "n/a")
|
||||
}
|
||||
|
||||
// GetStageName returns the human-readable name given to a stage. e.g. "Promote" or "Init"
|
||||
func (a *AzureDevOpsConfigProvider) GetStageName() string {
|
||||
return getEnv("SYSTEM_STAGEDISPLAYNAME", "n/a")
|
||||
}
|
||||
|
||||
// GetBranch returns the source branch name, e.g. main
|
||||
func (a *AzureDevOpsConfigProvider) GetBranch() string {
|
||||
tmp := getEnv("BUILD_SOURCEBRANCH", "n/a")
|
||||
return strings.TrimPrefix(tmp, "refs/heads/")
|
||||
return getEnv("BUILD_SOURCEBRANCHNAME", "n/a")
|
||||
}
|
||||
|
||||
func (a *AzureDevOpsConfigProvider) GetBuildUrl() string {
|
||||
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") + "/_build/results?buildId=" + os.Getenv("BUILD_BUILDID")
|
||||
// GetBuildURL returns the builds URL e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build/results?buildId=1234
|
||||
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 {
|
||||
// TODO: Check if thi is the correct URL
|
||||
return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT")
|
||||
// GetJobURL returns tje current job url e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build?definitionId=1234
|
||||
func (a *AzureDevOpsConfigProvider) GetJobURL() string {
|
||||
// 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 {
|
||||
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")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
return getEnv("BUILD_REASON", "n/a") == "PullRequest"
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -12,21 +18,23 @@ func TestAzure(t *testing.T) {
|
||||
defer resetEnv(os.Environ())
|
||||
os.Clearenv()
|
||||
os.Setenv("AZURE_HTTP_USER_AGENT", "FOO BAR BAZ")
|
||||
os.Setenv("BUILD_SOURCEBRANCH", "refs/heads/feat/test-azure")
|
||||
os.Setenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://pogo.foo")
|
||||
os.Setenv("SYSTEM_TEAMPROJECT", "bar")
|
||||
os.Setenv("BUILD_SOURCEBRANCHNAME", "feat/test-azure")
|
||||
os.Setenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://pogo.sap/")
|
||||
os.Setenv("SYSTEM_TEAMPROJECT", "foo")
|
||||
os.Setenv("BUILD_BUILDID", "42")
|
||||
os.Setenv("BUILD_SOURCEVERSION", "abcdef42713")
|
||||
os.Setenv("BUILD_REPOSITORY_URI", "github.com/foo/bar")
|
||||
|
||||
os.Setenv("SYSTEM_DEFINITIONNAME", "bar")
|
||||
os.Setenv("SYSTEM_DEFINITIONID", "1234")
|
||||
p, _ := NewOrchestratorSpecificConfigProvider()
|
||||
|
||||
assert.False(t, p.IsPullRequest())
|
||||
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, "github.com/foo/bar", p.GetRepoUrl())
|
||||
assert.Equal(t, "github.com/foo/bar", p.GetRepoURL())
|
||||
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) {
|
||||
@ -74,4 +82,279 @@ func TestAzure(t *testing.T) {
|
||||
|
||||
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()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
type GitHubActionsConfigProvider struct{}
|
||||
|
||||
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 {
|
||||
@ -23,51 +23,55 @@ func (g *GitHubActionsConfigProvider) OrchestratorType() string {
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetBuildStatus() string {
|
||||
log.Entry().Infof("GetBuildStatus() for GitHub Actions not yet implemented.")
|
||||
return "SUCCESS"
|
||||
return "FAILURE"
|
||||
}
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) {
|
||||
log.Entry().Infof("GetLog() for GitHub Actions not yet implemented.")
|
||||
return nil, nil
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetBuildId() string {
|
||||
log.Entry().Infof("GetBuildId() for GitHub Actions not yet implemented.")
|
||||
func (g *GitHubActionsConfigProvider) GetBuildID() string {
|
||||
log.Entry().Infof("GetBuildID() for GitHub Actions not yet implemented.")
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time {
|
||||
log.Entry().Infof("GetPipelineStartTime() for GitHub Actions not yet implemented.")
|
||||
timestamp, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 1970")
|
||||
return timestamp
|
||||
return time.Time{}.UTC()
|
||||
}
|
||||
func (g *GitHubActionsConfigProvider) GetStageName() string {
|
||||
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 {
|
||||
return strings.TrimPrefix(getEnv("GITHUB_REF", "n/a"), "refs/heads/")
|
||||
}
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetBuildUrl() string {
|
||||
return g.GetRepoUrl() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a")
|
||||
func (g *GitHubActionsConfigProvider) GetBuildURL() string {
|
||||
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.")
|
||||
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 {
|
||||
log.Entry().Debugf("GetJobName() for GitHubActions not yet implemented.")
|
||||
return "N/A"
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
func (g *GitHubActionsConfigProvider) GetCommit() string {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,10 @@ func TestGitHubActions(t *testing.T) {
|
||||
p, _ := NewOrchestratorSpecificConfigProvider()
|
||||
|
||||
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, "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())
|
||||
})
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/Jeffail/gabs/v2"
|
||||
piperHttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/pkg/errors"
|
||||
@ -9,10 +11,12 @@ import (
|
||||
)
|
||||
|
||||
type JenkinsConfigProvider struct {
|
||||
client piperHttp.Client
|
||||
options piperHttp.ClientOptions
|
||||
client piperHttp.Client
|
||||
options piperHttp.ClientOptions
|
||||
apiInformation map[string]interface{}
|
||||
}
|
||||
|
||||
// InitOrchestratorProvider initializes the Jenkins orchestrator with credentials
|
||||
func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
||||
j.client = piperHttp.Client{}
|
||||
j.options = piperHttp.ClientOptions{
|
||||
@ -25,43 +29,51 @@ func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorS
|
||||
log.Entry().Debug("Successfully initialized Jenkins config provider")
|
||||
}
|
||||
|
||||
// OrchestratorVersion returns the orchestrator version currently running on
|
||||
func (j *JenkinsConfigProvider) OrchestratorVersion() string {
|
||||
return getEnv("JENKINS_VERSION", "n/a")
|
||||
}
|
||||
|
||||
// OrchestratorType returns the orchestrator type Jenkins
|
||||
func (j *JenkinsConfigProvider) OrchestratorType() string {
|
||||
return "Jenkins"
|
||||
}
|
||||
|
||||
func (j *JenkinsConfigProvider) getAPIInformation() map[string]interface{} {
|
||||
URL := j.GetBuildUrl() + "api/json"
|
||||
func (j *JenkinsConfigProvider) fetchAPIInformation() {
|
||||
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 err != nil {
|
||||
log.Entry().WithError(err).Error("could not get api information from Jenkins")
|
||||
return map[string]interface{}{}
|
||||
if response.StatusCode != 200 { //http.StatusNoContent
|
||||
log.Entry().Errorf("Response-Code is %v, could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode)
|
||||
j.apiInformation = 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 {
|
||||
responseInterface := j.getAPIInformation()
|
||||
|
||||
if val, ok := responseInterface["result"]; ok {
|
||||
j.fetchAPIInformation()
|
||||
if val, ok := j.apiInformation["result"]; ok {
|
||||
// cases in ADO: succeeded, failed, canceled, none, partiallySucceeded
|
||||
switch result := responseInterface["result"]; result {
|
||||
switch result := val; result {
|
||||
case "SUCCESS":
|
||||
return "SUCCESS"
|
||||
case "ABORTED":
|
||||
@ -70,50 +82,47 @@ func (j *JenkinsConfigProvider) GetBuildStatus() string {
|
||||
// FAILURE, NOT_BUILT
|
||||
return "FAILURE"
|
||||
}
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
return "FAILURE"
|
||||
}
|
||||
|
||||
// GetLog returns the logfile from the current job as byte object
|
||||
func (j *JenkinsConfigProvider) GetLog() ([]byte, error) {
|
||||
URL := j.GetBuildUrl() + "consoleText"
|
||||
URL := j.GetBuildURL() + "consoleText"
|
||||
|
||||
response, err := j.client.GetRequest(URL, nil, nil)
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrapf(err, "Could not read Jenkins logfile. %v", err)
|
||||
}
|
||||
if response.StatusCode != 200 {
|
||||
log.Entry().Error("Could not get log information from Jenkins. Returning with empty log.")
|
||||
return []byte{}, errors.Wrapf(err, "could not GET Jenkins log file %v", err)
|
||||
} else if response.StatusCode != 200 {
|
||||
log.Entry().Error("response code !=200 could not get log information from Jenkins, returning with empty log.")
|
||||
return []byte{}, nil
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
logFile, err := ioutil.ReadAll(response.Body)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetPipelineStartTime returns the pipeline start time in UTC
|
||||
func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time {
|
||||
URL := j.GetBuildUrl() + "api/json"
|
||||
|
||||
URL := j.GetBuildURL() + "api/json"
|
||||
response, err := j.client.GetRequest(URL, nil, 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!
|
||||
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)
|
||||
log.Entry().Errorf("response code is %v . \n Could not get timestamp from Jenkins. Setting timestamp to 1970.", response.StatusCode)
|
||||
return time.Time{}.UTC()
|
||||
}
|
||||
var responseInterface map[string]interface{}
|
||||
err = piperHttp.ParseHTTPResponseBodyJSON(response, &responseInterface)
|
||||
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)
|
||||
@ -121,45 +130,88 @@ func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time {
|
||||
|
||||
log.Entry().Debugf("Pipeline start time: %v", timeStamp.String())
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
// getJenkinsHome returns the jenkins home e.g. /var/lib/jenkins
|
||||
func (j *JenkinsConfigProvider) getJenkinsHome() string {
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
//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 {
|
||||
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")
|
||||
}
|
||||
|
||||
// GetCommit returns the commit SHA from the current build, only works with the git plugin enabled
|
||||
func (j *JenkinsConfigProvider) GetCommit() string {
|
||||
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")
|
||||
}
|
||||
|
||||
// GetPullRequestConfig returns the pull request config
|
||||
func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig {
|
||||
return PullRequestConfig{
|
||||
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 {
|
||||
return truthy("CHANGE_ID")
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func TestJenkins(t *testing.T) {
|
||||
@ -12,7 +19,7 @@ func TestJenkins(t *testing.T) {
|
||||
defer resetEnv(os.Environ())
|
||||
os.Clearenv()
|
||||
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("GIT_COMMIT", "abcdef42713")
|
||||
os.Setenv("GIT_URL", "github.com/foo/bar")
|
||||
@ -20,10 +27,10 @@ func TestJenkins(t *testing.T) {
|
||||
p, _ := NewOrchestratorSpecificConfigProvider()
|
||||
|
||||
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, "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())
|
||||
})
|
||||
|
||||
@ -43,4 +50,449 @@ func TestJenkins(t *testing.T) {
|
||||
assert.Equal(t, "main", c.Base)
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -22,17 +22,18 @@ type OrchestratorSpecificConfigProviding interface {
|
||||
OrchestratorVersion() string
|
||||
GetStageName() string
|
||||
GetBranch() string
|
||||
GetBuildUrl() string
|
||||
GetBuildId() string
|
||||
GetJobUrl() string
|
||||
GetBuildURL() string
|
||||
GetBuildID() string
|
||||
GetJobURL() string
|
||||
GetJobName() string
|
||||
GetCommit() string
|
||||
GetPullRequestConfig() PullRequestConfig
|
||||
GetRepoUrl() string
|
||||
GetRepoURL() string
|
||||
IsPullRequest() bool
|
||||
GetLog() ([]byte, error)
|
||||
GetPipelineStartTime() time.Time
|
||||
GetBuildStatus() string
|
||||
GetBuildReason() string
|
||||
}
|
||||
|
||||
type PullRequestConfig struct {
|
||||
@ -41,6 +42,7 @@ type PullRequestConfig struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
// OrchestratorSettings struct to set orchestrator specific settings e.g. Jenkins credentials
|
||||
type OrchestratorSettings struct {
|
||||
JenkinsUser 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 {
|
||||
if isAzure() {
|
||||
return Orchestrator(AzureDevOps)
|
||||
|
@ -7,75 +7,96 @@ import (
|
||||
|
||||
type UnknownOrchestratorConfigProvider struct{}
|
||||
|
||||
// InitOrchestratorProvider returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
}
|
||||
|
||||
// OrchestratorVersion returns n/a for the unknownOrchestrator
|
||||
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.")
|
||||
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 {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
// OrchestratorType returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) OrchestratorType() string {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// GetLog returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) GetLog() ([]byte, error) {
|
||||
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 {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
timestamp, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 1970")
|
||||
return timestamp
|
||||
return time.Time{}.UTC()
|
||||
}
|
||||
|
||||
// GetStageName returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) GetStageName() string {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
// GetBranch returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) GetBranch() string {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
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.")
|
||||
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.")
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
// GetCommit returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) GetCommit() string {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
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.")
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
// GetPullRequestConfig returns n/a for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestConfig {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
return PullRequestConfig{
|
||||
@ -85,6 +106,7 @@ func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestCo
|
||||
}
|
||||
}
|
||||
|
||||
// IsPullRequest returns false for the unknownOrchestrator
|
||||
func (u *UnknownOrchestratorConfigProvider) IsPullRequest() bool {
|
||||
log.Entry().Warning("Unknown orchestrator - returning default values.")
|
||||
return false
|
||||
|
@ -3,6 +3,7 @@ package orchestrator
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -15,10 +16,10 @@ func TestUnknownOrchestrator(t *testing.T) {
|
||||
p, _ := NewOrchestratorSpecificConfigProvider()
|
||||
|
||||
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.GetCommit())
|
||||
assert.Equal(t, "n/a", p.GetRepoUrl())
|
||||
assert.Equal(t, "n/a", p.GetRepoURL())
|
||||
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.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)
|
||||
})
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ type BaseData struct {
|
||||
URL string `json:"url"`
|
||||
StepName string `json:"e_3"` // set by step generator
|
||||
StageName string `json:"e_10"`
|
||||
PipelineURLHash string `json:"e_4"` // defaults to sha1 of provider.GetBuildUrl()
|
||||
BuildURLHash string `json:"e_5"` // defaults to sha1 of provider.GetJobUrl()
|
||||
PipelineURLHash string `json:"e_4"` // defaults to sha1 of provider.GetBuildURL()
|
||||
BuildURLHash string `json:"e_5"` // defaults to sha1 of provider.GetJobURL()
|
||||
Orchestrator string `json:"e_14"` // defaults to provider.OrchestratorType()
|
||||
}
|
||||
|
||||
|
@ -88,13 +88,13 @@ func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) {
|
||||
}
|
||||
|
||||
func (t *Telemetry) getPipelineURLHash() string {
|
||||
jobUrl := t.provider.GetJobUrl()
|
||||
return t.toSha1OrNA(jobUrl)
|
||||
jobURL := t.provider.GetJobURL()
|
||||
return t.toSha1OrNA(jobURL)
|
||||
}
|
||||
|
||||
func (t *Telemetry) getBuildURLHash() string {
|
||||
buildUrl := t.provider.GetBuildUrl()
|
||||
return t.toSha1OrNA(buildUrl)
|
||||
buildURL := t.provider.GetBuildURL()
|
||||
return t.toSha1OrNA(buildURL)
|
||||
}
|
||||
|
||||
func (t *Telemetry) toSha1OrNA(input string) string {
|
||||
@ -161,7 +161,7 @@ func (t *Telemetry) logStepTelemetryData() {
|
||||
StepDuration: t.data.CustomData.Duration,
|
||||
ErrorCategory: t.data.CustomData.ErrorCategory,
|
||||
ErrorDetail: fatalError,
|
||||
CorrelationID: t.provider.GetBuildUrl(),
|
||||
CorrelationID: t.provider.GetBuildURL(),
|
||||
PiperCommitHash: t.data.CustomData.PiperCommitHash,
|
||||
}
|
||||
stepTelemetryJSON, err := json.Marshal(stepTelemetryData)
|
||||
|
Loading…
Reference in New Issue
Block a user