Binary catalog install/update paths (installBinary/replaceBinary) saved raw plugin binaries into plugin_archives.archive_bytes, but startup preload opens that column with zip.NewReader expecting a package containing manifest.json and the plugin binary. Affected installs failed preload after restart with "zip: not a valid zip file". - Package manifest.json + binary into a validated zip before SaveArchive on both binary install paths. - Self-repair legacy rows in ArchiveCache.Ensure: when stored bytes are not a zip but hash-match both the stored checksum and the stored manifest's checksum, repackage and persist the repaired archive. A persist failure is logged, not fatal — the in-memory archive is already valid and recovery retries on the next preload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
151 lines
4.4 KiB
Go
151 lines
4.4 KiB
Go
package plugins
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
)
|
|
|
|
func TestArchiveCacheEnsureRecoversLegacyBinaryArchive(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
binaryData := []byte("#!/bin/sh\nexit 0\n")
|
|
checksum := sha256.Sum256(binaryData)
|
|
|
|
manifest := testPluginManifest(t, "silo.metadb", "0.0.19")
|
|
manifest.Checksum = hex.EncodeToString(checksum[:])
|
|
manifestBytes, err := protojson.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatalf("protojson.Marshal() returned error: %v", err)
|
|
}
|
|
|
|
store := &legacyArchiveStore{
|
|
archive: &InstallationArchive{
|
|
InstallationID: 42,
|
|
ManifestJSON: manifestBytes,
|
|
Checksum: manifest.GetChecksum(),
|
|
Bytes: binaryData,
|
|
},
|
|
}
|
|
|
|
installDir := filepath.Join(t.TempDir(), "plugins", "silo.metadb", "0.0.19")
|
|
installation := &Installation{
|
|
ID: 42,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: filepath.Join(installDir, "plugin"),
|
|
}
|
|
|
|
got, err := NewArchiveCache(store).Ensure(ctx, installation)
|
|
if err != nil {
|
|
t.Fatalf("Ensure() returned error: %v", err)
|
|
}
|
|
if got.GetPluginId() != manifest.GetPluginId() {
|
|
t.Fatalf("manifest plugin_id = %q, want %q", got.GetPluginId(), manifest.GetPluginId())
|
|
}
|
|
|
|
if store.savedInstallationID != installation.ID {
|
|
t.Fatalf("repaired archive installation id = %d, want %d", store.savedInstallationID, installation.ID)
|
|
}
|
|
if !bytes.Equal(store.savedManifestJSON, manifestBytes) {
|
|
t.Fatal("repaired archive manifest does not match stored manifest")
|
|
}
|
|
if _, _, savedManifest, err := openPluginArchive(store.savedArchiveBytes); err != nil {
|
|
t.Fatalf("repaired archive is not a valid plugin archive: %v", err)
|
|
} else if savedManifest.GetChecksum() != manifest.GetChecksum() {
|
|
t.Fatalf("repaired archive checksum = %q, want %q", savedManifest.GetChecksum(), manifest.GetChecksum())
|
|
}
|
|
|
|
installedBinary, err := os.ReadFile(installation.InstallPath)
|
|
if err != nil {
|
|
t.Fatalf("read rehydrated plugin binary: %v", err)
|
|
}
|
|
if !bytes.Equal(installedBinary, binaryData) {
|
|
t.Fatal("rehydrated plugin binary does not match legacy binary bytes")
|
|
}
|
|
if _, err := os.Stat(InstalledManifestPath(installation.InstallPath)); err != nil {
|
|
t.Fatalf("expected rehydrated manifest file: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestArchiveCacheEnsureRecoveryToleratesPersistFailure(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
binaryData := []byte("#!/bin/sh\nexit 0\n")
|
|
checksum := sha256.Sum256(binaryData)
|
|
|
|
manifest := testPluginManifest(t, "silo.metadb", "0.0.19")
|
|
manifest.Checksum = hex.EncodeToString(checksum[:])
|
|
manifestBytes, err := protojson.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatalf("protojson.Marshal() returned error: %v", err)
|
|
}
|
|
|
|
store := &legacyArchiveStore{
|
|
archive: &InstallationArchive{
|
|
InstallationID: 42,
|
|
ManifestJSON: manifestBytes,
|
|
Checksum: manifest.GetChecksum(),
|
|
Bytes: binaryData,
|
|
},
|
|
saveErr: errors.New("db unavailable"),
|
|
}
|
|
|
|
installDir := filepath.Join(t.TempDir(), "plugins", "silo.metadb", "0.0.19")
|
|
installation := &Installation{
|
|
ID: 42,
|
|
PluginID: manifest.GetPluginId(),
|
|
Version: manifest.GetVersion(),
|
|
InstallPath: filepath.Join(installDir, "plugin"),
|
|
}
|
|
|
|
if _, err := NewArchiveCache(store).Ensure(ctx, installation); err != nil {
|
|
t.Fatalf("Ensure() returned error despite in-memory recovery: %v", err)
|
|
}
|
|
|
|
installedBinary, err := os.ReadFile(installation.InstallPath)
|
|
if err != nil {
|
|
t.Fatalf("read rehydrated plugin binary: %v", err)
|
|
}
|
|
if !bytes.Equal(installedBinary, binaryData) {
|
|
t.Fatal("rehydrated plugin binary does not match legacy binary bytes")
|
|
}
|
|
}
|
|
|
|
type legacyArchiveStore struct {
|
|
archive *InstallationArchive
|
|
saveErr error
|
|
savedInstallationID int
|
|
savedManifestJSON []byte
|
|
savedChecksum string
|
|
savedArchiveBytes []byte
|
|
}
|
|
|
|
func (s *legacyArchiveStore) GetArchive(context.Context, int) (*InstallationArchive, error) {
|
|
return s.archive, nil
|
|
}
|
|
|
|
func (s *legacyArchiveStore) SaveArchive(
|
|
_ context.Context,
|
|
installationID int,
|
|
manifestJSON []byte,
|
|
checksum string,
|
|
archiveBytes []byte,
|
|
) error {
|
|
if s.saveErr != nil {
|
|
return s.saveErr
|
|
}
|
|
s.savedInstallationID = installationID
|
|
s.savedManifestJSON = append([]byte(nil), manifestJSON...)
|
|
s.savedChecksum = checksum
|
|
s.savedArchiveBytes = append([]byte(nil), archiveBytes...)
|
|
return nil
|
|
}
|