mirror of
https://github.com/luxfi/lpm.git
synced 2026-10-30 16:20:57 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers, and the repository lookup is now a generic method on DiskRepository; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
114 lines
3.5 KiB
Go
114 lines
3.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/luxfi/lpm/git"
|
|
"github.com/luxfi/lpm/types"
|
|
)
|
|
|
|
var (
|
|
vmDir = "vms"
|
|
chainDir = "chains"
|
|
|
|
extension = "yaml"
|
|
|
|
// Wrapper keys used by on-disk plugin manifests. By convention every
|
|
// definition file is a single-key mapping (`vm:` for VMs, `subnet:` for
|
|
// chains) whose value is the actual definition. The keys match the
|
|
// wrappers in plugins-core/examples/{vm,subnet}.yaml and every shipped
|
|
// registry entry. The chain wrapper key remains `subnet` on disk for
|
|
// backwards compatibility with shipped manifests (was upstream Lux v0.x
|
|
// terminology); renaming it would break every registry entry already
|
|
// published.
|
|
vmWrapperKey = "vm"
|
|
chainWrapperKey = "subnet"
|
|
)
|
|
|
|
// Repository wraps a plugin repository's VMs and Chains
|
|
type Repository interface {
|
|
GetPath() string
|
|
GetVM(name string) (Definition[types.VM], error)
|
|
GetChain(name string) (Definition[types.Chain], error)
|
|
}
|
|
|
|
type DiskRepository struct {
|
|
Git git.Factory
|
|
Path string
|
|
}
|
|
|
|
func (d DiskRepository) GetVM(name string) (Definition[types.VM], error) {
|
|
return d.get[types.VM](vmDir, name, vmWrapperKey)
|
|
}
|
|
|
|
func (d DiskRepository) GetChain(name string) (Definition[types.Chain], error) {
|
|
return d.get[types.Chain](chainDir, name, chainWrapperKey)
|
|
}
|
|
|
|
func (d DiskRepository) GetPath() string {
|
|
return d.Path
|
|
}
|
|
|
|
func (d DiskRepository) get[T types.Definition](dir string, file string, wrapperKey string) (Definition[T], error) {
|
|
relativePathWithExtension := filepath.Join(dir, fmt.Sprintf("%s.%s", file, extension))
|
|
absolutePathWithExtension := filepath.Join(d.Path, relativePathWithExtension)
|
|
bytes, err := os.ReadFile(absolutePathWithExtension)
|
|
if err != nil {
|
|
return Definition[T]{}, err
|
|
}
|
|
|
|
definition, err := decodeWrapped[T](bytes, wrapperKey)
|
|
if err != nil {
|
|
return Definition[T]{}, fmt.Errorf("decoding %s: %w", relativePathWithExtension, err)
|
|
}
|
|
|
|
commit, err := d.Git.GetLastModified(d.Path, relativePathWithExtension)
|
|
if err != nil {
|
|
return Definition[T]{}, err
|
|
}
|
|
|
|
return Definition[T]{
|
|
Definition: definition,
|
|
Commit: commit,
|
|
}, nil
|
|
}
|
|
|
|
// decodeWrapped unmarshals a definition YAML that wraps its payload under a
|
|
// single top-level key (e.g. `vm:` or `subnet:`). Empty or missing wrappers
|
|
// are reported as errors so callers do not silently get a zero-valued T and
|
|
// crash later (see CI-PREMORTEM.md §12a).
|
|
func decodeWrapped[T types.Definition](data []byte, wrapperKey string) (T, error) {
|
|
var zero T
|
|
var root yaml.Node
|
|
if err := yaml.Unmarshal(data, &root); err != nil {
|
|
return zero, err
|
|
}
|
|
// A document node has exactly one content child: the top-level mapping.
|
|
if root.Kind != yaml.DocumentNode || len(root.Content) != 1 {
|
|
return zero, fmt.Errorf("expected a single YAML document, got kind=%d content=%d", root.Kind, len(root.Content))
|
|
}
|
|
mapping := root.Content[0]
|
|
if mapping.Kind != yaml.MappingNode {
|
|
return zero, fmt.Errorf("expected top-level mapping, got kind=%d", mapping.Kind)
|
|
}
|
|
// MappingNode.Content is a flat [key0, value0, key1, value1, ...] list.
|
|
for i := 0; i+1 < len(mapping.Content); i += 2 {
|
|
key := mapping.Content[i]
|
|
if key.Kind == yaml.ScalarNode && key.Value == wrapperKey {
|
|
var out T
|
|
if err := mapping.Content[i+1].Decode(&out); err != nil {
|
|
return zero, err
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|
|
return zero, fmt.Errorf("missing required top-level %q key", wrapperKey)
|
|
}
|