Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(golangBuild): BOM creation failed with private Go modules #4460

Merged
merged 16 commits into from
Aug 14, 2023
66 changes: 18 additions & 48 deletions cmd/golangBuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/SAP/jenkins-library/pkg/buildsettings"
Expand All @@ -24,7 +23,6 @@ import (
"github.com/SAP/jenkins-library/pkg/versioning"

"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
)

const (
Expand Down Expand Up @@ -341,26 +339,11 @@ func prepareGolangEnvironment(config *golangBuildOptions, goModFile *modfile.Fil
// pass private repos to go process
os.Setenv("GOPRIVATE", config.PrivateModules)

repoURLs, err := lookupGolangPrivateModulesRepositories(goModFile, config.PrivateModules, utils)

err = gitConfigurationForPrivateModules(config.PrivateModules, config.PrivateModulesGitToken, utils)
if err != nil {
return err
}

// configure credentials git shall use for pulling repos
for _, repoURL := range repoURLs {
if match, _ := regexp.MatchString("(?i)^https?://", repoURL); !match {
continue
}

authenticatedRepoURL := strings.Replace(repoURL, "://", fmt.Sprintf("://%s@", config.PrivateModulesGitToken), 1)

err = utils.RunExecutable("git", "config", "--global", fmt.Sprintf("url.%s.insteadOf", authenticatedRepoURL), repoURL)
if err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -539,36 +522,6 @@ func runGolangBuildPerArchitecture(config *golangBuildOptions, goModFile *modfil
return binaryNames, nil
}

// lookupPrivateModulesRepositories returns a slice of all modules that match the given glob pattern
func lookupGolangPrivateModulesRepositories(goModFile *modfile.File, globPattern string, utils golangBuildUtils) ([]string, error) {
if globPattern == "" {
return []string{}, nil
}

if goModFile == nil {
return nil, fmt.Errorf("couldn't find go.mod file")
} else if goModFile.Require == nil {
return []string{}, nil // no modules referenced, nothing to do
}

privateModules := []string{}

for _, goModule := range goModFile.Require {
if !module.MatchPrefixPatterns(globPattern, goModule.Mod.Path) {
continue
}

repo, err := utils.GetRepositoryURL(goModule.Mod.Path)

if err != nil {
return nil, err
}

privateModules = append(privateModules, repo)
}
return privateModules, nil
}

func runBOMCreation(utils golangBuildUtils, outputFilename string) error {

if err := utils.RunExecutable("cyclonedx-gomod", "mod", "-licenses", fmt.Sprintf("-verbose=%t", GeneralConfig.Verbose), "-test", "-output", outputFilename, "-output-version", "1.4"); err != nil {
Expand Down Expand Up @@ -631,3 +584,20 @@ func isMainPackage(utils golangBuildUtils, pkg string) (bool, error) {

return true, nil
}

func gitConfigurationForPrivateModules(privateMod string, token string, utils golangBuildUtils) error {
privateMod = strings.ReplaceAll(privateMod, "/*", "")
privateMod = strings.ReplaceAll(privateMod, "*.", "")
jliempt marked this conversation as resolved.
Show resolved Hide resolved
modules := strings.Split(privateMod, ",")
for _, v := range modules {
authenticatedRepoURL := fmt.Sprintf("https://%s@%s", token, v)
aibaend1 marked this conversation as resolved.
Show resolved Hide resolved
repoBaseURL := fmt.Sprintf("https://%s", v)
err := utils.RunExecutable("git", "config", "--global", fmt.Sprintf("url.%s.insteadOf", authenticatedRepoURL), repoBaseURL)
if err != nil {
return err
}

}

return nil
}
142 changes: 57 additions & 85 deletions cmd/golangBuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,8 +911,7 @@ go 1.17`
expect: expectations{
envVars: []string{"GOPRIVATE=*.example.com"},
commandsExecuted: [][]string{
{"git", "config", "--global", "url.https://[email protected]/private/repo.git.insteadOf", "https://private1.example.com/private/repo.git"},
{"git", "config", "--global", "url.https://[email protected]/another/repo.git.insteadOf", "https://private2.example.com/another/repo.git"},
{"git", "config", "--global", "url.https://[email protected]", "https://example.com"},
},
},
},
Expand Down Expand Up @@ -943,89 +942,6 @@ go 1.17`
}
}

func TestLookupGolangPrivateModulesRepositories(t *testing.T) {
t.Parallel()

modTestFile := `
module private.example.com/m

require (
example.com/public/module v1.0.0
private1.example.com/private/repo v0.1.0
private2.example.com/another/repo v0.2.0
)

go 1.17`

type expectations struct {
repos []string
errorMessage string
}
tests := []struct {
name string
modFileContent string
globPattern string
expect expectations
}{
{
name: "Does nothing if glob pattern is empty",
modFileContent: modTestFile,
expect: expectations{
repos: []string{},
},
},
{
name: "Does nothing if there is no go.mod file",
globPattern: "private.example.com",
modFileContent: "",
expect: expectations{
repos: []string{},
},
},
{
name: "Detects all private repos using a glob pattern",
modFileContent: modTestFile,
globPattern: "*.example.com",
expect: expectations{
repos: []string{"https://private1.example.com/private/repo.git", "https://private2.example.com/another/repo.git"},
},
},
{
name: "Detects all private repos",
modFileContent: modTestFile,
globPattern: "private1.example.com,private2.example.com",
expect: expectations{
repos: []string{"https://private1.example.com/private/repo.git", "https://private2.example.com/another/repo.git"},
},
},
{
name: "Detects a dedicated repo",
modFileContent: modTestFile,
globPattern: "private2.example.com",
expect: expectations{
repos: []string{"https://private2.example.com/another/repo.git"},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
utils := newGolangBuildTestsUtils()

goModFile, _ := modfile.Parse("", []byte(tt.modFileContent), nil)

repos, err := lookupGolangPrivateModulesRepositories(goModFile, tt.globPattern, utils)

if tt.expect.errorMessage == "" {
assert.NoError(t, err)
assert.Equal(t, tt.expect.repos, repos)
} else {
assert.EqualError(t, err, tt.expect.errorMessage)
}
})
}
}

func TestRunGolangciLint(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1153,3 +1069,59 @@ func TestRetrieveGolangciLint(t *testing.T) {
})
}
}

func Test_gitConfigurationForPrivateModules(t *testing.T) {
type args struct {
privateMod string
token string
}
type expectations struct {
commandsExecuted [][]string
}
tests := []struct {
name string
args args

expected expectations
}{
{
name: "with one private module",
args: args{
privateMod: "example.com/*",
token: "mytoken",
},
expected: expectations{
commandsExecuted: [][]string{
{"git", "config", "--global", "url.https://[email protected]", "https://example.com"},
},
},
},
{
name: "with multiple private modules",
args: args{
privateMod: "example.com/*,test.com/*",
token: "mytoken",
},
expected: expectations{
commandsExecuted: [][]string{
{"git", "config", "--global", "url.https://[email protected]", "https://example.com"},
{"git", "config", "--global", "url.https://[email protected]", "https://test.com"},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
utils := newGolangBuildTestsUtils()

err := gitConfigurationForPrivateModules(tt.args.privateMod, tt.args.token, utils)

if assert.NoError(t, err) {
for i, expectedCommand := range tt.expected.commandsExecuted {
assert.Equal(t, expectedCommand[0], utils.Calls[i].Exec)
assert.Equal(t, expectedCommand[1:], utils.Calls[i].Params)
}
}
})
}
}