Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions cmd/devnet-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ var globalLoader *plugin.Loader
func main() {
// Enable color output
color.NoColor = false
logger := output.NewLogger()

// Load plugins from ~/.devnet-builder/plugins/
globalLoader = plugin.NewLoader()

// Load all discovered plugins with detailed error information
loadResult, loadErr := globalLoader.LoadAllWithErrors()
if loadErr != nil {
output.DefaultLogger.Debug("Plugin loading error: %v", loadErr)
logger.Debug("Plugin loading error: %v", loadErr)
}

// Log which plugins were successfully loaded
Expand All @@ -38,11 +39,11 @@ func main() {
for _, p := range loadResult.Loaded {
loadedNames = append(loadedNames, p.Name())
}
output.DefaultLogger.Debug("Successfully loaded %d plugins: %v", len(loadResult.Loaded), loadedNames)
logger.Debug("Successfully loaded %d plugins: %v", len(loadResult.Loaded), loadedNames)

// Log detailed errors for plugins that failed to load
for _, loadErr := range loadResult.Errors {
output.DefaultLogger.Warn("Failed to load plugin %q: %v", loadErr.PluginName, loadErr.Err)
logger.Warn("Failed to load plugin %q: %v", loadErr.PluginName, loadErr.Err)
}
}

Expand All @@ -57,7 +58,7 @@ func main() {
// Create an adapter to convert pkg/network.Module to internal/network.NetworkModule
adapter := newPluginAdapter(p.Module())
if err := network.MustRegister(adapter, false); err != nil {
output.DefaultLogger.Warn("Failed to register plugin %q: %v", p.Name(), err)
logger.Warn("Failed to register plugin %q: %v", p.Name(), err)
}
}

Expand All @@ -67,7 +68,7 @@ func main() {
if envHome := os.Getenv("DEVNET_HOME"); envHome != "" {
homeDir = envHome
}
if err := checkAndMigrateVersion(homeDir); err != nil {
if err := checkAndMigrateVersion(homeDir, logger); err != nil {
fmt.Fprintf(os.Stderr, "Version migration failed: %v\n", err)
globalLoader.Close()
os.Exit(1)
Expand Down Expand Up @@ -103,10 +104,7 @@ func GetPluginLoader() *plugin.Loader {
}

// checkAndMigrateVersion checks the current version and applies migrations if needed.
func checkAndMigrateVersion(homeDir string) error {
// Use default logger for migration
logger := output.DefaultLogger

func checkAndMigrateVersion(homeDir string, logger *output.Logger) error {
// Create infrastructure factory
factory := di.NewInfrastructureFactory(homeDir, logger)

Expand Down
31 changes: 5 additions & 26 deletions internal/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func NewDevnetService(homeDir string, logger *output.Logger, opts ...di.Option)
func NewDevnetServiceWithConfig(cfg ServiceConfig) (*DevnetService, error) {
logger := cfg.Logger
if logger == nil {
logger = output.DefaultLogger
logger = output.NewLogger()
}

// Create infrastructure factory
Expand Down Expand Up @@ -818,38 +818,17 @@ func (e *NodeNotFoundError) Error() string {
}

// GetService returns a DevnetService instance using global homeDir.
// It automatically loads the network module if devnet exists with stored blockchain network.
func GetService(homeDir string) (*DevnetService, error) {
// Try to load network module from existing devnet metadata
var networkModule network.NetworkModule

// Check if devnet exists and load its metadata to get blockchain network
metadataPath := paths.DevnetMetadataPath(homeDir)
if data, err := os.ReadFile(metadataPath); err == nil {
var meta struct {
BlockchainNetwork string `json:"blockchain_network"`
}
if json.Unmarshal(data, &meta) == nil && meta.BlockchainNetwork != "" {
if module, err := network.Get(meta.BlockchainNetwork); err == nil {
networkModule = module
}
}
}

if networkModule != nil {
return GetServiceWithConfig(ServiceConfig{
HomeDir: homeDir,
NetworkModule: networkModule,
})
}
return NewDevnetService(homeDir, output.DefaultLogger)
return GetServiceWithConfig(ServiceConfig{
HomeDir: homeDir,
})
}

// GetServiceWithConfig returns a DevnetService with full configuration.
// Use this when you need to specify network module, docker mode, etc.
func GetServiceWithConfig(cfg ServiceConfig) (*DevnetService, error) {
if cfg.Logger == nil {
cfg.Logger = output.DefaultLogger
cfg.Logger = output.NewLogger()
}
return NewDevnetServiceWithConfig(cfg)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/application/version/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Service struct {
// NewService creates a new migration service.
func NewService(repository ports.VersionRepository, logger *output.Logger) *Service {
if logger == nil {
logger = output.DefaultLogger
logger = output.NewLogger()
}
return &Service{
repository: repository,
Expand Down
13 changes: 9 additions & 4 deletions internal/daemon/server/network_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ type NetworkService struct {
v1.UnimplementedNetworkServiceServer
githubFactory GitHubClientFactory
logger *slog.Logger
registry network.Registry
}

// NewNetworkService creates a new NetworkService.
func NewNetworkService(githubFactory GitHubClientFactory) *NetworkService {
func NewNetworkService(githubFactory GitHubClientFactory, registry network.Registry) *NetworkService {
if registry == nil {
registry = network.GlobalRegistry()
}
return &NetworkService{
githubFactory: githubFactory,
logger: slog.Default(),
registry: registry,
}
}

Expand All @@ -33,7 +38,7 @@ func (s *NetworkService) SetLogger(logger *slog.Logger) {

// ListNetworks returns all registered network modules.
func (s *NetworkService) ListNetworks(ctx context.Context, req *v1.ListNetworksRequest) (*v1.ListNetworksResponse, error) {
modules := network.ListModules()
modules := s.registry.ListModules()

summaries := make([]*v1.NetworkSummary, 0, len(modules))
for _, module := range modules {
Expand All @@ -51,7 +56,7 @@ func (s *NetworkService) GetNetworkInfo(ctx context.Context, req *v1.GetNetworkI
return nil, status.Error(codes.InvalidArgument, "name is required")
}

module, err := network.Get(req.Name)
module, err := s.registry.Get(req.Name)
if err != nil {
return nil, status.Errorf(codes.NotFound, "network %q not found: %v", req.Name, err)
}
Expand Down Expand Up @@ -129,7 +134,7 @@ func (s *NetworkService) ListBinaryVersions(ctx context.Context, req *v1.ListBin
}

// Get network module
module, err := network.Get(req.NetworkName)
module, err := s.registry.Get(req.NetworkName)
if err != nil {
return nil, status.Errorf(codes.NotFound, "network %q not found: %v", req.NetworkName, err)
}
Expand Down
16 changes: 8 additions & 8 deletions internal/daemon/server/network_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (f *mockGitHubClientFactory) CreateClient(networkName, owner, repo string)

func TestNetworkService_ListBinaryVersions_MissingNetworkName(t *testing.T) {
factory := &mockGitHubClientFactory{}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

_, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
NetworkName: "",
Expand All @@ -65,7 +65,7 @@ func TestNetworkService_ListBinaryVersions_MissingNetworkName(t *testing.T) {

func TestNetworkService_ListBinaryVersions_NetworkNotFound(t *testing.T) {
factory := &mockGitHubClientFactory{}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

_, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
NetworkName: "nonexistent-network",
Expand All @@ -78,7 +78,7 @@ func TestNetworkService_ListBinaryVersions_NetworkNotFound(t *testing.T) {

func TestNetworkService_ListBinaryVersions_NilFactory(t *testing.T) {
// Test that nil factory is handled gracefully
svc := NewNetworkService(nil)
svc := NewNetworkService(nil, network.GlobalRegistry())

// This will fail at network lookup stage for non-existent network
// but if we had a registered network with GitHub source, it should catch nil factory
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestNetworkService_ListBinaryVersions_Success(t *testing.T) {
}

factory := &mockGitHubClientFactory{client: mockClient}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

// Test without prereleases
resp, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
Expand Down Expand Up @@ -182,7 +182,7 @@ func TestNetworkService_ListBinaryVersions_IncludePrerelease(t *testing.T) {
}

factory := &mockGitHubClientFactory{client: mockClient}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

// Test with prereleases
resp, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
Expand All @@ -209,7 +209,7 @@ func TestNetworkService_ListBinaryVersions_FetchError(t *testing.T) {
}

factory := &mockGitHubClientFactory{client: mockClient}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

_, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
NetworkName: testModule.Name(),
Expand All @@ -231,7 +231,7 @@ func TestNetworkService_ListBinaryVersions_EmptyReleases(t *testing.T) {
}

factory := &mockGitHubClientFactory{client: mockClient}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

resp, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
NetworkName: testModule.Name(),
Expand Down Expand Up @@ -264,7 +264,7 @@ func TestNetworkService_ListBinaryVersions_OnlyPrereleases(t *testing.T) {
}

factory := &mockGitHubClientFactory{client: mockClient}
svc := NewNetworkService(factory)
svc := NewNetworkService(factory, network.GlobalRegistry())

// Without prereleases - should return empty
resp, err := svc.ListBinaryVersions(context.Background(), &v1.ListBinaryVersionsRequest{
Expand Down
32 changes: 21 additions & 11 deletions internal/daemon/server/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (

// PluginManager handles plugin discovery, loading, and registration.
type PluginManager struct {
loader *plugin.Loader
logger *slog.Logger
loader *plugin.Loader
logger *slog.Logger
registry network.Registry
}

// PluginManagerConfig configures the PluginManager.
Expand All @@ -25,6 +26,10 @@ type PluginManagerConfig struct {

// Logger for logging plugin operations.
Logger *slog.Logger

// NetworkRegistry receives discovered plugin modules.
// If nil, the package-global registry is used for backward compatibility.
NetworkRegistry network.Registry
}

// NewPluginManager creates a new PluginManager.
Expand All @@ -33,6 +38,10 @@ func NewPluginManager(config PluginManagerConfig) *PluginManager {
if logger == nil {
logger = slog.Default()
}
registry := config.NetworkRegistry
if registry == nil {
registry = network.GlobalRegistry()
}

// Create hashicorp logger adapter
hcLogger := hclog.New(&hclog.LoggerOptions{
Expand All @@ -49,8 +58,9 @@ func NewPluginManager(config PluginManagerConfig) *PluginManager {
}

return &PluginManager{
loader: plugin.NewLoader(opts...),
logger: logger,
loader: plugin.NewLoader(opts...),
logger: logger,
registry: registry,
}
}

Expand All @@ -69,7 +79,7 @@ type PluginLoadError struct {
Error error
}

// LoadAndRegister discovers plugins, loads them, and registers them with the global network registry.
// LoadAndRegister discovers plugins, loads them, and registers them with the injected network registry.
// Returns the load results including any errors encountered.
func (pm *PluginManager) LoadAndRegister() (*LoadResult, error) {
result := &LoadResult{
Expand All @@ -91,7 +101,7 @@ func (pm *PluginManager) LoadAndRegister() (*LoadResult, error) {

pm.logger.Info("discovered plugins", "count", len(discovered), "plugins", discovered)

// Load each plugin and register with global registry
// Load each plugin and register with injected registry
for _, name := range discovered {
if err := pm.loadAndRegisterPlugin(name); err != nil {
pm.logger.Warn("failed to load plugin",
Expand All @@ -111,7 +121,7 @@ func (pm *PluginManager) LoadAndRegister() (*LoadResult, error) {
return result, nil
}

// loadAndRegisterPlugin loads a single plugin and registers it with the global registry.
// loadAndRegisterPlugin loads a single plugin and registers it with the injected registry.
func (pm *PluginManager) loadAndRegisterPlugin(name string) error {
// Load the plugin
client, err := pm.loader.Load(name)
Expand All @@ -125,8 +135,8 @@ func (pm *PluginManager) loadAndRegisterPlugin(name string) error {
// Wrap with adapter to convert pkg/network.Module to internal/network.NetworkModule
adapter := network.NewPluginAdapter(module)

// Register with global registry
if err := network.MustRegister(adapter, false); err != nil {
// Register with injected registry
if err := pm.registry.MustRegister(adapter, false); err != nil {
return fmt.Errorf("failed to register plugin module: %w", err)
}

Expand Down Expand Up @@ -157,11 +167,11 @@ func (pm *PluginManager) Reload(name string) error {
// Wrap with adapter
adapter := network.NewPluginAdapter(module)

// Re-register with global registry
// Re-register with injected registry
// Note: The registry doesn't support re-registration, so this may fail
// if the module was previously registered. In production, you'd want
// a mechanism to update existing registrations.
if err := network.MustRegister(adapter, false); err != nil {
if err := pm.registry.MustRegister(adapter, false); err != nil {
pm.logger.Warn("plugin reloaded but registry update failed (already registered)",
"plugin", name,
"error", err)
Expand Down
16 changes: 11 additions & 5 deletions internal/daemon/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/altuslabsxyz/devnet-builder/internal/daemon/subnet"
"github.com/altuslabsxyz/devnet-builder/internal/daemon/types"
"github.com/altuslabsxyz/devnet-builder/internal/daemon/upgrader"
"github.com/altuslabsxyz/devnet-builder/internal/infrastructure/network"
"google.golang.org/grpc"
)

Expand Down Expand Up @@ -93,6 +94,7 @@ type Server struct {
manager *controller.Manager
healthCtrl *controller.HealthController
pluginManager *PluginManager
networkRegistry network.Registry
subnetAllocator *subnet.Allocator
nodeRuntime runtime.NodeRuntime // Node runtime for process management
grpcServer *grpc.Server
Expand Down Expand Up @@ -136,12 +138,15 @@ func New(config *Config) (*Server, error) {
multiWriter := io.MultiWriter(os.Stdout, logFile)
logger := slog.New(slog.NewTextHandler(multiWriter, &slog.HandlerOptions{Level: level}))

networkRegistry := network.NewRegistry()

// Load network plugins from plugin directories
// Plugins are discovered from ~/.devnet-builder/plugins/ and registered
// with the global network registry so they can be queried via NetworkService
// with the injected network registry so they can be queried via NetworkService
pluginMgr := NewPluginManager(PluginManagerConfig{
PluginDirs: []string{filepath.Join(config.DataDir, "plugins")},
Logger: logger,
PluginDirs: []string{filepath.Join(config.DataDir, "plugins")},
Logger: logger,
NetworkRegistry: networkRegistry,
})

result, err := pluginMgr.LoadAndRegister()
Expand Down Expand Up @@ -185,7 +190,7 @@ func New(config *Config) (*Server, error) {
mgr.SetLogger(logger)

// Create orchestrator factory for full provisioning flow (build, fork, init)
orchFactory := NewOrchestratorFactory(config.DataDir, logger)
orchFactory := NewOrchestratorFactory(config.DataDir, logger, networkRegistry)

// Create devnet provisioner with orchestrator factory and subnet allocator
// The factory enables full provisioning (build, fork, init) before creating Node resources
Expand Down Expand Up @@ -324,7 +329,7 @@ func New(config *Config) (*Server, error) {

// Create network service first (needed by ante handler)
githubFactory := NewDefaultGitHubClientFactory(config.DataDir, logger)
networkSvc := NewNetworkService(githubFactory)
networkSvc := NewNetworkService(githubFactory, networkRegistry)
networkSvc.SetLogger(logger)

// Create ante handler for request validation
Expand Down Expand Up @@ -364,6 +369,7 @@ func New(config *Config) (*Server, error) {
manager: mgr,
healthCtrl: healthCtrl,
pluginManager: pluginMgr,
networkRegistry: networkRegistry,
subnetAllocator: subnetAlloc,
nodeRuntime: nodeRuntime,
grpcServer: grpcServer,
Expand Down
Loading
Loading