Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled
235 lines
6.7 KiB
Go
235 lines
6.7 KiB
Go
package pluginmanager
|
|
|
|
import (
|
|
"archive/zip"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestArtifactStoreValidateAndStore(t *testing.T) {
|
|
packagePath := writeTestMCGP(t, map[string][]byte{
|
|
"manifest.json": testManifestBytes(t, "test-plugin"),
|
|
"plugin.so": []byte("fake plugin bytes"),
|
|
})
|
|
store := NewArtifactStore(t.TempDir())
|
|
artifact, err := store.ValidateAndStore(ArtifactUpload{
|
|
SourcePath: packagePath,
|
|
FileName: "test-plugin.mcgp",
|
|
Actor: "admin",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ValidateAndStore() error = %v", err)
|
|
}
|
|
if artifact.PluginID != "test-plugin" || artifact.Status != ArtifactStatusLoadable {
|
|
t.Fatalf("artifact = %+v, want loadable test-plugin", artifact)
|
|
}
|
|
if artifact.SHA256 == "" || artifact.PackageSHA256 == "" {
|
|
t.Fatalf("artifact hashes not set: %+v", artifact)
|
|
}
|
|
if _, err := os.Stat(artifact.FilePath); err != nil {
|
|
t.Fatalf("stored runtime entry stat error = %v", err)
|
|
}
|
|
if !strings.HasSuffix(artifact.FilePath, filepath.Join("test-plugin", artifact.ID, "plugin.so")) {
|
|
t.Fatalf("artifact file path = %q", artifact.FilePath)
|
|
}
|
|
}
|
|
|
|
func TestArtifactStoreValidateAndStoreSource(t *testing.T) {
|
|
packagePath := writeTestMCGP(t, map[string][]byte{
|
|
"manifest.json": testSourceManifestBytes(t, "source-plugin"),
|
|
"go.mod": []byte("module example.com/source-plugin\n\ngo 1.24.0\n"),
|
|
"main.go": []byte("package main\n"),
|
|
"README.md": []byte("source fixture"),
|
|
})
|
|
store := NewArtifactStore(t.TempDir())
|
|
source, err := store.ValidateAndStoreSource(ArtifactUpload{
|
|
SourcePath: packagePath,
|
|
FileName: "source-plugin.mcgp",
|
|
Actor: "admin",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ValidateAndStoreSource() error = %v", err)
|
|
}
|
|
if source.ArtifactType != ArtifactTypeSource || source.Status != ArtifactStatusValidated {
|
|
t.Fatalf("source = %+v, want validated source", source)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(source.FilePath, "go.mod")); err != nil {
|
|
t.Fatalf("stored source go.mod stat error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestArtifactStoreRejectsUnsafePackage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
entries map[string][]byte
|
|
want string
|
|
}{
|
|
{
|
|
name: "zip slip",
|
|
entries: map[string][]byte{
|
|
"manifest.json": testManifestBytes(t, "test-plugin"),
|
|
"../plugin.so": []byte("fake"),
|
|
},
|
|
want: "unsafe zip entry",
|
|
},
|
|
{
|
|
name: "normalized escape",
|
|
entries: map[string][]byte{
|
|
"manifest.json": testManifestBytes(t, "test-plugin"),
|
|
"nested/../plugin.so": []byte("fake"),
|
|
},
|
|
want: "unsafe zip entry",
|
|
},
|
|
{
|
|
name: "missing manifest",
|
|
entries: map[string][]byte{
|
|
"plugin.so": []byte("fake"),
|
|
},
|
|
want: "manifest.json is required",
|
|
},
|
|
{
|
|
name: "missing runtime",
|
|
entries: map[string][]byte{
|
|
"manifest.json": testManifestBytes(t, "test-plugin"),
|
|
},
|
|
want: `runtime entry "plugin.so" is required`,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store := NewArtifactStore(t.TempDir())
|
|
_, err := store.ValidateAndStore(ArtifactUpload{
|
|
SourcePath: writeTestMCGP(t, tt.entries),
|
|
FileName: "bad.mcgp",
|
|
Actor: "admin",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
t.Fatalf("ValidateAndStore() error = %v, want containing %q", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArtifactStoreRejectsSourceShellScripts(t *testing.T) {
|
|
store := NewArtifactStore(t.TempDir())
|
|
_, err := store.ValidateAndStoreSource(ArtifactUpload{
|
|
SourcePath: writeTestMCGP(t, map[string][]byte{
|
|
"manifest.json": testSourceManifestBytes(t, "test-plugin"),
|
|
"go.mod": []byte("module example.com/test\n"),
|
|
"main.go": []byte("package main\n"),
|
|
"build.sh": []byte("go build"),
|
|
}),
|
|
FileName: "bad-source.mcgp",
|
|
Actor: "admin",
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "unsupported source package entry") {
|
|
t.Fatalf("ValidateAndStoreSource() error = %v, want unsupported source entry", err)
|
|
}
|
|
}
|
|
|
|
func testSourceManifestBytes(t *testing.T, pluginID string) []byte {
|
|
t.Helper()
|
|
manifest := Manifest{
|
|
SchemaVersion: SchemaVersion,
|
|
ID: pluginID,
|
|
Name: "Source Plugin",
|
|
Version: "0.1.0",
|
|
ArtifactType: ArtifactTypeSource,
|
|
Runtime: RuntimeManifest{
|
|
Type: RuntimeGoPlugin,
|
|
EntrySymbol: "Plugin",
|
|
},
|
|
Build: BuildManifest{
|
|
Type: BuildTypeGo,
|
|
Entry: ".",
|
|
GoVersion: runtime.Version(),
|
|
Tags: []string{},
|
|
VendorRequired: false,
|
|
Output: RuntimeEntry,
|
|
},
|
|
APIVersion: APIVersion,
|
|
GoVersion: runtime.Version(),
|
|
GOOS: runtime.GOOS,
|
|
GOARCH: runtime.GOARCH,
|
|
ExtensionPoints: []ExtensionPoint{{
|
|
Type: "hook",
|
|
Key: ExtensionUpstreamConnect,
|
|
}},
|
|
Capabilities: json.RawMessage(`{"extension_points":["upstream.connect/v1"]}`),
|
|
}
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatalf("Marshal source manifest error = %v", err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func testManifestBytes(t *testing.T, pluginID string) []byte {
|
|
return testManifestBytesWithCapabilities(t, pluginID, json.RawMessage(`{"extension_points":["upstream.connect/v1"]}`))
|
|
}
|
|
|
|
func testManifestBytesWithCapabilities(t *testing.T, pluginID string, capabilities json.RawMessage) []byte {
|
|
t.Helper()
|
|
if len(capabilities) == 0 {
|
|
capabilities = json.RawMessage(`{"extension_points":["upstream.connect/v1"]}`)
|
|
}
|
|
manifest := Manifest{
|
|
SchemaVersion: SchemaVersion,
|
|
ID: pluginID,
|
|
Name: "Test Plugin",
|
|
Version: "0.1.0",
|
|
ArtifactType: ArtifactTypeBinary,
|
|
Runtime: RuntimeManifest{
|
|
Type: RuntimeGoPlugin,
|
|
Entry: RuntimeEntry,
|
|
EntrySymbol: "Plugin",
|
|
},
|
|
APIVersion: APIVersion,
|
|
GoVersion: runtime.Version(),
|
|
GOOS: runtime.GOOS,
|
|
GOARCH: runtime.GOARCH,
|
|
ExtensionPoints: []ExtensionPoint{{
|
|
Type: "hook",
|
|
Key: ExtensionUpstreamConnect,
|
|
}},
|
|
Capabilities: capabilities,
|
|
ConfigSchema: json.RawMessage(`{"type":"object"}`),
|
|
RuntimeLimits: RuntimeLimits{HandlerTimeoutMS: 3000},
|
|
}
|
|
data, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatalf("Marshal manifest error = %v", err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func writeTestMCGP(t *testing.T, entries map[string][]byte) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "plugin.mcgp")
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("Create package error = %v", err)
|
|
}
|
|
zipWriter := zip.NewWriter(file)
|
|
for name, data := range entries {
|
|
writer, err := zipWriter.Create(name)
|
|
if err != nil {
|
|
t.Fatalf("Create zip entry error = %v", err)
|
|
}
|
|
if _, err := writer.Write(data); err != nil {
|
|
t.Fatalf("Write zip entry error = %v", err)
|
|
}
|
|
}
|
|
if err := zipWriter.Close(); err != nil {
|
|
t.Fatalf("Close zip error = %v", err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatalf("Close package error = %v", err)
|
|
}
|
|
return path
|
|
}
|