fix: finalize durable Pi lifecycle
This commit is contained in:
@@ -101,6 +101,13 @@ func Load(path string) (Installation, error) {
|
||||
return Installation{}, err
|
||||
}
|
||||
}
|
||||
if info, err := os.Lstat(installation.CurrentImageOverridePath()); err == nil {
|
||||
if !info.Mode().IsRegular() {
|
||||
return Installation{}, errors.New("installation current-image override must be a regular file")
|
||||
}
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return Installation{}, errors.New("installation current-image override could not be inspected")
|
||||
}
|
||||
return installation, nil
|
||||
}
|
||||
|
||||
@@ -111,7 +118,26 @@ func (i Installation) ComposeFiles() []string {
|
||||
filepath.Join(i.ProjectDirectory, "compose.yaml"),
|
||||
filepath.Join(i.ProjectDirectory, "deploy", "compose."+i.Profile+".yaml"),
|
||||
}
|
||||
return append(files, i.Overrides...)
|
||||
files = append(files, i.Overrides...)
|
||||
currentImage := i.CurrentImageOverridePath()
|
||||
if info, err := os.Lstat(currentImage); err == nil && info.Mode().IsRegular() {
|
||||
files = append(files, currentImage)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
// ControlDirectory contains state that is private to one installation descriptor, even when
|
||||
// multiple installations intentionally share one source checkout.
|
||||
func (i Installation) ControlDirectory() string {
|
||||
return filepath.Join(i.ProjectDirectory, ".thothctl", i.ProjectName())
|
||||
}
|
||||
|
||||
func (i Installation) CurrentImageOverridePath() string {
|
||||
return filepath.Join(i.ControlDirectory(), "current-image.yaml")
|
||||
}
|
||||
|
||||
func (i Installation) UpdateStatePath() string {
|
||||
return filepath.Join(i.ControlDirectory(), "update-state.json")
|
||||
}
|
||||
|
||||
// ProjectName is stable for one installation and avoids collisions between different checkouts.
|
||||
@@ -168,9 +194,13 @@ func (i Installation) SecretFiles() ([]string, error) {
|
||||
// callers. It is used only for operator-visible file locations, never for secret content.
|
||||
func (i Installation) EnvironmentValue(name string) (string, error) {
|
||||
contents, err := safeio.ReadCanonicalRegular(i.EnvFile, maxEnvironmentFileBytes)
|
||||
if err != nil { return "", errors.New("installation environment could not be read") }
|
||||
if err != nil {
|
||||
return "", errors.New("installation environment could not be read")
|
||||
}
|
||||
values, err := parseComposeDotenv(contents)
|
||||
if err != nil { return "", errors.New("installation environment could not be read") }
|
||||
if err != nil {
|
||||
return "", errors.New("installation environment could not be read")
|
||||
}
|
||||
return values[name], nil
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,51 @@ func TestLoadSelectsServerComposeFiles(t *testing.T) {
|
||||
assertStringsEqual(t, installation.ComposeFiles(), want)
|
||||
}
|
||||
|
||||
func TestComposeArgsAutomaticallyIncludeTheInstallationCurrentImageOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
installationPath, _, _, _ := writeInstallation(t, "local")
|
||||
seed, err := Load(installationPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
currentImage := seed.CurrentImageOverridePath()
|
||||
if err := os.MkdirAll(filepath.Dir(currentImage), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(currentImage, []byte("services:\n core:\n image: candidate\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
installation, err := Load(installationPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
args := installation.ComposeArgs("up", "--detach")
|
||||
want := []string{"-f", currentImage, "up", "--detach"}
|
||||
if !containsSequence(args, want) {
|
||||
t.Fatalf("ComposeArgs() = %#v, want durable override immediately before command", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallationControlPathsAreIsolatedForDescriptorsSharingOneCheckout(t *testing.T) {
|
||||
projectDirectory := t.TempDir()
|
||||
first := Installation{Path: filepath.Join(t.TempDir(), installationFileName), ProjectDirectory: projectDirectory}
|
||||
second := Installation{Path: filepath.Join(t.TempDir(), installationFileName), ProjectDirectory: projectDirectory}
|
||||
|
||||
if first.CurrentImageOverridePath() == second.CurrentImageOverridePath() {
|
||||
t.Fatalf("shared-checkout installations reused %q", first.CurrentImageOverridePath())
|
||||
}
|
||||
for _, installation := range []Installation{first, second} {
|
||||
if filepath.Dir(filepath.Dir(installation.CurrentImageOverridePath())) != filepath.Join(projectDirectory, ".thothctl") {
|
||||
t.Fatalf("current-image path %q is not installation-specific under .thothctl", installation.CurrentImageOverridePath())
|
||||
}
|
||||
if filepath.Dir(installation.UpdateStatePath()) != filepath.Dir(installation.CurrentImageOverridePath()) {
|
||||
t.Fatalf("state %q and selector %q do not share one installation control directory", installation.UpdateStatePath(), installation.CurrentImageOverridePath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRejectsRelativeInstallationPaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -100,3 +145,22 @@ func assertStringsEqual(t *testing.T, got, want []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsSequence(values, wanted []string) bool {
|
||||
for start := range values {
|
||||
if len(values)-start < len(wanted) {
|
||||
continue
|
||||
}
|
||||
matched := true
|
||||
for offset := range wanted {
|
||||
if values[start+offset] != wanted[offset] {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user