Merging upstream version 0.8.13.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
4ccf9dc8f1
commit
bc0f764250
28 changed files with 556 additions and 76 deletions
3
internal/meta/doc.go
Normal file
3
internal/meta/doc.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package meta provides functionality to parse and manage metadata information
|
||||
// for Shoutrrr using Go's debug.ReadBuildInfo and GoReleaser build flags.
|
||||
package meta
|
161
internal/meta/meta.go
Normal file
161
internal/meta/meta.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constants for repeated string values.
|
||||
const (
|
||||
devVersion = "dev"
|
||||
unknownValue = "unknown"
|
||||
trueValue = "true"
|
||||
commitSHALength = 7 // Length to shorten Git commit SHA
|
||||
)
|
||||
|
||||
// These values are populated by GoReleaser during release builds.
|
||||
var (
|
||||
// Version is the Shoutrrr version (e.g., "v0.0.1").
|
||||
Version = devVersion
|
||||
// Commit is the Git commit SHA (e.g., "abc123").
|
||||
Commit = unknownValue
|
||||
// Date is the build or commit timestamp in RFC3339 format (e.g., "2025-05-07T00:00:00Z").
|
||||
Date = unknownValue
|
||||
)
|
||||
|
||||
// Info holds version information for Shoutrrr.
|
||||
type Info struct {
|
||||
Version string
|
||||
Commit string
|
||||
Date string
|
||||
}
|
||||
|
||||
// GetMetaStr returns the formatted version string, including commit info only if available.
|
||||
func GetMetaStr() string {
|
||||
version := GetVersion()
|
||||
date := GetDate()
|
||||
commit := GetCommit()
|
||||
|
||||
if commit == unknownValue {
|
||||
return fmt.Sprintf("%s (Built on %s)", version, date)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s (Built on %s from Git SHA %s)", version, date, commit)
|
||||
}
|
||||
|
||||
// GetVersion returns the version string, using debug.ReadBuildInfo for source builds
|
||||
// or GoReleaser variables for release builds.
|
||||
func GetVersion() string {
|
||||
version := Version
|
||||
|
||||
// If building from source (not GoReleaser), try to get version from debug.ReadBuildInfo
|
||||
if version == devVersion || version == "" {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
// Get the module version (e.g., v1.1.4 or v1.1.4+dirty)
|
||||
version = info.Main.Version
|
||||
if version == "(devel)" || version == "" {
|
||||
version = devVersion
|
||||
}
|
||||
// Check for dirty state
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.modified" && setting.Value == trueValue &&
|
||||
version != unknownValue && !contains(version, "+dirty") {
|
||||
version += "+dirty"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GoReleaser provides a valid version without 'v' prefix, so add it
|
||||
if version != "" && version != "v" {
|
||||
version = "v" + version
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback default if still unset or invalid
|
||||
if version == "" || version == devVersion || version == "v" {
|
||||
return unknownValue
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// GetCommit returns the commit SHA, using debug.ReadBuildInfo for source builds
|
||||
// or GoReleaser variables for release builds.
|
||||
func GetCommit() string {
|
||||
// Return Commit if set by GoReleaser (non-empty and not "unknown")
|
||||
if Commit != unknownValue && Commit != "" {
|
||||
if len(Commit) >= commitSHALength {
|
||||
return Commit[:commitSHALength]
|
||||
}
|
||||
|
||||
return Commit
|
||||
}
|
||||
|
||||
// Try to get commit from debug.ReadBuildInfo for source builds
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.revision" && setting.Value != "" {
|
||||
if len(setting.Value) >= commitSHALength {
|
||||
return setting.Value[:commitSHALength]
|
||||
}
|
||||
|
||||
return setting.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to unknown if no commit is found
|
||||
return unknownValue
|
||||
}
|
||||
|
||||
// GetDate returns the build or commit date, using debug.ReadBuildInfo for source builds
|
||||
// or GoReleaser variables for release builds.
|
||||
func GetDate() string {
|
||||
date := Date
|
||||
|
||||
// If building from source (not GoReleaser), try to get date from debug.ReadBuildInfo
|
||||
if date == unknownValue || date == "" {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.time" {
|
||||
if t, err := time.Parse(time.RFC3339, setting.Value); err == nil {
|
||||
return t.Format("2006-01-02") // Shorten to YYYY-MM-DD
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to current date if no VCS time is available
|
||||
return time.Now().UTC().Format("2006-01-02")
|
||||
}
|
||||
|
||||
// Shorten date if provided by GoReleaser
|
||||
if date != "" && date != unknownValue {
|
||||
if t, err := time.Parse(time.RFC3339, date); err == nil {
|
||||
return t.Format("2006-01-02") // Shorten to YYYY-MM-DD
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to current date if date is invalid
|
||||
return time.Now().UTC().Format("2006-01-02")
|
||||
}
|
||||
|
||||
// GetMetaInfo returns version information by combining GetVersion, GetCommit, and GetDate.
|
||||
func GetMetaInfo() Info {
|
||||
return Info{
|
||||
Version: GetVersion(),
|
||||
Commit: GetCommit(),
|
||||
Date: GetDate(),
|
||||
}
|
||||
}
|
||||
|
||||
// contains checks if a string contains a substring.
|
||||
func contains(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
282
internal/meta/meta_test.go
Normal file
282
internal/meta/meta_test.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetVersionInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setVars func()
|
||||
expect Info
|
||||
partialMatch bool
|
||||
}{
|
||||
{
|
||||
name: "GoReleaser build",
|
||||
setVars: func() {
|
||||
Version = "0.0.1"
|
||||
Commit = "abc123456789"
|
||||
Date = "2025-05-07T00:00:00Z"
|
||||
},
|
||||
expect: Info{
|
||||
Version: "v0.0.1",
|
||||
Commit: "abc1234",
|
||||
Date: "2025-05-07",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Source build with default values",
|
||||
setVars: func() {
|
||||
Version = devVersion
|
||||
Commit = unknownValue
|
||||
Date = unknownValue
|
||||
},
|
||||
expect: Info{
|
||||
Version: unknownValue,
|
||||
Commit: unknownValue,
|
||||
Date: time.Now().UTC().Format("2006-01-02"),
|
||||
},
|
||||
partialMatch: true,
|
||||
},
|
||||
{
|
||||
name: "Source build with empty values",
|
||||
setVars: func() {
|
||||
Version = ""
|
||||
Commit = ""
|
||||
Date = ""
|
||||
},
|
||||
expect: Info{
|
||||
Version: unknownValue,
|
||||
Commit: unknownValue,
|
||||
Date: time.Now().UTC().Format("2006-01-02"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid GoReleaser version",
|
||||
setVars: func() {
|
||||
Version = "v"
|
||||
Commit = ""
|
||||
Date = ""
|
||||
},
|
||||
expect: Info{
|
||||
Version: unknownValue,
|
||||
Commit: unknownValue,
|
||||
Date: time.Now().UTC().Format("2006-01-02"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setVars()
|
||||
|
||||
info := GetMetaInfo()
|
||||
|
||||
if !tt.partialMatch {
|
||||
if info.Version != tt.expect.Version {
|
||||
t.Errorf("Version = %q, want %q", info.Version, tt.expect.Version)
|
||||
}
|
||||
|
||||
if info.Commit != tt.expect.Commit {
|
||||
t.Errorf("Commit = %q, want %q", info.Commit, tt.expect.Commit)
|
||||
}
|
||||
|
||||
// Validate Date format (YYYY-MM-DD) instead of exact match
|
||||
if !regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(info.Date) {
|
||||
t.Errorf("Date = %q, want valid YYYY-MM-DD format", info.Date)
|
||||
}
|
||||
} else if info.Version != tt.expect.Version && !strings.Contains(info.Version, "+dirty") {
|
||||
t.Errorf("Version = %q, want %q or dirty variant", info.Version, tt.expect.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionInfo_VCSData(t *testing.T) {
|
||||
Version = devVersion
|
||||
Commit = unknownValue
|
||||
Date = unknownValue
|
||||
|
||||
info := GetMetaInfo()
|
||||
|
||||
if buildInfo, ok := debug.ReadBuildInfo(); ok {
|
||||
var vcsRevision, vcsTime, vcsModified string
|
||||
|
||||
for _, setting := range buildInfo.Settings {
|
||||
switch setting.Key {
|
||||
case "vcs.revision":
|
||||
vcsRevision = setting.Value
|
||||
case "vcs.time":
|
||||
vcsTime = setting.Value
|
||||
case "vcs.modified":
|
||||
vcsModified = setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
if vcsRevision != "" {
|
||||
expectedCommit := vcsRevision
|
||||
if len(vcsRevision) >= 7 {
|
||||
expectedCommit = vcsRevision[:7]
|
||||
}
|
||||
|
||||
if info.Commit == unknownValue {
|
||||
t.Errorf(
|
||||
"Expected commit %q, got %q; ensure repository has commit history",
|
||||
expectedCommit,
|
||||
info.Commit,
|
||||
)
|
||||
} else if info.Commit != expectedCommit {
|
||||
t.Errorf("Commit = %q, want %q", info.Commit, expectedCommit)
|
||||
}
|
||||
} else {
|
||||
t.Logf("No vcs.revision found; ensure repository has Git metadata to cover commit assignment")
|
||||
}
|
||||
|
||||
if vcsTime != "" {
|
||||
if parsedTime, err := time.Parse(time.RFC3339, vcsTime); err == nil {
|
||||
expectedDate := parsedTime.Format("2006-01-02")
|
||||
if info.Date == unknownValue {
|
||||
t.Errorf(
|
||||
"Expected date %q, got %q; ensure vcs.time is a valid RFC3339 timestamp",
|
||||
expectedDate,
|
||||
info.Date,
|
||||
)
|
||||
} else if info.Date != expectedDate {
|
||||
t.Errorf("Date = %q, want %q", info.Date, expectedDate)
|
||||
}
|
||||
} else {
|
||||
t.Logf("vcs.time %q is invalid; date should be in YYYY-MM-DD format", vcsTime)
|
||||
|
||||
if !regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(info.Date) {
|
||||
t.Errorf("Date = %q, want valid YYYY-MM-DD format", info.Date)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Logf("No vcs.time found; date should be in YYYY-MM-DD format")
|
||||
|
||||
if !regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(info.Date) {
|
||||
t.Errorf("Date = %q, want valid YYYY-MM-DD format", info.Date)
|
||||
}
|
||||
}
|
||||
|
||||
if vcsModified == trueValue && info.Version != unknownValue {
|
||||
if !strings.Contains(info.Version, "+dirty") {
|
||||
t.Errorf(
|
||||
"Expected version to contain '+dirty', got %q; ensure repository has uncommitted changes",
|
||||
info.Version,
|
||||
)
|
||||
}
|
||||
} else if vcsModified != trueValue {
|
||||
t.Logf("Repository is clean (vcs.modified=%q); make uncommitted changes to cover '+dirty' case", vcsModified)
|
||||
}
|
||||
} else {
|
||||
t.Logf("debug.ReadBuildInfo() failed; ensure tests run in a Git repository to cover VCS parsing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionInfo_InvalidVCSTime(t *testing.T) {
|
||||
Version = devVersion
|
||||
Commit = unknownValue
|
||||
Date = unknownValue
|
||||
|
||||
info := GetMetaInfo()
|
||||
|
||||
if !regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(info.Date) {
|
||||
t.Errorf("Date = %q, want valid YYYY-MM-DD format", info.Date)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMetaStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setVars func()
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "With commit (GoReleaser build)",
|
||||
setVars: func() {
|
||||
Version = "0.8.10"
|
||||
Commit = "a6fcf77abcdef"
|
||||
Date = "2025-05-27T00:00:00Z"
|
||||
},
|
||||
expect: "v0.8.10 (Built on 2025-05-27 from Git SHA a6fcf77)",
|
||||
},
|
||||
{
|
||||
name: "Without commit (go install build)",
|
||||
setVars: func() {
|
||||
Version = "0.8.10"
|
||||
Commit = unknownValue
|
||||
Date = unknownValue
|
||||
},
|
||||
expect: "v0.8.10 (Built on " + time.Now().UTC().Format("2006-01-02") + ")",
|
||||
},
|
||||
{
|
||||
name: "Invalid version",
|
||||
setVars: func() {
|
||||
Version = "v"
|
||||
Commit = unknownValue
|
||||
Date = unknownValue
|
||||
},
|
||||
expect: "unknown (Built on " + time.Now().UTC().Format("2006-01-02") + ")",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.setVars()
|
||||
|
||||
result := GetMetaStr()
|
||||
if !strings.HasPrefix(result, strings.Split(tt.expect, " (")[0]) ||
|
||||
!regexp.MustCompile(`\d{4}-\d{2}-\d{2}`).MatchString(result) {
|
||||
t.Errorf("GetMetaStr() = %q, want format like %q", result, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
s string
|
||||
substr string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Substring found",
|
||||
s: "v1.0.0+dirty",
|
||||
substr: "+dirty",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Substring not found",
|
||||
s: "v1.0.0",
|
||||
substr: "+dirty",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty string",
|
||||
s: "",
|
||||
substr: "+dirty",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty substring",
|
||||
s: "v1.0.0",
|
||||
substr: "",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := contains(tt.s, tt.substr)
|
||||
if result != tt.expected {
|
||||
t.Errorf("contains(%q, %q) = %v, want %v", tt.s, tt.substr, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package meta
|
||||
|
||||
// Version of Shoutrrr.
|
||||
const Version = `0.6-dev`
|
||||
|
||||
// DocsVersion is prepended to documentation URLs and usually equals MAJOR.MINOR of Version.
|
||||
const DocsVersion = `dev`
|
Loading…
Add table
Add a link
Reference in a new issue