diff --git a/cmd/ocm-backplane/upgrade/upgrade.go b/cmd/ocm-backplane/upgrade/upgrade.go index c44050ce6..640e7ee59 100644 --- a/cmd/ocm-backplane/upgrade/upgrade.go +++ b/cmd/ocm-backplane/upgrade/upgrade.go @@ -31,6 +31,10 @@ var UpgradeCmd = &cobra.Command{ func runUpgrade(cmd *cobra.Command, _ []string) error { + if err := validateReleaseVersion(info.Version); err != nil { + return err + } + ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() @@ -44,3 +48,18 @@ func runUpgrade(cmd *cobra.Command, _ []string) error { return upgrade.UpgradePlugin(ctx, info.Version) } + +// validateReleaseVersion ensures the binary carries the release version metadata +// that the native self-upgrade relies on for the SemVer comparison. This metadata +// is injected via ldflags only when building official release binaries, so a CLI +// built without that injection (commonly a local source build) has an empty +// version and cannot self-upgrade. +func validateReleaseVersion(version string) error { + if version == "" { + return fmt.Errorf("release version metadata is missing; this commonly happens when the CLI is built from source without release version injection. " + + "Native self-upgrade requires release version metadata. " + + "Install or update Backplane CLI through an official release channel") + } + + return nil +} diff --git a/cmd/ocm-backplane/upgrade/upgrade_suite_test.go b/cmd/ocm-backplane/upgrade/upgrade_suite_test.go new file mode 100644 index 000000000..d47c1869c --- /dev/null +++ b/cmd/ocm-backplane/upgrade/upgrade_suite_test.go @@ -0,0 +1,13 @@ +package upgrade + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestUpgrade(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Upgrade Command Test Suite") +} diff --git a/cmd/ocm-backplane/upgrade/upgrade_test.go b/cmd/ocm-backplane/upgrade/upgrade_test.go new file mode 100644 index 000000000..aad9b3db8 --- /dev/null +++ b/cmd/ocm-backplane/upgrade/upgrade_test.go @@ -0,0 +1,36 @@ +package upgrade + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("validateReleaseVersion", func() { + Context("when the release version metadata is missing", func() { + It("returns a clear, actionable error", func() { + err := validateReleaseVersion("") + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("release version metadata is missing")) + Expect(err.Error()).To(ContainSubstring("built from source")) + Expect(err.Error()).To(ContainSubstring("official release channel")) + }) + + It("does not expose the raw SemVer parsing error", func() { + err := validateReleaseVersion("") + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).ToNot(ContainSubstring("Invalid Semantic Version")) + }) + }) + + Context("when the release version metadata is present", func() { + It("does not trigger the missing-version validation", func() { + Expect(validateReleaseVersion("1.0.1")).To(Succeed()) + }) + + It("passes the version through unchanged regardless of its format", func() { + Expect(validateReleaseVersion("1.0.1-0.20261009184111-fc8c9e8dedd0")).To(Succeed()) + }) + }) +})