Skip to content
Merged
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
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ func runIt(recipe playground.Recipe) error {

components := recipe.Apply(exCtx)
svcManager := playground.NewManifest(sessionID, components)
svcManager.Bootnode = exCtx.Bootnode

// generate the dot graph
slog.Debug("Generating dot graph...")
Expand Down
50 changes: 29 additions & 21 deletions playground/local_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,38 +381,46 @@ func (d *LocalRunner) applyTemplate(s *Service) ([]string, map[string]string, er
return defaultPort
}

resolveAddr := func(targetSvc *Service, port *Port, protocol, user string) string {
// - Service runs on host:
// A: target is inside docker: access with localhost:hostPort
// B: target is on the host: access with localhost:hostPort
// - Service runs inside docker:
// C: target is inside docker: access it with DNS service:port
// D: target is on the host: access it with host.docker.internal:hostPort
if d.isHostService(s.Name) {
// A and B
return printAddr(protocol, "localhost", port.HostPort, user)
} else {
if d.isHostService(targetSvc.Name) {
// D
return printAddr(protocol, "host.docker.internal", port.HostPort, user)
}
// C
return printAddr(protocol, targetSvc.Name, port.Port, user)
}
}

funcs := template.FuncMap{
"Service": func(name, portLabel, protocol, user string) string {
// For {{Service "name" "portLabel"}}:
// - Service runs on host:
// A: target is inside docker: access with localhost:hostPort
// B: target is on the host: access with localhost:hostPort
// - Service runs inside docker:
// C: target is inside docker: access it with DNS service:port
// D: target is on the host: access it with host.docker.internal:hostPort

// find the service and the port that it resolves for that label
svc := d.manifest.MustGetService(name)
port := svc.MustGetPort(portLabel)

if d.isHostService(s.Name) {
// A and B
return printAddr(protocol, "localhost", port.HostPort, user)
} else {
if d.isHostService(svc.Name) {
// D
return printAddr(protocol, "host.docker.internal", port.HostPort, user)
}
// C
return printAddr(protocol, svc.Name, port.Port, user)
}
return resolveAddr(svc, port, protocol, user)
},
"Port": func(name string, defaultPort int) int {
return resolvePort(name, defaultPort, ProtocolTCP)
},
"PortUDP": func(name string, defaultPort int) int {
return resolvePort(name, defaultPort, ProtocolUDP)
},
"Bootnode": func() string {
if d.manifest.Bootnode == nil {
return ""
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This silently returns an empty string when Bootnode is nil (e.g., when using base: l1). This could lead to confusing behavior where commands have empty values where the enode URL was expected. Consider logging a warning or returning an error indicator so users know something is wrong.

svc := d.manifest.MustGetService(d.manifest.Bootnode.Service)
port := svc.MustGetPort("rpc")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded port name "rpc" assumes the bootnode service always has this port. If a custom YAML recipe uses a different port name, this will panic via MustGetPort. Consider making the port name part of BootnodeRef or documenting this requirement.

return resolveAddr(svc, port, "enode", d.manifest.Bootnode.ID)
},
}

runTemplate := func(arg string) (string, error) {
Expand Down
2 changes: 2 additions & 0 deletions playground/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type Manifest struct {
// list of Services
Services []*Service `json:"services"`

Bootnode *BootnodeRef `json:"bootnode,omitempty"`

// overrides is a map of service name to the path of the executable to run
// on the host machine instead of a container.
overrides map[string]string
Expand Down
11 changes: 11 additions & 0 deletions playground/recipe_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type YAMLRecipeConfig struct {
// Base is the name of the base recipe (l1, opstack, buildernet)
Base string `yaml:"base"`

// BaseArgs are arguments to pass to the base recipe's flags
BaseArgs []string `yaml:"base_args,omitempty"`

// Description is an optional description of the recipe
Description string `yaml:"description,omitempty"`

Expand Down Expand Up @@ -125,6 +128,14 @@ func ParseYAMLRecipe(filePath string, baseRecipes []Recipe) (*YAMLRecipe, error)
return nil, fmt.Errorf("unknown base recipe: %s", config.Base)
}

// Parse base_args into the base recipe's flags
if len(config.BaseArgs) > 0 {
flags := baseRecipe.Flags()
if err := flags.Parse(config.BaseArgs); err != nil {
return nil, fmt.Errorf("failed to parse base_args: %w", err)
}
}

return &YAMLRecipe{
baseRecipe: baseRecipe,
config: &config,
Expand Down