Skip to content

Commit

Permalink
[PoC] Add support for Jenkins in Image Builder client (kyma-project#1…
Browse files Browse the repository at this point in the history
…2489)

* Add support for Jenkins

* Add Jenkins

* Update to go 1.23

* Parse jenkins env

* Rename env

* [Debug Print] Update config.go

* [Debug Print] Update config.go

* [Debug Print]

* Update .golangci.yaml

* [Debug Print]

* [Debug Print]

* The copy of the 'for' variable "change" can be deleted

* [Debug Print]

* Using logger in loadJenkinsGitState for better tracking.
Added checking if required environment variables exists.
Fixed regex to not match a .git suffix in reponame matching group. Using named matching groups for better readability.

* More logger usage.
Deprecation warning.

* Fixed tests

---------

Co-authored-by: Patryk Dobrowolski <[email protected]>
Co-authored-by: Wojciech Sołtys <[email protected]>
Co-authored-by: Wojciech Soltys <[email protected]>
Co-authored-by: dekiel <[email protected]>
  • Loading branch information
5 people authored Jan 9, 2025
1 parent 8631d10 commit 7f01316
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ linters:
disable-all: true
enable:
- unused
- exportloopref
- copyloopvar
- gosimple
- govet
- ineffassign
Expand Down
1 change: 0 additions & 1 deletion cmd/external-plugins/automated-approver/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ func (ac *ApproveCondition) checkChangedFiles(logger *zap.SugaredLogger, changes
defer logger.Sync()
logger.Debugf("Checking if PR changed only allowed files: %v", ac.ChangedFiles)
for _, change := range changes {
change := change
logger.Debugf("Checking file: %s", change.Filename)
matched := slices.ContainsFunc(ac.ChangedFiles, func(allowedFile string) bool {
filesMatcher := regexp.MustCompile(allowedFile)
Expand Down
82 changes: 81 additions & 1 deletion cmd/image-builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"regexp"
"slices"
"strconv"

Expand All @@ -21,6 +22,7 @@ const (
Prow CISystem = "Prow"
GithubActions CISystem = "GithubActions"
AzureDevOps CISystem = "AzureDevOps"
Jenkins CISystem = "Jenkins"
)

type Config struct {
Expand Down Expand Up @@ -147,14 +149,17 @@ func (gitState GitStateConfig) IsPullRequest() bool {
return gitState.isPullRequest
}

func LoadGitStateConfig(ciSystem CISystem) (GitStateConfig, error) {
// TODO (dekiel): Add logger parameter to all functions reading a git state.
func LoadGitStateConfig(logger Logger, ciSystem CISystem) (GitStateConfig, error) {
switch ciSystem {
// Load from env specific for Azure DevOps and Prow Jobs
case AzureDevOps, Prow:
return loadADOGitState()
// Load from env specific for Github Actions
case GithubActions:
return loadGithubActionsGitState()
case Jenkins:
return loadJenkinsGitState(logger)
default:
// Unknown CI System, return error and empty git state
return GitStateConfig{}, fmt.Errorf("unknown ci system, got %s", ciSystem)
Expand Down Expand Up @@ -326,6 +331,75 @@ func loadGithubActionsGitState() (GitStateConfig, error) {
}
}

// loadJenkinsGitState loads git state from environment variables specific for Jenkins.
func loadJenkinsGitState(logger Logger) (GitStateConfig, error) {
// Load from env specific for Jenkins Jobs
prID, isPullRequest := os.LookupEnv("CHANGE_ID")
gitURL, present := os.LookupEnv("GIT_URL")
if !present {
return GitStateConfig{}, fmt.Errorf("GIT_URL environment variable is not set, please set it to valid git URL")
}

owner, repo, err := extractOwnerAndRepoFromGitURL(logger, gitURL)
if err != nil {
return GitStateConfig{}, fmt.Errorf("failed to extract owner and repository from git URL %s: %w", gitURL, err)
}

baseCommitSHA, present := os.LookupEnv("GIT_COMMIT")
if !present {
return GitStateConfig{}, fmt.Errorf("GIT_COMMIT environment variable is not set, please set it to valid commit SHA")
}

gitState := GitStateConfig{
RepositoryName: repo,
RepositoryOwner: owner,
JobType: "postsubmit",
BaseCommitSHA: baseCommitSHA,
}

if isPullRequest {
pullNumber, err := strconv.Atoi(prID)
if err != nil {
return GitStateConfig{}, fmt.Errorf("failed to convert prID string variable to integer: %w", err)
}

baseRef, present := os.LookupEnv("CHANGE_BRANCH")
if !present {
return GitStateConfig{}, fmt.Errorf("CHANGE_BRANCH environment variable is not set, please set it to valid base branch name")
}
pullRequestHeadSHA, present := os.LookupEnv("CHANGE_HEAD_SHA")
if !present {
return GitStateConfig{}, fmt.Errorf("CHANGE_HEAD_SHA environment variable is not set, please set it to valid commit SHA")
}
gitState.JobType = "presubmit"
gitState.PullRequestNumber = pullNumber
gitState.BaseCommitRef = baseRef
gitState.PullHeadCommitSHA = pullRequestHeadSHA
gitState.isPullRequest = true
}

return gitState, nil
}

func extractOwnerAndRepoFromGitURL(logger Logger, gitURL string) (string, string, error) {
re := regexp.MustCompile(`.*/(?P<owner>.*)/(?P<repo>.*).git`)
matches := re.FindStringSubmatch(gitURL)

logger.Debugw("Extracted matches from git URL", "matches", matches, "gitURL", gitURL)

if len(matches) != 3 {
return "", "", fmt.Errorf("failed to extract owner and repository from git URL")
}

owner := matches[re.SubexpIndex("owner")]
repo := matches[re.SubexpIndex("repo")]

logger.Debugw("Extracted owner from git URL", "owner", owner)
logger.Debugw("Extracted repository from git URL", "repo", repo)

return owner, repo, nil
}

// DetermineUsedCISystem return CISystem bind to system in which image builder is running or error if unknown
// It is used to avoid getting env variables in multiple parts of image builder
func DetermineUsedCISystem() (CISystem, error) {
Expand Down Expand Up @@ -358,5 +432,11 @@ func determineUsedCISystem(envGetter func(key string) string, envLookup func(key
return AzureDevOps, nil
}

// JENKINS_HOME environment variable is set in Jenkins
_, isJenkins := envLookup("JENKINS_HOME")
if isJenkins {
return Jenkins, nil
}

return "", fmt.Errorf("cannot determine ci system: unknown system")
}
61 changes: 59 additions & 2 deletions cmd/image-builder/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/kyma-project/test-infra/pkg/tags"
"go.uber.org/zap"
)

func Test_ParseConfig(t *testing.T) {
Expand Down Expand Up @@ -379,6 +380,48 @@ func TestLoadGitStateConfig(t *testing.T) {
PullHeadCommitSHA: "e47034172c36d3e5fb407b5ba57adf0f7868599d",
},
},
{
name: "load data from push event for jenkins",
options: options{
ciSystem: Jenkins,
},
env: map[string]string{
"CHANGE_BRANCH": "refs/heads/main",
"JENKINS_HOME": "/some/absolute/path",
"GIT_URL": "github.com/kyma-project/test-infra.git",
"GIT_COMMIT": "1234",
},
gitState: GitStateConfig{
RepositoryName: "test-infra",
RepositoryOwner: "kyma-project",
JobType: "postsubmit",
BaseCommitSHA: "1234",
},
},
{
name: "load data from pull request event for jenkins",
options: options{
ciSystem: Jenkins,
},
env: map[string]string{
"CHANGE_BRANCH": "refs/heads/main",
"JENKINS_HOME": "/some/absolute/path",
"CHANGE_ID": "14",
"GIT_URL": "github.com/kyma-project/test-infra.git",
"GIT_COMMIT": "1234",
"CHANGE_HEAD_SHA": "4321", // Must be explicitly set when calling docker run
},
gitState: GitStateConfig{
RepositoryName: "test-infra",
RepositoryOwner: "kyma-project",
JobType: "presubmit",
BaseCommitSHA: "1234",
BaseCommitRef: "refs/heads/main",
PullRequestNumber: 14,
PullHeadCommitSHA: "4321",
isPullRequest: true,
},
},
}

for _, c := range tc {
Expand All @@ -388,8 +431,15 @@ func TestLoadGitStateConfig(t *testing.T) {
t.Setenv(key, value)
}

// Setup logger
zapLogger, err := zap.NewDevelopment()
if err != nil {
t.Errorf("Failed to initialize logger: %s", err)
}
logger := zapLogger.Sugar()

// Load git state
state, err := LoadGitStateConfig(c.options.ciSystem)
state, err := LoadGitStateConfig(logger, c.options.ciSystem)
if err != nil && !c.expectError {
t.Errorf("unexpected error occured %s", err)
}
Expand Down Expand Up @@ -436,6 +486,13 @@ func Test_determineCISystem(t *testing.T) {
},
ciSystem: GithubActions,
},
{
name: "detect running in jenkins",
env: mockEnv{
"JENKINS_HOME": "/some/absolute/path",
},
ciSystem: Jenkins,
},
{
name: "unknown ci system",
env: mockEnv{
Expand Down Expand Up @@ -463,4 +520,4 @@ func Test_determineCISystem(t *testing.T) {
}
})
}
}
}
15 changes: 9 additions & 6 deletions cmd/image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -869,14 +869,16 @@ func main() {

// If running inside some CI system, determine which system is used
if o.isCI {
ciSystem, err := DetermineUsedCISystem()
o.ciSystem, err = DetermineUsedCISystem()
if err != nil {
log.Fatalf("Failed to determine current ci system: %s", err)
o.logger.Errorw("Failed to determine current ci system", "error", err)
os.Exit(1)
}
o.ciSystem = ciSystem
o.gitState, err = LoadGitStateConfig(ciSystem)

o.gitState, err = LoadGitStateConfig(o.logger, o.ciSystem)
if err != nil {
log.Fatalf("Failed to load current git state: %s", err)
o.logger.Errorw("Failed to load current git state", "error", err)
os.Exit(1)
}

o.logger.Debugw("Git state loaded", "gitState", o.gitState)
Expand Down Expand Up @@ -922,12 +924,13 @@ func main() {
if o.buildInADO {
err = buildInADO(o)
if err != nil {
fmt.Printf("Image build failed with error: %s\n", err)
o.logger.Errorw("Image build failed", "error", err, "JobType", o.gitState.JobType)
os.Exit(1)
}
os.Exit(0)
}

o.logger.Warnw("Local build is deprecated and will be removed soon, the tool will not support local building anymore. Please migrate to the ADO build backend.")
err = buildLocally(o)
if err != nil {
fmt.Println(err)
Expand Down

0 comments on commit 7f01316

Please sign in to comment.