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 {
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(parallel int)
// 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(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
}

View File

@ -37,6 +37,8 @@ import (
var _ client.APIClient = &DryRunClient{}
type DryRunKey struct{}
// DryRunClient implements APIClient by delegating to implementation functions. This allows lazy init and per-method overrides
type DryRunClient struct {
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 {
return ErrNotImplemented
return nil
}
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)
WatchFn func(ctx context.Context, project *types.Project, services []string, options WatchOptions) error
MaxConcurrencyFn func(parallel int)
DryRunModeFn func(dryRun bool) error
DryRunModeFn func(ctx context.Context, dryRun bool) (context.Context, error)
interceptors []Interceptor
}
@ -327,6 +327,6 @@ func (s *ServiceProxy) MaxConcurrency(i int) {
s.MaxConcurrencyFn(i)
}
func (s *ServiceProxy) DryRunMode(dryRun bool) error {
return s.DryRunModeFn(dryRun)
func (s *ServiceProxy) DryRunMode(ctx context.Context, dryRun bool) (context.Context, error) {
return s.DryRunModeFn(ctx, dryRun)
}

View File

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

View File

@ -108,17 +108,18 @@ func (mr *MockServiceMockRecorder) Down(ctx, projectName, options interface{}) *
}
// 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()
ret := m.ctrl.Call(m, "DryRunMode", dryRun)
ret0, _ := ret[0].(error)
return ret0
ret := m.ctrl.Call(m, "DryRunMode", ctx, dryRun)
ret0, _ := ret[0].(context.Context)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// 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()
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.

View File

@ -23,8 +23,9 @@ import (
)
type plainWriter struct {
out io.Writer
done chan bool
out io.Writer
done chan bool
dryRun bool
}
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) {
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) {
@ -47,7 +52,11 @@ func (p *plainWriter) Events(events []Event) {
}
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() {

View File

@ -40,6 +40,7 @@ type ttyWriter struct {
done chan bool
mtx *sync.Mutex
tailEvents []string
dryRun bool
}
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{}) {
w.mtx.Lock()
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() {
@ -167,7 +172,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
if event.ParentID != "" {
continue
}
line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows")
line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
fmt.Fprint(w.out, line)
numLines++
for _, v := range w.eventIDs {
@ -176,7 +181,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
if skipChildEvents {
continue
}
line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows")
line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
fmt.Fprint(w.out, line)
numLines++
}
@ -191,7 +196,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
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()
if event.Status != Working {
endTime = event.startTime
@ -199,6 +204,10 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
endTime = event.endTime
}
}
prefix := ""
if dryRun {
prefix = DRYRUN_PREFIX
}
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 {
status = status[:maxStatusLen] + "..."
}
text := fmt.Sprintf("%s %s %s %s%s %s",
text := fmt.Sprintf("%s %s%s %s %s%s %s",
pad,
event.spinner.String(),
prefix,
event.ID,
event.Text,
strings.Repeat(" ", padding),

View File

@ -41,22 +41,22 @@ func TestLineText(t *testing.T) {
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")
out = lineText(ev, "", 50, lineWidth, false)
out = lineText(ev, "", 50, lineWidth, false, false)
assert.Equal(t, out, " . id Text Status 0.0s\n")
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")
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")
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")
}
@ -75,7 +75,7 @@ func TestLineTextSingleEvent(t *testing.T) {
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")
}

View File

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