Skip to content

Commit c7c37c3

Browse files
authored
Merge pull request #1760 from ktock/monitor-commands
monitor: Move commands to a separated package
2 parents 43a07f3 + a43837d commit c7c37c3

File tree

10 files changed

+536
-209
lines changed

10 files changed

+536
-209
lines changed

monitor/commands/attach.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
8+
"github.com/docker/buildx/monitor/types"
9+
"github.com/pkg/errors"
10+
)
11+
12+
type AttachCmd struct {
13+
m types.Monitor
14+
15+
stdout io.WriteCloser
16+
}
17+
18+
func NewAttachCmd(m types.Monitor, stdout io.WriteCloser) types.Command {
19+
return &AttachCmd{m, stdout}
20+
}
21+
22+
func (cm *AttachCmd) Info() types.CommandInfo {
23+
return types.CommandInfo{HelpMessage: "attach to a buildx server or a process in the container"}
24+
}
25+
26+
func (cm *AttachCmd) Exec(ctx context.Context, args []string) error {
27+
if len(args) < 2 {
28+
return errors.Errorf("server name must be passed")
29+
}
30+
ref := args[1]
31+
var id string
32+
33+
isProcess, err := isProcessID(ctx, cm.m, ref)
34+
if err == nil && isProcess {
35+
cm.m.Attach(ctx, ref)
36+
id = ref
37+
}
38+
if id == "" {
39+
refs, err := cm.m.List(ctx)
40+
if err != nil {
41+
return errors.Errorf("failed to get the list of sessions: %v", err)
42+
}
43+
found := false
44+
for _, s := range refs {
45+
if s == ref {
46+
found = true
47+
break
48+
}
49+
}
50+
if !found {
51+
return errors.Errorf("unknown ID: %q", ref)
52+
}
53+
cm.m.Detach() // Finish existing attach
54+
cm.m.AttachSession(ref)
55+
}
56+
fmt.Fprintf(cm.stdout, "Attached to process %q. Press Ctrl-a-c to switch to the new container\n", id)
57+
return nil
58+
}
59+
60+
func isProcessID(ctx context.Context, c types.Monitor, ref string) (bool, error) {
61+
infos, err := c.ListProcesses(ctx, c.AttachedSessionID())
62+
if err != nil {
63+
return false, err
64+
}
65+
for _, p := range infos {
66+
if p.ProcessID == ref {
67+
return true, nil
68+
}
69+
}
70+
return false, nil
71+
}

monitor/commands/disconnect.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
6+
"github.com/docker/buildx/monitor/types"
7+
"github.com/pkg/errors"
8+
)
9+
10+
type DisconnectCmd struct {
11+
m types.Monitor
12+
}
13+
14+
func NewDisconnectCmd(m types.Monitor) types.Command {
15+
return &DisconnectCmd{m}
16+
}
17+
18+
func (cm *DisconnectCmd) Info() types.CommandInfo {
19+
return types.CommandInfo{HelpMessage: "disconnect a client from a buildx server. Specific session ID can be specified an arg"}
20+
}
21+
22+
func (cm *DisconnectCmd) Exec(ctx context.Context, args []string) error {
23+
target := cm.m.AttachedSessionID()
24+
if len(args) >= 2 {
25+
target = args[1]
26+
}
27+
isProcess, err := isProcessID(ctx, cm.m, target)
28+
if err == nil && isProcess {
29+
if err := cm.m.DisconnectProcess(ctx, cm.m.AttachedSessionID(), target); err != nil {
30+
return errors.Errorf("disconnecting from process failed %v", target)
31+
}
32+
return nil
33+
}
34+
if err := cm.m.DisconnectSession(ctx, target); err != nil {
35+
return errors.Errorf("disconnecting from session failed: %v", err)
36+
}
37+
return nil
38+
}

monitor/commands/exec.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
8+
controllerapi "github.com/docker/buildx/controller/pb"
9+
"github.com/docker/buildx/monitor/types"
10+
"github.com/pkg/errors"
11+
)
12+
13+
type ExecCmd struct {
14+
m types.Monitor
15+
16+
invokeConfig controllerapi.InvokeConfig
17+
stdout io.WriteCloser
18+
}
19+
20+
func NewExecCmd(m types.Monitor, invokeConfig controllerapi.InvokeConfig, stdout io.WriteCloser) types.Command {
21+
return &ExecCmd{m, invokeConfig, stdout}
22+
}
23+
24+
func (cm *ExecCmd) Info() types.CommandInfo {
25+
return types.CommandInfo{HelpMessage: "execute a process in the interactive container"}
26+
}
27+
28+
func (cm *ExecCmd) Exec(ctx context.Context, args []string) error {
29+
if len(args) < 2 {
30+
return errors.Errorf("command must be passed")
31+
}
32+
cfg := controllerapi.InvokeConfig{
33+
Entrypoint: []string{args[1]},
34+
Cmd: args[2:],
35+
// TODO: support other options as well via flags
36+
Env: cm.invokeConfig.Env,
37+
User: cm.invokeConfig.User,
38+
Cwd: cm.invokeConfig.Cwd,
39+
Tty: true,
40+
}
41+
pid := cm.m.Exec(ctx, cfg)
42+
fmt.Fprintf(cm.stdout, "Process %q started. Press Ctrl-a-c to switch to that process.\n", pid)
43+
return nil
44+
}

monitor/commands/kill.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
6+
"github.com/docker/buildx/monitor/types"
7+
"github.com/pkg/errors"
8+
)
9+
10+
type KillCmd struct {
11+
m types.Monitor
12+
}
13+
14+
func NewKillCmd(m types.Monitor) types.Command {
15+
return &KillCmd{m}
16+
}
17+
18+
func (cm *KillCmd) Info() types.CommandInfo {
19+
return types.CommandInfo{HelpMessage: "kill buildx server"}
20+
}
21+
22+
func (cm *KillCmd) Exec(ctx context.Context, args []string) error {
23+
if err := cm.m.Kill(ctx); err != nil {
24+
return errors.Errorf("failed to kill: %v", err)
25+
}
26+
return nil
27+
}

monitor/commands/list.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"sort"
8+
"text/tabwriter"
9+
10+
"github.com/docker/buildx/monitor/types"
11+
)
12+
13+
type ListCmd struct {
14+
m types.Monitor
15+
16+
stdout io.WriteCloser
17+
}
18+
19+
func NewListCmd(m types.Monitor, stdout io.WriteCloser) types.Command {
20+
return &ListCmd{m, stdout}
21+
}
22+
23+
func (cm *ListCmd) Info() types.CommandInfo {
24+
return types.CommandInfo{HelpMessage: "list buildx sessions"}
25+
}
26+
27+
func (cm *ListCmd) Exec(ctx context.Context, args []string) error {
28+
refs, err := cm.m.List(ctx)
29+
if err != nil {
30+
return err
31+
}
32+
sort.Strings(refs)
33+
tw := tabwriter.NewWriter(cm.stdout, 1, 8, 1, '\t', 0)
34+
fmt.Fprintln(tw, "ID\tCURRENT_SESSION")
35+
for _, k := range refs {
36+
fmt.Fprintf(tw, "%-20s\t%v\n", k, k == cm.m.AttachedSessionID())
37+
}
38+
tw.Flush()
39+
return nil
40+
}

monitor/commands/ps.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"text/tabwriter"
8+
9+
"github.com/docker/buildx/monitor/types"
10+
)
11+
12+
type PsCmd struct {
13+
m types.Monitor
14+
stdout io.WriteCloser
15+
}
16+
17+
func NewPsCmd(m types.Monitor, stdout io.WriteCloser) types.Command {
18+
return &PsCmd{m, stdout}
19+
}
20+
21+
func (cm *PsCmd) Info() types.CommandInfo {
22+
return types.CommandInfo{HelpMessage: `list processes invoked by "exec". Use "attach" to attach IO to that process`}
23+
}
24+
25+
func (cm *PsCmd) Exec(ctx context.Context, args []string) error {
26+
ref := cm.m.AttachedSessionID()
27+
plist, err := cm.m.ListProcesses(ctx, ref)
28+
if err != nil {
29+
return err
30+
}
31+
tw := tabwriter.NewWriter(cm.stdout, 1, 8, 1, '\t', 0)
32+
fmt.Fprintln(tw, "PID\tCURRENT_SESSION\tCOMMAND")
33+
for _, p := range plist {
34+
fmt.Fprintf(tw, "%-20s\t%v\t%v\n", p.ProcessID, p.ProcessID == cm.m.AttachedPID(), append(p.InvokeConfig.Entrypoint, p.InvokeConfig.Cmd...))
35+
}
36+
tw.Flush()
37+
return nil
38+
}

monitor/commands/reload.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
8+
controllererrors "github.com/docker/buildx/controller/errdefs"
9+
controllerapi "github.com/docker/buildx/controller/pb"
10+
"github.com/docker/buildx/monitor/types"
11+
"github.com/docker/buildx/util/progress"
12+
"github.com/pkg/errors"
13+
)
14+
15+
type ReloadCmd struct {
16+
m types.Monitor
17+
18+
stdout io.WriteCloser
19+
progress *progress.Printer
20+
21+
options *controllerapi.BuildOptions
22+
invokeConfig controllerapi.InvokeConfig
23+
}
24+
25+
func NewReloadCmd(m types.Monitor, stdout io.WriteCloser, progress *progress.Printer, options *controllerapi.BuildOptions, invokeConfig controllerapi.InvokeConfig) types.Command {
26+
return &ReloadCmd{m, stdout, progress, options, invokeConfig}
27+
}
28+
29+
func (cm *ReloadCmd) Info() types.CommandInfo {
30+
return types.CommandInfo{HelpMessage: "reloads the context and build it"}
31+
}
32+
33+
func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
34+
var bo *controllerapi.BuildOptions
35+
if ref := cm.m.AttachedSessionID(); ref != "" {
36+
// Rebuilding an existing session; Restore the build option used for building this session.
37+
res, err := cm.m.Inspect(ctx, ref)
38+
if err != nil {
39+
fmt.Printf("failed to inspect the current build session: %v\n", err)
40+
} else {
41+
bo = res.Options
42+
}
43+
} else {
44+
bo = cm.options
45+
}
46+
if bo == nil {
47+
return errors.Errorf("no build option is provided")
48+
}
49+
if ref := cm.m.AttachedSessionID(); ref != "" {
50+
if err := cm.m.Disconnect(ctx, ref); err != nil {
51+
fmt.Println("disconnect error", err)
52+
}
53+
}
54+
var resultUpdated bool
55+
cm.progress.Unpause()
56+
ref, _, err := cm.m.Build(ctx, *bo, nil, cm.progress) // TODO: support stdin, hold build ref
57+
cm.progress.Pause()
58+
if err != nil {
59+
var be *controllererrors.BuildError
60+
if errors.As(err, &be) {
61+
ref = be.Ref
62+
resultUpdated = true
63+
} else {
64+
fmt.Printf("failed to reload: %v\n", err)
65+
}
66+
} else {
67+
resultUpdated = true
68+
}
69+
cm.m.AttachSession(ref)
70+
if resultUpdated {
71+
// rollback the running container with the new result
72+
id := cm.m.Rollback(ctx, cm.invokeConfig)
73+
fmt.Fprintf(cm.stdout, "Interactive container was restarted with process %q. Press Ctrl-a-c to switch to the new container\n", id)
74+
}
75+
return nil
76+
}

monitor/commands/rollback.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
8+
controllerapi "github.com/docker/buildx/controller/pb"
9+
"github.com/docker/buildx/monitor/types"
10+
)
11+
12+
type RollbackCmd struct {
13+
m types.Monitor
14+
15+
invokeConfig controllerapi.InvokeConfig
16+
stdout io.WriteCloser
17+
}
18+
19+
func NewRollbackCmd(m types.Monitor, invokeConfig controllerapi.InvokeConfig, stdout io.WriteCloser) types.Command {
20+
return &RollbackCmd{m, invokeConfig, stdout}
21+
}
22+
23+
func (cm *RollbackCmd) Info() types.CommandInfo {
24+
return types.CommandInfo{HelpMessage: "re-runs the interactive container with initial rootfs contents"}
25+
}
26+
27+
func (cm *RollbackCmd) Exec(ctx context.Context, args []string) error {
28+
cfg := cm.invokeConfig
29+
if len(args) >= 2 {
30+
cmds := args[1:]
31+
if cmds[0] == "--init" {
32+
cfg.Initial = true
33+
cmds = cmds[1:]
34+
}
35+
if len(cmds) > 0 {
36+
cfg.Entrypoint = []string{cmds[0]}
37+
cfg.Cmd = cmds[1:]
38+
}
39+
}
40+
id := cm.m.Rollback(ctx, cfg)
41+
fmt.Fprintf(cm.stdout, "Interactive container was restarted with process %q. Press Ctrl-a-c to switch to the new container\n", id)
42+
return nil
43+
}

0 commit comments

Comments
 (0)