fix(core): keep workspace runtime available
This commit is contained in:
@@ -140,7 +140,8 @@ func Render(installation config.Installation) (map[string][]byte, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Generate publishes all adapters as one directory generation. A failed replacement restores the
|
||||
// Generate publishes all adapters as one directory generation. An unchanged generation stays in
|
||||
// place so active file bind mounts keep their source identity. A failed replacement restores the
|
||||
// previous directory, so callers never observe a successfully returned mixed generation.
|
||||
func Generate(installation config.Installation) error {
|
||||
artifacts, err := Render(installation)
|
||||
@@ -152,6 +153,17 @@ func Generate(installation config.Installation) error {
|
||||
if err := os.MkdirAll(parent, 0o755); err != nil {
|
||||
return fmt.Errorf("create model projection parent: %w", err)
|
||||
}
|
||||
info, statErr := os.Lstat(target)
|
||||
if statErr == nil {
|
||||
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return fmt.Errorf("current model projection path is not a regular directory")
|
||||
}
|
||||
if projectionMatches(target, artifacts) {
|
||||
return nil
|
||||
}
|
||||
} else if !os.IsNotExist(statErr) {
|
||||
return fmt.Errorf("inspect current model projection generation: %w", statErr)
|
||||
}
|
||||
candidate, err := os.MkdirTemp(parent, ".model-projections-candidate-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create model projection candidate: %w", err)
|
||||
@@ -161,19 +173,12 @@ func Generate(installation config.Installation) error {
|
||||
return err
|
||||
}
|
||||
|
||||
info, statErr := os.Lstat(target)
|
||||
if os.IsNotExist(statErr) {
|
||||
if err := renameProjectionDirectory(candidate, target); err != nil {
|
||||
return fmt.Errorf("publish model projection generation: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if statErr != nil {
|
||||
return fmt.Errorf("inspect current model projection generation: %w", statErr)
|
||||
}
|
||||
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return fmt.Errorf("current model projection path is not a regular directory")
|
||||
}
|
||||
previous, err := absentTemporaryPath(parent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reserve previous model projection generation: %w", err)
|
||||
@@ -191,6 +196,21 @@ func Generate(installation config.Installation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func projectionMatches(directory string, artifacts map[string][]byte) bool {
|
||||
for _, relative := range sortedArtifactPaths(artifacts) {
|
||||
path := filepath.Join(directory, filepath.FromSlash(relative))
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil || !info.Mode().IsRegular() {
|
||||
return false
|
||||
}
|
||||
actual, err := os.ReadFile(path)
|
||||
if err != nil || !bytes.Equal(actual, artifacts[relative]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeProjectionCandidate(directory string, artifacts map[string][]byte) error {
|
||||
if err := os.Chmod(directory, 0o755); err != nil {
|
||||
return fmt.Errorf("protect model projection candidate: %w", err)
|
||||
|
||||
@@ -56,6 +56,50 @@ func TestRenderProducesDeterministicCatalogPiAndComposeProjections(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDoesNotReplaceUnchangedProjection(t *testing.T) {
|
||||
installation := projectionFixture(t)
|
||||
if err := Generate(installation); err != nil {
|
||||
t.Fatalf("Generate() initial error = %v", err)
|
||||
}
|
||||
paths := []string{
|
||||
installation.GeneratedModelCatalogPath(),
|
||||
installation.GeneratedPiModelsPath(),
|
||||
installation.GeneratedPiSettingsPath(),
|
||||
installation.ModelProjectionComposePath(),
|
||||
}
|
||||
before := make(map[string]os.FileInfo, len(paths))
|
||||
for _, path := range paths {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before[path] = info
|
||||
}
|
||||
|
||||
originalRename := renameProjectionDirectory
|
||||
t.Cleanup(func() { renameProjectionDirectory = originalRename })
|
||||
renames := 0
|
||||
renameProjectionDirectory = func(oldPath, newPath string) error {
|
||||
renames++
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
if err := Generate(installation); err != nil {
|
||||
t.Fatalf("Generate() repeated error = %v", err)
|
||||
}
|
||||
if renames != 0 {
|
||||
t.Fatalf("Generate() replaced an unchanged generation with %d renames", renames)
|
||||
}
|
||||
for _, path := range paths {
|
||||
after, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !os.SameFile(before[path], after) {
|
||||
t.Fatalf("Generate() replaced unchanged artifact %q", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRestoresWholePreviousGenerationWhenPublishFails(t *testing.T) {
|
||||
installation := projectionFixture(t)
|
||||
if err := Generate(installation); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user