mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
feat(orchestrator): add implementation for GitHub (#4525)
* add comments with examples to methods * a bit refactoring and cleanup * actionsURL * GetBuildStatus * GetBuildID, GetChangeSet, GetPipelineStartTime * GetStageName and GetBuildReason * refactor fetching jobs * GetJobName and GetJobURL * chnage GetBuildURL * refactor actionsURL * fix guessCurrentJob bug * unit tests for all * refactor GetLog * refactor and fix tests * change GetBuildURL to use env vars * fix issues * leftover * add comment * fix according to review comments --------- Co-authored-by: Gulom Alimov <gulomjon.alimov@sap.com> Co-authored-by: Jordi van Liempt <35920075+jliempt@users.noreply.github.com>
This commit is contained in:
parent
b7663466f3
commit
e805beda70
@ -13,20 +13,17 @@ import (
|
|||||||
|
|
||||||
type AzureDevOpsConfigProvider struct {
|
type AzureDevOpsConfigProvider struct {
|
||||||
client piperHttp.Client
|
client piperHttp.Client
|
||||||
options piperHttp.ClientOptions
|
|
||||||
apiInformation map[string]interface{}
|
apiInformation map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitOrchestratorProvider initializes http client for AzureDevopsConfigProvider
|
// InitOrchestratorProvider initializes http client for AzureDevopsConfigProvider
|
||||||
func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
func (a *AzureDevOpsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
||||||
a.client = piperHttp.Client{}
|
a.client.SetOptions(piperHttp.ClientOptions{
|
||||||
a.options = piperHttp.ClientOptions{
|
|
||||||
Username: "",
|
Username: "",
|
||||||
Password: settings.AzureToken,
|
Password: settings.AzureToken,
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
TransportTimeout: time.Second * 10,
|
TransportTimeout: time.Second * 10,
|
||||||
}
|
})
|
||||||
a.client.SetOptions(a.options)
|
|
||||||
log.Entry().Debug("Successfully initialized Azure config provider")
|
log.Entry().Debug("Successfully initialized Azure config provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +99,12 @@ func (a *AzureDevOpsConfigProvider) GetBuildStatus() string {
|
|||||||
// cases to align with Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED
|
// cases to align with Jenkins: SUCCESS, FAILURE, NOT_BUILD, ABORTED
|
||||||
switch buildStatus := getEnv("AGENT_JOBSTATUS", "FAILURE"); buildStatus {
|
switch buildStatus := getEnv("AGENT_JOBSTATUS", "FAILURE"); buildStatus {
|
||||||
case "Succeeded":
|
case "Succeeded":
|
||||||
return "SUCCESS"
|
return BuildStatusSuccess
|
||||||
case "Canceled":
|
case "Canceled":
|
||||||
return "ABORTED"
|
return BuildStatusAborted
|
||||||
default:
|
default:
|
||||||
// Failed, SucceededWithIssues
|
// Failed, SucceededWithIssues
|
||||||
return "FAILURE"
|
return BuildStatusFailure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package orchestrator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -14,53 +13,51 @@ import (
|
|||||||
"github.com/SAP/jenkins-library/pkg/log"
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"golang.org/x/sync/semaphore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var httpHeader = http.Header{
|
type GitHubActionsConfigProvider struct {
|
||||||
"Accept": {"application/vnd.github+json"},
|
client piperHttp.Client
|
||||||
|
runData run
|
||||||
|
jobs []job
|
||||||
|
jobsFetched bool
|
||||||
|
currentJob job
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitHubActionsConfigProvider struct {
|
type run struct {
|
||||||
client piperHttp.Client
|
fetched bool
|
||||||
|
Status string `json:"status"`
|
||||||
|
StartedAt time.Time `json:"run_started_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type job struct {
|
type job struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
HtmlURL string `json:"html_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type stagesID struct {
|
type fullLog struct {
|
||||||
Jobs []job `json:"jobs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type logs struct {
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
b [][]byte
|
b [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var httpHeaders = http.Header{
|
||||||
|
"Accept": {"application/vnd.github+json"},
|
||||||
|
"X-GitHub-Api-Version": {"2022-11-28"},
|
||||||
|
}
|
||||||
|
|
||||||
// InitOrchestratorProvider initializes http client for GitHubActionsDevopsConfigProvider
|
// InitOrchestratorProvider initializes http client for GitHubActionsDevopsConfigProvider
|
||||||
func (g *GitHubActionsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
func (g *GitHubActionsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
||||||
g.client = piperHttp.Client{}
|
|
||||||
g.client.SetOptions(piperHttp.ClientOptions{
|
g.client.SetOptions(piperHttp.ClientOptions{
|
||||||
Password: settings.GitHubToken,
|
Token: "Bearer " + settings.GitHubToken,
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
TransportTimeout: time.Second * 10,
|
TransportTimeout: time.Second * 10,
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Entry().Debug("Successfully initialized GitHubActions config provider")
|
log.Entry().Debug("Successfully initialized GitHubActions config provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getActionsURL() string {
|
|
||||||
ghURL := getEnv("GITHUB_URL", "")
|
|
||||||
switch ghURL {
|
|
||||||
case "https://github.com/":
|
|
||||||
ghURL = "https://api.github.com"
|
|
||||||
default:
|
|
||||||
ghURL += "api/v3"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s/repos/%s/actions", ghURL, getEnv("GITHUB_REPOSITORY", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GitHubActionsConfigProvider) OrchestratorVersion() string {
|
func (g *GitHubActionsConfigProvider) OrchestratorVersion() string {
|
||||||
|
log.Entry().Debugf("OrchestratorVersion() for GitHub Actions is not applicable.")
|
||||||
return "n/a"
|
return "n/a"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,109 +65,141 @@ func (g *GitHubActionsConfigProvider) OrchestratorType() string {
|
|||||||
return "GitHubActions"
|
return "GitHubActions"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBuildStatus returns current run status
|
||||||
func (g *GitHubActionsConfigProvider) GetBuildStatus() string {
|
func (g *GitHubActionsConfigProvider) GetBuildStatus() string {
|
||||||
log.Entry().Infof("GetBuildStatus() for GitHub Actions not yet implemented.")
|
g.fetchRunData()
|
||||||
return "FAILURE"
|
switch g.runData.Status {
|
||||||
|
case "success":
|
||||||
|
return BuildStatusSuccess
|
||||||
|
case "cancelled":
|
||||||
|
return BuildStatusAborted
|
||||||
|
case "in_progress":
|
||||||
|
return BuildStatusInProgress
|
||||||
|
default:
|
||||||
|
return BuildStatusFailure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLog returns the whole logfile for the current pipeline run
|
// GetLog returns the whole logfile for the current pipeline run
|
||||||
func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) {
|
func (g *GitHubActionsConfigProvider) GetLog() ([]byte, error) {
|
||||||
ids, err := g.getStageIds()
|
if err := g.fetchJobs(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Ignore the last stage (job) as it is not possible in GitHub to fetch logs for a running job.
|
||||||
|
jobs := g.jobs[:len(g.jobs)-1]
|
||||||
|
|
||||||
logs := logs{
|
fullLogs := fullLog{b: make([][]byte, len(jobs))}
|
||||||
b: make([][]byte, len(ids)),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
sem := semaphore.NewWeighted(10)
|
|
||||||
wg := errgroup.Group{}
|
wg := errgroup.Group{}
|
||||||
for i := range ids {
|
wg.SetLimit(10)
|
||||||
|
for i := range jobs {
|
||||||
i := i // https://golang.org/doc/faq#closures_and_goroutines
|
i := i // https://golang.org/doc/faq#closures_and_goroutines
|
||||||
if err := sem.Acquire(ctx, 1); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to acquire semaphore: %w", err)
|
|
||||||
}
|
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
defer sem.Release(1)
|
resp, err := g.client.GetRequest(fmt.Sprintf("%s/jobs/%d/logs", actionsURL(), jobs[i].ID), httpHeaders, nil)
|
||||||
resp, err := g.client.GetRequest(fmt.Sprintf("%s/jobs/%d/logs", getActionsURL(), ids[i]), httpHeader, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get API data: %w", err)
|
return fmt.Errorf("failed to get API data: %w", err)
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
b, err := io.ReadAll(resp.Body)
|
b, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read response body: %w", err)
|
return fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
logs.Lock()
|
fullLogs.Lock()
|
||||||
defer logs.Unlock()
|
fullLogs.b[i] = b
|
||||||
logs.b[i] = b
|
fullLogs.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err = wg.Wait(); err != nil {
|
if err := wg.Wait(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to get logs: %w", err)
|
return nil, fmt.Errorf("failed to get logs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes.Join(logs.b, []byte("")), nil
|
return bytes.Join(fullLogs.b, []byte("")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBuildID returns current run ID
|
||||||
func (g *GitHubActionsConfigProvider) GetBuildID() string {
|
func (g *GitHubActionsConfigProvider) GetBuildID() string {
|
||||||
log.Entry().Infof("GetBuildID() for GitHub Actions not yet implemented.")
|
return getEnv("GITHUB_RUN_ID", "n/a")
|
||||||
return "n/a"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHubActionsConfigProvider) GetChangeSet() []ChangeSet {
|
func (g *GitHubActionsConfigProvider) GetChangeSet() []ChangeSet {
|
||||||
log.Entry().Warn("GetChangeSet for GitHubActions not yet implemented")
|
log.Entry().Debug("GetChangeSet for GitHubActions not implemented")
|
||||||
return []ChangeSet{}
|
return []ChangeSet{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPipelineStartTime returns the pipeline start time in UTC
|
||||||
func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time {
|
func (g *GitHubActionsConfigProvider) GetPipelineStartTime() time.Time {
|
||||||
log.Entry().Infof("GetPipelineStartTime() for GitHub Actions not yet implemented.")
|
g.fetchRunData()
|
||||||
return time.Time{}.UTC()
|
return g.runData.StartedAt.UTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStageName returns the human-readable name given to a stage.
|
||||||
func (g *GitHubActionsConfigProvider) GetStageName() string {
|
func (g *GitHubActionsConfigProvider) GetStageName() string {
|
||||||
return "GITHUB_WORKFLOW" // TODO: is there something like is "stage" in GH Actions?
|
return getEnv("GITHUB_JOB", "unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBuildReason 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) GetBuildReason() string {
|
||||||
log.Entry().Infof("GetBuildReason() for GitHub Actions not yet implemented.")
|
switch getEnv("GITHUB_EVENT_NAME", "") {
|
||||||
return "n/a"
|
case "workflow_dispatch":
|
||||||
|
return BuildReasonManual
|
||||||
|
case "schedule":
|
||||||
|
return BuildReasonSchedule
|
||||||
|
case "pull_request":
|
||||||
|
return BuildReasonPullRequest
|
||||||
|
case "workflow_call":
|
||||||
|
return BuildReasonResourceTrigger
|
||||||
|
case "push":
|
||||||
|
return BuildReasonIndividualCI
|
||||||
|
default:
|
||||||
|
return BuildReasonUnknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBranch returns the source branch name, e.g. main
|
||||||
func (g *GitHubActionsConfigProvider) GetBranch() string {
|
func (g *GitHubActionsConfigProvider) GetBranch() string {
|
||||||
return strings.TrimPrefix(getEnv("GITHUB_REF", "n/a"), "refs/heads/")
|
return getEnv("GITHUB_REF_NAME", "n/a")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReference return the git reference. For example, refs/heads/your_branch_name
|
||||||
func (g *GitHubActionsConfigProvider) GetReference() string {
|
func (g *GitHubActionsConfigProvider) GetReference() string {
|
||||||
return getEnv("GITHUB_REF", "n/a")
|
return getEnv("GITHUB_REF", "n/a")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBuildURL returns the builds URL. For example, https://github.com/SAP/jenkins-library/actions/runs/5815297487
|
||||||
func (g *GitHubActionsConfigProvider) GetBuildURL() string {
|
func (g *GitHubActionsConfigProvider) GetBuildURL() string {
|
||||||
return g.GetRepoURL() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a")
|
return g.GetRepoURL() + "/actions/runs/" + g.GetBuildID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJobURL returns the current job HTML URL (not API URL).
|
||||||
|
// For example, https://github.com/SAP/jenkins-library/actions/runs/123456/jobs/7654321
|
||||||
func (g *GitHubActionsConfigProvider) GetJobURL() string {
|
func (g *GitHubActionsConfigProvider) GetJobURL() string {
|
||||||
log.Entry().Debugf("Not yet implemented.")
|
// We need to query the GitHub API here because the environment variable GITHUB_JOB returns
|
||||||
return g.GetRepoURL() + "/actions/runs/" + getEnv("GITHUB_RUN_ID", "n/a")
|
// the name of the job, not a numeric ID (which we need to form the URL)
|
||||||
|
g.guessCurrentJob()
|
||||||
|
return g.currentJob.HtmlURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJobName returns the current workflow name. For example, "Piper workflow"
|
||||||
func (g *GitHubActionsConfigProvider) GetJobName() string {
|
func (g *GitHubActionsConfigProvider) GetJobName() string {
|
||||||
log.Entry().Debugf("GetJobName() for GitHubActions not yet implemented.")
|
return getEnv("GITHUB_WORKFLOW", "unknown")
|
||||||
return "n/a"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommit returns the commit SHA that triggered the workflow. For example, ffac537e6cbbf934b08745a378932722df287a53
|
||||||
func (g *GitHubActionsConfigProvider) GetCommit() string {
|
func (g *GitHubActionsConfigProvider) GetCommit() string {
|
||||||
return getEnv("GITHUB_SHA", "n/a")
|
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 {
|
func (g *GitHubActionsConfigProvider) GetRepoURL() string {
|
||||||
return getEnv("GITHUB_SERVER_URL", "n/a") + "/" + getEnv("GITHUB_REPOSITORY", "n/a")
|
return getEnv("GITHUB_SERVER_URL", "n/a") + "/" + getEnv("GITHUB_REPOSITORY", "n/a")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPullRequestConfig returns pull request configuration
|
||||||
func (g *GitHubActionsConfigProvider) GetPullRequestConfig() PullRequestConfig {
|
func (g *GitHubActionsConfigProvider) GetPullRequestConfig() PullRequestConfig {
|
||||||
// See https://docs.github.com/en/enterprise-server@3.6/actions/learn-github-actions/variables#default-environment-variables
|
// See https://docs.github.com/en/enterprise-server@3.6/actions/learn-github-actions/variables#default-environment-variables
|
||||||
githubRef := getEnv("GITHUB_REF", "n/a")
|
githubRef := getEnv("GITHUB_REF", "n/a")
|
||||||
@ -182,6 +211,7 @@ func (g *GitHubActionsConfigProvider) GetPullRequestConfig() PullRequestConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPullRequest indicates whether the current build is triggered by a PR
|
||||||
func (g *GitHubActionsConfigProvider) IsPullRequest() bool {
|
func (g *GitHubActionsConfigProvider) IsPullRequest() bool {
|
||||||
return truthy("GITHUB_HEAD_REF")
|
return truthy("GITHUB_HEAD_REF")
|
||||||
}
|
}
|
||||||
@ -191,26 +221,82 @@ func isGitHubActions() bool {
|
|||||||
return areIndicatingEnvVarsSet(envVars)
|
return areIndicatingEnvVarsSet(envVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitHubActionsConfigProvider) getStageIds() ([]int, error) {
|
// actionsURL returns URL to actions resource. For example,
|
||||||
resp, err := g.client.GetRequest(fmt.Sprintf("%s/runs/%s/jobs", getActionsURL(), getEnv("GITHUB_RUN_ID", "")), httpHeader, nil)
|
// https://api.github.com/repos/SAP/jenkins-library/actions
|
||||||
if err != nil {
|
func actionsURL() string {
|
||||||
return nil, fmt.Errorf("failed to get API data: %w", err)
|
return getEnv("GITHUB_API_URL", "") + "/repos/" + getEnv("GITHUB_REPOSITORY", "") + "/actions"
|
||||||
}
|
}
|
||||||
|
|
||||||
var stagesID stagesID
|
func (g *GitHubActionsConfigProvider) fetchRunData() {
|
||||||
err = piperHttp.ParseHTTPResponseBodyJSON(resp, &stagesID)
|
if g.runData.fetched {
|
||||||
if err != nil {
|
return
|
||||||
return nil, fmt.Errorf("failed to parse JSON data: %w", err)
|
}
|
||||||
}
|
|
||||||
|
url := fmt.Sprintf("%s/runs/%s", actionsURL(), getEnv("GITHUB_RUN_ID", ""))
|
||||||
ids := make([]int, len(stagesID.Jobs))
|
resp, err := g.client.GetRequest(url, httpHeaders, nil)
|
||||||
for i, job := range stagesID.Jobs {
|
if err != nil || resp.StatusCode != 200 {
|
||||||
ids[i] = job.ID
|
log.Entry().Errorf("failed to get API data: %s", err)
|
||||||
}
|
return
|
||||||
if len(ids) == 0 {
|
}
|
||||||
return nil, fmt.Errorf("failed to get IDs")
|
|
||||||
}
|
err = piperHttp.ParseHTTPResponseBodyJSON(resp, &g.runData)
|
||||||
|
if err != nil {
|
||||||
// execution of the last stage hasn't finished yet - we can't get logs of the last stage
|
log.Entry().Errorf("failed to parse JSON data: %s", err)
|
||||||
return ids[:len(stagesID.Jobs)-1], nil
|
return
|
||||||
|
}
|
||||||
|
g.runData.fetched = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitHubActionsConfigProvider) fetchJobs() error {
|
||||||
|
if g.jobsFetched {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/runs/%s/jobs", actionsURL(), g.GetBuildID())
|
||||||
|
resp, err := g.client.GetRequest(url, httpHeaders, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get API data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Jobs []job `json:"jobs"`
|
||||||
|
}
|
||||||
|
err = piperHttp.ParseHTTPResponseBodyJSON(resp, &result)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse JSON data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Jobs) == 0 {
|
||||||
|
return fmt.Errorf("no jobs found in response")
|
||||||
|
}
|
||||||
|
g.jobs = result.Jobs
|
||||||
|
g.jobsFetched = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitHubActionsConfigProvider) guessCurrentJob() {
|
||||||
|
// check if the current job has already been guessed
|
||||||
|
if g.currentJob.ID != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch jobs if they haven't been fetched yet
|
||||||
|
if err := g.fetchJobs(); err != nil {
|
||||||
|
log.Entry().Errorf("failed to fetch jobs: %s", err)
|
||||||
|
g.jobs = []job{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetJobName := getEnv("GITHUB_JOB", "unknown")
|
||||||
|
log.Entry().Debugf("looking for job '%s' in jobs list: %v", targetJobName, g.jobs)
|
||||||
|
for _, j := range g.jobs {
|
||||||
|
// j.Name may be something like "piper / Init / Init"
|
||||||
|
// but GITHUB_JOB env may contain only "Init"
|
||||||
|
if strings.HasSuffix(j.Name, targetJobName) {
|
||||||
|
log.Entry().Debugf("current job id: %d", j.ID)
|
||||||
|
g.currentJob = j
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ package orchestrator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -17,187 +17,328 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGitHubActions(t *testing.T) {
|
func TestGitHubActionsConfigProvider_GetBuildStatus(t *testing.T) {
|
||||||
t.Run("BranchBuild", func(t *testing.T) {
|
tests := []struct {
|
||||||
defer resetEnv(os.Environ())
|
name string
|
||||||
os.Clearenv()
|
runData run
|
||||||
os.Unsetenv("GITHUB_HEAD_REF")
|
want string
|
||||||
os.Setenv("GITHUB_ACTIONS", "true")
|
}{
|
||||||
os.Setenv("GITHUB_REF", "refs/heads/feat/test-gh-actions")
|
{"BuildStatusSuccess", run{fetched: true, Status: "success"}, BuildStatusSuccess},
|
||||||
os.Setenv("GITHUB_RUN_ID", "42")
|
{"BuildStatusAborted", run{fetched: true, Status: "cancelled"}, BuildStatusAborted},
|
||||||
os.Setenv("GITHUB_SHA", "abcdef42713")
|
{"BuildStatusInProgress", run{fetched: true, Status: "in_progress"}, BuildStatusInProgress},
|
||||||
os.Setenv("GITHUB_SERVER_URL", "github.com")
|
{"BuildStatusFailure", run{fetched: true, Status: "qwertyu"}, BuildStatusFailure},
|
||||||
os.Setenv("GITHUB_REPOSITORY", "foo/bar")
|
{"BuildStatusFailure", run{fetched: true, Status: ""}, BuildStatusFailure},
|
||||||
|
}
|
||||||
p, _ := NewOrchestratorSpecificConfigProvider()
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.False(t, p.IsPullRequest())
|
g := &GitHubActionsConfigProvider{
|
||||||
assert.Equal(t, "github.com/foo/bar/actions/runs/42", p.GetBuildURL())
|
runData: tt.runData,
|
||||||
assert.Equal(t, "feat/test-gh-actions", p.GetBranch())
|
}
|
||||||
assert.Equal(t, "refs/heads/feat/test-gh-actions", p.GetReference())
|
assert.Equalf(t, tt.want, g.GetBuildStatus(), "GetBuildStatus()")
|
||||||
assert.Equal(t, "abcdef42713", p.GetCommit())
|
})
|
||||||
assert.Equal(t, "github.com/foo/bar", p.GetRepoURL())
|
}
|
||||||
assert.Equal(t, "GitHubActions", p.OrchestratorType())
|
}
|
||||||
})
|
|
||||||
|
func TestGitHubActionsConfigProvider_GetBuildReason(t *testing.T) {
|
||||||
t.Run("PR", func(t *testing.T) {
|
tests := []struct {
|
||||||
defer resetEnv(os.Environ())
|
name string
|
||||||
os.Clearenv()
|
envGithubRef string
|
||||||
os.Setenv("GITHUB_HEAD_REF", "feat/test-gh-actions")
|
want string
|
||||||
os.Setenv("GITHUB_BASE_REF", "main")
|
}{
|
||||||
os.Setenv("GITHUB_REF", "refs/pull/42/merge")
|
{"BuildReasonManual", "workflow_dispatch", BuildReasonManual},
|
||||||
|
{"BuildReasonSchedule", "schedule", BuildReasonSchedule},
|
||||||
p := GitHubActionsConfigProvider{}
|
{"BuildReasonPullRequest", "pull_request", BuildReasonPullRequest},
|
||||||
c := p.GetPullRequestConfig()
|
{"BuildReasonResourceTrigger", "workflow_call", BuildReasonResourceTrigger},
|
||||||
|
{"BuildReasonIndividualCI", "push", BuildReasonIndividualCI},
|
||||||
assert.True(t, p.IsPullRequest())
|
{"BuildReasonUnknown", "qwerty", BuildReasonUnknown},
|
||||||
assert.Equal(t, "feat/test-gh-actions", c.Branch)
|
{"BuildReasonUnknown", "", BuildReasonUnknown},
|
||||||
assert.Equal(t, "main", c.Base)
|
}
|
||||||
assert.Equal(t, "42", c.Key)
|
for _, tt := range tests {
|
||||||
})
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := &GitHubActionsConfigProvider{}
|
||||||
t.Run("Test get logs - success", func(t *testing.T) {
|
|
||||||
defer resetEnv(os.Environ())
|
_ = os.Setenv("GITHUB_EVENT_NAME", tt.envGithubRef)
|
||||||
os.Clearenv()
|
assert.Equalf(t, tt.want, g.GetBuildReason(), "GetBuildReason()")
|
||||||
os.Unsetenv("GITHUB_HEAD_REF")
|
})
|
||||||
os.Setenv("GITHUB_ACTIONS", "true")
|
}
|
||||||
os.Setenv("GITHUB_REF_NAME", "feat/test-gh-actions")
|
}
|
||||||
os.Setenv("GITHUB_REF", "refs/heads/feat/test-gh-actions")
|
|
||||||
os.Setenv("GITHUB_RUN_ID", "42")
|
func TestGitHubActionsConfigProvider_GetRepoURL(t *testing.T) {
|
||||||
os.Setenv("GITHUB_SHA", "abcdef42713")
|
tests := []struct {
|
||||||
os.Setenv("GITHUB_REPOSITORY", "foo/bar")
|
name string
|
||||||
os.Setenv("GITHUB_URL", "https://github.com/")
|
envServerURL string
|
||||||
p := func() OrchestratorSpecificConfigProviding {
|
envRepo string
|
||||||
g := GitHubActionsConfigProvider{}
|
want string
|
||||||
g.client = piperHttp.Client{}
|
}{
|
||||||
g.client.SetOptions(piperHttp.ClientOptions{
|
{"github.com", "https://github.com", "SAP/jenkins-library", "https://github.com/SAP/jenkins-library"},
|
||||||
MaxRequestDuration: 5 * time.Second,
|
}
|
||||||
Password: "TOKEN",
|
for _, tt := range tests {
|
||||||
TransportSkipVerification: true,
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
UseDefaultTransport: true, // need to use default transport for http mock
|
g := &GitHubActionsConfigProvider{}
|
||||||
MaxRetries: -1,
|
|
||||||
})
|
_ = os.Setenv("GITHUB_SERVER_URL", tt.envServerURL)
|
||||||
return &g
|
_ = os.Setenv("GITHUB_REPOSITORY", tt.envRepo)
|
||||||
}()
|
assert.Equalf(t, tt.want, g.GetRepoURL(), "GetRepoURL()")
|
||||||
stagesID := stagesID{
|
})
|
||||||
Jobs: []job{
|
}
|
||||||
{ID: 123},
|
}
|
||||||
{ID: 124},
|
|
||||||
{ID: 125},
|
func TestGitHubActionsConfigProvider_GetPullRequestConfig(t *testing.T) {
|
||||||
},
|
tests := []struct {
|
||||||
}
|
name string
|
||||||
logs := []string{
|
envRef string
|
||||||
"log_record1\n",
|
want PullRequestConfig
|
||||||
"log_record2\n",
|
}{
|
||||||
}
|
{"1", "refs/pull/1234/merge", PullRequestConfig{"n/a", "n/a", "1234"}},
|
||||||
httpmock.Activate()
|
{"2", "refs/pull/1234", PullRequestConfig{"n/a", "n/a", "1234"}},
|
||||||
defer httpmock.DeactivateAndReset()
|
{"2", "1234/merge", PullRequestConfig{"n/a", "n/a", "1234"}},
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/runs/42/jobs",
|
}
|
||||||
func(req *http.Request) (*http.Response, error) {
|
for _, tt := range tests {
|
||||||
return httpmock.NewJsonResponse(200, stagesID)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
},
|
g := &GitHubActionsConfigProvider{}
|
||||||
)
|
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/jobs/123/logs",
|
_ = os.Setenv("GITHUB_REF", tt.envRef)
|
||||||
func(req *http.Request) (*http.Response, error) {
|
_ = os.Setenv("GITHUB_HEAD_REF", "n/a")
|
||||||
return httpmock.NewStringResponse(200, string(logs[0])), nil
|
_ = os.Setenv("GITHUB_BASE_REF", "n/a")
|
||||||
},
|
assert.Equalf(t, tt.want, g.GetPullRequestConfig(), "GetPullRequestConfig()")
|
||||||
)
|
})
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/jobs/124/logs",
|
}
|
||||||
func(req *http.Request) (*http.Response, error) {
|
}
|
||||||
return httpmock.NewStringResponse(200, string(logs[1])), nil
|
|
||||||
},
|
func TestGitHubActionsConfigProvider_guessCurrentJob(t *testing.T) {
|
||||||
)
|
tests := []struct {
|
||||||
|
name string
|
||||||
actual, err := p.GetLog()
|
jobs []job
|
||||||
|
jobsFetched bool
|
||||||
assert.NoError(t, err)
|
targetJobName string
|
||||||
assert.Equal(t, strings.Join(logs, ""), string(actual))
|
wantJob job
|
||||||
})
|
}{
|
||||||
|
{
|
||||||
t.Run("Test get logs - error: failed to get stages ID", func(t *testing.T) {
|
name: "job found",
|
||||||
defer resetEnv(os.Environ())
|
jobs: []job{{Name: "Job1"}, {Name: "Job2"}, {Name: "Job3"}},
|
||||||
os.Clearenv()
|
jobsFetched: true,
|
||||||
os.Unsetenv("GITHUB_HEAD_REF")
|
targetJobName: "Job2",
|
||||||
os.Setenv("GITHUB_ACTIONS", "true")
|
wantJob: job{Name: "Job2"},
|
||||||
os.Setenv("GITHUB_REF_NAME", "feat/test-gh-actions")
|
},
|
||||||
os.Setenv("GITHUB_REF", "refs/heads/feat/test-gh-actions")
|
{
|
||||||
os.Setenv("GITHUB_RUN_ID", "42")
|
name: "job found",
|
||||||
os.Setenv("GITHUB_SHA", "abcdef42713")
|
jobs: []job{{Name: "Piper / Job1"}, {Name: "Piper / Job2"}, {Name: "Piper / Job3"}},
|
||||||
os.Setenv("GITHUB_REPOSITORY", "foo/bar")
|
jobsFetched: true,
|
||||||
os.Setenv("GITHUB_URL", "https://github.com/")
|
targetJobName: "Job2",
|
||||||
p := func() OrchestratorSpecificConfigProviding {
|
wantJob: job{Name: "Piper / Job2"},
|
||||||
g := GitHubActionsConfigProvider{}
|
},
|
||||||
g.client = piperHttp.Client{}
|
{
|
||||||
g.client.SetOptions(piperHttp.ClientOptions{
|
name: "job not found",
|
||||||
MaxRequestDuration: 5 * time.Second,
|
jobs: []job{{Name: "Job1"}, {Name: "Job2"}, {Name: "Job3"}},
|
||||||
Password: "TOKEN",
|
jobsFetched: true,
|
||||||
TransportSkipVerification: true,
|
targetJobName: "Job123",
|
||||||
UseDefaultTransport: true, // need to use default transport for http mock
|
wantJob: job{},
|
||||||
MaxRetries: -1,
|
},
|
||||||
})
|
}
|
||||||
return &g
|
for _, tt := range tests {
|
||||||
}()
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
httpmock.Activate()
|
g := &GitHubActionsConfigProvider{
|
||||||
defer httpmock.DeactivateAndReset()
|
jobs: tt.jobs,
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/runs/42/jobs",
|
jobsFetched: tt.jobsFetched,
|
||||||
func(req *http.Request) (*http.Response, error) {
|
}
|
||||||
return nil, fmt.Errorf("err")
|
_ = os.Setenv("GITHUB_JOB", tt.targetJobName)
|
||||||
},
|
g.guessCurrentJob()
|
||||||
)
|
|
||||||
actual, err := p.GetLog()
|
assert.Equal(t, tt.wantJob, g.currentJob)
|
||||||
|
})
|
||||||
assert.Nil(t, actual)
|
}
|
||||||
assert.EqualError(t, err, "failed to get API data: HTTP request to https://api.github.com/repos/foo/bar/actions/runs/42/jobs failed with error: HTTP GET request to https://api.github.com/repos/foo/bar/actions/runs/42/jobs failed: Get \"https://api.github.com/repos/foo/bar/actions/runs/42/jobs\": err")
|
}
|
||||||
})
|
|
||||||
|
func TestGitHubActionsConfigProvider_fetchRunData(t *testing.T) {
|
||||||
t.Run("Test get logs - failed to get logs", func(t *testing.T) {
|
// data
|
||||||
defer resetEnv(os.Environ())
|
respJson := map[string]interface{}{
|
||||||
os.Clearenv()
|
"status": "completed",
|
||||||
os.Unsetenv("GITHUB_HEAD_REF")
|
"run_started_at": "2023-08-11T07:28:24Z",
|
||||||
os.Setenv("GITHUB_ACTIONS", "true")
|
"html_url": "https://github.com/SAP/jenkins-library/actions/runs/11111",
|
||||||
os.Setenv("GITHUB_REF_NAME", "feat/test-gh-actions")
|
}
|
||||||
os.Setenv("GITHUB_REF", "refs/heads/feat/test-gh-actions")
|
startedAt, _ := time.Parse(time.RFC3339, "2023-08-11T07:28:24Z")
|
||||||
os.Setenv("GITHUB_RUN_ID", "42")
|
wantRunData := run{
|
||||||
os.Setenv("GITHUB_SHA", "abcdef42713")
|
fetched: true,
|
||||||
os.Setenv("GITHUB_REPOSITORY", "foo/bar")
|
Status: "completed",
|
||||||
os.Setenv("GITHUB_URL", "https://github.com/")
|
StartedAt: startedAt,
|
||||||
p := func() OrchestratorSpecificConfigProviding {
|
}
|
||||||
g := GitHubActionsConfigProvider{}
|
|
||||||
g.client = piperHttp.Client{}
|
// setup provider
|
||||||
g.client.SetOptions(piperHttp.ClientOptions{
|
g := &GitHubActionsConfigProvider{}
|
||||||
MaxRequestDuration: 5 * time.Second,
|
g.client.SetOptions(piperHttp.ClientOptions{
|
||||||
Password: "TOKEN",
|
UseDefaultTransport: true, // need to use default transport for http mock
|
||||||
TransportSkipVerification: true,
|
MaxRetries: -1,
|
||||||
UseDefaultTransport: true, // need to use default transport for http mock
|
})
|
||||||
MaxRetries: -1,
|
// setup http mock
|
||||||
})
|
httpmock.Activate()
|
||||||
return &g
|
defer httpmock.DeactivateAndReset()
|
||||||
}()
|
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/SAP/jenkins-library/actions/runs/11111",
|
||||||
stagesID := stagesID{
|
func(req *http.Request) (*http.Response, error) {
|
||||||
Jobs: []job{
|
return httpmock.NewJsonResponse(200, respJson)
|
||||||
{ID: 123},
|
},
|
||||||
{ID: 124},
|
)
|
||||||
{ID: 125},
|
// setup env vars
|
||||||
},
|
defer resetEnv(os.Environ())
|
||||||
}
|
os.Clearenv()
|
||||||
httpmock.Activate()
|
_ = os.Setenv("GITHUB_API_URL", "https://api.github.com")
|
||||||
defer httpmock.DeactivateAndReset()
|
_ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library")
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/runs/42/jobs",
|
_ = os.Setenv("GITHUB_RUN_ID", "11111")
|
||||||
func(req *http.Request) (*http.Response, error) {
|
|
||||||
return httpmock.NewJsonResponse(200, stagesID)
|
// run
|
||||||
},
|
g.fetchRunData()
|
||||||
)
|
assert.Equal(t, wantRunData, g.runData)
|
||||||
httpmock.RegisterResponder(http.MethodGet, "https://api.github.com/repos/foo/bar/actions/jobs/123/logs",
|
}
|
||||||
func(req *http.Request) (*http.Response, error) {
|
|
||||||
return nil, fmt.Errorf("err")
|
func TestGitHubActionsConfigProvider_fetchJobs(t *testing.T) {
|
||||||
},
|
// data
|
||||||
)
|
respJson := map[string]interface{}{"jobs": []map[string]interface{}{{
|
||||||
|
"id": 111,
|
||||||
actual, err := p.GetLog()
|
"name": "Piper / Init",
|
||||||
|
"html_url": "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/111",
|
||||||
assert.Nil(t, actual)
|
}, {
|
||||||
// GitHubActionsConfigProvider.GetLog calls http.GetRequest concurrently, so we don't know what log (123 or 124) will be got first
|
"id": 222,
|
||||||
// ref: pkg/orchestrator/gitHubActions.go:90
|
"name": "Piper / Build",
|
||||||
assert.ErrorContains(t, err, "failed to get logs: failed to get API data: HTTP request to https://api.github.com/repos/foo/bar/actions/jobs/12")
|
"html_url": "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/222",
|
||||||
})
|
}, {
|
||||||
|
"id": 333,
|
||||||
|
"name": "Piper / Acceptance",
|
||||||
|
"html_url": "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/333",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
wantJobs := []job{{
|
||||||
|
ID: 111,
|
||||||
|
Name: "Piper / Init",
|
||||||
|
HtmlURL: "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/111",
|
||||||
|
}, {
|
||||||
|
ID: 222,
|
||||||
|
Name: "Piper / Build",
|
||||||
|
HtmlURL: "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/222",
|
||||||
|
}, {
|
||||||
|
ID: 333,
|
||||||
|
Name: "Piper / Acceptance",
|
||||||
|
HtmlURL: "https://github.com/SAP/jenkins-library/actions/runs/11111/jobs/333",
|
||||||
|
}}
|
||||||
|
|
||||||
|
// setup provider
|
||||||
|
g := &GitHubActionsConfigProvider{}
|
||||||
|
g.client.SetOptions(piperHttp.ClientOptions{
|
||||||
|
UseDefaultTransport: true, // need to use default transport for http mock
|
||||||
|
MaxRetries: -1,
|
||||||
|
})
|
||||||
|
// setup http mock
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
httpmock.RegisterResponder(
|
||||||
|
http.MethodGet,
|
||||||
|
"https://api.github.com/repos/SAP/jenkins-library/actions/runs/11111/jobs",
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
return httpmock.NewJsonResponse(200, respJson)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// setup env vars
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("GITHUB_API_URL", "https://api.github.com")
|
||||||
|
_ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library")
|
||||||
|
_ = os.Setenv("GITHUB_RUN_ID", "11111")
|
||||||
|
|
||||||
|
// run
|
||||||
|
err := g.fetchJobs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, wantJobs, g.jobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitHubActionsConfigProvider_GetLog(t *testing.T) {
|
||||||
|
// data
|
||||||
|
respLogs := []string{
|
||||||
|
"log_record11\nlog_record12\nlog_record13\n",
|
||||||
|
"log_record21\nlog_record22\n",
|
||||||
|
"log_record31\nlog_record32\n",
|
||||||
|
"log_record41\n",
|
||||||
|
}
|
||||||
|
wantLogs := "log_record11\nlog_record12\nlog_record13\nlog_record21\n" +
|
||||||
|
"log_record22\nlog_record31\nlog_record32\nlog_record41\n"
|
||||||
|
jobs := []job{
|
||||||
|
{ID: 111}, {ID: 222}, {ID: 333}, {ID: 444}, {ID: 555},
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup provider
|
||||||
|
g := &GitHubActionsConfigProvider{
|
||||||
|
client: piperHttp.Client{},
|
||||||
|
jobs: jobs,
|
||||||
|
jobsFetched: true,
|
||||||
|
}
|
||||||
|
g.client.SetOptions(piperHttp.ClientOptions{
|
||||||
|
UseDefaultTransport: true, // need to use default transport for http mock
|
||||||
|
MaxRetries: -1,
|
||||||
|
})
|
||||||
|
// setup http mock
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
latencyMin, latencyMax := 15, 500 // milliseconds
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
for i, j := range jobs {
|
||||||
|
idx := i
|
||||||
|
httpmock.RegisterResponder(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("https://api.github.com/repos/SAP/jenkins-library/actions/jobs/%d/logs", j.ID),
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
// simulate response delay
|
||||||
|
latency := rand.Intn(latencyMax-latencyMin) + latencyMin
|
||||||
|
time.Sleep(time.Duration(latency) * time.Millisecond)
|
||||||
|
return httpmock.NewStringResponse(200, respLogs[idx]), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// setup env vars
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("GITHUB_API_URL", "https://api.github.com")
|
||||||
|
_ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library")
|
||||||
|
|
||||||
|
// run
|
||||||
|
logs, err := g.GetLog()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, wantLogs, string(logs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitHubActionsConfigProvider_Others(t *testing.T) {
|
||||||
|
defer resetEnv(os.Environ())
|
||||||
|
os.Clearenv()
|
||||||
|
_ = os.Setenv("GITHUB_ACTION", "1")
|
||||||
|
_ = os.Setenv("GITHUB_JOB", "Build")
|
||||||
|
_ = os.Setenv("GITHUB_RUN_ID", "11111")
|
||||||
|
_ = os.Setenv("GITHUB_REF_NAME", "main")
|
||||||
|
_ = os.Setenv("GITHUB_HEAD_REF", "feature-branch-1")
|
||||||
|
_ = os.Setenv("GITHUB_REF", "refs/pull/42/merge")
|
||||||
|
_ = os.Setenv("GITHUB_WORKFLOW", "Piper workflow")
|
||||||
|
_ = os.Setenv("GITHUB_SHA", "ffac537e6cbbf934b08745a378932722df287a53")
|
||||||
|
_ = os.Setenv("GITHUB_API_URL", "https://api.github.com")
|
||||||
|
_ = os.Setenv("GITHUB_SERVER_URL", "https://github.com")
|
||||||
|
_ = os.Setenv("GITHUB_REPOSITORY", "SAP/jenkins-library")
|
||||||
|
|
||||||
|
p := GitHubActionsConfigProvider{}
|
||||||
|
startedAt, _ := time.Parse(time.RFC3339, "2023-08-11T07:28:24Z")
|
||||||
|
p.runData = run{
|
||||||
|
fetched: true,
|
||||||
|
Status: "",
|
||||||
|
StartedAt: startedAt,
|
||||||
|
}
|
||||||
|
p.currentJob = job{ID: 111, Name: "job1", HtmlURL: "https://github.com/SAP/jenkins-library/actions/runs/123456/jobs/7654321"}
|
||||||
|
|
||||||
|
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/runs/123456/jobs/7654321", p.GetJobURL())
|
||||||
|
assert.Equal(t, "Piper workflow", p.GetJobName())
|
||||||
|
assert.Equal(t, "ffac537e6cbbf934b08745a378932722df287a53", p.GetCommit())
|
||||||
|
assert.Equal(t, "https://api.github.com/repos/SAP/jenkins-library/actions", actionsURL())
|
||||||
|
assert.True(t, p.IsPullRequest())
|
||||||
|
assert.True(t, isGitHubActions())
|
||||||
}
|
}
|
||||||
|
@ -14,20 +14,17 @@ import (
|
|||||||
|
|
||||||
type JenkinsConfigProvider struct {
|
type JenkinsConfigProvider struct {
|
||||||
client piperHttp.Client
|
client piperHttp.Client
|
||||||
options piperHttp.ClientOptions
|
|
||||||
apiInformation map[string]interface{}
|
apiInformation map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitOrchestratorProvider initializes the Jenkins orchestrator with credentials
|
// InitOrchestratorProvider initializes the Jenkins orchestrator with credentials
|
||||||
func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
func (j *JenkinsConfigProvider) InitOrchestratorProvider(settings *OrchestratorSettings) {
|
||||||
j.client = piperHttp.Client{}
|
j.client.SetOptions(piperHttp.ClientOptions{
|
||||||
j.options = piperHttp.ClientOptions{
|
|
||||||
Username: settings.JenkinsUser,
|
Username: settings.JenkinsUser,
|
||||||
Password: settings.JenkinsToken,
|
Password: settings.JenkinsToken,
|
||||||
MaxRetries: 3,
|
MaxRetries: 3,
|
||||||
TransportTimeout: time.Second * 10,
|
TransportTimeout: time.Second * 10,
|
||||||
}
|
})
|
||||||
j.client.SetOptions(j.options)
|
|
||||||
log.Entry().Debug("Successfully initialized Jenkins config provider")
|
log.Entry().Debug("Successfully initialized Jenkins config provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,15 +74,15 @@ func (j *JenkinsConfigProvider) GetBuildStatus() string {
|
|||||||
// cases in ADO: succeeded, failed, canceled, none, partiallySucceeded
|
// cases in ADO: succeeded, failed, canceled, none, partiallySucceeded
|
||||||
switch result := val; result {
|
switch result := val; result {
|
||||||
case "SUCCESS":
|
case "SUCCESS":
|
||||||
return "SUCCESS"
|
return BuildStatusSuccess
|
||||||
case "ABORTED":
|
case "ABORTED":
|
||||||
return "ABORTED"
|
return BuildStatusAborted
|
||||||
default:
|
default:
|
||||||
// FAILURE, NOT_BUILT
|
// FAILURE, NOT_BUILT
|
||||||
return "FAILURE"
|
return BuildStatusFailure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "FAILURE"
|
return BuildStatusFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChangeSet returns the commitIds and timestamp of the changeSet of the current run
|
// GetChangeSet returns the commitIds and timestamp of the changeSet of the current run
|
||||||
@ -200,12 +197,12 @@ func (j *JenkinsConfigProvider) GetBuildReason() string {
|
|||||||
marshal, err := json.Marshal(j.apiInformation)
|
marshal, err := json.Marshal(j.apiInformation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Entry().WithError(err).Debugf("could not marshal apiInformation")
|
log.Entry().WithError(err).Debugf("could not marshal apiInformation")
|
||||||
return "Unknown"
|
return BuildReasonUnknown
|
||||||
}
|
}
|
||||||
jsonParsed, err := gabs.ParseJSON(marshal)
|
jsonParsed, err := gabs.ParseJSON(marshal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Entry().WithError(err).Debugf("could not parse apiInformation")
|
log.Entry().WithError(err).Debugf("could not parse apiInformation")
|
||||||
return "Unknown"
|
return BuildReasonUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, child := range jsonParsed.Path("actions").Children() {
|
for _, child := range jsonParsed.Path("actions").Children() {
|
||||||
@ -217,21 +214,21 @@ func (j *JenkinsConfigProvider) GetBuildReason() string {
|
|||||||
for _, val := range child.Path("causes").Children() {
|
for _, val := range child.Path("causes").Children() {
|
||||||
subclass := val.S("_class")
|
subclass := val.S("_class")
|
||||||
if subclass.Data().(string) == "hudson.model.Cause$UserIdCause" {
|
if subclass.Data().(string) == "hudson.model.Cause$UserIdCause" {
|
||||||
return "Manual"
|
return BuildReasonManual
|
||||||
} else if subclass.Data().(string) == "hudson.triggers.TimerTrigger$TimerTriggerCause" {
|
} else if subclass.Data().(string) == "hudson.triggers.TimerTrigger$TimerTriggerCause" {
|
||||||
return "Schedule"
|
return BuildReasonSchedule
|
||||||
} else if subclass.Data().(string) == "jenkins.branch.BranchEventCause" {
|
} else if subclass.Data().(string) == "jenkins.branch.BranchEventCause" {
|
||||||
return "PullRequest"
|
return BuildReasonPullRequest
|
||||||
} else if subclass.Data().(string) == "org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause" {
|
} else if subclass.Data().(string) == "org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause" {
|
||||||
return "ResourceTrigger"
|
return BuildReasonResourceTrigger
|
||||||
} else {
|
} else {
|
||||||
return "Unknown"
|
return BuildReasonUnknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return "Unknown"
|
return BuildReasonUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranch returns the branch name, only works with the git plugin enabled
|
// GetBranch returns the branch name, only works with the git plugin enabled
|
||||||
|
@ -17,6 +17,20 @@ const (
|
|||||||
Jenkins
|
Jenkins
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BuildStatusSuccess = "SUCCESS"
|
||||||
|
BuildStatusAborted = "ABORTED"
|
||||||
|
BuildStatusFailure = "FAILURE"
|
||||||
|
BuildStatusInProgress = "IN_PROGRESS"
|
||||||
|
|
||||||
|
BuildReasonManual = "Manual"
|
||||||
|
BuildReasonSchedule = "Schedule"
|
||||||
|
BuildReasonPullRequest = "PullRequest"
|
||||||
|
BuildReasonResourceTrigger = "ResourceTrigger"
|
||||||
|
BuildReasonIndividualCI = "IndividualCI"
|
||||||
|
BuildReasonUnknown = "Unknown"
|
||||||
|
)
|
||||||
|
|
||||||
type OrchestratorSpecificConfigProviding interface {
|
type OrchestratorSpecificConfigProviding interface {
|
||||||
InitOrchestratorProvider(settings *OrchestratorSettings)
|
InitOrchestratorProvider(settings *OrchestratorSettings)
|
||||||
OrchestratorType() string
|
OrchestratorType() string
|
||||||
@ -75,13 +89,13 @@ func NewOrchestratorSpecificConfigProvider() (OrchestratorSpecificConfigProvidin
|
|||||||
// DetectOrchestrator returns the name of the current orchestrator e.g. Jenkins, Azure, Unknown
|
// DetectOrchestrator returns the name of the current orchestrator e.g. Jenkins, Azure, Unknown
|
||||||
func DetectOrchestrator() Orchestrator {
|
func DetectOrchestrator() Orchestrator {
|
||||||
if isAzure() {
|
if isAzure() {
|
||||||
return Orchestrator(AzureDevOps)
|
return AzureDevOps
|
||||||
} else if isGitHubActions() {
|
} else if isGitHubActions() {
|
||||||
return Orchestrator(GitHubActions)
|
return GitHubActions
|
||||||
} else if isJenkins() {
|
} else if isJenkins() {
|
||||||
return Orchestrator(Jenkins)
|
return Jenkins
|
||||||
} else {
|
} else {
|
||||||
return Orchestrator(Unknown)
|
return Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user