mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
fix: bug in go mod version reading (#5652)
This commit is contained in:
+43
-24
@@ -1,14 +1,14 @@
|
||||
package versioning
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/mod/module"
|
||||
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GoMod utility to interact with Go Modules specific versioning
|
||||
@@ -37,36 +37,55 @@ func (m *GoMod) init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVersion returns the go.mod descriptor version property
|
||||
// GetVersion returns the version from a VERSION file, or falls back to go.mod
|
||||
func (m *GoMod) GetVersion() (string, error) {
|
||||
buildDescriptorFilePath := m.path
|
||||
var err error
|
||||
if strings.Contains(m.path, "go.mod") {
|
||||
buildDescriptorFilePath, err = searchDescriptor([]string{"version.txt", "VERSION"}, m.fileExists)
|
||||
if err != nil {
|
||||
err = m.init()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read file '%v': %w", m.path, err)
|
||||
}
|
||||
|
||||
parsed, err := modfile.Parse(m.path, []byte(m.buildDescriptorContent), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse go.mod file: %w", err)
|
||||
}
|
||||
if parsed.Module.Mod.Version != "" {
|
||||
return parsed.Module.Mod.Version, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to retrieve version: %w", err)
|
||||
}
|
||||
// If path is not go.mod, just read it as a version file
|
||||
if filepath.Base(m.path) != "go.mod" {
|
||||
return m.readVersionFile(m.path)
|
||||
}
|
||||
|
||||
// For go.mod projects, first try to find a dedicated version file
|
||||
versionFile, versionFileErr := m.findVersionFile()
|
||||
if versionFileErr == nil {
|
||||
return m.readVersionFile(versionFile)
|
||||
}
|
||||
|
||||
// Fall back to extracting version from go.mod itself
|
||||
version, gomodErr := m.extractVersionFromGoMod()
|
||||
if gomodErr != nil {
|
||||
return "", fmt.Errorf("no version file found (%v) and %w", versionFileErr, gomodErr)
|
||||
}
|
||||
if version == "" {
|
||||
return "", fmt.Errorf("no version file found (%v) and go.mod has no version", versionFileErr)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (m *GoMod) findVersionFile() (string, error) {
|
||||
return searchDescriptor([]string{"version.txt", "VERSION"}, m.fileExists)
|
||||
}
|
||||
|
||||
func (m *GoMod) readVersionFile(path string) (string, error) {
|
||||
artifact := &Versionfile{
|
||||
path: buildDescriptorFilePath,
|
||||
path: path,
|
||||
versioningScheme: m.VersioningScheme(),
|
||||
}
|
||||
return artifact.GetVersion()
|
||||
}
|
||||
|
||||
func (m *GoMod) extractVersionFromGoMod() (string, error) {
|
||||
if err := m.init(); err != nil {
|
||||
return "", fmt.Errorf("failed to read go.mod: %w", err)
|
||||
}
|
||||
|
||||
parsed, err := modfile.Parse(m.path, []byte(m.buildDescriptorContent), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse go.mod: %w", err)
|
||||
}
|
||||
|
||||
return parsed.Module.Mod.Version, nil
|
||||
}
|
||||
|
||||
// SetVersion sets the go.mod descriptor version property
|
||||
func (m *GoMod) SetVersion(v string) error {
|
||||
return nil
|
||||
|
||||
@@ -9,6 +9,148 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGoModGetVersion(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
goModContent string
|
||||
versionFile string // "VERSION", "version.txt", or "" for none
|
||||
versionContent string
|
||||
fileExistsFunc func(string) (bool, error)
|
||||
expectedVer string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "reads from VERSION file when present",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "VERSION",
|
||||
versionContent: "1.2.3",
|
||||
expectedVer: "1.2.3",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "reads from version.txt file when present",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "version.txt",
|
||||
versionContent: "2.0.0",
|
||||
expectedVer: "2.0.0",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "prefers version.txt over VERSION",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "both", // special case: create both files
|
||||
versionContent: "from-version-txt",
|
||||
expectedVer: "from-version-txt",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "error when no version file and go.mod has no version",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "",
|
||||
versionContent: "",
|
||||
expectedVer: "",
|
||||
expectedErr: "go.mod has no version",
|
||||
},
|
||||
{
|
||||
name: "error includes version file search failure",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "",
|
||||
versionContent: "",
|
||||
expectedVer: "",
|
||||
expectedErr: "no version file found",
|
||||
},
|
||||
{
|
||||
name: "handles version with trailing newline",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
versionFile: "VERSION",
|
||||
versionContent: "1.0.0\n",
|
||||
expectedVer: "1.0.0",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "fileExists returns error falls back to go.mod",
|
||||
goModContent: "module github.com/test/module\n\ngo 1.21",
|
||||
fileExistsFunc: func(f string) (bool, error) {
|
||||
return false, fmt.Errorf("permission denied")
|
||||
},
|
||||
expectedVer: "",
|
||||
expectedErr: "go.mod has no version",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// prepare temp directory and change to it
|
||||
tmpFolder := t.TempDir()
|
||||
originalWd, _ := os.Getwd()
|
||||
os.Chdir(tmpFolder)
|
||||
defer os.Chdir(originalWd)
|
||||
|
||||
goModFilePath := filepath.Join(tmpFolder, "go.mod")
|
||||
os.WriteFile(goModFilePath, []byte(tc.goModContent), 0o666)
|
||||
|
||||
// create version file(s) if specified
|
||||
fileExistsFunc := tc.fileExistsFunc
|
||||
if fileExistsFunc == nil {
|
||||
switch tc.versionFile {
|
||||
case "VERSION":
|
||||
os.WriteFile("VERSION", []byte(tc.versionContent), 0o666)
|
||||
fileExistsFunc = func(f string) (bool, error) {
|
||||
return f == "VERSION", nil
|
||||
}
|
||||
case "version.txt":
|
||||
os.WriteFile("version.txt", []byte(tc.versionContent), 0o666)
|
||||
fileExistsFunc = func(f string) (bool, error) {
|
||||
return f == "version.txt", nil
|
||||
}
|
||||
case "both":
|
||||
os.WriteFile("version.txt", []byte(tc.versionContent), 0o666)
|
||||
os.WriteFile("VERSION", []byte("from-VERSION"), 0o666)
|
||||
fileExistsFunc = func(f string) (bool, error) {
|
||||
return f == "version.txt" || f == "VERSION", nil
|
||||
}
|
||||
default:
|
||||
fileExistsFunc = func(f string) (bool, error) { return false, nil }
|
||||
}
|
||||
}
|
||||
|
||||
gomod := &GoMod{
|
||||
path: goModFilePath,
|
||||
fileExists: fileExistsFunc,
|
||||
}
|
||||
|
||||
// test
|
||||
version, err := gomod.GetVersion()
|
||||
|
||||
// assert
|
||||
if tc.expectedErr != "" {
|
||||
assert.ErrorContains(t, err, tc.expectedErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.expectedVer, version)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoModGetVersionNonGoMod(t *testing.T) {
|
||||
t.Run("reads directly when path is not go.mod", func(t *testing.T) {
|
||||
tmpFolder := t.TempDir()
|
||||
versionPath := filepath.Join(tmpFolder, "VERSION")
|
||||
os.WriteFile(versionPath, []byte("5.0.0"), 0o666)
|
||||
|
||||
gomod := &GoMod{
|
||||
path: versionPath,
|
||||
fileExists: func(f string) (bool, error) { return false, nil },
|
||||
}
|
||||
|
||||
version, err := gomod.GetVersion()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5.0.0", version)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoModGetCoordinates(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -27,7 +169,7 @@ func TestGoModGetCoordinates(t *testing.T) {
|
||||
// prepare
|
||||
tmpFolder := t.TempDir()
|
||||
goModFilePath := filepath.Join(tmpFolder, "go.mod")
|
||||
os.WriteFile(goModFilePath, []byte(fmt.Sprintf("module %s\n\ngo 1.24.0", tc.moduleName)), 0666)
|
||||
os.WriteFile(goModFilePath, []byte(fmt.Sprintf("module %s\n\ngo 1.24.0", tc.moduleName)), 0o666)
|
||||
gomod := &GoMod{
|
||||
path: goModFilePath,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user