mirror of https://github.com/docker/compose.git
Merge pull request #10210 from glours/dry-run-kill-support
Dry run kill support
This commit is contained in:
commit
a93f09efac
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue