1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

chore(multiarch): helper to parse targetArchitectures (#3525)

* chore(docker): helper to parse targetArchitectures

* missing files
This commit is contained in:
Christian Volk 2022-02-10 16:46:00 +01:00 committed by GitHub
parent 93e3801945
commit c888e21e6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 248 additions and 19 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/multiarch"
"github.com/SAP/jenkins-library/pkg/versioning"
"golang.org/x/mod/modfile"
@ -177,8 +178,14 @@ func runGolangBuild(config *golangBuildOptions, telemetryData *telemetry.CustomD
binaries := []string{}
for _, architecture := range config.TargetArchitectures {
binary, err := runGolangBuildPerArchitecture(config, utils, ldflags, architecture)
platforms, err := multiarch.ParsePlatformStrings(config.TargetArchitectures)
if err != nil {
return err
}
for _, platform := range platforms {
binary, err := runGolangBuildPerArchitecture(config, utils, ldflags, platform)
if err != nil {
return err
@ -393,12 +400,11 @@ func prepareLdflags(config *golangBuildOptions, utils golangBuildUtils, envRootP
return generatedLdflags.String(), nil
}
func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuildUtils, ldflags, architecture string) (string, error) {
func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuildUtils, ldflags string, architecture multiarch.Platform) (string, error) {
var binaryName string
envVars := os.Environ()
goos, goarch := splitTargetArchitecture(architecture)
envVars = append(envVars, fmt.Sprintf("GOOS=%v", goos), fmt.Sprintf("GOARCH=%v", goarch))
envVars = append(envVars, fmt.Sprintf("GOOS=%v", architecture.OS), fmt.Sprintf("GOARCH=%v", architecture.Arch))
if !config.CgoEnabled {
envVars = append(envVars, "CGO_ENABLED=0")
@ -408,10 +414,10 @@ func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuild
buildOptions := []string{"build"}
if len(config.Output) > 0 {
fileExtension := ""
if goos == "windows" {
if architecture.OS == "windows" {
fileExtension = ".exe"
}
binaryName = fmt.Sprintf("%v-%v.%v%v", config.Output, goos, goarch, fileExtension)
binaryName = fmt.Sprintf("%v-%v.%v%v", config.Output, architecture.OS, architecture.Arch, fileExtension)
buildOptions = append(buildOptions, "-o", binaryName)
}
buildOptions = append(buildOptions, config.BuildFlags...)
@ -423,18 +429,11 @@ func runGolangBuildPerArchitecture(config *golangBuildOptions, utils golangBuild
if err := utils.RunExecutable("go", buildOptions...); err != nil {
log.Entry().Debugf("buildOptions: %v", buildOptions)
log.SetErrorCategory(log.ErrorBuild)
return "", fmt.Errorf("failed to run build for %v.%v: %w", goos, goarch, err)
return "", fmt.Errorf("failed to run build for %v.%v: %w", architecture.OS, architecture.Arch, err)
}
return binaryName, nil
}
func splitTargetArchitecture(architecture string) (string, string) {
// architecture expected to be in format os,arch due to possibleValues check of step
architectureParts := strings.Split(architecture, ",")
return architectureParts[0], architectureParts[1]
}
// 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 == "" {

View File

@ -11,7 +11,9 @@ import (
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/multiarch"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/stretchr/testify/assert"
"golang.org/x/mod/modfile"
@ -603,7 +605,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
config := golangBuildOptions{}
utils := newGolangBuildTestsUtils()
ldflags := ""
architecture := "linux,amd64"
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
assert.NoError(t, err)
@ -621,7 +623,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
config := golangBuildOptions{BuildFlags: []string{"--flag1", "val1", "--flag2", "val2"}, Output: "testBin", Packages: []string{"./test/.."}}
utils := newGolangBuildTestsUtils()
ldflags := "-X test=test"
architecture := "linux,amd64"
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
assert.NoError(t, err)
@ -638,7 +640,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
config := golangBuildOptions{Output: "testBin"}
utils := newGolangBuildTestsUtils()
ldflags := ""
architecture := "windows,amd64"
architecture, _ := multiarch.ParsePlatformString("windows,amd64")
binaryName, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
assert.NoError(t, err)
@ -653,7 +655,7 @@ func TestRunGolangBuildPerArchitecture(t *testing.T) {
utils := newGolangBuildTestsUtils()
utils.ShouldFailOnCommand = map[string]error{"go build": fmt.Errorf("execution error")}
ldflags := ""
architecture := "linux,amd64"
architecture, _ := multiarch.ParsePlatformString("linux,amd64")
_, err := runGolangBuildPerArchitecture(&config, utils, ldflags, architecture)
assert.EqualError(t, err, "failed to run build for linux.amd64: execution error")

View File

@ -0,0 +1,73 @@
package multiarch
import (
"fmt"
"regexp"
"strings"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
)
var knownGoos = []string{"aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos"}
var knownGoarch = []string{"386", "amd64", "amd64p32", "arm", "arm64", "arm64be", "armbe", "loong64", "mips", "mips64", "mips64le", "mips64p32", "mips64p32le", "mipsle", "ppc", "ppc64", "ppc64le", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm"}
// Platform .
type Platform struct {
OS string
Arch string
Variant string
}
// ToString returns a string representation of the platform
func (p Platform) ToString() string {
if len(p.Variant) > 0 {
return fmt.Sprintf("%s/%s/%s", p.OS, p.Arch, p.Variant)
}
return fmt.Sprintf("%s/%s", p.OS, p.Arch)
}
// ParsePlatformString parses the given string and returns a platform obj
func ParsePlatformString(s string) (Platform, error) {
r := regexp.MustCompile(`(?P<os>[^,/]+)[,/](?P<arch>[^,/]+)(?:[,/](?P<variant>[^,/]+))?`)
matches := r.FindStringSubmatch(strings.ToLower(s))
if len(matches) < 2 {
return Platform{}, fmt.Errorf("unable to parse platform '%s'", s)
}
p := Platform{}
p.OS = strings.Trim(matches[1], " ")
if !piperutils.ContainsString(knownGoos, p.OS) {
log.Entry().Warningf("OS '%s' is unknown to us", p.OS)
}
p.Arch = strings.Trim(matches[2], " ")
if !piperutils.ContainsString(knownGoarch, p.Arch) {
log.Entry().Warningf("Architecture '%s' is unknown to us", p.Arch)
}
p.Variant = strings.Trim(matches[3], " ")
return p, nil
}
// ParsePlatformStrings parses the given slice of strings and returns a slice with platform objects
func ParsePlatformStrings(ss []string) ([]Platform, error) {
pp := []Platform{}
for _, s := range ss {
if p, err := ParsePlatformString(s); err == nil {
pp = append(pp, p)
} else {
return nil, err
}
}
return pp, nil
}

View File

@ -0,0 +1,155 @@
package multiarch
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPlatformToString(t *testing.T) {
tt := []struct {
uut Platform
expect string
}{
{
uut: Platform{
OS: "linux",
Arch: "arm64",
},
expect: "linux/arm64",
},
{
uut: Platform{
OS: "linux",
Arch: "arm64",
Variant: "v8",
},
expect: "linux/arm64/v8",
},
}
for _, test := range tt {
t.Run(test.expect, func(t *testing.T) {
assert.Equal(t, test.expect, test.uut.ToString())
})
}
}
func TestParsePlatformString(t *testing.T) {
tt := []struct {
description string
input string
expect Platform
expectError string
}{
{
description: "format used by golangBuild - only os + arch",
input: "linux,amd64",
expect: Platform{
OS: "linux",
Arch: "amd64",
},
},
{
description: "format used by kanikoExecute - os/arch/variant",
input: "linux/amd64/v8",
expect: Platform{
OS: "linux",
Arch: "amd64",
Variant: "v8",
},
},
{
description: "sth in between - os,arch,variant",
input: "linux,amd64,v8",
expect: Platform{
OS: "linux",
Arch: "amd64",
Variant: "v8",
},
},
{
description: "should be case insensitive",
input: "LINUX/AMD64/V8",
expect: Platform{
OS: "linux",
Arch: "amd64",
Variant: "v8",
},
},
{
description: "whitespaces shall be trimmed",
input: " linux/ amd64 / v8",
expect: Platform{
OS: "linux",
Arch: "amd64",
Variant: "v8",
},
},
{
description: "reads unsupported values",
input: "myfancyos/runningonafancyarchitecture",
expect: Platform{
OS: "myfancyos",
Arch: "runningonafancyarchitecture",
},
},
{
description: "os + arch are required, variant is optional",
input: "linux",
expectError: "unable to parse platform 'linux'",
},
}
for _, test := range tt {
t.Run(test.description, func(t *testing.T) {
p, err := ParsePlatformString(test.input)
if len(test.expectError) > 0 {
assert.EqualError(t, err, test.expectError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expect, p)
})
}
}
func TestParsePlatformStringStrings(t *testing.T) {
tt := []struct {
description string
inputs []string
expect []Platform
expectError string
}{
{
description: "format used by golangBuild - only os + arch",
inputs: []string{"linux,amd64", "windows,amd64"},
expect: []Platform{
Platform{
OS: "linux",
Arch: "amd64",
},
Platform{
OS: "windows",
Arch: "amd64",
},
},
},
}
for _, test := range tt {
t.Run(test.description, func(t *testing.T) {
p, err := ParsePlatformStrings(test.inputs)
if len(test.expectError) > 0 {
assert.EqualError(t, err, test.expectError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.expect, p)
})
}
}