Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ synthetic secrets) ships in [`examples/`](examples/README.md).

```sh
# from a released tag (binaries also attach to each GitHub Release):
go install github.com/Kelcode-Dev/proxops/cmd/proxops@v0.6.0
go install github.com/Kelcode-Dev/proxops/cmd/proxops@v0.6.1

# or build from source:
git clone https://github.com/Kelcode-Dev/proxops && cd proxops
Expand All @@ -107,7 +107,7 @@ make build # -> bin/proxops
Versioned releases follow SemVer tags (`vX.Y.Z`); `@main` installs the
current main-branch build instead. For PVE hosts without a Go toolchain,
each GitHub Release also carries static `proxops_vX.Y.Z_<os>_<arch>`
binaries (`make cross` produces the linux/amd64 one). See
binaries (`make cross` produces both Linux amd64 and arm64 binaries). See
`docs/getting-started.md`.

SOPS-backed credentials additionally require `sops` (+ `age`) on PATH.
Expand Down
31 changes: 28 additions & 3 deletions cmd/proxops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"os"
"os/signal"
"path/filepath"
"runtime/debug"
"syscall"
"time"

Expand All @@ -55,9 +56,32 @@ import (
"github.com/Kelcode-Dev/proxops/internal/metrics"
)

// version is set at build time via -ldflags.
// version is overridden at build time via -ldflags. Module-aware builds
// fall back to the version embedded by the Go toolchain.
var version = "dev"

func resolveVersion(linked, module string) string {
if linked != "dev" {
return linked
}

if module != "" && module != "(devel)" {
return module
}

return linked
}

func resolvedVersion() string {
moduleVersion := ""

if info, ok := debug.ReadBuildInfo(); ok {
moduleVersion = info.Main.Version
}

return resolveVersion(version, moduleVersion)
}

type globalFlags struct {
configPath string
logLevel string
Expand Down Expand Up @@ -98,6 +122,7 @@ var errSilent = errors.New("silent")

func newRootCmd() *cobra.Command {
var gf globalFlags
v := resolvedVersion()

root := &cobra.Command{
Use: "proxops",
Expand Down Expand Up @@ -125,7 +150,7 @@ func newRootCmd() *cobra.Command {
"(a proxops.yaml with git.url) and single-cluster configs.\n" +
"diff/apply/status/run process every discovered cluster in sorted\n" +
"name order; adopt requires an explicit --cluster=<name>.\n",
Version: version,
Version: v,
SilenceUsage: true,
SilenceErrors: true,
}
Expand Down Expand Up @@ -268,7 +293,7 @@ func buildAgent(gf *globalFlags, log *slog.Logger, persistent, local *pflag.Flag
// Validate() with empty SopsResolved, causing SOPS configs that have
// no global pve.user/token triple (which is correct — SOPS supplies
// them) to fail validation prematurely.
return app.New(cfg, log, metrics.Register(), version)
return app.New(cfg, log, metrics.Register(), resolvedVersion())
}

// newRunCmd is the daemon mode: poll git and reconcile PVE continuously,
Expand Down
58 changes: 58 additions & 0 deletions cmd/proxops/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,61 @@ func TestBuildAgentRepositoryFirst(t *testing.T) {
t.Fatalf("agent build must fail at the git stage, got: %v", aerr)
}
}

// TestResolveVersion pins version precedence across release builds,
// `go install @version`, and local development builds. An explicit
// linker-supplied version always wins; otherwise the Go module version
// is used when available, falling back to "dev".
func TestResolveVersion(t *testing.T) {
tests := []struct {
name string
linked string
module string
want string
}{
{
name: "release linker version wins",
linked: "v0.6.1",
module: "v0.6.0",
want: "v0.6.1",
},
{
name: "git-derived linker version wins",
linked: "v0.6.0-3-gabc1234",
module: "(devel)",
want: "v0.6.0-3-gabc1234",
},
{
name: "go install uses module version",
linked: "dev",
module: "v0.6.1",
want: "v0.6.1",
},
{
name: "development build remains dev",
linked: "dev",
module: "(devel)",
want: "dev",
},
{
name: "missing build info remains dev",
linked: "dev",
module: "",
want: "dev",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := resolveVersion(tt.linked, tt.module); got != tt.want {
t.Fatalf(
"resolveVersion(%q, %q) = %q, want %q",
tt.linked,
tt.module,
got,
tt.want,
)
}
})
}
}
Loading