mirror of
				https://github.com/docker/compose.git
				synced 2025-11-04 05:34:09 +01:00 
			
		
		
		
	Add Navigation Menu to compose up
Signed-off-by: Joana Hrotko <joana.hrotko@docker.com>
This commit is contained in:
		
							parent
							
								
									3950460703
								
							
						
					
					
						commit
						e9dc82011f
					
				@ -101,6 +101,7 @@ RUN --mount=type=bind,target=. \
 | 
				
			|||||||
FROM build-base AS test
 | 
					FROM build-base AS test
 | 
				
			||||||
ARG CGO_ENABLED=0
 | 
					ARG CGO_ENABLED=0
 | 
				
			||||||
ARG BUILD_TAGS
 | 
					ARG BUILD_TAGS
 | 
				
			||||||
 | 
					ENV COMPOSE_MENU=FALSE
 | 
				
			||||||
RUN --mount=type=bind,target=. \
 | 
					RUN --mount=type=bind,target=. \
 | 
				
			||||||
    --mount=type=cache,target=/root/.cache \
 | 
					    --mount=type=cache,target=/root/.cache \
 | 
				
			||||||
    --mount=type=cache,target=/go/pkg/mod \
 | 
					    --mount=type=cache,target=/go/pkg/mod \
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Makefile
									
									
									
									
									
								
							@ -13,6 +13,7 @@
 | 
				
			|||||||
#   limitations under the License.
 | 
					#   limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PKG := github.com/docker/compose/v2
 | 
					PKG := github.com/docker/compose/v2
 | 
				
			||||||
 | 
					export COMPOSE_MENU = FALSE
 | 
				
			||||||
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
 | 
					VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GO_LDFLAGS ?= -w -X ${PKG}/internal.Version=${VERSION}
 | 
					GO_LDFLAGS ?= -w -X ${PKG}/internal.Version=${VERSION}
 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,8 @@ const (
 | 
				
			|||||||
	ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
 | 
						ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
 | 
				
			||||||
	// ComposeEnvFiles defines the env files to use if --env-file isn't used
 | 
						// ComposeEnvFiles defines the env files to use if --env-file isn't used
 | 
				
			||||||
	ComposeEnvFiles = "COMPOSE_ENV_FILES"
 | 
						ComposeEnvFiles = "COMPOSE_ENV_FILES"
 | 
				
			||||||
 | 
						// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
 | 
				
			||||||
 | 
						ComposeMenu = "COMPOSE_MENU"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Backend interface {
 | 
					type Backend interface {
 | 
				
			||||||
@ -620,3 +622,15 @@ var printerModes = []string{
 | 
				
			|||||||
	ui.ModePlain,
 | 
						ui.ModePlain,
 | 
				
			||||||
	ui.ModeQuiet,
 | 
						ui.ModeQuiet,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetUnchangedOption(name string, experimentalFlag bool) bool {
 | 
				
			||||||
 | 
						var value bool
 | 
				
			||||||
 | 
						// If the var is defined we use that value first
 | 
				
			||||||
 | 
						if envVar, ok := os.LookupEnv(name); ok {
 | 
				
			||||||
 | 
							value = utils.StringToBool(envVar)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// if not, we try to get it from experimental feature flag
 | 
				
			||||||
 | 
							value = experimentalFlag
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -42,20 +42,22 @@ type composeOptions struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type upOptions struct {
 | 
					type upOptions struct {
 | 
				
			||||||
	*composeOptions
 | 
						*composeOptions
 | 
				
			||||||
	Detach             bool
 | 
						Detach                bool
 | 
				
			||||||
	noStart            bool
 | 
						noStart               bool
 | 
				
			||||||
	noDeps             bool
 | 
						noDeps                bool
 | 
				
			||||||
	cascadeStop        bool
 | 
						cascadeStop           bool
 | 
				
			||||||
	exitCodeFrom       string
 | 
						exitCodeFrom          string
 | 
				
			||||||
	noColor            bool
 | 
						noColor               bool
 | 
				
			||||||
	noPrefix           bool
 | 
						noPrefix              bool
 | 
				
			||||||
	attachDependencies bool
 | 
						attachDependencies    bool
 | 
				
			||||||
	attach             []string
 | 
						attach                []string
 | 
				
			||||||
	noAttach           []string
 | 
						noAttach              []string
 | 
				
			||||||
	timestamp          bool
 | 
						timestamp             bool
 | 
				
			||||||
	wait               bool
 | 
						wait                  bool
 | 
				
			||||||
	waitTimeout        int
 | 
						waitTimeout           int
 | 
				
			||||||
	watch              bool
 | 
						watch                 bool
 | 
				
			||||||
 | 
						navigationMenu        bool
 | 
				
			||||||
 | 
						navigationMenuChanged bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
 | 
					func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
 | 
				
			||||||
@ -87,6 +89,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
 | 
				
			|||||||
		PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
 | 
							PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
 | 
				
			||||||
			create.pullChanged = cmd.Flags().Changed("pull")
 | 
								create.pullChanged = cmd.Flags().Changed("pull")
 | 
				
			||||||
			create.timeChanged = cmd.Flags().Changed("timeout")
 | 
								create.timeChanged = cmd.Flags().Changed("timeout")
 | 
				
			||||||
 | 
								up.navigationMenuChanged = cmd.Flags().Changed("menu")
 | 
				
			||||||
			return validateFlags(&up, &create)
 | 
								return validateFlags(&up, &create)
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
 | 
							RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
 | 
				
			||||||
@ -128,6 +131,8 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
 | 
				
			|||||||
	flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
 | 
						flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
 | 
				
			||||||
	flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy")
 | 
						flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy")
 | 
				
			||||||
	flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
 | 
						flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
 | 
				
			||||||
 | 
						flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached (Experimental). Incompatible with --detach.")
 | 
				
			||||||
 | 
						flags.MarkHidden("menu") //nolint:errcheck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return upCmd
 | 
						return upCmd
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -161,7 +166,7 @@ func runUp(
 | 
				
			|||||||
	ctx context.Context,
 | 
						ctx context.Context,
 | 
				
			||||||
	dockerCli command.Cli,
 | 
						dockerCli command.Cli,
 | 
				
			||||||
	backend api.Service,
 | 
						backend api.Service,
 | 
				
			||||||
	_ *experimental.State,
 | 
						experimentals *experimental.State,
 | 
				
			||||||
	createOptions createOptions,
 | 
						createOptions createOptions,
 | 
				
			||||||
	upOptions upOptions,
 | 
						upOptions upOptions,
 | 
				
			||||||
	buildOptions buildOptions,
 | 
						buildOptions buildOptions,
 | 
				
			||||||
@ -181,6 +186,9 @@ func runUp(
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if !upOptions.navigationMenuChanged {
 | 
				
			||||||
 | 
							upOptions.navigationMenu = SetUnchangedOption(ComposeMenu, experimentals.NavBar())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var build *api.BuildOptions
 | 
						var build *api.BuildOptions
 | 
				
			||||||
	if !createOptions.noBuild {
 | 
						if !createOptions.noBuild {
 | 
				
			||||||
@ -253,15 +261,16 @@ func runUp(
 | 
				
			|||||||
	return backend.Up(ctx, project, api.UpOptions{
 | 
						return backend.Up(ctx, project, api.UpOptions{
 | 
				
			||||||
		Create: create,
 | 
							Create: create,
 | 
				
			||||||
		Start: api.StartOptions{
 | 
							Start: api.StartOptions{
 | 
				
			||||||
			Project:      project,
 | 
								Project:        project,
 | 
				
			||||||
			Attach:       consumer,
 | 
								Attach:         consumer,
 | 
				
			||||||
			AttachTo:     attach,
 | 
								AttachTo:       attach,
 | 
				
			||||||
			ExitCodeFrom: upOptions.exitCodeFrom,
 | 
								ExitCodeFrom:   upOptions.exitCodeFrom,
 | 
				
			||||||
			CascadeStop:  upOptions.cascadeStop,
 | 
								CascadeStop:    upOptions.cascadeStop,
 | 
				
			||||||
			Wait:         upOptions.wait,
 | 
								Wait:           upOptions.wait,
 | 
				
			||||||
			WaitTimeout:  timeout,
 | 
								WaitTimeout:    timeout,
 | 
				
			||||||
			Watch:        upOptions.watch,
 | 
								Watch:          upOptions.watch,
 | 
				
			||||||
			Services:     services,
 | 
								Services:       services,
 | 
				
			||||||
 | 
								NavigationMenu: upOptions.navigationMenu,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										67
									
								
								cmd/formatter/ansi.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								cmd/formatter/ansi.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright 2024 Docker Compose CLI authors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package formatter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/acarl005/stripansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ansi(code string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("\033%s", code)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func SaveCursor() {
 | 
				
			||||||
 | 
						fmt.Print(ansi("7"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func RestoreCursor() {
 | 
				
			||||||
 | 
						fmt.Print(ansi("8"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func HideCursor() {
 | 
				
			||||||
 | 
						fmt.Print(ansi("[?25l"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func ShowCursor() {
 | 
				
			||||||
 | 
						fmt.Print(ansi("[?25h"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func MoveCursor(y, x int) {
 | 
				
			||||||
 | 
						fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func MoveCursorX(pos int) {
 | 
				
			||||||
 | 
						fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func ClearLine() {
 | 
				
			||||||
 | 
						// Does not move cursor from its current position
 | 
				
			||||||
 | 
						fmt.Print(ansi("[2K"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func MoveCursorUp(lines int) {
 | 
				
			||||||
 | 
						// Does not add new lines
 | 
				
			||||||
 | 
						fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func MoveCursorDown(lines int) {
 | 
				
			||||||
 | 
						// Does not add new lines
 | 
				
			||||||
 | 
						fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func NewLine() {
 | 
				
			||||||
 | 
						// Like \n
 | 
				
			||||||
 | 
						fmt.Print("\012")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func lenAnsi(s string) int {
 | 
				
			||||||
 | 
						// len has into consideration ansi codes, if we want
 | 
				
			||||||
 | 
						// the len of the actual len(string) we need to strip
 | 
				
			||||||
 | 
						// all ansi codes
 | 
				
			||||||
 | 
						return len(stripansi.Strip(s))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -35,6 +35,18 @@ var names = []string{
 | 
				
			|||||||
	"white",
 | 
						"white",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						BOLD      = "1"
 | 
				
			||||||
 | 
						FAINT     = "2"
 | 
				
			||||||
 | 
						ITALIC    = "3"
 | 
				
			||||||
 | 
						UNDERLINE = "4"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						RESET = "0"
 | 
				
			||||||
 | 
						CYAN  = "36"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// Never use ANSI codes
 | 
						// Never use ANSI codes
 | 
				
			||||||
	Never = "never"
 | 
						Never = "never"
 | 
				
			||||||
@ -72,12 +84,17 @@ var monochrome = func(s string) string {
 | 
				
			|||||||
	return s
 | 
						return s
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ansiColor(code, s string) string {
 | 
					func ansiColor(code, s string, formatOpts ...string) string {
 | 
				
			||||||
	return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
 | 
						return fmt.Sprintf("%s%s%s", ansiColorCode(code, formatOpts...), s, ansiColorCode("0"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ansi(code string) string {
 | 
					// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
 | 
				
			||||||
	return fmt.Sprintf("\033[%sm", code)
 | 
					func ansiColorCode(code string, formatOpts ...string) string {
 | 
				
			||||||
 | 
						res := "\033["
 | 
				
			||||||
 | 
						for _, c := range formatOpts {
 | 
				
			||||||
 | 
							res = fmt.Sprintf("%s%s;", res, c)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s%sm", res, code)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func makeColorFunc(code string) colorFunc {
 | 
					func makeColorFunc(code string) colorFunc {
 | 
				
			||||||
 | 
				
			|||||||
@ -102,19 +102,29 @@ func (l *logConsumer) Err(container, message string) {
 | 
				
			|||||||
	l.write(l.stderr, container, message)
 | 
						l.write(l.stderr, container, message)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var navColor = makeColorFunc("90")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (l *logConsumer) write(w io.Writer, container, message string) {
 | 
					func (l *logConsumer) write(w io.Writer, container, message string) {
 | 
				
			||||||
	if l.ctx.Err() != nil {
 | 
						if l.ctx.Err() != nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	p := l.getPresenter(container)
 | 
						printFn := func() {
 | 
				
			||||||
	timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
 | 
							p := l.getPresenter(container)
 | 
				
			||||||
	for _, line := range strings.Split(message, "\n") {
 | 
							timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
 | 
				
			||||||
		if l.timestamp {
 | 
							for _, line := range strings.Split(message, "\n") {
 | 
				
			||||||
			fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
 | 
								ClearLine()
 | 
				
			||||||
		} else {
 | 
								if l.timestamp {
 | 
				
			||||||
			fmt.Fprintf(w, "%s%s\n", p.prefix, line)
 | 
									fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									fmt.Fprintf(w, "%s%s\n", p.prefix, line)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if KeyboardManager != nil {
 | 
				
			||||||
 | 
							KeyboardManager.PrintKeyboardInfo(printFn)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							printFn()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (l *logConsumer) Status(container, msg string) {
 | 
					func (l *logConsumer) Status(container, msg string) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										321
									
								
								cmd/formatter/shortcut.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								cmd/formatter/shortcut.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,321 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright 2024 Docker Compose CLI authors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package formatter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/buger/goterm"
 | 
				
			||||||
 | 
						"github.com/compose-spec/compose-go/v2/types"
 | 
				
			||||||
 | 
						"github.com/docker/compose/v2/internal/tracing"
 | 
				
			||||||
 | 
						"github.com/docker/compose/v2/pkg/api"
 | 
				
			||||||
 | 
						"github.com/docker/compose/v2/pkg/watch"
 | 
				
			||||||
 | 
						"github.com/eiannone/keyboard"
 | 
				
			||||||
 | 
						"github.com/hashicorp/go-multierror"
 | 
				
			||||||
 | 
						"github.com/skratchdot/open-golang/open"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DISPLAY_ERROR_TIME = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type KeyboardError struct {
 | 
				
			||||||
 | 
						err       error
 | 
				
			||||||
 | 
						timeStart time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ke *KeyboardError) shouldDisplay() bool {
 | 
				
			||||||
 | 
						return ke.err != nil && int(time.Since(ke.timeStart).Seconds()) < DISPLAY_ERROR_TIME
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ke *KeyboardError) printError(height int, info string) {
 | 
				
			||||||
 | 
						if ke.shouldDisplay() {
 | 
				
			||||||
 | 
							errMessage := ke.err.Error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MoveCursor(height-linesOffset(info)-linesOffset(errMessage)-1, 0)
 | 
				
			||||||
 | 
							ClearLine()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fmt.Print(errMessage)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ke *KeyboardError) addError(prefix string, err error) {
 | 
				
			||||||
 | 
						ke.timeStart = time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						prefix = ansiColor(CYAN, fmt.Sprintf("%s →", prefix), BOLD)
 | 
				
			||||||
 | 
						errorString := fmt.Sprintf("%s  %s", prefix, err.Error())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ke.err = errors.New(errorString)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ke *KeyboardError) error() string {
 | 
				
			||||||
 | 
						return ke.err.Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type KeyboardWatch struct {
 | 
				
			||||||
 | 
						Watcher  watch.Notify
 | 
				
			||||||
 | 
						Watching bool
 | 
				
			||||||
 | 
						WatchFn  func(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error
 | 
				
			||||||
 | 
						Ctx      context.Context
 | 
				
			||||||
 | 
						Cancel   context.CancelFunc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (kw *KeyboardWatch) isWatching() bool {
 | 
				
			||||||
 | 
						return kw.Watching
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (kw *KeyboardWatch) switchWatching() {
 | 
				
			||||||
 | 
						kw.Watching = !kw.Watching
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (kw *KeyboardWatch) newContext(ctx context.Context) context.CancelFunc {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithCancel(ctx)
 | 
				
			||||||
 | 
						kw.Ctx = ctx
 | 
				
			||||||
 | 
						kw.Cancel = cancel
 | 
				
			||||||
 | 
						return cancel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type KEYBOARD_LOG_LEVEL int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						NONE  KEYBOARD_LOG_LEVEL = 0
 | 
				
			||||||
 | 
						INFO  KEYBOARD_LOG_LEVEL = 1
 | 
				
			||||||
 | 
						DEBUG KEYBOARD_LOG_LEVEL = 2
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LogKeyboard struct {
 | 
				
			||||||
 | 
						kError                KeyboardError
 | 
				
			||||||
 | 
						Watch                 KeyboardWatch
 | 
				
			||||||
 | 
						IsDockerDesktopActive bool
 | 
				
			||||||
 | 
						IsWatchConfigured     bool
 | 
				
			||||||
 | 
						logLevel              KEYBOARD_LOG_LEVEL
 | 
				
			||||||
 | 
						signalChannel         chan<- os.Signal
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var KeyboardManager *LogKeyboard
 | 
				
			||||||
 | 
					var eg multierror.Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
 | 
				
			||||||
 | 
						sc chan<- os.Signal,
 | 
				
			||||||
 | 
						watchFn func(ctx context.Context,
 | 
				
			||||||
 | 
							project *types.Project,
 | 
				
			||||||
 | 
							services []string,
 | 
				
			||||||
 | 
							options api.WatchOptions,
 | 
				
			||||||
 | 
						) error,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						km := LogKeyboard{}
 | 
				
			||||||
 | 
						km.IsDockerDesktopActive = isDockerDesktopActive
 | 
				
			||||||
 | 
						km.IsWatchConfigured = isWatchConfigured
 | 
				
			||||||
 | 
						km.logLevel = INFO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						km.Watch.Watching = false
 | 
				
			||||||
 | 
						km.Watch.WatchFn = watchFn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						km.signalChannel = sc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						KeyboardManager = &km
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						HideCursor()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) PrintKeyboardInfo(printFn func()) {
 | 
				
			||||||
 | 
						printFn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lk.logLevel == INFO {
 | 
				
			||||||
 | 
							lk.printNavigationMenu()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates space to print error and menu string
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) createBuffer(lines int) {
 | 
				
			||||||
 | 
						allocateSpace(lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lk.kError.shouldDisplay() {
 | 
				
			||||||
 | 
							extraLines := linesOffset(lk.kError.error()) + 1
 | 
				
			||||||
 | 
							allocateSpace(extraLines)
 | 
				
			||||||
 | 
							lines += extraLines
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						infoMessage := lk.navigationMenu()
 | 
				
			||||||
 | 
						extraLines := linesOffset(infoMessage) + 1
 | 
				
			||||||
 | 
						allocateSpace(extraLines)
 | 
				
			||||||
 | 
						lines += extraLines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lines > 0 {
 | 
				
			||||||
 | 
							MoveCursorUp(lines)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) printNavigationMenu() {
 | 
				
			||||||
 | 
						lk.clearNavigationMenu()
 | 
				
			||||||
 | 
						lk.createBuffer(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if lk.logLevel == INFO {
 | 
				
			||||||
 | 
							height := goterm.Height()
 | 
				
			||||||
 | 
							menu := lk.navigationMenu()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MoveCursorX(0)
 | 
				
			||||||
 | 
							SaveCursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lk.kError.printError(height, menu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MoveCursor(height-linesOffset(menu), 0)
 | 
				
			||||||
 | 
							ClearLine()
 | 
				
			||||||
 | 
							fmt.Print(menu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MoveCursorX(0)
 | 
				
			||||||
 | 
							RestoreCursor()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) navigationMenu() string {
 | 
				
			||||||
 | 
						var options string
 | 
				
			||||||
 | 
						var openDDInfo string
 | 
				
			||||||
 | 
						if lk.IsDockerDesktopActive {
 | 
				
			||||||
 | 
							openDDInfo = shortcutKeyColor("v") + navColor(" View in Docker Desktop")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var watchInfo string
 | 
				
			||||||
 | 
						if openDDInfo != "" {
 | 
				
			||||||
 | 
							watchInfo = navColor("   ")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var isEnabled = " Enable"
 | 
				
			||||||
 | 
						if lk.Watch.Watching {
 | 
				
			||||||
 | 
							isEnabled = " Disable"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
 | 
				
			||||||
 | 
						return options + openDDInfo + watchInfo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) clearNavigationMenu() {
 | 
				
			||||||
 | 
						height := goterm.Height()
 | 
				
			||||||
 | 
						MoveCursorX(0)
 | 
				
			||||||
 | 
						SaveCursor()
 | 
				
			||||||
 | 
						for i := 0; i < height; i++ {
 | 
				
			||||||
 | 
							MoveCursorDown(1)
 | 
				
			||||||
 | 
							ClearLine()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						RestoreCursor()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
 | 
				
			||||||
 | 
						if !lk.IsDockerDesktopActive {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
 | 
				
			||||||
 | 
							func(ctx context.Context) error {
 | 
				
			||||||
 | 
								link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
 | 
				
			||||||
 | 
								err := open.Run(link)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									err = fmt.Errorf("Could not open Docker Desktop")
 | 
				
			||||||
 | 
									lk.keyboardError("View", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) keyboardError(prefix string, err error) {
 | 
				
			||||||
 | 
						lk.kError.addError(prefix, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lk.printNavigationMenu()
 | 
				
			||||||
 | 
						timer1 := time.NewTimer((DISPLAY_ERROR_TIME + 1) * time.Second)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							<-timer1.C
 | 
				
			||||||
 | 
							lk.printNavigationMenu()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) StartWatch(ctx context.Context, project *types.Project, options api.UpOptions) {
 | 
				
			||||||
 | 
						if !lk.IsWatchConfigured {
 | 
				
			||||||
 | 
							eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
 | 
				
			||||||
 | 
								func(ctx context.Context) error {
 | 
				
			||||||
 | 
									err := fmt.Errorf("Watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
 | 
				
			||||||
 | 
									lk.keyboardError("Watch", err)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						lk.Watch.switchWatching()
 | 
				
			||||||
 | 
						if !lk.Watch.isWatching() {
 | 
				
			||||||
 | 
							lk.Watch.Cancel()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
 | 
				
			||||||
 | 
								func(ctx context.Context) error {
 | 
				
			||||||
 | 
									lk.Watch.newContext(ctx)
 | 
				
			||||||
 | 
									buildOpts := *options.Create.Build
 | 
				
			||||||
 | 
									buildOpts.Quiet = true
 | 
				
			||||||
 | 
									return lk.Watch.WatchFn(lk.Watch.Ctx, project, options.Start.Services, api.WatchOptions{
 | 
				
			||||||
 | 
										Build: &buildOpts,
 | 
				
			||||||
 | 
										LogTo: options.Start.Attach,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
								}))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) KeyboardClose() {
 | 
				
			||||||
 | 
						_ = keyboard.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, project *types.Project, options api.UpOptions) {
 | 
				
			||||||
 | 
						switch kRune := event.Rune; kRune {
 | 
				
			||||||
 | 
						case 'v':
 | 
				
			||||||
 | 
							lk.openDockerDesktop(ctx, project)
 | 
				
			||||||
 | 
						case 'w':
 | 
				
			||||||
 | 
							lk.StartWatch(ctx, project, options)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch key := event.Key; key {
 | 
				
			||||||
 | 
						case keyboard.KeyCtrlC:
 | 
				
			||||||
 | 
							lk.KeyboardClose()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lk.clearNavigationMenu()
 | 
				
			||||||
 | 
							ShowCursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lk.logLevel = NONE
 | 
				
			||||||
 | 
							if lk.Watch.Watching && lk.Watch.Cancel != nil {
 | 
				
			||||||
 | 
								lk.Watch.Cancel()
 | 
				
			||||||
 | 
								_ = eg.Wait().ErrorOrNil() // Need to print this ?
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// will notify main thread to kill and will handle gracefully
 | 
				
			||||||
 | 
							lk.signalChannel <- syscall.SIGINT
 | 
				
			||||||
 | 
						case keyboard.KeyEnter:
 | 
				
			||||||
 | 
							lk.printNavigationMenu()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func allocateSpace(lines int) {
 | 
				
			||||||
 | 
						for i := 0; i < lines; i++ {
 | 
				
			||||||
 | 
							ClearLine()
 | 
				
			||||||
 | 
							NewLine()
 | 
				
			||||||
 | 
							MoveCursorX(0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func linesOffset(s string) int {
 | 
				
			||||||
 | 
						return int(math.Floor(float64(lenAnsi(s)) / float64(goterm.Width())))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func shortcutKeyColor(key string) string {
 | 
				
			||||||
 | 
						foreground := "38;2"
 | 
				
			||||||
 | 
						black := "0;0;0"
 | 
				
			||||||
 | 
						background := "48;2"
 | 
				
			||||||
 | 
						white := "255;255;255"
 | 
				
			||||||
 | 
						return ansiColor(foreground+";"+black+";"+background+";"+white, key, BOLD)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -108,6 +108,17 @@ options:
 | 
				
			|||||||
      experimentalcli: false
 | 
					      experimentalcli: false
 | 
				
			||||||
      kubernetes: false
 | 
					      kubernetes: false
 | 
				
			||||||
      swarm: false
 | 
					      swarm: false
 | 
				
			||||||
 | 
					    - option: menu
 | 
				
			||||||
 | 
					      value_type: bool
 | 
				
			||||||
 | 
					      default_value: "false"
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Enable interactive shortcuts when running attached (Experimental). Incompatible with --detach.
 | 
				
			||||||
 | 
					      deprecated: false
 | 
				
			||||||
 | 
					      hidden: true
 | 
				
			||||||
 | 
					      experimental: false
 | 
				
			||||||
 | 
					      experimentalcli: false
 | 
				
			||||||
 | 
					      kubernetes: false
 | 
				
			||||||
 | 
					      swarm: false
 | 
				
			||||||
    - option: no-attach
 | 
					    - option: no-attach
 | 
				
			||||||
      value_type: stringArray
 | 
					      value_type: stringArray
 | 
				
			||||||
      default_value: '[]'
 | 
					      default_value: '[]'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							@ -5,6 +5,7 @@ go 1.21
 | 
				
			|||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/AlecAivazis/survey/v2 v2.3.7
 | 
						github.com/AlecAivazis/survey/v2 v2.3.7
 | 
				
			||||||
	github.com/Microsoft/go-winio v0.6.1
 | 
						github.com/Microsoft/go-winio v0.6.1
 | 
				
			||||||
 | 
						github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
 | 
				
			||||||
	github.com/buger/goterm v1.0.4
 | 
						github.com/buger/goterm v1.0.4
 | 
				
			||||||
	github.com/compose-spec/compose-go/v2 v2.0.2
 | 
						github.com/compose-spec/compose-go/v2 v2.0.2
 | 
				
			||||||
	github.com/containerd/console v1.0.4
 | 
						github.com/containerd/console v1.0.4
 | 
				
			||||||
@ -34,6 +35,7 @@ require (
 | 
				
			|||||||
	github.com/otiai10/copy v1.14.0
 | 
						github.com/otiai10/copy v1.14.0
 | 
				
			||||||
	github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc
 | 
						github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc
 | 
				
			||||||
	github.com/sirupsen/logrus v1.9.3
 | 
						github.com/sirupsen/logrus v1.9.3
 | 
				
			||||||
 | 
						github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
 | 
				
			||||||
	github.com/spf13/cobra v1.8.0
 | 
						github.com/spf13/cobra v1.8.0
 | 
				
			||||||
	github.com/spf13/pflag v1.0.5
 | 
						github.com/spf13/pflag v1.0.5
 | 
				
			||||||
	github.com/stretchr/testify v1.8.4
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
@ -85,6 +87,7 @@ require (
 | 
				
			|||||||
	github.com/docker/docker-credential-helpers v0.8.0 // indirect
 | 
						github.com/docker/docker-credential-helpers v0.8.0 // indirect
 | 
				
			||||||
	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 | 
						github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 | 
				
			||||||
	github.com/docker/go-metrics v0.0.1 // indirect
 | 
						github.com/docker/go-metrics v0.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
 | 
				
			||||||
	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 | 
						github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 | 
				
			||||||
	github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
						github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
				
			||||||
	github.com/fvbommel/sortorder v1.0.2 // indirect
 | 
						github.com/fvbommel/sortorder v1.0.2 // indirect
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							@ -27,6 +27,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
 | 
				
			|||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 | 
					github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 | 
				
			||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
 | 
					github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
 | 
				
			||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 | 
					github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
 | 
				
			||||||
 | 
					github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
 | 
				
			||||||
 | 
					github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
 | 
				
			||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
					github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
				
			||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
					github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
				
			||||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
 | 
					github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
 | 
				
			||||||
@ -150,6 +152,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 | 
				
			|||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
 | 
					github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
 | 
				
			||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 | 
					github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 | 
				
			||||||
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
 | 
					github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
 | 
				
			||||||
 | 
					github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
 | 
				
			||||||
 | 
					github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
 | 
				
			||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
 | 
					github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
 | 
				
			||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
 | 
					github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
 | 
				
			||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
					github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
				
			||||||
@ -440,6 +444,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 | 
				
			|||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
					github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
				
			||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 | 
					github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 | 
				
			||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 | 
					github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 | 
				
			||||||
 | 
					github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
 | 
				
			||||||
 | 
					github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
 | 
				
			||||||
github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
 | 
					github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY=
 | 
				
			||||||
github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
 | 
					github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
 | 
				
			||||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
 | 
					github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 h1:JmfC365KywYwHB946TTiQWEb8kqPY+pybPLoGE9GgVk=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								internal/tracing/keyboard_metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/tracing/keyboard_metrics.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   Copyright 2024 Docker Compose CLI authors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					   you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					   You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					   distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					   See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					   limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package tracing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go.opentelemetry.io/otel/attribute"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
 | 
				
			||||||
 | 
						commandAvailable := []string{}
 | 
				
			||||||
 | 
						if isDockerDesktopActive {
 | 
				
			||||||
 | 
							commandAvailable = append(commandAvailable, "gui")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if isWatchConfigured {
 | 
				
			||||||
 | 
							commandAvailable = append(commandAvailable, "watch")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						AddAttributeToSpan(ctx,
 | 
				
			||||||
 | 
							attribute.Bool("navmenu.enabled", enabled),
 | 
				
			||||||
 | 
							attribute.StringSlice("navmenu.command_available", commandAvailable))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -19,6 +19,8 @@ package tracing
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/acarl005/stripansi"
 | 
				
			||||||
 | 
						"go.opentelemetry.io/otel/attribute"
 | 
				
			||||||
	"go.opentelemetry.io/otel/codes"
 | 
						"go.opentelemetry.io/otel/codes"
 | 
				
			||||||
	semconv "go.opentelemetry.io/otel/semconv/v1.19.0"
 | 
						semconv "go.opentelemetry.io/otel/semconv/v1.19.0"
 | 
				
			||||||
	"go.opentelemetry.io/otel/trace"
 | 
						"go.opentelemetry.io/otel/trace"
 | 
				
			||||||
@ -80,12 +82,16 @@ func EventWrapFuncForErrGroup(ctx context.Context, eventName string, opts SpanOp
 | 
				
			|||||||
		eventOpts := opts.EventOptions()
 | 
							eventOpts := opts.EventOptions()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		err := fn(ctx)
 | 
							err := fn(ctx)
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			eventOpts = append(eventOpts, trace.WithAttributes(semconv.ExceptionMessage(err.Error())))
 | 
								eventOpts = append(eventOpts, trace.WithAttributes(semconv.ExceptionMessage(stripansi.Strip(err.Error()))))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		span.AddEvent(eventName, eventOpts...)
 | 
							span.AddEvent(eventName, eventOpts...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddAttributeToSpan(ctx context.Context, attr ...attribute.KeyValue) {
 | 
				
			||||||
 | 
						span := trace.SpanFromContext(ctx)
 | 
				
			||||||
 | 
						span.SetAttributes(attr...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -217,8 +217,9 @@ type StartOptions struct {
 | 
				
			|||||||
	Wait        bool
 | 
						Wait        bool
 | 
				
			||||||
	WaitTimeout time.Duration
 | 
						WaitTimeout time.Duration
 | 
				
			||||||
	// Services passed in the command line to be started
 | 
						// Services passed in the command line to be started
 | 
				
			||||||
	Services []string
 | 
						Services       []string
 | 
				
			||||||
	Watch    bool
 | 
						Watch          bool
 | 
				
			||||||
 | 
						NavigationMenu bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RestartOptions group options of the Restart API
 | 
					// RestartOptions group options of the Restart API
 | 
				
			||||||
 | 
				
			|||||||
@ -81,7 +81,7 @@ func (s *composeService) Close() error {
 | 
				
			|||||||
	if s.dockerCli != nil {
 | 
						if s.dockerCli != nil {
 | 
				
			||||||
		errs = append(errs, s.dockerCli.Client().Close())
 | 
							errs = append(errs, s.dockerCli.Client().Close())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if s.desktopCli != nil {
 | 
						if s.isDesktopIntegrationActive() {
 | 
				
			||||||
		errs = append(errs, s.desktopCli.Close())
 | 
							errs = append(errs, s.desktopCli.Close())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return errors.Join(errs...)
 | 
						return errors.Join(errs...)
 | 
				
			||||||
@ -320,3 +320,7 @@ func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) {
 | 
				
			|||||||
	return runtimeVersion.val, runtimeVersion.err
 | 
						return runtimeVersion.val, runtimeVersion.err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *composeService) isDesktopIntegrationActive() bool {
 | 
				
			||||||
 | 
						return s.desktopCli != nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -152,7 +152,7 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := func() error {
 | 
						err := func() error {
 | 
				
			||||||
		if s.experiments.AutoFileShares() && s.desktopCli != nil {
 | 
							if s.experiments.AutoFileShares() && s.isDesktopIntegrationActive() {
 | 
				
			||||||
			// collect all the bind mount paths and try to set up file shares in
 | 
								// collect all the bind mount paths and try to set up file shares in
 | 
				
			||||||
			// Docker Desktop for them
 | 
								// Docker Desktop for them
 | 
				
			||||||
			var paths []string
 | 
								var paths []string
 | 
				
			||||||
 | 
				
			|||||||
@ -145,7 +145,7 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if s.experiments.AutoFileShares() && s.desktopCli != nil {
 | 
						if s.experiments.AutoFileShares() && s.isDesktopIntegrationActive() {
 | 
				
			||||||
		ops = append(ops, func() error {
 | 
							ops = append(ops, func() error {
 | 
				
			||||||
			desktop.RemoveFileSharesForProject(ctx, s.desktopCli, project.Name)
 | 
								desktop.RemoveFileSharesForProject(ctx, s.desktopCli, project.Name)
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
 | 
				
			|||||||
@ -25,9 +25,11 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/compose-spec/compose-go/v2/types"
 | 
						"github.com/compose-spec/compose-go/v2/types"
 | 
				
			||||||
	"github.com/docker/cli/cli"
 | 
						"github.com/docker/cli/cli"
 | 
				
			||||||
 | 
						"github.com/docker/compose/v2/cmd/formatter"
 | 
				
			||||||
	"github.com/docker/compose/v2/internal/tracing"
 | 
						"github.com/docker/compose/v2/internal/tracing"
 | 
				
			||||||
	"github.com/docker/compose/v2/pkg/api"
 | 
						"github.com/docker/compose/v2/pkg/api"
 | 
				
			||||||
	"github.com/docker/compose/v2/pkg/progress"
 | 
						"github.com/docker/compose/v2/pkg/progress"
 | 
				
			||||||
 | 
						"github.com/eiannone/keyboard"
 | 
				
			||||||
	"github.com/hashicorp/go-multierror"
 | 
						"github.com/hashicorp/go-multierror"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,6 +75,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 | 
				
			|||||||
		first := true
 | 
							first := true
 | 
				
			||||||
		gracefulTeardown := func() {
 | 
							gracefulTeardown := func() {
 | 
				
			||||||
			printer.Cancel()
 | 
								printer.Cancel()
 | 
				
			||||||
 | 
								formatter.ClearLine()
 | 
				
			||||||
			fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
 | 
								fmt.Fprintln(s.stdinfo(), "Gracefully stopping... (press Ctrl+C again to force)")
 | 
				
			||||||
			eg.Go(func() error {
 | 
								eg.Go(func() error {
 | 
				
			||||||
				err := s.Stop(context.Background(), project.Name, api.StopOptions{
 | 
									err := s.Stop(context.Background(), project.Name, api.StopOptions{
 | 
				
			||||||
@ -85,6 +88,23 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 | 
				
			|||||||
			})
 | 
								})
 | 
				
			||||||
			first = false
 | 
								first = false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var kEvents <-chan keyboard.KeyEvent
 | 
				
			||||||
 | 
							isWatchConfigured := s.shouldWatch(project)
 | 
				
			||||||
 | 
							isDockerDesktopActive := s.isDesktopIntegrationActive()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, isWatchConfigured)
 | 
				
			||||||
 | 
							if options.Start.NavigationMenu {
 | 
				
			||||||
 | 
								kEvents, err = keyboard.GetKeys(100)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									panic(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								formatter.NewKeyboardManager(ctx, isDockerDesktopActive, isWatchConfigured, signalChan, s.Watch)
 | 
				
			||||||
 | 
								if options.Start.Watch {
 | 
				
			||||||
 | 
									formatter.KeyboardManager.StartWatch(ctx, project, options)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								defer formatter.KeyboardManager.KeyboardClose()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		for {
 | 
							for {
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-doneCh:
 | 
								case <-doneCh:
 | 
				
			||||||
@ -105,6 +125,8 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 | 
				
			|||||||
					})
 | 
										})
 | 
				
			||||||
					return nil
 | 
										return nil
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								case event := <-kEvents:
 | 
				
			||||||
 | 
									formatter.KeyboardManager.HandleKeyEvents(event, ctx, project, options)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@ -124,7 +146,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if options.Start.Watch {
 | 
						if options.Start.Watch && !options.Start.NavigationMenu {
 | 
				
			||||||
		eg.Go(func() error {
 | 
							eg.Go(func() error {
 | 
				
			||||||
			buildOpts := *options.Create.Build
 | 
								buildOpts := *options.Create.Build
 | 
				
			||||||
			buildOpts.Quiet = true
 | 
								buildOpts.Quiet = true
 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,17 @@ func (s *composeService) getSyncImplementation(project *types.Project) (sync.Syn
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return sync.NewTar(project.Name, tarDockerClient{s: s}), nil
 | 
						return sync.NewTar(project.Name, tarDockerClient{s: s}), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (s *composeService) shouldWatch(project *types.Project) bool {
 | 
				
			||||||
 | 
						var shouldWatch bool
 | 
				
			||||||
 | 
						for i := range project.Services {
 | 
				
			||||||
 | 
							service := project.Services[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if service.Develop != nil && service.Develop.Watch != nil {
 | 
				
			||||||
 | 
								shouldWatch = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return shouldWatch
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo
 | 
					func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error { //nolint: gocyclo
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
@ -159,17 +170,15 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
 | 
				
			|||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		watching = true
 | 
							watching = true
 | 
				
			||||||
 | 
					 | 
				
			||||||
		eg.Go(func() error {
 | 
							eg.Go(func() error {
 | 
				
			||||||
			defer watcher.Close() //nolint:errcheck
 | 
								defer watcher.Close() //nolint:errcheck
 | 
				
			||||||
			return s.watch(ctx, project, service.Name, options, watcher, syncer, config.Watch)
 | 
								return s.watch(ctx, project, service.Name, options, watcher, syncer, config.Watch)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !watching {
 | 
						if !watching {
 | 
				
			||||||
		return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section")
 | 
							return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	options.LogTo.Log(api.WatchLogger, "watch enabled")
 | 
						options.LogTo.Log(api.WatchLogger, "Watch enabled")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return eg.Wait()
 | 
						return eg.Wait()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -189,10 +198,12 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, name
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	events := make(chan fileEvent)
 | 
						events := make(chan fileEvent)
 | 
				
			||||||
	batchEvents := batchDebounceEvents(ctx, s.clock, quietPeriod, events)
 | 
						batchEvents := batchDebounceEvents(ctx, s.clock, quietPeriod, events)
 | 
				
			||||||
 | 
						quit := make(chan bool)
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		for {
 | 
							for {
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case <-ctx.Done():
 | 
								case <-ctx.Done():
 | 
				
			||||||
 | 
									quit <- true
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			case batch := <-batchEvents:
 | 
								case batch := <-batchEvents:
 | 
				
			||||||
				start := time.Now()
 | 
									start := time.Now()
 | 
				
			||||||
@ -208,9 +219,11 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, name
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-ctx.Done():
 | 
							case <-quit:
 | 
				
			||||||
 | 
								options.LogTo.Log(api.WatchLogger, "Watch disabled")
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		case err := <-watcher.Errors():
 | 
							case err := <-watcher.Errors():
 | 
				
			||||||
 | 
								options.LogTo.Err(api.WatchLogger, "Watch disabled with errors")
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		case event := <-watcher.Events():
 | 
							case event := <-watcher.Events():
 | 
				
			||||||
			hostPath := event.Path()
 | 
								hostPath := event.Path()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user