Skip to content
Closed
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
51 changes: 48 additions & 3 deletions playground/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ func (p *Component) AddService(ctx *ExContext, srv ComponentGen) {
p.Inner = append(p.Inner, srv.Apply(ctx))
}

// WalkServices invokes fn for every service in the component tree.
func (p *Component) WalkServices(fn func(*Service)) {
for _, svc := range p.Services {
fn(svc)
}
for _, inner := range p.Inner {
inner.WalkServices(fn)
}
}

// FindService finds a service by name in the component tree
func (p *Component) FindService(name string) *Service {
for _, svc := range p.Services {
Expand All @@ -102,16 +112,37 @@ func (p *Component) FindService(name string) *Service {
return nil
}

// RemoveService removes a service by name from the component tree
// RemoveService removes a service by name from the component tree.
// It also clears any health-check-sidecar labels that reference the removed
// service so manifest validation does not fail on a dangling sidecar pointer.
func (p *Component) RemoveService(name string) {
p.removeServiceRecursive(name)
p.clearSidecarLabel(name)
}

func (p *Component) removeServiceRecursive(name string) bool {
for i, svc := range p.Services {
if svc.Name == name {
p.Services = append(p.Services[:i], p.Services[i+1:]...)
return
return true
}
}
for _, inner := range p.Inner {
inner.RemoveService(name)
if inner.removeServiceRecursive(name) {
return true
}
}
return false
}

func (p *Component) clearSidecarLabel(sidecarName string) {
for _, svc := range p.Services {
if svc.Labels[healthCheckSidecarLabel] == sidecarName {
delete(svc.Labels, healthCheckSidecarLabel)
}
}
for _, inner := range p.Inner {
inner.clearSidecarLabel(sidecarName)
}
}
Comment on lines +115 to 147
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.

The new RemoveService semantics (always cleans up dangling sidecar labels) and the new WalkServices / ReplaceDependency helpers are not covered by manifest_test.go — only the original RemoveService cases are. A few small unit tests would lock in the contract being introduced here (in particular, that RemoveService("X") clears any health-check-sidecar=X label across the tree, including in nested components), so a future caller doesn't accidentally regress this back to the dangling-pointer state this PR is fixing.

Minor: clearSidecarLabel always walks the entire tree even when removeServiceRecursive returned false (i.e. nothing was removed). Harmless, but you could skip it in that case.


Expand Down Expand Up @@ -663,6 +694,20 @@ func (s *Service) DependsOnRunning(name string) *Service {
return s
}

// ReplaceDependency updates the condition of an existing depends-on edge to
// the named service. It is a no-op if no such edge exists. Useful when a
// downstream recipe knows that a target service can never reach the strict
// condition declared by an upstream component (e.g. a beacon that only goes
// healthy after its consumer connects).
func (s *Service) ReplaceDependency(name string, condition DependsOnCondition) *Service {
for _, dep := range s.DependsOn {
if dep.Name == name {
dep.Condition = condition
}
}
return s
}

func applyTemplate(templateStr string) (string, []Port, []NodeRef) {
// TODO: Can we remove the return argument string?

Expand Down
14 changes: 10 additions & 4 deletions playground/recipe_buildernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func (b *BuilderNetRecipe) Apply(ctx *ExContext) *Component {
// Apply beacon service overrides for buildernet.
// We need these for letting the builder connect to the beacon node.
// Basically, the beacon node can never be healthy until the builder
// connects.
// connects, so we also drop its healthmon and downgrade any consumer's
// dependency on beacon from healthy to running.
if beacon := component.FindService("beacon"); beacon != nil {
beacon.ReplaceArgs(map[string]string{
"--target-peers": "1",
Expand All @@ -54,10 +55,15 @@ func (b *BuilderNetRecipe) Apply(ctx *ExContext) *Component {
if mevBoostRelay := component.FindService("mev-boost-relay"); mevBoostRelay != nil {
mevBoostRelay.DependsOnNone()
}
// Remove beacon healthmon - doesn't work with --target-peers=1 which is required for builder VM
component.RemoveService("beacon_healthmon")

component.RunContenderIfEnabled(ctx)
// Beacon never reaches healthy in buildernet, so any consumer that asked
// for healthy must accept running instead.
component.WalkServices(func(svc *Service) {
svc.ReplaceDependency("beacon", DependsOnConditionRunning)
})
Comment on lines +59 to +63
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.

WalkServices visits every service in the tree, but FindService("beacon") above only finds the first match in DFS order. If a future recipe ends up with multiple beacon services in the tree (e.g. a combined L1+L2 setup), WalkServices will downgrade beacon-deps for all of them, while only the first beacon's args/healthmon get touched. Today there's exactly one beacon so this is fine; just worth noting that the two helpers have different scoping semantics in case this recipe is ever extended.


// Note: contender is already added by L1Recipe.Apply, so we do not call
// RunContenderIfEnabled here.

return component
}
Expand Down
Loading