Skip to content

Commit 7ea4a6f

Browse files
authored
Cleaning all or specific playground sessions (#258)
```sh $ builder-playground clean # or specific session $ builder-playground clean major-hornet ``` The session list command is delivered from PR #257.
1 parent 9428632 commit 7ea4a6f

File tree

4 files changed

+83
-68
lines changed

4 files changed

+83
-68
lines changed

README.md

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ Quick start:
1616

1717
```bash
1818
# L1 environment with mev-boost relay
19-
builder-playground cook l1
19+
builder-playground start l1
2020

2121
# L2 OpStack with external builder support
22-
builder-playground cook opstack --external-builder http://localhost:4444
22+
builder-playground start opstack --external-builder http://localhost:4444
2323
```
2424

2525
## Getting started
2626

27-
Clone the repository and use the `cook` command to deploy a specific recipe:
27+
Clone the repository and use the `start` command to deploy a specific recipe:
2828

2929
```bash
30-
$ builder-playground cook <recipe>
30+
$ builder-playground start <recipe>
3131
```
3232

3333
Currently available recipes:
@@ -41,7 +41,7 @@ Deploys a complete L1 environment with:
4141
- An in-memory [mev-boost-relay](https://github.com/flashbots/mev-boost-relay).
4242

4343
```bash
44-
$ builder-playground cook l1 [flags]
44+
$ builder-playground start l1 [flags]
4545
```
4646

4747
Flags:
@@ -61,7 +61,7 @@ Deploys an L2 environment with:
6161
- A complete sequencer with op-node, op-geth and op-batcher
6262

6363
```bash
64-
$ builder-playground cook opstack [flags]
64+
$ builder-playground start opstack [flags]
6565
```
6666

6767
Flags:
@@ -74,15 +74,15 @@ Flags:
7474
Here's a complete example showing how to run the L1 recipe with the latest fork enabled and custom output directory:
7575

7676
```bash
77-
$ builder-playground cook l1 --latest-fork --output ~/my-builder-testnet --genesis-delay 15 --log-level debug
77+
$ builder-playground start l1 --latest-fork --output ~/my-builder-testnet --genesis-delay 15 --log-level debug
7878
```
7979

8080
### Generate transaction flow with contender
8181

8282
builder-playground can generate transaction flow to its nodes with [contender](https://github.com/flashbots/contender). Just pass the `--contender` flag to send spam transactions that fill each block:
8383

8484
```bash
85-
go run main.go cook l1 --contender
85+
go run main.go start l1 --contender
8686
```
8787

8888
The default contender flags are as follows:
@@ -95,7 +95,7 @@ To add or modify contender flags, use `--contender.arg`:
9595

9696
```bash
9797
# run the builtin erc20 scenario instead of the default "fill block" scenario, at 100 TPS
98-
go run main.go cook l1 --contender \
98+
go run main.go start l1 --contender \
9999
--contender.arg "--tps 100" \
100100
--contender.arg "erc20"
101101
```
@@ -137,18 +137,30 @@ $ builder-playground inspect <service> <port>
137137
Example:
138138

139139
```bash
140-
$ builder-playground cook opstack
140+
$ builder-playground start opstack
141141
$ builder-playground inspect op-geth authrpc
142142
```
143143

144144
This command starts a `tcpflow` container in the same network interface as the service and captures the traffic to the specified port.
145145

146146
## Clean
147147

148-
Removes a recipe running in the background
148+
Remove local playground sessions:
149149

150150
```bash
151-
$ builder-playground clean [--output ./output]
151+
$ builder-playground stop all
152+
```
153+
154+
You can also stop specific session:
155+
156+
```bash
157+
$ builder-playground list
158+
honest-opossum
159+
major-hornet
160+
sacred-giraffe
161+
$ builder-playground stop honest-opossum sacred-giraffe
162+
Cleaning session: honest-opossum
163+
Cleaning session: sacred-giraffe
152164
```
153165

154166
## Telemetry
@@ -167,8 +179,8 @@ By default, Prometheus scrapes the `/metrics` path, but services can override th
167179
Enable Prometheus for any recipe:
168180

169181
```bash
170-
$ builder-playground cook l1 --with-prometheus
171-
$ builder-playground cook opstack --with-prometheus
182+
$ builder-playground start l1 --with-prometheus
183+
$ builder-playground start opstack --with-prometheus
172184
```
173185

174186
## Internals

main.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ var rootCmd = &cobra.Command{
4747
},
4848
}
4949

50-
var cookCmd = &cobra.Command{
51-
Use: "cook",
52-
Short: "Cook a recipe",
50+
var startCmd = &cobra.Command{
51+
Use: "start",
52+
Short: "Start a recipe",
53+
Aliases: []string{"cook"},
5354
RunE: func(cmd *cobra.Command, args []string) error {
5455
recipeNames := []string{}
5556
for _, recipe := range recipes {
@@ -59,18 +60,27 @@ var cookCmd = &cobra.Command{
5960
},
6061
}
6162

62-
var cleanCmd = &cobra.Command{
63-
Use: "clean",
64-
Short: "Clean a recipe",
63+
var stopCmd = &cobra.Command{
64+
Use: "stop",
65+
Short: "Stop a playground session",
6566
RunE: func(cmd *cobra.Command, args []string) error {
66-
manifest, err := playground.ReadManifest(outputFlag)
67-
if err != nil {
68-
return err
67+
sessions := args
68+
if len(sessions) == 0 {
69+
return fmt.Errorf("please specify at least one session name or 'all' to stop all sessions")
6970
}
70-
if err := playground.StopContainersBySessionID(manifest.ID); err != nil {
71-
return err
71+
if len(sessions) == 1 && sessions[0] == "all" {
72+
var err error
73+
sessions, err = playground.GetLocalSessions()
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
for _, session := range sessions {
79+
if err := playground.StopSession(session); err != nil {
80+
return err
81+
}
82+
fmt.Println("Stopping session:", session)
7283
}
73-
fmt.Println("The recipe has been stopped and cleaned.")
7484
return nil
7585
},
7686
}
@@ -143,15 +153,15 @@ func main() {
143153
recipeCmd.Flags().StringVar(&contenderTarget, "contender.target", "", "override the node that contender spams -- accepts names like \"el\"")
144154
recipeCmd.Flags().BoolVar(&detached, "detached", false, "Detached mode: Run the recipes in the background")
145155

146-
cookCmd.AddCommand(recipeCmd)
156+
startCmd.AddCommand(recipeCmd)
147157
}
148158

149-
rootCmd.AddCommand(cookCmd)
159+
rootCmd.AddCommand(startCmd)
150160
rootCmd.AddCommand(inspectCmd)
151161
rootCmd.AddCommand(versionCmd)
152162

153-
rootCmd.AddCommand(cleanCmd)
154-
cleanCmd.Flags().StringVar(&outputFlag, "output", "", "Output folder for the artifacts")
163+
rootCmd.AddCommand(stopCmd)
164+
stopCmd.Flags().StringVar(&outputFlag, "output", "", "Output folder for the artifacts")
155165

156166
if err := rootCmd.Execute(); err != nil {
157167
fmt.Println(err)

playground/local_runner.go

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"log/slog"
89
"maps"
910
"net"
1011
"net/url"
1112
"os"
1213
"os/exec"
1314
"path/filepath"
1415
"runtime"
16+
"slices"
1517
"strings"
1618
"sync"
1719
"text/template"
@@ -228,11 +230,14 @@ func (d *LocalRunner) Stop() error {
228230
for _, handle := range d.handles {
229231
handle.Process.Kill()
230232
}
233+
return StopSession(d.manifest.ID)
234+
}
231235

236+
func StopSession(id string) error {
232237
// stop the docker-compose
233238
cmd := exec.CommandContext(
234239
context.Background(), "docker", "compose",
235-
"-p", d.manifest.ID,
240+
"-p", id,
236241
"down",
237242
"-v", // removes containers and volumes
238243
)
@@ -247,6 +252,28 @@ func (d *LocalRunner) Stop() error {
247252
return nil
248253
}
249254

255+
func GetLocalSessions() ([]string, error) {
256+
var sessions []string
257+
client, err := newDockerClient()
258+
if err != nil {
259+
return nil, err
260+
}
261+
containers, err := client.ContainerList(context.Background(), container.ListOptions{
262+
All: true,
263+
})
264+
if err != nil {
265+
return nil, err
266+
}
267+
for _, container := range containers {
268+
if container.Labels["playground"] == "true" {
269+
sessions = append(sessions, container.Labels["playground.session"])
270+
}
271+
}
272+
// Return sorted unique occurences
273+
slices.Sort(sessions)
274+
return slices.Compact(sessions), nil
275+
}
276+
250277
// reservePort finds the first available port from the startPort and reserves it
251278
// Note that we have to keep track of the port in 'reservedPorts' because
252279
// the port allocation happens before the services uses it and binds to it.
@@ -839,6 +866,7 @@ func (d *LocalRunner) ensureImage(ctx context.Context, imageName string) error {
839866
// Image not found locally, pull it
840867
d.config.Callback(imageName, TaskStatusPulling)
841868

869+
slog.Info("pulling image", "image", imageName)
842870
reader, err := d.client.ImagePull(ctx, imageName, image.PullOptions{})
843871
if err != nil {
844872
return fmt.Errorf("failed to pull image %s: %w", imageName, err)
@@ -925,40 +953,3 @@ func (d *LocalRunner) Run(ctx context.Context) error {
925953

926954
return g.Wait()
927955
}
928-
929-
// StopContainersBySessionID removes all Docker containers associated with a specific playground session ID.
930-
// This is a standalone utility function used by the clean command to stop containers without requiring
931-
// a LocalRunner instance or manifest reference.
932-
//
933-
// TODO: Refactor to reduce code duplication with LocalRunner.Stop()
934-
// Consider creating a shared dockerClient wrapper with helper methods for container management
935-
// that both LocalRunner and this function can use.
936-
func StopContainersBySessionID(id string) error {
937-
client, err := newDockerClient()
938-
if err != nil {
939-
return err
940-
}
941-
942-
containers, err := client.ContainerList(context.Background(), container.ListOptions{
943-
Filters: filters.NewArgs(filters.Arg("label", fmt.Sprintf("playground.session=%s", id))),
944-
})
945-
if err != nil {
946-
return fmt.Errorf("error getting container list: %w", err)
947-
}
948-
949-
g := new(errgroup.Group)
950-
for _, cont := range containers {
951-
g.Go(func() error {
952-
if err := client.ContainerRemove(context.Background(), cont.ID, container.RemoveOptions{
953-
RemoveVolumes: true,
954-
RemoveLinks: false,
955-
Force: true,
956-
}); err != nil {
957-
return fmt.Errorf("error removing container: %w", err)
958-
}
959-
return nil
960-
})
961-
}
962-
963-
return g.Wait()
964-
}

playground/releases.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"log"
9+
"log/slog"
910
"net/http"
1011
"os"
1112
"os/exec"
@@ -67,6 +68,7 @@ func DownloadRelease(outputFolder string, artifact *release) (string, error) {
6768
}
6869

6970
func downloadArtifact(url, expectedFile, outPath string) error {
71+
slog.Info("downloading artifact", "url", url, "expectedFile", expectedFile, "outPath", outPath)
7072
// Download the file
7173
resp, err := http.Get(url)
7274
if err != nil {

0 commit comments

Comments
 (0)