You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	chore(multiarch): helper to parse targetArchitectures (#3525)
* chore(docker): helper to parse targetArchitectures * missing files
This commit is contained in:
		| @@ -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 == "" { | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
							
								
								
									
										73
									
								
								pkg/multiarch/multiarch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								pkg/multiarch/multiarch.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										155
									
								
								pkg/multiarch/multiarch_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								pkg/multiarch/multiarch_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user