Loading cmd/felis/update.go +34 −3 Changes for cmd/felis/update.go: 34 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -50,10 +50,13 @@ type updateTarget struct { // and even on the bootstrap path it re-images felis-api from the binary setup is already // running (FELIS_BOOTSTRAP_BINARY), which looks like an update and changes nothing. // // The URL is the same one-liner both READMEs hand out. While the repo is private it // The URL is the one-liner both READMEs hand out, read at a tag rather than main: the // script's release channel installs the newest release's binary, and main can carry // installer changes that binary was never tested with. installerRef picks the tag and // renderApplyGuidance substitutes it for {ref}. While the repo is private the URL // answers 404 (raw.githubusercontent.com hides private repos), which is why the trailer // below points at the README's token'd form for that case. const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash" const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/{ref}/deploy/bootstrap.sh | sudo bash" // updateTargets is the selector table. panel and plugins both resolve to felis-api // because they are not separately versioned: the panel is compiled into the felis Loading Loading @@ -258,7 +261,7 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo // component, and reinstalling the current release is a valid repair action. fmt.Fprintf(&b, " note: cannot tell whether %s is current — its latest version could not be discovered (see above); this reinstalls it either way\n", t.component) } fmt.Fprintf(&b, " run: %s\n", t.command) fmt.Fprintf(&b, " run: %s\n", strings.ReplaceAll(t.command, "{ref}", installerRef(byComponent))) offeredCommand = true } // Only explain the command when one was actually offered; a --mc-only run has Loading @@ -277,3 +280,31 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo } return b.String() } // installerRef is the git ref the installer re-run reads bootstrap.sh from: the newest // stable felis release when the feed answered, which is the release that script then // installs; else the release this host runs; main only when neither is a release tag. func installerRef(byComponent map[string]updates.Action) string { a, ok := byComponent["felis-api"] if !ok { return "main" } if a.LatestKnown && isReleaseTag(a.Latest) { return a.Latest.String() } if isReleaseTag(a.Current) { return a.Current.String() } return "main" } // isReleaseTag reports whether v was read from a stable vX.Y.Z tag, the only refs // release.yml publishes a binary for. func isReleaseTag(v updates.Version) bool { s := v.String() if !strings.HasPrefix(s, "v") || v.IsPrerelease() { return false } _, err := updates.Parse(s) return err == nil } cmd/felis/update_test.go +32 −0 Changes for cmd/felis/update_test.go: 32 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -167,3 +167,35 @@ func TestApplyGuidancePointsEveryComponentAtTheInstaller(t *testing.T) { t.Fatalf("--mc offers no command; the trailer is a non-sequitur:\n%s", mc) } } // The re-run reads bootstrap.sh at the tag whose binary it installs. main can carry // installer changes no release was tested with. func TestApplyGuidanceReadsTheInstallerAtTheReleaseTag(t *testing.T) { v := func(s string) updates.Version { t.Helper() out, err := updates.Parse(s) if err != nil { t.Fatal(err) } return out } cases := []struct { name string api []updates.Action want string }{ {"latest known", []updates.Action{{Component: "felis-api", Kind: updates.ActionNotify, Current: v("v1.3.0"), Latest: v("v1.4.0"), LatestKnown: true}}, "/FelisMC/Felis/v1.4.0/deploy/bootstrap.sh"}, {"latest unknown", []updates.Action{{Component: "felis-api", Kind: updates.ActionNone, Current: v("v1.3.0")}}, "/FelisMC/Felis/v1.3.0/deploy/bootstrap.sh"}, {"prerelease latest", []updates.Action{{Component: "felis-api", Kind: updates.ActionNone, Current: v("v1.3.0"), Latest: v("v1.4.0-rc.1"), LatestKnown: true}}, "/FelisMC/Felis/v1.3.0/deploy/bootstrap.sh"}, {"nothing known", nil, "/FelisMC/Felis/main/deploy/bootstrap.sh"}, } for _, c := range cases { out := renderApplyGuidance(planResult(c.api), map[string]bool{"velocity": true, "panel": true}, true) if !strings.Contains(out, c.want) { t.Errorf("%s: want %q in:\n%s", c.name, c.want, out) } if strings.Contains(out, "{ref}") { t.Errorf("%s: placeholder left in:\n%s", c.name, out) } } } Loading
cmd/felis/update.go +34 −3 Changes for cmd/felis/update.go: 34 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -50,10 +50,13 @@ type updateTarget struct { // and even on the bootstrap path it re-images felis-api from the binary setup is already // running (FELIS_BOOTSTRAP_BINARY), which looks like an update and changes nothing. // // The URL is the same one-liner both READMEs hand out. While the repo is private it // The URL is the one-liner both READMEs hand out, read at a tag rather than main: the // script's release channel installs the newest release's binary, and main can carry // installer changes that binary was never tested with. installerRef picks the tag and // renderApplyGuidance substitutes it for {ref}. While the repo is private the URL // answers 404 (raw.githubusercontent.com hides private repos), which is why the trailer // below points at the README's token'd form for that case. const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash" const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/{ref}/deploy/bootstrap.sh | sudo bash" // updateTargets is the selector table. panel and plugins both resolve to felis-api // because they are not separately versioned: the panel is compiled into the felis Loading Loading @@ -258,7 +261,7 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo // component, and reinstalling the current release is a valid repair action. fmt.Fprintf(&b, " note: cannot tell whether %s is current — its latest version could not be discovered (see above); this reinstalls it either way\n", t.component) } fmt.Fprintf(&b, " run: %s\n", t.command) fmt.Fprintf(&b, " run: %s\n", strings.ReplaceAll(t.command, "{ref}", installerRef(byComponent))) offeredCommand = true } // Only explain the command when one was actually offered; a --mc-only run has Loading @@ -277,3 +280,31 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo } return b.String() } // installerRef is the git ref the installer re-run reads bootstrap.sh from: the newest // stable felis release when the feed answered, which is the release that script then // installs; else the release this host runs; main only when neither is a release tag. func installerRef(byComponent map[string]updates.Action) string { a, ok := byComponent["felis-api"] if !ok { return "main" } if a.LatestKnown && isReleaseTag(a.Latest) { return a.Latest.String() } if isReleaseTag(a.Current) { return a.Current.String() } return "main" } // isReleaseTag reports whether v was read from a stable vX.Y.Z tag, the only refs // release.yml publishes a binary for. func isReleaseTag(v updates.Version) bool { s := v.String() if !strings.HasPrefix(s, "v") || v.IsPrerelease() { return false } _, err := updates.Parse(s) return err == nil }
cmd/felis/update_test.go +32 −0 Changes for cmd/felis/update_test.go: 32 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -167,3 +167,35 @@ func TestApplyGuidancePointsEveryComponentAtTheInstaller(t *testing.T) { t.Fatalf("--mc offers no command; the trailer is a non-sequitur:\n%s", mc) } } // The re-run reads bootstrap.sh at the tag whose binary it installs. main can carry // installer changes no release was tested with. func TestApplyGuidanceReadsTheInstallerAtTheReleaseTag(t *testing.T) { v := func(s string) updates.Version { t.Helper() out, err := updates.Parse(s) if err != nil { t.Fatal(err) } return out } cases := []struct { name string api []updates.Action want string }{ {"latest known", []updates.Action{{Component: "felis-api", Kind: updates.ActionNotify, Current: v("v1.3.0"), Latest: v("v1.4.0"), LatestKnown: true}}, "/FelisMC/Felis/v1.4.0/deploy/bootstrap.sh"}, {"latest unknown", []updates.Action{{Component: "felis-api", Kind: updates.ActionNone, Current: v("v1.3.0")}}, "/FelisMC/Felis/v1.3.0/deploy/bootstrap.sh"}, {"prerelease latest", []updates.Action{{Component: "felis-api", Kind: updates.ActionNone, Current: v("v1.3.0"), Latest: v("v1.4.0-rc.1"), LatestKnown: true}}, "/FelisMC/Felis/v1.3.0/deploy/bootstrap.sh"}, {"nothing known", nil, "/FelisMC/Felis/main/deploy/bootstrap.sh"}, } for _, c := range cases { out := renderApplyGuidance(planResult(c.api), map[string]bool{"velocity": true, "panel": true}, true) if !strings.Contains(out, c.want) { t.Errorf("%s: want %q in:\n%s", c.name, c.want, out) } if strings.Contains(out, "{ref}") { t.Errorf("%s: placeholder left in:\n%s", c.name, out) } } }