From ac5cf17317028250543bc7d4de5df0cd7b4d61c4 Mon Sep 17 00:00:00 2001 From: Googlom <36107508+Googlom@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:01:15 +0500 Subject: [PATCH] refactor(orchestrator): Use singleton in orchestrator package and rename methods (#4639) * rename interface, types and methods. some type changes and refactor * update dependent methods and variables * fix unit tests * a bit more refactor and fix * concurrent safe singleton * return old Options struct * refactor creating config provider and fix nil pointer derefernce * fix unit test and linter errors * introduce resetting config provider (for unit tests) * fix annoying error message when config provider is not configured --------- Co-authored-by: Gulom Alimov Co-authored-by: Muhammadali Nazarov --- .gitignore | 1 + cmd/artifactPrepareVersion.go | 8 +- cmd/artifactPrepareVersion_test.go | 2 +- cmd/codeqlExecuteScan.go | 8 +- cmd/codeqlExecuteScan_test.go | 2 +- cmd/credentialdiggerScan.go | 6 +- cmd/detectExecuteScan.go | 14 +- cmd/detectExecuteScan_test.go | 2 +- cmd/piper.go | 11 +- cmd/piper_test.go | 2 + cmd/sonarExecuteScan.go | 6 +- pkg/orchestrator/{azureDevOps.go => azure.go} | 90 +++++----- .../{azureDevOps_test.go => azure_test.go} | 50 +++--- .../{gitHubActions.go => github_actions.go} | 114 ++++++------ ...Actions_test.go => github_actions_test.go} | 58 +++---- pkg/orchestrator/helpers.go | 40 +++++ .../{orchestrator_test.go => helpers_test.go} | 36 +--- pkg/orchestrator/jenkins.go | 96 +++++----- pkg/orchestrator/jenkins_test.go | 78 +++------ pkg/orchestrator/orchestrator.go | 164 +++++++++--------- pkg/orchestrator/unknownOrchestrator.go | 95 +++++----- pkg/orchestrator/unknownOrchestrator_test.go | 61 ------- pkg/reporting/policyViolation.go | 8 +- pkg/reporting/securityVulnerability.go | 10 +- pkg/telemetry/data.go | 4 +- pkg/telemetry/telemetry.go | 16 +- pkg/telemetry/telemetry_test.go | 6 +- 27 files changed, 466 insertions(+), 522 deletions(-) rename pkg/orchestrator/{azureDevOps.go => azure.go} (71%) rename pkg/orchestrator/{azureDevOps_test.go => azure_test.go} (89%) rename pkg/orchestrator/{gitHubActions.go => github_actions.go} (67%) rename pkg/orchestrator/{gitHubActions_test.go => github_actions_test.go} (88%) create mode 100644 pkg/orchestrator/helpers.go rename pkg/orchestrator/{orchestrator_test.go => helpers_test.go} (60%) delete mode 100644 pkg/orchestrator/unknownOrchestrator_test.go diff --git a/.gitignore b/.gitignore index 5bac9e2b1e..caeb15e0bd 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ debug.test /piper_master.exe /jenkins-library /jenkins-library.exe +node_modules/ # piper binary outputs .pipeline/commonPipelineEnvironment/ diff --git a/cmd/artifactPrepareVersion.go b/cmd/artifactPrepareVersion.go index 9634b40b4c..150f3ec57f 100644 --- a/cmd/artifactPrepareVersion.go +++ b/cmd/artifactPrepareVersion.go @@ -66,7 +66,7 @@ type artifactPrepareVersionUtils interface { FileRead(path string) ([]byte, error) FileRemove(path string) error - NewOrchestratorSpecificConfigProvider() (orchestrator.OrchestratorSpecificConfigProviding, error) + GetConfigProvider() (orchestrator.ConfigProvider, error) } type artifactPrepareVersionUtilsBundle struct { @@ -75,8 +75,8 @@ type artifactPrepareVersionUtilsBundle struct { *piperhttp.Client } -func (a *artifactPrepareVersionUtilsBundle) NewOrchestratorSpecificConfigProvider() (orchestrator.OrchestratorSpecificConfigProviding, error) { - return orchestrator.NewOrchestratorSpecificConfigProvider() +func (a *artifactPrepareVersionUtilsBundle) GetConfigProvider() (orchestrator.ConfigProvider, error) { + return orchestrator.GetOrchestratorConfigProvider(nil) } func newArtifactPrepareVersionUtilsBundle() artifactPrepareVersionUtils { @@ -160,7 +160,7 @@ func runArtifactPrepareVersion(config *artifactPrepareVersionOptions, telemetryD if config.VersioningType == "cloud" || config.VersioningType == "cloud_noTag" { // make sure that versioning does not create tags (when set to "cloud") // for PR pipelines, optimized pipelines (= no build) - provider, err := utils.NewOrchestratorSpecificConfigProvider() + provider, err := utils.GetConfigProvider() if err != nil { log.Entry().WithError(err).Warning("Cannot infer config from CI environment") } diff --git a/cmd/artifactPrepareVersion_test.go b/cmd/artifactPrepareVersion_test.go index 24b8ee79f3..2a4ea75036 100644 --- a/cmd/artifactPrepareVersion_test.go +++ b/cmd/artifactPrepareVersion_test.go @@ -192,7 +192,7 @@ func (a *artifactPrepareVersionMockUtils) DownloadFile(url, filename string, hea return nil } -func (a *artifactPrepareVersionMockUtils) NewOrchestratorSpecificConfigProvider() (orchestrator.OrchestratorSpecificConfigProviding, error) { +func (a *artifactPrepareVersionMockUtils) GetConfigProvider() (orchestrator.ConfigProvider, error) { return &orchestrator.UnknownOrchestratorConfigProvider{}, nil } diff --git a/cmd/codeqlExecuteScan.go b/cmd/codeqlExecuteScan.go index d9ac049512..d3e6e46dbf 100644 --- a/cmd/codeqlExecuteScan.go +++ b/cmd/codeqlExecuteScan.go @@ -130,20 +130,20 @@ func initGitInfo(config *codeqlExecuteScanOptions) (codeql.RepoInfo, error) { repoInfo.Ref = config.AnalyzedRef repoInfo.CommitId = config.CommitID - provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) if err != nil { log.Entry().Warn("No orchestrator found. We assume piper is running locally.") } else { if repoInfo.Ref == "" { - repoInfo.Ref = provider.GetReference() + repoInfo.Ref = provider.GitReference() } if repoInfo.CommitId == "" || repoInfo.CommitId == "NA" { - repoInfo.CommitId = provider.GetCommit() + repoInfo.CommitId = provider.CommitSHA() } if repoInfo.ServerUrl == "" { - err = getGitRepoInfo(provider.GetRepoURL(), &repoInfo) + err = getGitRepoInfo(provider.RepoURL(), &repoInfo) if err != nil { log.Entry().Error(err) } diff --git a/cmd/codeqlExecuteScan_test.go b/cmd/codeqlExecuteScan_test.go index 28f056b0c7..3e43fe03a8 100644 --- a/cmd/codeqlExecuteScan_test.go +++ b/cmd/codeqlExecuteScan_test.go @@ -249,7 +249,7 @@ func TestInitGitInfo(t *testing.T) { config := codeqlExecuteScanOptions{Repository: "https://github.hello.test", AnalyzedRef: "refs/head/branch", CommitID: "abcd1234"} repoInfo, err := initGitInfo(&config) assert.NoError(t, err) - _, err = orchestrator.NewOrchestratorSpecificConfigProvider() + _, err = orchestrator.GetOrchestratorConfigProvider(nil) assert.Equal(t, "abcd1234", repoInfo.CommitId) assert.Equal(t, "refs/head/branch", repoInfo.Ref) if err != nil { diff --git a/cmd/credentialdiggerScan.go b/cmd/credentialdiggerScan.go index 31e84b39b1..2be4a32549 100644 --- a/cmd/credentialdiggerScan.go +++ b/cmd/credentialdiggerScan.go @@ -42,14 +42,14 @@ func newCDUtils() credentialdiggerUtils { func credentialdiggerScan(config credentialdiggerScanOptions, telemetryData *telemetry.CustomData) error { utils := newCDUtils() // 0: Get attributes from orchestrator - provider, prov_err := orchestrator.NewOrchestratorSpecificConfigProvider() + provider, prov_err := orchestrator.GetOrchestratorConfigProvider(nil) if prov_err != nil { log.Entry().WithError(prov_err).Error( "credentialdiggerScan: unable to load orchestrator specific configuration.") } if config.Repository == "" { // Get current repository from orchestrator - repoUrlOrchestrator := provider.GetRepoURL() + repoUrlOrchestrator := provider.RepoURL() if repoUrlOrchestrator == "n/a" { // Jenkins configuration error log.Entry().WithError(errors.New( @@ -61,7 +61,7 @@ func credentialdiggerScan(config credentialdiggerScanOptions, telemetryData *tel } if provider.IsPullRequest() { // set the pr number - config.PrNumber, _ = strconv.Atoi(provider.GetPullRequestConfig().Key) + config.PrNumber, _ = strconv.Atoi(provider.PullRequestConfig().Key) log.Entry().Debug("Scan the current pull request: number ", config.PrNumber) } diff --git a/cmd/detectExecuteScan.go b/cmd/detectExecuteScan.go index 8ee82bf3be..4150ae9308 100644 --- a/cmd/detectExecuteScan.go +++ b/cmd/detectExecuteScan.go @@ -46,7 +46,7 @@ type detectUtils interface { GetIssueService() *github.IssuesService GetSearchService() *github.SearchService - GetProvider() orchestrator.OrchestratorSpecificConfigProviding + GetProvider() orchestrator.ConfigProvider } type detectUtilsBundle struct { @@ -55,7 +55,7 @@ type detectUtilsBundle struct { *piperhttp.Client issues *github.IssuesService search *github.SearchService - provider orchestrator.OrchestratorSpecificConfigProviding + provider orchestrator.ConfigProvider } func (d *detectUtilsBundle) GetIssueService() *github.IssuesService { @@ -66,7 +66,7 @@ func (d *detectUtilsBundle) GetSearchService() *github.SearchService { return d.search } -func (d *detectUtilsBundle) GetProvider() orchestrator.OrchestratorSpecificConfigProviding { +func (d *detectUtilsBundle) GetProvider() orchestrator.ConfigProvider { return d.provider } @@ -112,7 +112,7 @@ func newDetectUtils(client *github.Client) detectUtils { utils.Stdout(log.Writer()) utils.Stderr(log.Writer()) - provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) if err != nil { log.Entry().WithError(err).Warning(err) provider = &orchestrator.UnknownOrchestratorConfigProvider{} @@ -568,9 +568,9 @@ func isMajorVulnerability(v bd.Vulnerability) bool { } func postScanChecksAndReporting(ctx context.Context, config detectExecuteScanOptions, influx *detectExecuteScanInflux, utils detectUtils, sys *blackduckSystem) error { - - if utils.GetProvider().IsPullRequest() { - issueNumber, err := strconv.Atoi(utils.GetProvider().GetPullRequestConfig().Key) + provider := utils.GetProvider() + if provider.IsPullRequest() { + issueNumber, err := strconv.Atoi(provider.PullRequestConfig().Key) if err != nil { log.Entry().Warning("Can not get issue number ", err) return nil diff --git a/cmd/detectExecuteScan_test.go b/cmd/detectExecuteScan_test.go index c2d3a4ca2b..0fbcee1326 100644 --- a/cmd/detectExecuteScan_test.go +++ b/cmd/detectExecuteScan_test.go @@ -33,7 +33,7 @@ type detectTestUtilsBundle struct { orchestrator *orchestratorConfigProviderMock } -func (d *detectTestUtilsBundle) GetProvider() orchestrator.OrchestratorSpecificConfigProviding { +func (d *detectTestUtilsBundle) GetProvider() orchestrator.ConfigProvider { return d.orchestrator } diff --git a/cmd/piper.go b/cmd/piper.go index 9d440be570..f5f7471d0e 100644 --- a/cmd/piper.go +++ b/cmd/piper.go @@ -212,16 +212,13 @@ func Execute() { } func addRootFlags(rootCmd *cobra.Command) { - var provider orchestrator.OrchestratorSpecificConfigProviding - var err error - - provider, err = orchestrator.NewOrchestratorSpecificConfigProvider() + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) if err != nil { log.Entry().Error(err) 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.BuildURL(), "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 : 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") @@ -290,12 +287,12 @@ func initStageName(outputToLog bool) { } // Use stageName from ENV as fall-back, for when extracting it from parametersJSON fails below - provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) if err != nil { log.Entry().WithError(err).Warning("Cannot infer stage name from CI environment") } else { stageNameSource = "env variable" - GeneralConfig.StageName = provider.GetStageName() + GeneralConfig.StageName = provider.StageName() } if len(GeneralConfig.ParametersJSON) == 0 { diff --git a/cmd/piper_test.go b/cmd/piper_test.go index 557f0ec199..da711144e5 100644 --- a/cmd/piper_test.go +++ b/cmd/piper_test.go @@ -7,6 +7,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/SAP/jenkins-library/pkg/orchestrator" "os" "path/filepath" "strings" @@ -62,6 +63,7 @@ func TestAdoptStageNameFromParametersJSON(t *testing.T) { // init defer resetEnv(os.Environ()) os.Clearenv() + orchestrator.ResetConfigProvider() //mock Jenkins env os.Setenv("JENKINS_HOME", "anything") diff --git a/cmd/sonarExecuteScan.go b/cmd/sonarExecuteScan.go index 31322e16d0..45bf019b2e 100644 --- a/cmd/sonarExecuteScan.go +++ b/cmd/sonarExecuteScan.go @@ -477,14 +477,14 @@ func getTempDir() string { // Fetches parameters from environment variables and updates the options accordingly (only if not already set) func detectParametersFromCI(options *sonarExecuteScanOptions) { - provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) if err != nil { log.Entry().WithError(err).Warning("Cannot infer config from CI environment") return } if provider.IsPullRequest() { - config := provider.GetPullRequestConfig() + config := provider.PullRequestConfig() if len(options.ChangeBranch) == 0 { log.Entry().Info("Inferring parameter changeBranch from environment: " + config.Branch) options.ChangeBranch = config.Branch @@ -498,7 +498,7 @@ func detectParametersFromCI(options *sonarExecuteScanOptions) { options.ChangeID = config.Key } } else { - branch := provider.GetBranch() + branch := provider.Branch() if options.InferBranchName && len(options.BranchName) == 0 { log.Entry().Info("Inferring parameter branchName from environment: " + branch) options.BranchName = branch diff --git a/pkg/orchestrator/azureDevOps.go b/pkg/orchestrator/azure.go similarity index 71% rename from pkg/orchestrator/azureDevOps.go rename to pkg/orchestrator/azure.go index 505e20c3e7..28b0f5f2c6 100644 --- a/pkg/orchestrator/azureDevOps.go +++ b/pkg/orchestrator/azure.go @@ -11,24 +11,30 @@ import ( "github.com/SAP/jenkins-library/pkg/log" ) -type AzureDevOpsConfigProvider struct { +type azureDevopsConfigProvider struct { client piperHttp.Client apiInformation map[string]interface{} } -// InitOrchestratorProvider initializes http client for AzureDevopsConfigProvider -func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { +func newAzureDevopsConfigProvider() *azureDevopsConfigProvider { + return &azureDevopsConfigProvider{} +} + +// Configure initializes http client for AzureDevopsConfigProvider +func (a *azureDevopsConfigProvider) Configure(opts *Options) error { a.client.SetOptions(piperHttp.ClientOptions{ Username: "", - Password: settings.AzureToken, + Password: opts.AzureToken, MaxRetries: 3, TransportTimeout: time.Second * 10, }) + log.Entry().Debug("Successfully initialized Azure config provider") + return nil } // fetchAPIInformation fetches Azure API information of current build -func (a *AzureDevOpsConfigProvider) fetchAPIInformation() { +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") @@ -57,45 +63,45 @@ func (a *AzureDevOpsConfigProvider) fetchAPIInformation() { } } -func (a *AzureDevOpsConfigProvider) GetChangeSet() []ChangeSet { - log.Entry().Warn("GetChangeSet for AzureDevOps not yet implemented") +func (a *azureDevopsConfigProvider) ChangeSets() []ChangeSet { + log.Entry().Warn("ChangeSets for AzureDevOps not yet implemented") return []ChangeSet{} } // getSystemCollectionURI returns the URI of the TFS collection or Azure DevOps organization e.g. https://dev.azure.com/fabrikamfiber/ -func (a *AzureDevOpsConfigProvider) getSystemCollectionURI() string { +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 { +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 { +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 { +// JobName returns the pipeline job name, currently org/repo +func (a *azureDevopsConfigProvider) JobName() string { return getEnv("BUILD_REPOSITORY_NAME", "n/a") } // OrchestratorVersion returns the agent version on ADO -func (a *AzureDevOpsConfigProvider) OrchestratorVersion() string { +func (a *azureDevopsConfigProvider) OrchestratorVersion() string { return getEnv("AGENT_VERSION", "n/a") } // OrchestratorType returns the orchestrator name e.g. Azure/GitHubActions/Jenkins -func (a *AzureDevOpsConfigProvider) OrchestratorType() string { +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 { +// BuildStatus returns status of the build. Return variables are aligned with Jenkins build statuses. +func (a *azureDevopsConfigProvider) BuildStatus() string { // cases to align with Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED switch buildStatus := getEnv("AGENT_JOBSTATUS", "FAILURE"); buildStatus { case "Succeeded": @@ -108,8 +114,8 @@ func (a *AzureDevOpsConfigProvider) GetBuildStatus() string { } } -// GetLog returns the whole logfile for the current pipeline run -func (a *AzureDevOpsConfigProvider) GetLog() ([]byte, error) { +// FullLogs returns the whole logfile for the current pipeline run +func (a *azureDevopsConfigProvider) FullLogs() ([]byte, error) { URL := a.getSystemCollectionURI() + a.getTeamProjectID() + "/_apis/build/builds/" + a.getAzureBuildID() + "/logs" response, err := a.client.GetRequest(URL, nil, nil) @@ -161,8 +167,8 @@ func (a *AzureDevOpsConfigProvider) GetLog() ([]byte, error) { return logs, nil } -// GetPipelineStartTime returns the pipeline start time in UTC -func (a *AzureDevOpsConfigProvider) GetPipelineStartTime() time.Time { +// PipelineStartTime returns the pipeline start time in UTC +func (a *azureDevopsConfigProvider) PipelineStartTime() time.Time { //"2022-03-18T07:30:31.1915758Z" a.fetchAPIInformation() if val, ok := a.apiInformation["startTime"]; ok { @@ -176,59 +182,59 @@ func (a *AzureDevOpsConfigProvider) GetPipelineStartTime() time.Time { return time.Time{}.UTC() } -// GetBuildID returns the BuildNumber displayed in the ADO UI -func (a *AzureDevOpsConfigProvider) GetBuildID() string { +// BuildID returns the BuildNumber displayed in the ADO UI +func (a *azureDevopsConfigProvider) BuildID() 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 { +// StageName returns the human-readable name given to a stage. e.g. "Promote" or "Init" +func (a *azureDevopsConfigProvider) StageName() string { return getEnv("SYSTEM_STAGEDISPLAYNAME", "n/a") } -// GetBranch returns the source branch name, e.g. main -func (a *AzureDevOpsConfigProvider) GetBranch() string { +// Branch returns the source branch name, e.g. main +func (a *azureDevopsConfigProvider) Branch() string { tmp := getEnv("BUILD_SOURCEBRANCH", "n/a") return strings.TrimPrefix(tmp, "refs/heads/") } -// GetReference return the git reference -func (a *AzureDevOpsConfigProvider) GetReference() string { +// GitReference return the git reference +func (a *azureDevopsConfigProvider) GitReference() string { return getEnv("BUILD_SOURCEBRANCH", "n/a") } -// GetBuildURL returns the builds URL e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build/results?buildId=1234 -func (a *AzureDevOpsConfigProvider) GetBuildURL() string { +// BuildURL returns the builds URL e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build/results?buildId=1234 +func (a *azureDevopsConfigProvider) BuildURL() string { return os.Getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") + os.Getenv("SYSTEM_TEAMPROJECT") + "/" + os.Getenv("SYSTEM_DEFINITIONNAME") + "/_build/results?buildId=" + a.getAzureBuildID() } -// GetJobURL returns tje current job url e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build?definitionId=1234 -func (a *AzureDevOpsConfigProvider) GetJobURL() string { +// JobURL returns tje current job url e.g. https://dev.azure.com/fabrikamfiber/your-repo-name/_build?definitionId=1234 +func (a *azureDevopsConfigProvider) JobURL() 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 { +// CommitSHA returns commit SHA of current build +func (a *azureDevopsConfigProvider) CommitSHA() string { return getEnv("BUILD_SOURCEVERSION", "n/a") } -// GetRepoURL returns current repo URL e.g. https://github.com/SAP/jenkins-library -func (a *AzureDevOpsConfigProvider) GetRepoURL() string { +// RepoURL returns current repo URL e.g. https://github.com/SAP/jenkins-library +func (a *azureDevopsConfigProvider) RepoURL() string { return getEnv("BUILD_REPOSITORY_URI", "n/a") } -// GetBuildReason returns the build reason -func (a *AzureDevOpsConfigProvider) GetBuildReason() string { +// BuildReason returns the build reason +func (a *azureDevopsConfigProvider) BuildReason() 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 { +// PullRequestConfig returns pull request configuration +func (a *azureDevopsConfigProvider) PullRequestConfig() PullRequestConfig { prKey := getEnv("SYSTEM_PULLREQUEST_PULLREQUESTID", "n/a") // This variable is populated for pull requests which have a different pull request ID and pull request number. @@ -247,11 +253,11 @@ 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" } func isAzure() bool { envVars := []string{"AZURE_HTTP_USER_AGENT"} - return areIndicatingEnvVarsSet(envVars) + return envVarsAreSet(envVars) } diff --git a/pkg/orchestrator/azureDevOps_test.go b/pkg/orchestrator/azure_test.go similarity index 89% rename from pkg/orchestrator/azureDevOps_test.go rename to pkg/orchestrator/azure_test.go index 117f175c81..cfe8201923 100644 --- a/pkg/orchestrator/azureDevOps_test.go +++ b/pkg/orchestrator/azure_test.go @@ -30,16 +30,16 @@ func TestAzure(t *testing.T) { os.Setenv("BUILD_REPOSITORY_URI", "github.com/foo/bar") os.Setenv("SYSTEM_DEFINITIONNAME", "bar") os.Setenv("SYSTEM_DEFINITIONID", "1234") - p, _ := NewOrchestratorSpecificConfigProvider() + p, _ := GetOrchestratorConfigProvider(nil) assert.False(t, p.IsPullRequest()) - assert.Equal(t, "feat/test-azure", p.GetBranch()) - assert.Equal(t, "refs/heads/feat/test-azure", p.GetReference()) - 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, "feat/test-azure", p.Branch()) + assert.Equal(t, "refs/heads/feat/test-azure", p.GitReference()) + assert.Equal(t, "https://pogo.sap/foo/bar/_build/results?buildId=42", p.BuildURL()) + assert.Equal(t, "abcdef42713", p.CommitSHA()) + assert.Equal(t, "github.com/foo/bar", p.RepoURL()) assert.Equal(t, "Azure", p.OrchestratorType()) - assert.Equal(t, "https://pogo.sap/foo/bar/_build?definitionId=1234", p.GetJobURL()) + assert.Equal(t, "https://pogo.sap/foo/bar/_build?definitionId=1234", p.JobURL()) }) t.Run("PR", func(t *testing.T) { @@ -50,8 +50,8 @@ func TestAzure(t *testing.T) { os.Setenv("SYSTEM_PULLREQUEST_PULLREQUESTID", "42") os.Setenv("BUILD_REASON", "PullRequest") - p := AzureDevOpsConfigProvider{} - c := p.GetPullRequestConfig() + p := azureDevopsConfigProvider{} + c := p.PullRequestConfig() assert.True(t, p.IsPullRequest()) assert.Equal(t, "feat/test-azure", c.Branch) @@ -68,8 +68,8 @@ func TestAzure(t *testing.T) { os.Setenv("SYSTEM_PULLREQUEST_PULLREQUESTNUMBER", "42") os.Setenv("BUILD_REASON", "PullRequest") - p := AzureDevOpsConfigProvider{} - c := p.GetPullRequestConfig() + p := azureDevopsConfigProvider{} + c := p.PullRequestConfig() assert.True(t, p.IsPullRequest()) assert.Equal(t, "feat/test-azure", c.Branch) @@ -98,14 +98,14 @@ func TestAzure(t *testing.T) { os.Setenv("BUILD_BUILDNUMBER", "20220318.16") os.Setenv("BUILD_REPOSITORY_NAME", "repo-org/repo-name") - p := AzureDevOpsConfigProvider{} + 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, "42", p.getAzureBuildID()) // Don't confuse getAzureBuildID and provider.BuildID! + assert.Equal(t, "20220318.16", p.BuildID()) // buildNumber is used in the UI assert.Equal(t, "2.193.0", p.OrchestratorVersion()) - assert.Equal(t, "repo-org/repo-name", p.GetJobName()) + assert.Equal(t, "repo-org/repo-name", p.JobName()) }) } @@ -140,10 +140,10 @@ func TestAzureDevOpsConfigProvider_GetPipelineStartTime(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := &AzureDevOpsConfigProvider{} + a := &azureDevopsConfigProvider{} a.apiInformation = tt.apiInformation - pipelineStartTime := a.GetPipelineStartTime() - assert.Equalf(t, tt.want, pipelineStartTime, "GetPipelineStartTime()") + pipelineStartTime := a.PipelineStartTime() + assert.Equalf(t, tt.want, pipelineStartTime, "PipelineStartTime()") }) } } @@ -181,9 +181,9 @@ func TestAzureDevOpsConfigProvider_GetBuildStatus(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() os.Setenv("AGENT_JOBSTATUS", tt.envVar) - a := &AzureDevOpsConfigProvider{} + a := &azureDevopsConfigProvider{} - assert.Equalf(t, tt.want, a.GetBuildStatus(), "GetBuildStatus()") + assert.Equalf(t, tt.want, a.BuildStatus(), "BuildStatus()") }) } } @@ -229,7 +229,7 @@ func TestAzureDevOpsConfigProvider_getAPIInformation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := &AzureDevOpsConfigProvider{ + a := &azureDevopsConfigProvider{ apiInformation: tt.apiInformation, } @@ -311,7 +311,7 @@ func TestAzureDevOpsConfigProvider_GetLog(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a := &AzureDevOpsConfigProvider{} + a := &azureDevopsConfigProvider{} a.client.SetOptions(piperhttp.ClientOptions{ MaxRequestDuration: 5 * time.Second, Token: "TOKEN", @@ -355,11 +355,11 @@ func TestAzureDevOpsConfigProvider_GetLog(t *testing.T) { }) }, ) - got, err := a.GetLog() - if !tt.wantErr(t, err, fmt.Sprintf("GetLog()")) { + got, err := a.FullLogs() + if !tt.wantErr(t, err, fmt.Sprintf("FullLogs()")) { return } - assert.Equalf(t, tt.want, got, "GetLog()") + assert.Equalf(t, tt.want, got, "FullLogs()") }) } } diff --git a/pkg/orchestrator/gitHubActions.go b/pkg/orchestrator/github_actions.go similarity index 67% rename from pkg/orchestrator/gitHubActions.go rename to pkg/orchestrator/github_actions.go index ee68d92443..babd801c0b 100644 --- a/pkg/orchestrator/gitHubActions.go +++ b/pkg/orchestrator/github_actions.go @@ -19,7 +19,7 @@ import ( "golang.org/x/sync/errgroup" ) -type GitHubActionsConfigProvider struct { +type githubActionsConfigProvider struct { client *github.Client ctx context.Context owner string @@ -46,31 +46,37 @@ type fullLog struct { b [][]byte } -// InitOrchestratorProvider initializes http client for GitHubActionsDevopsConfigProvider -func (g *GitHubActionsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { +func newGithubActionsConfigProvider() *githubActionsConfigProvider { + owner, repo := getOwnerAndRepoNames() + return &githubActionsConfigProvider{ + owner: owner, + repo: repo, + } +} + +// Configure initializes http client for GitHubActionsDevopsConfigProvider +func (g *githubActionsConfigProvider) Configure(opts *Options) error { var err error - g.ctx, g.client, err = piperGithub.NewClientBuilder(settings.GitHubToken, getEnv("GITHUB_API_URL", "")).Build() + g.ctx, g.client, err = piperGithub.NewClientBuilder(opts.GitHubToken, getEnv("GITHUB_API_URL", "")).Build() if err != nil { - log.Entry().Errorf("failed to create github client: %v", err) - return + return errors.Wrap(err, "failed to create github client") } - g.owner, g.repo = getOwnerAndRepoNames() - log.Entry().Debug("Successfully initialized GitHubActions config provider") + return nil } -func (g *GitHubActionsConfigProvider) OrchestratorVersion() string { +func (g *githubActionsConfigProvider) OrchestratorVersion() string { log.Entry().Debugf("OrchestratorVersion() for GitHub Actions is not applicable.") return "n/a" } -func (g *GitHubActionsConfigProvider) OrchestratorType() string { +func (g *githubActionsConfigProvider) OrchestratorType() string { return "GitHubActions" } -// GetBuildStatus returns current run status -func (g *GitHubActionsConfigProvider) GetBuildStatus() string { +// BuildStatus returns current run status +func (g *githubActionsConfigProvider) BuildStatus() string { g.fetchRunData() switch g.runData.Status { case "success": @@ -84,8 +90,13 @@ func (g *GitHubActionsConfigProvider) GetBuildStatus() string { } } -// GetLog returns the whole logfile for the current pipeline run -func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) { +// FullLogs returns the whole logfile for the current pipeline run +func (g *githubActionsConfigProvider) FullLogs() ([]byte, error) { + if g.client == nil { + log.Entry().Debug("ConfigProvider for GitHub Actions is not configured. Unable to fetch logs") + return []byte{}, nil + } + if err := g.fetchJobs(); err != nil { return nil, err } @@ -129,31 +140,31 @@ func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) { return bytes.Join(fullLogs.b, []byte("")), nil } -// GetBuildID returns current run ID -func (g *GitHubActionsConfigProvider) GetBuildID() string { +// BuildID returns current run ID +func (g *githubActionsConfigProvider) BuildID() string { return getEnv("GITHUB_RUN_ID", "n/a") } -func (g *GitHubActionsConfigProvider) GetChangeSet() []ChangeSet { - log.Entry().Debug("GetChangeSet for GitHubActions not implemented") +func (g *githubActionsConfigProvider) ChangeSets() []ChangeSet { + log.Entry().Debug("ChangeSets for GitHubActions not implemented") return []ChangeSet{} } -// GetPipelineStartTime returns the pipeline start time in UTC -func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time { +// PipelineStartTime returns the pipeline start time in UTC +func (g *githubActionsConfigProvider) PipelineStartTime() time.Time { g.fetchRunData() return g.runData.StartedAt.UTC() } -// GetStageName returns the human-readable name given to a stage. -func (g *GitHubActionsConfigProvider) GetStageName() string { +// StageName returns the human-readable name given to a stage. +func (g *githubActionsConfigProvider) StageName() string { return getEnv("GITHUB_JOB", "unknown") } -// GetBuildReason returns the reason of workflow trigger. +// BuildReason returns the reason of workflow trigger. // BuildReasons are unified with AzureDevOps build reasons, see // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services -func (g *GitHubActionsConfigProvider) GetBuildReason() string { +func (g *githubActionsConfigProvider) BuildReason() string { switch getEnv("GITHUB_EVENT_NAME", "") { case "workflow_dispatch": return BuildReasonManual @@ -170,50 +181,50 @@ func (g *GitHubActionsConfigProvider) GetBuildReason() string { } } -// GetBranch returns the source branch name, e.g. main -func (g *GitHubActionsConfigProvider) GetBranch() string { +// Branch returns the source branch name, e.g. main +func (g *githubActionsConfigProvider) Branch() string { return getEnv("GITHUB_REF_NAME", "n/a") } -// GetReference return the git reference. For example, refs/heads/your_branch_name -func (g *GitHubActionsConfigProvider) GetReference() string { +// GitReference return the git reference. For example, refs/heads/your_branch_name +func (g *githubActionsConfigProvider) GitReference() string { return getEnv("GITHUB_REF", "n/a") } -// GetBuildURL returns the builds URL. The URL should point to the pipeline (not to the stage) +// BuildURL returns the builds URL. The URL should point to the pipeline (not to the stage) // that is currently being executed. For example, https://github.com/SAP/jenkins-library/actions/runs/5815297487 -func (g *GitHubActionsConfigProvider) GetBuildURL() string { - return g.GetRepoURL() + "/actions/runs/" + g.GetBuildID() +func (g *githubActionsConfigProvider) BuildURL() string { + return g.RepoURL() + "/actions/runs/" + g.BuildID() } -// GetJobURL returns the job URL. The URL should point to project’s pipelines. +// JobURL returns the job URL. The URL should point to project’s pipelines. // For example, https://github.com/SAP/jenkins-library/actions/workflows/workflow-file-name.yaml -func (g *GitHubActionsConfigProvider) GetJobURL() string { +func (g *githubActionsConfigProvider) JobURL() string { fileName := workflowFileName() if fileName == "" { return "" } - return g.GetRepoURL() + "/actions/workflows/" + fileName + return g.RepoURL() + "/actions/workflows/" + fileName } -// GetJobName returns the current workflow name. For example, "Piper workflow" -func (g *GitHubActionsConfigProvider) GetJobName() string { +// JobName returns the current workflow name. For example, "Piper workflow" +func (g *githubActionsConfigProvider) JobName() string { return getEnv("GITHUB_WORKFLOW", "unknown") } -// GetCommit returns the commit SHA that triggered the workflow. For example, ffac537e6cbbf934b08745a378932722df287a53 -func (g *GitHubActionsConfigProvider) GetCommit() string { +// CommitSHA returns the commit SHA that triggered the workflow. For example, ffac537e6cbbf934b08745a378932722df287a53 +func (g *githubActionsConfigProvider) CommitSHA() string { return getEnv("GITHUB_SHA", "n/a") } -// GetRepoURL returns full url to repository. For example, https://github.com/SAP/jenkins-library -func (g *GitHubActionsConfigProvider) GetRepoURL() string { +// RepoURL returns full url to repository. For example, https://github.com/SAP/jenkins-library +func (g *githubActionsConfigProvider) RepoURL() string { return getEnv("GITHUB_SERVER_URL", "n/a") + "/" + getEnv("GITHUB_REPOSITORY", "n/a") } -// GetPullRequestConfig returns pull request configuration -func (g *GitHubActionsConfigProvider) GetPullRequestConfig() PullRequestConfig { +// PullRequestConfig returns pull request configuration +func (g *githubActionsConfigProvider) PullRequestConfig() PullRequestConfig { // See https://docs.github.com/en/enterprise-server@3.6/actions/learn-github-actions/variables#default-environment-variables githubRef := getEnv("GITHUB_REF", "n/a") prNumber := strings.TrimSuffix(strings.TrimPrefix(githubRef, "refs/pull/"), "/merge") @@ -225,13 +236,13 @@ func (g *GitHubActionsConfigProvider) GetPullRequestConfig() PullRequestConfig { } // IsPullRequest indicates whether the current build is triggered by a PR -func (g *GitHubActionsConfigProvider) IsPullRequest() bool { - return truthy("GITHUB_HEAD_REF") +func (g *githubActionsConfigProvider) IsPullRequest() bool { + return envVarIsTrue("GITHUB_HEAD_REF") } func isGitHubActions() bool { envVars := []string{"GITHUB_ACTION", "GITHUB_ACTIONS"} - return areIndicatingEnvVarsSet(envVars) + return envVarsAreSet(envVars) } // actionsURL returns URL to actions resource. For example, @@ -240,7 +251,12 @@ func actionsURL() string { return getEnv("GITHUB_API_URL", "") + "/repos/" + getEnv("GITHUB_REPOSITORY", "") + "/actions" } -func (g *GitHubActionsConfigProvider) fetchRunData() { +func (g *githubActionsConfigProvider) fetchRunData() { + if g.client == nil { + log.Entry().Debug("ConfigProvider for GitHub Actions is not configured. Unable to fetch run data") + return + } + if g.runData.fetched { return } @@ -268,7 +284,7 @@ func convertRunData(runData *github.WorkflowRun) run { } } -func (g *GitHubActionsConfigProvider) fetchJobs() error { +func (g *githubActionsConfigProvider) fetchJobs() error { if g.jobsFetched { return nil } @@ -304,8 +320,8 @@ func convertJobs(jobs []*github.WorkflowJob) []job { return result } -func (g *GitHubActionsConfigProvider) runIdInt64() (int64, error) { - strRunId := g.GetBuildID() +func (g *githubActionsConfigProvider) runIdInt64() (int64, error) { + strRunId := g.BuildID() runId, err := strconv.ParseInt(strRunId, 10, 64) if err != nil { return 0, errors.Wrapf(err, "invalid GITHUB_RUN_ID value %s: %s", strRunId, err) diff --git a/pkg/orchestrator/gitHubActions_test.go b/pkg/orchestrator/github_actions_test.go similarity index 88% rename from pkg/orchestrator/gitHubActions_test.go rename to pkg/orchestrator/github_actions_test.go index 6a18c5c9a8..be15ee0a88 100644 --- a/pkg/orchestrator/gitHubActions_test.go +++ b/pkg/orchestrator/github_actions_test.go @@ -30,10 +30,10 @@ func TestGitHubActionsConfigProvider_GetBuildStatus(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g := &GitHubActionsConfigProvider{ + g := &githubActionsConfigProvider{ runData: tt.runData, } - assert.Equalf(t, tt.want, g.GetBuildStatus(), "GetBuildStatus()") + assert.Equalf(t, tt.want, g.BuildStatus(), "BuildStatus()") }) } } @@ -54,10 +54,10 @@ func TestGitHubActionsConfigProvider_GetBuildReason(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g := &GitHubActionsConfigProvider{} + g := &githubActionsConfigProvider{} _ = os.Setenv("GITHUB_EVENT_NAME", tt.envGithubRef) - assert.Equalf(t, tt.want, g.GetBuildReason(), "GetBuildReason()") + assert.Equalf(t, tt.want, g.BuildReason(), "BuildReason()") }) } } @@ -73,11 +73,11 @@ func TestGitHubActionsConfigProvider_GetRepoURL(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g := &GitHubActionsConfigProvider{} + g := &githubActionsConfigProvider{} _ = os.Setenv("GITHUB_SERVER_URL", tt.envServerURL) _ = os.Setenv("GITHUB_REPOSITORY", tt.envRepo) - assert.Equalf(t, tt.want, g.GetRepoURL(), "GetRepoURL()") + assert.Equalf(t, tt.want, g.RepoURL(), "RepoURL()") }) } } @@ -94,12 +94,12 @@ func TestGitHubActionsConfigProvider_GetPullRequestConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - g := &GitHubActionsConfigProvider{} + g := &githubActionsConfigProvider{} _ = os.Setenv("GITHUB_REF", tt.envRef) _ = os.Setenv("GITHUB_HEAD_REF", "n/a") _ = os.Setenv("GITHUB_BASE_REF", "n/a") - assert.Equalf(t, tt.want, g.GetPullRequestConfig(), "GetPullRequestConfig()") + assert.Equalf(t, tt.want, g.PullRequestConfig(), "PullRequestConfig()") }) } } @@ -126,8 +126,8 @@ func TestGitHubActionsConfigProvider_fetchRunData(t *testing.T) { _ = os.Setenv("GITHUB_RUN_ID", "11111") // setup provider - g := &GitHubActionsConfigProvider{} - g.InitOrchestratorProvider(&OrchestratorSettings{}) + g := newGithubActionsConfigProvider() + assert.NoError(t, g.Configure(&Options{})) g.client = github.NewClient(http.DefaultClient) // setup http mock @@ -182,8 +182,8 @@ func TestGitHubActionsConfigProvider_fetchJobs(t *testing.T) { _ = os.Setenv("GITHUB_RUN_ID", "11111") // setup provider - g := &GitHubActionsConfigProvider{} - g.InitOrchestratorProvider(&OrchestratorSettings{}) + g := newGithubActionsConfigProvider() + assert.NoError(t, g.Configure(&Options{})) g.client = github.NewClient(http.DefaultClient) // setup http mock @@ -224,11 +224,11 @@ func TestGitHubActionsConfigProvider_GetLog(t *testing.T) { _ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library") // setup provider - g := &GitHubActionsConfigProvider{ - jobs: jobs, - jobsFetched: true, - } - g.InitOrchestratorProvider(&OrchestratorSettings{}) + g := newGithubActionsConfigProvider() + g.jobs = jobs + g.jobsFetched = true + + assert.NoError(t, g.Configure(&Options{})) g.client = github.NewClient(http.DefaultClient) // setup http mock @@ -262,7 +262,7 @@ func TestGitHubActionsConfigProvider_GetLog(t *testing.T) { ) } // run - logs, err := g.GetLog() + logs, err := g.FullLogs() assert.NoError(t, err) assert.Equal(t, wantLogs, string(logs)) } @@ -283,7 +283,7 @@ func TestGitHubActionsConfigProvider_Others(t *testing.T) { _ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library") _ = os.Setenv("GITHUB_WORKFLOW_REF", "SAP/jenkins-library/.github/workflows/piper.yml@refs/heads/main") - p := GitHubActionsConfigProvider{} + p := githubActionsConfigProvider{} startedAt, _ := time.Parse(time.RFC3339, "2023-08-11T07:28:24Z") p.runData = run{ fetched: true, @@ -293,16 +293,16 @@ func TestGitHubActionsConfigProvider_Others(t *testing.T) { assert.Equal(t, "n/a", p.OrchestratorVersion()) assert.Equal(t, "GitHubActions", p.OrchestratorType()) - assert.Equal(t, "11111", p.GetBuildID()) - assert.Equal(t, []ChangeSet{}, p.GetChangeSet()) - assert.Equal(t, startedAt, p.GetPipelineStartTime()) - assert.Equal(t, "Build", p.GetStageName()) - assert.Equal(t, "main", p.GetBranch()) - assert.Equal(t, "refs/pull/42/merge", p.GetReference()) - assert.Equal(t, "https://github.com/SAP/jenkins-library/actions/runs/11111", p.GetBuildURL()) - assert.Equal(t, "https://github.com/SAP/jenkins-library/actions/workflows/piper.yml", p.GetJobURL()) - assert.Equal(t, "Piper workflow", p.GetJobName()) - assert.Equal(t, "ffac537e6cbbf934b08745a378932722df287a53", p.GetCommit()) + assert.Equal(t, "11111", p.BuildID()) + assert.Equal(t, []ChangeSet{}, p.ChangeSets()) + assert.Equal(t, startedAt, p.PipelineStartTime()) + assert.Equal(t, "Build", p.StageName()) + assert.Equal(t, "main", p.Branch()) + assert.Equal(t, "refs/pull/42/merge", p.GitReference()) + assert.Equal(t, "https://github.com/SAP/jenkins-library/actions/runs/11111", p.BuildURL()) + assert.Equal(t, "https://github.com/SAP/jenkins-library/actions/workflows/piper.yml", p.JobURL()) + assert.Equal(t, "Piper workflow", p.JobName()) + assert.Equal(t, "ffac537e6cbbf934b08745a378932722df287a53", p.CommitSHA()) assert.Equal(t, "https://api.github.com/repos/SAP/jenkins-library/actions", actionsURL()) assert.True(t, p.IsPullRequest()) assert.True(t, isGitHubActions()) diff --git a/pkg/orchestrator/helpers.go b/pkg/orchestrator/helpers.go new file mode 100644 index 0000000000..262ed865af --- /dev/null +++ b/pkg/orchestrator/helpers.go @@ -0,0 +1,40 @@ +package orchestrator + +import ( + "github.com/SAP/jenkins-library/pkg/log" + "os" +) + +// envVarsAreSet verifies if any envvar from the list has nona non-empty, non-false value +func envVarsAreSet(envVars []string) bool { + for _, v := range envVars { + if envVarIsTrue(v) { + return true + } + } + return false +} + +// envVarIsTrue verifies if the variable is set and has a non-empty, non-false value. +func envVarIsTrue(key string) bool { + val, exists := os.LookupEnv(key) + if !exists { + return false + } + if len(val) == 0 || val == "no" || val == "false" || val == "off" || val == "0" { + return false + } + + return true +} + +// Wrapper function to read env variable and set default value +func getEnv(key, fallback string) string { + if value, found := os.LookupEnv(key); found { + log.Entry().Debugf("For: %s, found: %s", key, value) + return value + } + + log.Entry().Debugf("Could not read env variable %v using fallback value %v", key, fallback) + return fallback +} diff --git a/pkg/orchestrator/orchestrator_test.go b/pkg/orchestrator/helpers_test.go similarity index 60% rename from pkg/orchestrator/orchestrator_test.go rename to pkg/orchestrator/helpers_test.go index 5411f8f68f..86200733ec 100644 --- a/pkg/orchestrator/orchestrator_test.go +++ b/pkg/orchestrator/helpers_test.go @@ -4,57 +4,35 @@ package orchestrator import ( + "github.com/stretchr/testify/assert" "os" "testing" - - "github.com/stretchr/testify/assert" ) -func TestOrchestrator(t *testing.T) { - t.Run("Not running on CI", func(t *testing.T) { - defer resetEnv(os.Environ()) - os.Clearenv() - - provider, err := NewOrchestratorSpecificConfigProvider() - - assert.EqualError(t, err, "unable to detect a supported orchestrator (Azure DevOps, GitHub Actions, Jenkins)") - assert.Equal(t, "Unknown", provider.OrchestratorType()) - }) - - t.Run("Test orchestrator.toString()", func(t *testing.T) { - defer resetEnv(os.Environ()) - os.Clearenv() - - os.Setenv("AZURE_HTTP_USER_AGENT", "FOO BAR BAZ") - - o := DetectOrchestrator() - - assert.Equal(t, "AzureDevOps", o.String()) - }) - - t.Run("Test areIndicatingEnvVarsSet", func(t *testing.T) { +func Test_envVarsAreSet(t *testing.T) { + t.Run("Test envVarsAreSet", func(t *testing.T) { defer resetEnv(os.Environ()) os.Clearenv() envVars := []string{"GITHUB_ACTION", "GITHUB_ACTIONS"} os.Setenv("GITHUB_ACTION", "true") - tmp := areIndicatingEnvVarsSet(envVars) + tmp := envVarsAreSet(envVars) assert.True(t, tmp) os.Unsetenv("GITHUB_ACTION") os.Setenv("GITHUB_ACTIONS", "true") - tmp = areIndicatingEnvVarsSet(envVars) + tmp = envVarsAreSet(envVars) assert.True(t, tmp) os.Setenv("GITHUB_ACTION", "1") os.Setenv("GITHUB_ACTIONS", "false") - tmp = areIndicatingEnvVarsSet(envVars) + tmp = envVarsAreSet(envVars) assert.True(t, tmp) os.Setenv("GITHUB_ACTION", "false") os.Setenv("GITHUB_ACTIONS", "0") - tmp = areIndicatingEnvVarsSet(envVars) + tmp = envVarsAreSet(envVars) assert.False(t, tmp) }) } diff --git a/pkg/orchestrator/jenkins.go b/pkg/orchestrator/jenkins.go index 85e100615d..1e21da34e2 100644 --- a/pkg/orchestrator/jenkins.go +++ b/pkg/orchestrator/jenkins.go @@ -12,36 +12,42 @@ import ( "github.com/pkg/errors" ) -type JenkinsConfigProvider struct { +type jenkinsConfigProvider struct { client piperHttp.Client apiInformation map[string]interface{} } -// InitOrchestratorProvider initializes the Jenkins orchestrator with credentials -func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) { +func newJenkinsConfigProvider() *jenkinsConfigProvider { + return &jenkinsConfigProvider{} +} + +// Configure initializes the Jenkins orchestrator with credentials +func (j *jenkinsConfigProvider) Configure(opts *Options) error { j.client.SetOptions(piperHttp.ClientOptions{ - Username: settings.JenkinsUser, - Password: settings.JenkinsToken, + Username: opts.JenkinsUser, + Password: opts.JenkinsToken, MaxRetries: 3, TransportTimeout: time.Second * 10, }) + log.Entry().Debug("Successfully initialized Jenkins config provider") + return nil } // OrchestratorVersion returns the orchestrator version currently running on -func (j *JenkinsConfigProvider) OrchestratorVersion() string { +func (j *jenkinsConfigProvider) OrchestratorVersion() string { return getEnv("JENKINS_VERSION", "n/a") } // OrchestratorType returns the orchestrator type Jenkins -func (j *JenkinsConfigProvider) OrchestratorType() string { +func (j *jenkinsConfigProvider) OrchestratorType() string { return "Jenkins" } -func (j *JenkinsConfigProvider) fetchAPIInformation() { +func (j *jenkinsConfigProvider) fetchAPIInformation() { if len(j.apiInformation) == 0 { log.Entry().Debugf("apiInformation is empty, getting infos from API") - URL := j.GetBuildURL() + "api/json" + URL := j.BuildURL() + "api/json" log.Entry().Debugf("API URL: %s", URL) response, err := j.client.GetRequest(URL, nil, nil) if err != nil { @@ -67,8 +73,8 @@ func (j *JenkinsConfigProvider) fetchAPIInformation() { } } -// GetBuildStatus returns build status of the current job -func (j *JenkinsConfigProvider) GetBuildStatus() string { +// BuildStatus returns build status of the current job +func (j *jenkinsConfigProvider) BuildStatus() string { j.fetchAPIInformation() if val, ok := j.apiInformation["result"]; ok { // cases in ADO: succeeded, failed, canceled, none, partiallySucceeded @@ -85,8 +91,8 @@ func (j *JenkinsConfigProvider) GetBuildStatus() string { return BuildStatusFailure } -// GetChangeSet returns the commitIds and timestamp of the changeSet of the current run -func (j *JenkinsConfigProvider) GetChangeSet() []ChangeSet { +// ChangeSet returns the commitIds and timestamp of the changeSet of the current run +func (j *jenkinsConfigProvider) ChangeSets() []ChangeSet { j.fetchAPIInformation() marshal, err := json.Marshal(j.apiInformation) @@ -116,9 +122,9 @@ func (j *JenkinsConfigProvider) GetChangeSet() []ChangeSet { return changeSetList } -// GetLog returns the logfile from the current job as byte object -func (j *JenkinsConfigProvider) GetLog() ([]byte, error) { - URL := j.GetBuildURL() + "consoleText" +// FullLogs returns the logfile from the current job as byte object +func (j *jenkinsConfigProvider) FullLogs() ([]byte, error) { + URL := j.BuildURL() + "consoleText" response, err := j.client.GetRequest(URL, nil, nil) if err != nil { @@ -135,9 +141,9 @@ func (j *JenkinsConfigProvider) GetLog() ([]byte, error) { return logFile, nil } -// GetPipelineStartTime returns the pipeline start time in UTC -func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time { - URL := j.GetBuildURL() + "api/json" +// PipelineStartTime returns the pipeline start time in UTC +func (j *jenkinsConfigProvider) PipelineStartTime() time.Time { + URL := j.BuildURL() + "api/json" response, err := j.client.GetRequest(URL, nil, nil) if err != nil { log.Entry().WithError(err).Errorf("could not getRequest to URL %s", URL) @@ -163,33 +169,33 @@ func (j *JenkinsConfigProvider) GetPipelineStartTime() time.Time { return timeStamp.UTC() } -// GetJobName returns the job name of the current job e.g. foo/bar/BRANCH -func (j *JenkinsConfigProvider) GetJobName() string { +// JobName returns the job name of the current job e.g. foo/bar/BRANCH +func (j *jenkinsConfigProvider) JobName() string { return getEnv("JOB_NAME", "n/a") } -// GetJobURL returns the current job URL e.g. https://jaas.url/job/foo/job/bar/job/main -func (j *JenkinsConfigProvider) GetJobURL() string { +// JobURL returns the current job URL e.g. https://jaas.url/job/foo/job/bar/job/main +func (j *jenkinsConfigProvider) JobURL() string { 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") } -// GetBuildID returns the build ID of the current job, e.g. 1234 -func (j *JenkinsConfigProvider) GetBuildID() string { +// BuildID returns the build ID of the current job, e.g. 1234 +func (j *jenkinsConfigProvider) BuildID() string { return getEnv("BUILD_ID", "n/a") } -// GetStageName returns the stage name the job is currently in, e.g. Promote -func (j *JenkinsConfigProvider) GetStageName() string { +// StageName returns the stage name the job is currently in, e.g. Promote +func (j *jenkinsConfigProvider) StageName() string { return getEnv("STAGE_NAME", "n/a") } -// GetBuildReason returns the build reason of the current build -func (j *JenkinsConfigProvider) GetBuildReason() string { +// BuildReason returns the build reason of the current build +func (j *jenkinsConfigProvider) BuildReason() string { // BuildReasons are unified with AzureDevOps build reasons,see // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#build-variables-devops-services // ResourceTrigger, PullRequest, Manual, IndividualCI, Schedule @@ -231,13 +237,13 @@ func (j *JenkinsConfigProvider) GetBuildReason() string { return BuildReasonUnknown } -// GetBranch returns the branch name, only works with the git plugin enabled -func (j *JenkinsConfigProvider) GetBranch() string { +// Branch returns the branch name, only works with the git plugin enabled +func (j *jenkinsConfigProvider) Branch() string { return getEnv("BRANCH_NAME", "n/a") } -// GetReference returns the git reference, only works with the git plugin enabled -func (j *JenkinsConfigProvider) GetReference() string { +// GitReference returns the git reference, only works with the git plugin enabled +func (j *jenkinsConfigProvider) GitReference() string { ref := getEnv("BRANCH_NAME", "n/a") if ref == "n/a" { return ref @@ -248,23 +254,23 @@ func (j *JenkinsConfigProvider) GetReference() string { } } -// GetBuildURL returns the build url, e.g. https://jaas.url/job/foo/job/bar/job/main/1234/ -func (j *JenkinsConfigProvider) GetBuildURL() string { +// BuildURL returns the build url, e.g. https://jaas.url/job/foo/job/bar/job/main/1234/ +func (j *jenkinsConfigProvider) BuildURL() 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 { +// CommitSHA returns the commit SHA from the current build, only works with the git plugin enabled +func (j *jenkinsConfigProvider) CommitSHA() string { return getEnv("GIT_COMMIT", "n/a") } -// GetRepoURL returns the repo URL of the current build, only works with the git plugin enabled -func (j *JenkinsConfigProvider) GetRepoURL() string { +// RepoURL returns the repo URL of the current build, only works with the git plugin enabled +func (j *jenkinsConfigProvider) RepoURL() string { return getEnv("GIT_URL", "n/a") } -// GetPullRequestConfig returns the pull request config -func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig { +// PullRequestConfig returns the pull request config +func (j *jenkinsConfigProvider) PullRequestConfig() PullRequestConfig { return PullRequestConfig{ Branch: getEnv("CHANGE_BRANCH", "n/a"), Base: getEnv("CHANGE_TARGET", "n/a"), @@ -273,11 +279,11 @@ func (j *JenkinsConfigProvider) GetPullRequestConfig() PullRequestConfig { } // IsPullRequest returns boolean indicating if current job is a PR -func (j *JenkinsConfigProvider) IsPullRequest() bool { - return truthy("CHANGE_ID") +func (j *jenkinsConfigProvider) IsPullRequest() bool { + return envVarIsTrue("CHANGE_ID") } func isJenkins() bool { envVars := []string{"JENKINS_HOME", "JENKINS_URL"} - return areIndicatingEnvVarsSet(envVars) + return envVarsAreSet(envVars) } diff --git a/pkg/orchestrator/jenkins_test.go b/pkg/orchestrator/jenkins_test.go index 3b2c8c6e0e..15da691553 100644 --- a/pkg/orchestrator/jenkins_test.go +++ b/pkg/orchestrator/jenkins_test.go @@ -29,14 +29,14 @@ func TestJenkins(t *testing.T) { os.Setenv("GIT_COMMIT", "abcdef42713") os.Setenv("GIT_URL", "github.com/foo/bar") - p, _ := NewOrchestratorSpecificConfigProvider() + p := &jenkinsConfigProvider{} assert.False(t, p.IsPullRequest()) - assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.GetBuildURL()) - assert.Equal(t, "main", p.GetBranch()) - assert.Equal(t, "refs/heads/main", p.GetReference()) - assert.Equal(t, "abcdef42713", p.GetCommit()) - assert.Equal(t, "github.com/foo/bar", p.GetRepoURL()) + assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.BuildURL()) + assert.Equal(t, "main", p.Branch()) + assert.Equal(t, "refs/heads/main", p.GitReference()) + assert.Equal(t, "abcdef42713", p.CommitSHA()) + assert.Equal(t, "github.com/foo/bar", p.RepoURL()) assert.Equal(t, "Jenkins", p.OrchestratorType()) }) @@ -48,11 +48,11 @@ func TestJenkins(t *testing.T) { os.Setenv("CHANGE_TARGET", "main") os.Setenv("CHANGE_ID", "42") - p := JenkinsConfigProvider{} - c := p.GetPullRequestConfig() + p := jenkinsConfigProvider{} + c := p.PullRequestConfig() assert.True(t, p.IsPullRequest()) - assert.Equal(t, "refs/pull/42/head", p.GetReference()) + assert.Equal(t, "refs/pull/42/head", p.GitReference()) assert.Equal(t, "feat/test-jenkins", c.Branch) assert.Equal(t, "main", c.Base) assert.Equal(t, "42", c.Key) @@ -70,16 +70,16 @@ func TestJenkins(t *testing.T) { os.Setenv("BUILD_URL", "https://jaas.url/job/foo/job/bar/job/main/1234/") os.Setenv("STAGE_NAME", "Promote") - p := JenkinsConfigProvider{} + 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, "1234", p.BuildID()) + assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main", p.JobURL()) 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()) + assert.Equal(t, "foo/bar/BRANCH", p.JobName()) + assert.Equal(t, "Promote", p.StageName()) + assert.Equal(t, "https://jaas.url/job/foo/job/bar/job/main/1234/", p.BuildURL()) }) } @@ -130,7 +130,7 @@ func TestJenkinsConfigProvider_GetPipelineStartTime(t *testing.T) { }, } - j := &JenkinsConfigProvider{ + j := &jenkinsConfigProvider{ client: piperhttp.Client{}, } j.client.SetOptions(piperhttp.ClientOptions{ @@ -171,7 +171,7 @@ func TestJenkinsConfigProvider_GetPipelineStartTime(t *testing.T) { }, ) - assert.Equalf(t, tt.want, j.GetPipelineStartTime(), "GetPipelineStartTime()") + assert.Equalf(t, tt.want, j.PipelineStartTime(), "PipelineStartTime()") }) } } @@ -228,10 +228,10 @@ func TestJenkinsConfigProvider_GetBuildStatus(t *testing.T) { if err != nil { t.Fatal("could not parse json:", err) } - j := &JenkinsConfigProvider{ + j := &jenkinsConfigProvider{ apiInformation: apiInformation, } - assert.Equalf(t, tt.want, j.GetBuildStatus(), "GetBuildStatus()") + assert.Equalf(t, tt.want, j.BuildStatus(), "BuildStatus()") }) } } @@ -367,9 +367,9 @@ func TestJenkinsConfigProvider_GetBuildReason(t *testing.T) { if err != nil { t.Fatal("could not parse json:", err) } - j := &JenkinsConfigProvider{apiInformation: apiInformation} + j := &jenkinsConfigProvider{apiInformation: apiInformation} - assert.Equalf(t, tt.want, j.GetBuildReason(), "GetBuildReason()") + assert.Equalf(t, tt.want, j.BuildReason(), "BuildReason()") }) } } @@ -415,7 +415,7 @@ func TestJenkinsConfigProvider_getAPIInformation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - j := &JenkinsConfigProvider{ + j := &jenkinsConfigProvider{ apiInformation: tt.apiInformation, } j.client.SetOptions(piperhttp.ClientOptions{ @@ -487,7 +487,7 @@ func TestJenkinsConfigProvider_GetLog(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - j := &JenkinsConfigProvider{} + j := &jenkinsConfigProvider{} j.client.SetOptions(piperhttp.ClientOptions{ MaxRequestDuration: 5 * time.Second, Token: "TOKEN", @@ -519,33 +519,11 @@ func TestJenkinsConfigProvider_GetLog(t *testing.T) { }, ) - got, err := j.GetLog() - if !tt.wantErr(t, err, fmt.Sprintf("GetLog()")) { + got, err := j.FullLogs() + if !tt.wantErr(t, err, fmt.Sprintf("FullLogs()")) { 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) + assert.Equalf(t, tt.want, got, "FullLogs()") }) } } @@ -664,8 +642,8 @@ func TestJenkinsConfigProvider_GetChangeSet(t *testing.T) { if err != nil { t.Fatal("could not parse json:", err) } - j := &JenkinsConfigProvider{apiInformation: apiInformation} - assert.Equalf(t, tt.want, j.GetChangeSet(), "GetChangeSet()") + j := &jenkinsConfigProvider{apiInformation: apiInformation} + assert.Equalf(t, tt.want, j.ChangeSets(), "ChangeSets()") }) } } diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index 20fcd51da7..24572d9256 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -1,15 +1,12 @@ package orchestrator import ( - "errors" - "os" - "time" - "github.com/SAP/jenkins-library/pkg/log" + "github.com/pkg/errors" + "sync" + "time" ) -type Orchestrator int - const ( Unknown Orchestrator = iota AzureDevOps @@ -31,67 +28,86 @@ const ( BuildReasonUnknown = "Unknown" ) -type OrchestratorSpecificConfigProviding interface { - InitOrchestratorProvider(settings *OrchestratorSettings) +var ( + provider ConfigProvider + providerOnce sync.Once +) + +type ConfigProvider interface { + Configure(opts *Options) error OrchestratorType() string OrchestratorVersion() string - GetStageName() string - GetBranch() string - GetReference() string - GetBuildURL() string - GetBuildID() string - GetJobURL() string - GetJobName() string - GetCommit() string - GetPullRequestConfig() PullRequestConfig - GetRepoURL() string + StageName() string + Branch() string + GitReference() string + RepoURL() string + BuildURL() string + BuildID() string + BuildStatus() string + BuildReason() string + JobURL() string + JobName() string + CommitSHA() string + PullRequestConfig() PullRequestConfig IsPullRequest() bool - GetLog() ([]byte, error) - GetPipelineStartTime() time.Time - GetBuildStatus() string - GetBuildReason() string - GetChangeSet() []ChangeSet + FullLogs() ([]byte, error) + PipelineStartTime() time.Time + ChangeSets() []ChangeSet } -type PullRequestConfig struct { - Branch string - Base string - Key string -} +type ( + Orchestrator int -type ChangeSet struct { - CommitId string - Timestamp string - PrNumber int -} + // Options used to set orchestrator specific settings. + Options struct { + JenkinsUser string + JenkinsToken string + AzureToken string + GitHubToken string + } -// OrchestratorSettings struct to set orchestrator specific settings e.g. Jenkins credentials -type OrchestratorSettings struct { - JenkinsUser string - JenkinsToken string - AzureToken string - GitHubToken string -} + PullRequestConfig struct { + Branch string + Base string + Key string + } -func NewOrchestratorSpecificConfigProvider() (OrchestratorSpecificConfigProviding, error) { - switch DetectOrchestrator() { - case AzureDevOps: - return &AzureDevOpsConfigProvider{}, nil - case GitHubActions: - ghProvider := &GitHubActionsConfigProvider{} - // Temporary workaround: The orchestrator provider is not always initialized after being created, - // which causes a panic in some places for GitHub Actions provider, as it needs to initialize - // github sdk client. - ghProvider.InitOrchestratorProvider(&OrchestratorSettings{}) - return ghProvider, nil - case Jenkins: - return &JenkinsConfigProvider{}, nil - default: - return &UnknownOrchestratorConfigProvider{}, errors.New("unable to detect a supported orchestrator (Azure DevOps, GitHub Actions, Jenkins)") + ChangeSet struct { + CommitId string + Timestamp string + PrNumber int } +) + +func GetOrchestratorConfigProvider(opts *Options) (ConfigProvider, error) { + var err error + providerOnce.Do(func() { + switch DetectOrchestrator() { + case AzureDevOps: + provider = newAzureDevopsConfigProvider() + case GitHubActions: + provider = newGithubActionsConfigProvider() + case Jenkins: + provider = newJenkinsConfigProvider() + default: + provider = newUnknownOrchestratorConfigProvider() + err = errors.New("unable to detect a supported orchestrator (Azure DevOps, GitHub Actions, Jenkins)") + } + + if opts == nil { + log.Entry().Debug("ConfigProvider initialized without options. Some data may be unavailable") + return + } + + if cfgErr := provider.Configure(opts); cfgErr != nil { + err = errors.Wrap(cfgErr, "provider configuration failed") + } + }) + + return provider, err } -// DetectOrchestrator returns the name of the current orchestrator e.g. Jenkins, Azure, Unknown +// DetectOrchestrator function determines in which orchestrator Piper is running by examining environment variables. func DetectOrchestrator() Orchestrator { if isAzure() { return AzureDevOps @@ -108,34 +124,10 @@ func (o Orchestrator) String() string { return [...]string{"Unknown", "AzureDevOps", "GitHubActions", "Jenkins"}[o] } -func areIndicatingEnvVarsSet(envVars []string) bool { - for _, v := range envVars { - if truthy(v) { - return true - } - } - return false -} - -// Checks if var is set and neither empty nor false -func truthy(key string) bool { - val, exists := os.LookupEnv(key) - if !exists { - return false - } - if len(val) == 0 || val == "no" || val == "false" || val == "off" || val == "0" { - return false - } - - return true -} - -// Wrapper function to read env variable and set default value -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - log.Entry().Debugf("For: %s, found: %s", key, value) - return value - } - log.Entry().Debugf("Could not read env variable %v using fallback value %v", key, fallback) - return fallback +// ResetConfigProvider is intended to be used only for unit tests because some of these tests +// run with different environment variables (for example, mock runs in various orchestrators). +// Usage in production code is not recommended. +func ResetConfigProvider() { + provider = nil + providerOnce = sync.Once{} } diff --git a/pkg/orchestrator/unknownOrchestrator.go b/pkg/orchestrator/unknownOrchestrator.go index 61c5134d88..ad036b5be3 100644 --- a/pkg/orchestrator/unknownOrchestrator.go +++ b/pkg/orchestrator/unknownOrchestrator.go @@ -8,109 +8,99 @@ 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.") +const unknownOrchestratorWarning = "Unknown orchestrator - returning default values." + +func newUnknownOrchestratorConfigProvider() *UnknownOrchestratorConfigProvider { + return &UnknownOrchestratorConfigProvider{} +} + +func (u *UnknownOrchestratorConfigProvider) Configure(_ *Options) error { + log.Entry().Warning(unknownOrchestratorWarning) + return nil } -// OrchestratorVersion returns n/a for the unknownOrchestrator func (u *UnknownOrchestratorConfigProvider) OrchestratorVersion() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetBuildStatus returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetBuildStatus() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) BuildStatus() string { + log.Entry().Warning(unknownOrchestratorWarning) return "FAILURE" } -func (u *UnknownOrchestratorConfigProvider) GetChangeSet() []ChangeSet { - log.Entry().Infof("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) ChangeSets() []ChangeSet { + log.Entry().Infof(unknownOrchestratorWarning) return []ChangeSet{} } -// GetBuildReason returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetBuildReason() string { - log.Entry().Infof("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) BuildReason() string { + log.Entry().Infof(unknownOrchestratorWarning) return "n/a" } -// GetBuildID returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetBuildID() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) BuildID() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetJobName returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetJobName() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) JobName() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// OrchestratorType returns n/a for the unknownOrchestrator func (u *UnknownOrchestratorConfigProvider) OrchestratorType() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") + log.Entry().Warning(unknownOrchestratorWarning) return "Unknown" } -// GetLog returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetLog() ([]byte, error) { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) FullLogs() ([]byte, error) { + log.Entry().Warning(unknownOrchestratorWarning) return []byte{}, nil } -// GetPipelineStartTime returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetPipelineStartTime() time.Time { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) PipelineStartTime() time.Time { + log.Entry().Warning(unknownOrchestratorWarning) return time.Time{}.UTC() } -// GetStageName returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetStageName() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) StageName() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetBranch returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetBranch() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) Branch() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetReference returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetReference() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) GitReference() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetBuildURL returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetBuildURL() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) BuildURL() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetJobURL returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetJobURL() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) JobURL() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetCommit returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetCommit() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) CommitSHA() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetRepoURL returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetRepoURL() string { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) RepoURL() string { + log.Entry().Warning(unknownOrchestratorWarning) return "n/a" } -// GetPullRequestConfig returns n/a for the unknownOrchestrator -func (u *UnknownOrchestratorConfigProvider) GetPullRequestConfig() PullRequestConfig { - log.Entry().Warning("Unknown orchestrator - returning default values.") +func (u *UnknownOrchestratorConfigProvider) PullRequestConfig() PullRequestConfig { + log.Entry().Warning(unknownOrchestratorWarning) return PullRequestConfig{ Branch: "n/a", Base: "n/a", @@ -118,8 +108,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.") + log.Entry().Warning(unknownOrchestratorWarning) return false } diff --git a/pkg/orchestrator/unknownOrchestrator_test.go b/pkg/orchestrator/unknownOrchestrator_test.go deleted file mode 100644 index ee85e24f3e..0000000000 --- a/pkg/orchestrator/unknownOrchestrator_test.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build unit -// +build unit - -package orchestrator - -import ( - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestUnknownOrchestrator(t *testing.T) { - t.Run("BranchBuild", func(t *testing.T) { - defer resetEnv(os.Environ()) - os.Clearenv() - - p, _ := NewOrchestratorSpecificConfigProvider() - - assert.False(t, p.IsPullRequest()) - 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, "Unknown", p.OrchestratorType()) - }) - - t.Run("PR", func(t *testing.T) { - defer resetEnv(os.Environ()) - os.Clearenv() - - p := UnknownOrchestratorConfigProvider{} - c := p.GetPullRequestConfig() - - assert.False(t, p.IsPullRequest()) - assert.Equal(t, "n/a", c.Branch) - 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) - }) -} diff --git a/pkg/reporting/policyViolation.go b/pkg/reporting/policyViolation.go index 669ea68968..34463def1f 100644 --- a/pkg/reporting/policyViolation.go +++ b/pkg/reporting/policyViolation.go @@ -67,14 +67,14 @@ func (p *PolicyViolationReport) ToMarkdown() ([]byte, error) { } // only fill with orchestrator information if orchestrator can be identified properly - if provider, err := orchestrator.NewOrchestratorSpecificConfigProvider(); err == nil { + if provider, err := orchestrator.GetOrchestratorConfigProvider(nil); err == nil { // only add information if not yet provided if len(p.CommitID) == 0 { - p.CommitID = provider.GetCommit() + p.CommitID = provider.CommitSHA() } if len(p.PipelineLink) == 0 { - p.PipelineLink = provider.GetJobURL() - p.PipelineName = provider.GetJobName() + p.PipelineLink = provider.JobURL() + p.PipelineName = provider.JobName() } } diff --git a/pkg/reporting/securityVulnerability.go b/pkg/reporting/securityVulnerability.go index a4d34c61f0..ddc5efe8a4 100644 --- a/pkg/reporting/securityVulnerability.go +++ b/pkg/reporting/securityVulnerability.go @@ -91,18 +91,18 @@ func (v *VulnerabilityReport) ToMarkdown() ([]byte, error) { } // only fill with orchestrator information if orchestrator can be identified properly - if provider, err := orchestrator.NewOrchestratorSpecificConfigProvider(); err == nil { + if provider, err := orchestrator.GetOrchestratorConfigProvider(nil); err == nil { // only add information if not yet provided if len(v.CommitID) == 0 { - v.CommitID = provider.GetCommit() + v.CommitID = provider.CommitSHA() } if len(v.PipelineLink) == 0 { - v.PipelineLink = provider.GetJobURL() - v.PipelineName = provider.GetJobName() + v.PipelineLink = provider.JobURL() + v.PipelineName = provider.JobName() } if len(v.Branch) == 0 { - v.Branch = provider.GetBranch() + v.Branch = provider.Branch() } } diff --git a/pkg/telemetry/data.go b/pkg/telemetry/data.go index b806f93aca..3b154c539f 100644 --- a/pkg/telemetry/data.go +++ b/pkg/telemetry/data.go @@ -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.BuildURL() + BuildURLHash string `json:"e_5"` // defaults to sha1 of provider.JobURL() Orchestrator string `json:"e_14"` // defaults to provider.OrchestratorType() } diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index e262010be0..0e1312f312 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -28,7 +28,7 @@ type Telemetry struct { baseData BaseData baseMetaData BaseMetaData data Data - provider orchestrator.OrchestratorSpecificConfigProviding + provider orchestrator.ConfigProvider disabled bool client *piperhttp.Client CustomReportingDsn string @@ -43,8 +43,8 @@ type Telemetry struct { func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) { t.disabled = telemetryDisabled - provider, err := orchestrator.NewOrchestratorSpecificConfigProvider() - if err != nil || provider == nil { + provider, err := orchestrator.GetOrchestratorConfigProvider(nil) + if err != nil { log.Entry().Warningf("could not get orchestrator config provider, leads to insufficient data") provider = &orchestrator.UnknownOrchestratorConfigProvider{} } @@ -73,8 +73,8 @@ func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) { } t.baseData = BaseData{ - Orchestrator: provider.OrchestratorType(), - StageName: provider.GetStageName(), + Orchestrator: t.provider.OrchestratorType(), + StageName: t.provider.StageName(), URL: LibraryRepository, ActionName: actionName, EventType: eventType, @@ -87,12 +87,12 @@ func (t *Telemetry) Initialize(telemetryDisabled bool, stepName string) { } func (t *Telemetry) getPipelineURLHash() string { - jobURL := t.provider.GetJobURL() + jobURL := t.provider.JobURL() return t.toSha1OrNA(jobURL) } func (t *Telemetry) getBuildURLHash() string { - buildURL := t.provider.GetBuildURL() + buildURL := t.provider.BuildURL() return t.toSha1OrNA(buildURL) } @@ -160,7 +160,7 @@ func (t *Telemetry) logStepTelemetryData() { StepDuration: t.data.CustomData.Duration, ErrorCategory: t.data.CustomData.ErrorCategory, ErrorDetail: fatalError, - CorrelationID: t.provider.GetBuildURL(), + CorrelationID: t.provider.BuildURL(), PiperCommitHash: t.data.CustomData.PiperCommitHash, } stepTelemetryJSON, err := json.Marshal(stepTelemetryData) diff --git a/pkg/telemetry/telemetry_test.go b/pkg/telemetry/telemetry_test.go index 91b88a496d..c878c93589 100644 --- a/pkg/telemetry/telemetry_test.go +++ b/pkg/telemetry/telemetry_test.go @@ -26,7 +26,7 @@ func TestTelemetry_Initialize(t *testing.T) { baseData BaseData baseMetaData BaseMetaData data Data - provider orchestrator.OrchestratorSpecificConfigProviding + provider orchestrator.ConfigProvider disabled bool client *piperhttp.Client CustomReportingDsn string @@ -81,7 +81,7 @@ func TestTelemetry_Send(t *testing.T) { baseData BaseData baseMetaData BaseMetaData data Data - provider orchestrator.OrchestratorSpecificConfigProviding + provider orchestrator.ConfigProvider disabled bool client *piperhttp.Client CustomReportingDsn string @@ -268,7 +268,7 @@ func TestTelemetry_logStepTelemetryData(t *testing.T) { type fields struct { data Data - provider orchestrator.OrchestratorSpecificConfigProviding + provider orchestrator.ConfigProvider } tests := []struct { name string