Merge pull request #10210 from glours/dry-run-kill-support

Dry run kill support
This commit is contained in:
Guillaume Lours 2023-01-30 10:35:33 +01:00 committed by GitHub
commit a93f09efac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 38 deletions

View File

@ -338,7 +338,12 @@ func RootCommand(streams command.Cli, backend api.Service) *cobra.Command { //no
if parallel > 0 { if parallel > 0 {
backend.MaxConcurrency(parallel) backend.MaxConcurrency(parallel)
} }
return backend.DryRunMode(dryRun) ctx, err := backend.DryRunMode(cmd.Context(), dryRun)
if err != nil {
return err
}
cmd.SetContext(ctx)
return nil
}, },
} }

View File

@ -78,7 +78,7 @@ type Service interface {
// MaxConcurrency defines upper limit for concurrent operations against engine API // MaxConcurrency defines upper limit for concurrent operations against engine API
MaxConcurrency(parallel int) MaxConcurrency(parallel int)
// DryRunMode defines if dry run applies to the command // DryRunMode defines if dry run applies to the command
DryRunMode(dryRun bool) error DryRunMode(ctx context.Context, dryRun bool) (context.Context, error)
// Watch services' development context and sync/notify/rebuild/restart on changes // Watch services' development context and sync/notify/rebuild/restart on changes
Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error Watch(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
} }

View File

@ -37,6 +37,8 @@ import (
var _ client.APIClient = &DryRunClient{} var _ client.APIClient = &DryRunClient{}
type DryRunKey struct{}
// DryRunClient implements APIClient by delegating to implementation functions. This allows lazy init and per-method overrides // DryRunClient implements APIClient by delegating to implementation functions. This allows lazy init and per-method overrides
type DryRunClient struct { type DryRunClient struct {
apiClient client.APIClient apiClient client.APIClient
@ -61,7 +63,7 @@ func (d *DryRunClient) ContainerCreate(ctx context.Context, config *containerTyp
} }
func (d *DryRunClient) ContainerKill(ctx context.Context, container, signal string) error { func (d *DryRunClient) ContainerKill(ctx context.Context, container, signal string) error {
return ErrNotImplemented return nil
} }
func (d *DryRunClient) ContainerPause(ctx context.Context, container string) error { func (d *DryRunClient) ContainerPause(ctx context.Context, container string) error {

View File

@ -52,7 +52,7 @@ type ServiceProxy struct {
ImagesFn func(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error) ImagesFn func(ctx context.Context, projectName string, options ImagesOptions) ([]ImageSummary, error)
WatchFn func(ctx context.Context, project *types.Project, services []string, options WatchOptions) error WatchFn func(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
MaxConcurrencyFn func(parallel int) MaxConcurrencyFn func(parallel int)
DryRunModeFn func(dryRun bool) error DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
interceptors []Interceptor interceptors []Interceptor
} }
@ -327,6 +327,6 @@ func (s *ServiceProxy) MaxConcurrency(i int) {
s.MaxConcurrencyFn(i) s.MaxConcurrencyFn(i)
} }
func (s *ServiceProxy) DryRunMode(dryRun bool) error { func (s *ServiceProxy) DryRunMode(ctx context.Context, dryRun bool) (context.Context, error) {
return s.DryRunModeFn(dryRun) return s.DryRunModeFn(ctx, dryRun)
} }

View File

@ -66,22 +66,22 @@ func (s *composeService) MaxConcurrency(i int) {
s.maxConcurrency = i s.maxConcurrency = i
} }
func (s *composeService) DryRunMode(dryRun bool) error { func (s *composeService) DryRunMode(ctx context.Context, dryRun bool) (context.Context, error) {
if dryRun { if dryRun {
cli, err := command.NewDockerCli() cli, err := command.NewDockerCli()
if err != nil { if err != nil {
return err return ctx, err
} }
err = cli.Initialize(flags.NewClientOptions(), command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) { err = cli.Initialize(flags.NewClientOptions(), command.WithInitializeClient(func(cli *command.DockerCli) (client.APIClient, error) {
dryRunClient := api.NewDryRunClient(s.apiClient()) dryRunClient := api.NewDryRunClient(s.apiClient())
return dryRunClient, nil return dryRunClient, nil
})) }))
if err != nil { if err != nil {
return err return ctx, err
} }
s.dockerCli = cli s.dockerCli = cli
} }
return nil return context.WithValue(ctx, api.DryRunKey{}, dryRun), nil
} }
func (s *composeService) stdout() *streams.Out { func (s *composeService) stdout() *streams.Out {

View File

@ -108,17 +108,18 @@ func (mr *MockServiceMockRecorder) Down(ctx, projectName, options interface{}) *
} }
// DryRunMode mocks base method. // DryRunMode mocks base method.
func (m *MockService) DryRunMode(dryRun bool) error { func (m *MockService) DryRunMode(ctx context.Context, dryRun bool) (context.Context, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DryRunMode", dryRun) ret := m.ctrl.Call(m, "DryRunMode", ctx, dryRun)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(context.Context)
return ret0 ret1, _ := ret[1].(error)
return ret0, ret1
} }
// DryRunMode indicates an expected call of DryRunMode. // DryRunMode indicates an expected call of DryRunMode.
func (mr *MockServiceMockRecorder) DryRunMode(dryRun interface{}) *gomock.Call { func (mr *MockServiceMockRecorder) DryRunMode(ctx, dryRun interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DryRunMode", reflect.TypeOf((*MockService)(nil).DryRunMode), dryRun) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DryRunMode", reflect.TypeOf((*MockService)(nil).DryRunMode), ctx, dryRun)
} }
// Events mocks base method. // Events mocks base method.

View File

@ -25,6 +25,7 @@ import (
type plainWriter struct { type plainWriter struct {
out io.Writer out io.Writer
done chan bool done chan bool
dryRun bool
} }
func (p *plainWriter) Start(ctx context.Context) error { func (p *plainWriter) Start(ctx context.Context) error {
@ -37,7 +38,11 @@ func (p *plainWriter) Start(ctx context.Context) error {
} }
func (p *plainWriter) Event(e Event) { func (p *plainWriter) Event(e Event) {
fmt.Fprintln(p.out, e.ID, e.Text, e.StatusText) prefix := ""
if p.dryRun {
prefix = "DRY RUN MODE - "
}
fmt.Fprintln(p.out, prefix, e.ID, e.Text, e.StatusText)
} }
func (p *plainWriter) Events(events []Event) { func (p *plainWriter) Events(events []Event) {
@ -47,7 +52,11 @@ func (p *plainWriter) Events(events []Event) {
} }
func (p *plainWriter) TailMsgf(m string, args ...interface{}) { func (p *plainWriter) TailMsgf(m string, args ...interface{}) {
fmt.Fprintln(p.out, append([]interface{}{m}, args...)...) prefix := ""
if p.dryRun {
prefix = DRYRUN_PREFIX
}
fmt.Fprintln(p.out, append([]interface{}{prefix, m}, args...)...)
} }
func (p *plainWriter) Stop() { func (p *plainWriter) Stop() {

View File

@ -40,6 +40,7 @@ type ttyWriter struct {
done chan bool done chan bool
mtx *sync.Mutex mtx *sync.Mutex
tailEvents []string tailEvents []string
dryRun bool
} }
func (w *ttyWriter) Start(ctx context.Context) error { func (w *ttyWriter) Start(ctx context.Context) error {
@ -107,7 +108,11 @@ func (w *ttyWriter) Events(events []Event) {
func (w *ttyWriter) TailMsgf(msg string, args ...interface{}) { func (w *ttyWriter) TailMsgf(msg string, args ...interface{}) {
w.mtx.Lock() w.mtx.Lock()
defer w.mtx.Unlock() defer w.mtx.Unlock()
w.tailEvents = append(w.tailEvents, fmt.Sprintf(msg, args...)) msgWithPrefix := msg
if w.dryRun {
msgWithPrefix = strings.TrimSpace(DRYRUN_PREFIX + msg)
}
w.tailEvents = append(w.tailEvents, fmt.Sprintf(msgWithPrefix, args...))
} }
func (w *ttyWriter) printTailEvents() { func (w *ttyWriter) printTailEvents() {
@ -167,7 +172,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
if event.ParentID != "" { if event.ParentID != "" {
continue continue
} }
line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows") line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
fmt.Fprint(w.out, line) fmt.Fprint(w.out, line)
numLines++ numLines++
for _, v := range w.eventIDs { for _, v := range w.eventIDs {
@ -176,7 +181,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
if skipChildEvents { if skipChildEvents {
continue continue
} }
line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows") line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
fmt.Fprint(w.out, line) fmt.Fprint(w.out, line)
numLines++ numLines++
} }
@ -191,7 +196,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
w.numLines = numLines w.numLines = numLines
} }
func lineText(event Event, pad string, terminalWidth, statusPadding int, color bool) string { func lineText(event Event, pad string, terminalWidth, statusPadding int, color bool, dryRun bool) string {
endTime := time.Now() endTime := time.Now()
if event.Status != Working { if event.Status != Working {
endTime = event.startTime endTime = event.startTime
@ -199,6 +204,10 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
endTime = event.endTime endTime = event.endTime
} }
} }
prefix := ""
if dryRun {
prefix = DRYRUN_PREFIX
}
elapsed := endTime.Sub(event.startTime).Seconds() elapsed := endTime.Sub(event.startTime).Seconds()
@ -215,9 +224,10 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
if maxStatusLen > 0 && len(status) > maxStatusLen { if maxStatusLen > 0 && len(status) > maxStatusLen {
status = status[:maxStatusLen] + "..." status = status[:maxStatusLen] + "..."
} }
text := fmt.Sprintf("%s %s %s %s%s %s", text := fmt.Sprintf("%s %s%s %s %s%s %s",
pad, pad,
event.spinner.String(), event.spinner.String(),
prefix,
event.ID, event.ID,
event.Text, event.Text,
strings.Repeat(" ", padding), strings.Repeat(" ", padding),

View File

@ -41,22 +41,22 @@ func TestLineText(t *testing.T) {
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text)) lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
out := lineText(ev, "", 50, lineWidth, true) out := lineText(ev, "", 50, lineWidth, true, false)
assert.Equal(t, out, "\x1b[37m . id Text Status 0.0s\n\x1b[0m") assert.Equal(t, out, "\x1b[37m . id Text Status 0.0s\n\x1b[0m")
out = lineText(ev, "", 50, lineWidth, false) out = lineText(ev, "", 50, lineWidth, false, false)
assert.Equal(t, out, " . id Text Status 0.0s\n") assert.Equal(t, out, " . id Text Status 0.0s\n")
ev.Status = Done ev.Status = Done
out = lineText(ev, "", 50, lineWidth, true) out = lineText(ev, "", 50, lineWidth, true, false)
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m") assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
ev.Status = Error ev.Status = Error
out = lineText(ev, "", 50, lineWidth, true) out = lineText(ev, "", 50, lineWidth, true, false)
assert.Equal(t, out, "\x1b[31m . id Text Status 0.0s\n\x1b[0m") assert.Equal(t, out, "\x1b[31m . id Text Status 0.0s\n\x1b[0m")
ev.Status = Warning ev.Status = Warning
out = lineText(ev, "", 50, lineWidth, true) out = lineText(ev, "", 50, lineWidth, true, false)
assert.Equal(t, out, "\x1b[33m . id Text Status 0.0s\n\x1b[0m") assert.Equal(t, out, "\x1b[33m . id Text Status 0.0s\n\x1b[0m")
} }
@ -75,7 +75,7 @@ func TestLineTextSingleEvent(t *testing.T) {
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text)) lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
out := lineText(ev, "", 50, lineWidth, true) out := lineText(ev, "", 50, lineWidth, true, false)
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m") assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
} }

View File

@ -21,11 +21,17 @@ import (
"os" "os"
"sync" "sync"
"github.com/docker/compose/v2/pkg/api"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/moby/term" "github.com/moby/term"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
const (
DRYRUN_PREFIX = " DRY-RUN MODE - "
)
// Writer can write multiple progress events // Writer can write multiple progress events
type Writer interface { type Writer interface {
Start(context.Context) error Start(context.Context) error
@ -66,7 +72,7 @@ func Run(ctx context.Context, pf progressFunc) error {
// RunWithStatus will run a writer and the progress function in parallel and return a status // RunWithStatus will run a writer and the progress function in parallel and return a status
func RunWithStatus(ctx context.Context, pf progressFuncWithStatus) (string, error) { func RunWithStatus(ctx context.Context, pf progressFuncWithStatus) (string, error) {
eg, _ := errgroup.WithContext(ctx) eg, _ := errgroup.WithContext(ctx)
w, err := NewWriter(os.Stderr) w, err := NewWriter(ctx, os.Stderr)
var result string var result string
if err != nil { if err != nil {
return "", err return "", err
@ -103,21 +109,26 @@ const (
var Mode = ModeAuto var Mode = ModeAuto
// NewWriter returns a new multi-progress writer // NewWriter returns a new multi-progress writer
func NewWriter(out console.File) (Writer, error) { func NewWriter(ctx context.Context, out console.File) (Writer, error) {
_, isTerminal := term.GetFdInfo(out) _, isTerminal := term.GetFdInfo(out)
dryRun, ok := ctx.Value(api.DryRunKey{}).(bool)
if !ok {
dryRun = false
}
if Mode == ModeAuto && isTerminal { if Mode == ModeAuto && isTerminal {
return newTTYWriter(out) return newTTYWriter(out, dryRun)
} }
if Mode == ModeTTY { if Mode == ModeTTY {
return newTTYWriter(out) return newTTYWriter(out, dryRun)
} }
return &plainWriter{ return &plainWriter{
out: out, out: out,
done: make(chan bool), done: make(chan bool),
dryRun: dryRun,
}, nil }, nil
} }
func newTTYWriter(out console.File) (Writer, error) { func newTTYWriter(out console.File, dryRun bool) (Writer, error) {
con, err := console.ConsoleFromFile(out) con, err := console.ConsoleFromFile(out)
if err != nil { if err != nil {
return nil, err return nil, err
@ -130,5 +141,6 @@ func newTTYWriter(out console.File) (Writer, error) {
repeated: false, repeated: false,
done: make(chan bool), done: make(chan bool),
mtx: &sync.Mutex{}, mtx: &sync.Mutex{},
dryRun: dryRun,
}, nil }, nil
} }