diff --git a/builder/build.go b/builder/build.go index 9d26b954..8087ef5a 100644 --- a/builder/build.go +++ b/builder/build.go @@ -4,9 +4,11 @@ package builder import ( + "context" "crypto/md5" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -18,7 +20,7 @@ import ( "sort" "strings" - v1execute "github.com/alexellis/go-execute/pkg/v1" + "github.com/openfaas/faas-cli/execute" "github.com/openfaas/faas-cli/schema" "github.com/openfaas/faas-cli/stack" vcs "github.com/openfaas/faas-cli/versioncontrol" @@ -30,7 +32,7 @@ const AdditionalPackageBuildArg = "ADDITIONAL_PACKAGE" // BuildImage construct Docker image from function parameters // TODO: refactor signature to a struct to simplify the length of the method header -func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string) error { +func BuildImage(ctx context.Context, image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string) error { if stack.IsValidTemplate(language) { pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language) @@ -135,7 +137,7 @@ func BuildImage(image string, handler string, functionName string, language stri envs = append(envs, "DOCKER_BUILDKIT=1") } - task := v1execute.ExecTask{ + task := execute.ExecTask{ Cwd: tempPath, Command: command, Args: args, @@ -143,14 +145,17 @@ func BuildImage(image string, handler string, functionName string, language stri Env: envs, } - res, err := task.Execute() - + res, err := task.Execute(ctx) if err != nil { return err } + if res.ExitCode == -1 && errors.Is(ctx.Err(), context.Canceled) { + return ctx.Err() + } + if res.ExitCode != 0 { - return fmt.Errorf("[%s] received non-zero exit code from build, error: %s", functionName, res.Stderr) + return fmt.Errorf("[%s] received non-zero exit code %d from build, error: %s", functionName, res.ExitCode, res.Stderr) } fmt.Printf("Image: %s built.\n", imageName) diff --git a/commands/build.go b/commands/build.go index e71a241c..a1114832 100644 --- a/commands/build.go +++ b/commands/build.go @@ -4,6 +4,8 @@ package commands import ( + "context" + "errors" "fmt" "log" "os" @@ -151,7 +153,7 @@ func parseBuildArgs(args []string) (map[string]string, error) { } func runBuild(cmd *cobra.Command, args []string) error { - + ctx := cmd.Context() var services stack.Services if len(yamlFile) > 0 { parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter, envsubst) @@ -192,7 +194,9 @@ func runBuild(cmd *cobra.Command, args []string) error { return fmt.Errorf("please provide the deployed --name of your function") } - if err := builder.BuildImage(image, + if err := builder.BuildImage( + ctx, + image, handler, functionName, language, @@ -214,7 +218,12 @@ func runBuild(cmd *cobra.Command, args []string) error { return nil } - errors := build(&services, parallel, shrinkwrap, quietBuild) + // a proper multi-error such as https://github.com/hashicorp/go-multierror + // would be nice here. In the current implementation we are unable to inspect + // the cause of the error, so we opted to just hide context cancel errors, + // but this makes it harder to detect this case upstream in the call stack. + + errors := build(ctx, &services, parallel, shrinkwrap, quietBuild) if len(errors) > 0 { errorSummary := "Errors received during build:\n" for _, err := range errors { @@ -226,10 +235,10 @@ func runBuild(cmd *cobra.Command, args []string) error { return nil } -func build(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool) []error { +func build(ctx context.Context, services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool) []error { startOuter := time.Now() - errors := []error{} + buildErrors := []error{} wg := sync.WaitGroup{} @@ -248,7 +257,9 @@ func build(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool combinedBuildOptions := combineBuildOpts(function.BuildOptions, buildOptions) combinedBuildArgMap := util.MergeMap(function.BuildArgs, buildArgMap) combinedExtraPaths := util.MergeSlice(services.StackConfiguration.CopyExtraPaths, copyExtra) - err := builder.BuildImage(function.Image, + err := builder.BuildImage( + ctx, + function.Image, function.Handler, function.Name, function.Language, @@ -264,9 +275,8 @@ func build(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool remoteBuilder, payloadSecretPath, ) - - if err != nil { - errors = append(errors, err) + if err != nil && !errors.Is(err, context.Canceled) { + buildErrors = append(buildErrors, err) } } @@ -295,7 +305,7 @@ func build(services *stack.Services, queueDepth int, shrinkwrap, quietBuild bool duration := time.Since(startOuter) fmt.Printf("\n%s\n", aec.Apply(fmt.Sprintf("Total build time: %1.2fs", duration.Seconds()), aec.YellowF)) - return errors + return buildErrors } // pullTemplates pulls templates from specified git remote. templateURL may be a pinned repository. diff --git a/commands/faas.go b/commands/faas.go index c888b090..ad2b5af3 100644 --- a/commands/faas.go +++ b/commands/faas.go @@ -4,6 +4,7 @@ package commands import ( + "context" "fmt" "log" "os" @@ -12,6 +13,7 @@ import ( "syscall" "github.com/moby/term" + "github.com/openfaas/faas-cli/contexts" "github.com/openfaas/faas-cli/version" "github.com/spf13/cobra" ) @@ -74,6 +76,9 @@ func init() { func Execute(customArgs []string) { checkAndSetDefaultYaml() + ctx, cancel := contexts.WithSignals(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + defer cancel() + faasCmd.SilenceUsage = true faasCmd.SilenceErrors = true faasCmd.SetArgs(customArgs[1:]) @@ -105,7 +110,7 @@ func Execute(customArgs []string) { } } - if err := faasCmd.Execute(); err != nil { + if err := faasCmd.ExecuteContext(ctx); err != nil { e := err.Error() fmt.Println(strings.ToUpper(e[:1]) + e[1:]) os.Exit(1) diff --git a/commands/local_run.go b/commands/local_run.go index 024636d7..a18ce441 100644 --- a/commands/local_run.go +++ b/commands/local_run.go @@ -8,16 +8,14 @@ import ( "os" "path/filepath" "strings" - "syscall" "os/exec" - "os/signal" "github.com/openfaas/faas-cli/builder" + "github.com/openfaas/faas-cli/logger" "github.com/openfaas/faas-cli/schema" "github.com/openfaas/faas-cli/stack" "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" ) const localSecretsDir = ".secrets" @@ -102,15 +100,20 @@ func runLocalRunE(cmd *cobra.Command, args []string) error { return watchLoop(cmd, args, localRunExec) } - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - return localRunExec(cmd, args, ctx) + return localRunExec(cmd, args) } -func localRunExec(cmd *cobra.Command, args []string, ctx context.Context) error { +func localRunExec(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() if opts.build { + log.Printf("[Local-Run] Building function") if err := localBuild(cmd, args); err != nil { + if err == context.Canceled { + log.Printf("[Local-Run] Context cancelled, build cancelled") + return nil + } + + logger.Debugf("[Local-Run] Error building function: %s", err.Error()) return err } } @@ -123,6 +126,13 @@ func localRunExec(cmd *cobra.Command, args []string, ctx context.Context) error name = args[0] } + // In watch mode, it is possible that runFunction might be invoked after a cancelled build. + if ctx.Err() != nil { + log.Printf("[Local-Run] Context cancelled, skipping run") + return nil + } + + logger.Debugf("[Local-Run] Starting execution: %s", name) return runFunction(ctx, name, opts, args) } @@ -204,63 +214,32 @@ func runFunction(ctx context.Context, name string, opts runOptions, args []strin fmt.Fprintf(opts.output, "%s\n", cmd.String()) return nil } - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - cmd.Stdout = opts.output cmd.Stderr = opts.err fmt.Printf("Starting local-run for: %s on: http://0.0.0.0:%d\n\n", name, opts.port) - grpContext := context.Background() - grpContext, cancel := context.WithCancel(grpContext) - defer cancel() - - errGrp, _ := errgroup.WithContext(grpContext) - - errGrp.Go(func() error { - if err = cmd.Start(); err != nil { - return err - } - - if err := cmd.Wait(); err != nil { - if strings.Contains(err.Error(), "signal: killed") { - return nil - } else if strings.Contains(err.Error(), "os: process already finished") { - return nil - } + // Always try to remove the container + defer removeContainer(name) - return err - } - return nil - }) + if err = cmd.Start(); err != nil { + return err + } - // Always try to remove the container - defer func() { - removeContainer(name) - }() - - errGrp.Go(func() error { - - select { - case <-sigs: - log.Printf("Caught signal, exiting") - cancel() - case <-ctx.Done(): - log.Printf("Context cancelled, exiting..") - cancel() + if err := cmd.Wait(); err != nil { + if strings.Contains(err.Error(), "signal: killed") { + return nil + } else if strings.Contains(err.Error(), "os: process already finished") { + return nil } - return nil - }) - return errGrp.Wait() + return err + } + return nil } func removeContainer(name string) { - runDockerRm := exec.Command("docker", "rm", "-f", name) runDockerRm.Run() - } // buildDockerRun constructs a exec.Cmd from the given stack Function diff --git a/commands/up.go b/commands/up.go index 6c4544bd..7e4da5a3 100644 --- a/commands/up.go +++ b/commands/up.go @@ -5,7 +5,6 @@ package commands import ( "bufio" - "context" "fmt" "os" "strings" @@ -89,9 +88,9 @@ func preRunUp(cmd *cobra.Command, args []string) error { func upHandler(cmd *cobra.Command, args []string) error { if watch { - return watchLoop(cmd, args, func(cmd *cobra.Command, args []string, ctx context.Context) error { + return watchLoop(cmd, args, func(cmd *cobra.Command, args []string) error { - if err := upRunner(cmd, args, ctx); err != nil { + if err := upRunner(cmd, args); err != nil { return err } fmt.Println("[Watch] Change a file to trigger a rebuild...") @@ -99,11 +98,10 @@ func upHandler(cmd *cobra.Command, args []string) error { }) } - ctx := context.Background() - return upRunner(cmd, args, ctx) + return upRunner(cmd, args) } -func upRunner(cmd *cobra.Command, args []string, ctx context.Context) error { +func upRunner(cmd *cobra.Command, args []string) error { if usePublish { if err := runPublish(cmd, args); err != nil { return err diff --git a/commands/watch.go b/commands/watch.go index 80012af1..581d5c9b 100644 --- a/commands/watch.go +++ b/commands/watch.go @@ -3,25 +3,27 @@ package commands import ( "context" "fmt" + "io/fs" "log" "os" - "os/signal" "path" "path/filepath" "strings" - "syscall" + "sync" "time" "github.com/bep/debounce" "github.com/fsnotify/fsnotify" "github.com/go-git/go-git/v5/plumbing/format/gitignore" + "github.com/openfaas/faas-cli/logger" "github.com/openfaas/faas-cli/stack" "github.com/spf13/cobra" ) // watchLoop will watch for changes to function handler files and the stack.yml // then call onChange when a change is detected -func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Command, args []string, ctx context.Context) error) error { +func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Command, args []string) error) error { + mainCtx := cmd.Context() var services stack.Services if len(yamlFile) > 0 { @@ -42,8 +44,6 @@ func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Comma fmt.Printf("[Watch] monitoring %d functions: %s\n", len(fnNames), strings.Join(fnNames, ", ")) - canceller := Cancel{} - watcher, err := fsnotify.NewWatcher() if err != nil { return err @@ -63,12 +63,7 @@ func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Comma } yamlPath := path.Join(cwd, yamlFile) - debug := os.Getenv("FAAS_DEBUG") - - if debug == "1" { - fmt.Printf("[Watch] added: %s\n", yamlPath) - } - + logger.Debugf("[Watch] added: %s\n", yamlPath) watcher.Add(yamlPath) // map to determine which function belongs to changed files @@ -85,23 +80,29 @@ func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Comma } } - signalChannel := make(chan os.Signal, 1) + bounce := debounce.New(1500 * time.Millisecond) - // Exit on Ctrl+C or kill - signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) + onChangeCtx, onChangeCancel := context.WithCancel(mainCtx) + defer onChangeCancel() - bounce := debounce.New(1500 * time.Millisecond) + // the WaitGroup is used to enable the watch+debounce to easily wait for + // each onChange invocation to complete or fully cancel before starting + // the next one. Without this, because the `cmd` is a shared pointer instead + // of a value, when we changed the onChangeCtx, it would propogate to the + // currently cancelling onChange invocation. If this handler contains many + // steps, it would be possible for it continue with the new context. + // This was seen in the local-run, the build would cancel but not return, + // so it would try to run the just aborted build and produce errors. + var wg sync.WaitGroup + wg.Add(1) go func() { + defer wg.Done() // An initial build is usually done on first load with // live reloaders - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - canceller.Set(ctx, cancel) - - if err := onChange(cmd, args, ctx); err != nil { - fmt.Println("Error rebuilding: ", err) - os.Exit(1) + cmd.SetContext(onChangeCtx) + if err := onChange(cmd, args); err != nil { + fmt.Println("Error on initial run: ", err) } }() @@ -113,79 +114,70 @@ func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Comma return fmt.Errorf("watcher's Events channel is closed") } - if debug == "1" { - log.Printf("[Watch] event: %s on: %s", strings.ToLower(event.Op.String()), event.Name) - } - if strings.HasSuffix(event.Name, ".swp") || strings.HasSuffix(event.Name, "~") || strings.HasSuffix(event.Name, ".swx") { + logger.Debugf("[Watch] event: %s on: %s", strings.ToLower(event.Op.String()), event.Name) + + info, trigger := shouldTrigger(event) + if !trigger { continue } - if event.Op == fsnotify.Write || event.Op == fsnotify.Create || event.Op == fsnotify.Remove || event.Op == fsnotify.Rename { - - info, err := os.Stat(event.Name) - if err != nil { - continue - } - ignore := false - if matcher.Match(strings.Split(event.Name, "/"), info.IsDir()) { - ignore = true + // exact match first + target := "" + for fnName, fnPath := range handlerMap { + if event.Name == fnPath { + target = fnName } + } - // exact match first - target := "" + // fuzzy match after, if none matched exactly + if target == "" { for fnName, fnPath := range handlerMap { - if event.Name == fnPath { + + if strings.HasPrefix(event.Name, fnPath) { target = fnName } } + } - // fuzzy match after, if none matched exactly - if target == "" { - for fnName, fnPath := range handlerMap { - - if strings.HasPrefix(event.Name, fnPath) { - target = fnName - } - } + // New sub-directory added for a function, start tracking it + if event.Op == fsnotify.Create && info.IsDir() && target != "" { + if err := addPath(watcher, event.Name); err != nil { + return err } + } - // New sub-directory added for a function, start tracking it - if event.Op == fsnotify.Create && info.IsDir() && target != "" { - if err := addPath(watcher, event.Name); err != nil { - return err - } - } - - if !ignore { - if target == "" { - fmt.Printf("[Watch] Rebuilding %d functions reason: %s to %s\n", len(fnNames), strings.ToLower(event.Op.String()), event.Name) - } else { - fmt.Printf("[Watch] Reloading %s reason: %s %s\n", target, strings.ToLower(event.Op.String()), event.Name) - } - - bounce(func() { - log.Printf("[Watch] Cancelling") + // now check if the file is ignored and should not trigger the onChange + if matcher.Match(strings.Split(event.Name, "/"), info.IsDir()) { + continue + } - canceller.Cancel() + if target == "" { + fmt.Printf("[Watch] Rebuilding %d functions reason: %s to %s\n", len(fnNames), strings.ToLower(event.Op.String()), event.Name) + } else { + fmt.Printf("[Watch] Reloading %s reason: %s %s\n", target, strings.ToLower(event.Op.String()), event.Name) + } - log.Printf("[Watch] Cancelled") - ctx, cancel := context.WithCancel(context.Background()) - canceller.Set(ctx, cancel) + bounce(func() { + log.Printf("[Watch] Cancelling") + onChangeCancel() + wg.Wait() - // Assign --filter to "" for all functions if we can't determine the - // changed function to direct the calls to build/push/deploy - filter = target + log.Printf("[Watch] Cancelled") + onChangeCtx, onChangeCancel = context.WithCancel(mainCtx) + cmd.SetContext(onChangeCtx) - go func() { - if err := onChange(cmd, args, ctx); err != nil { - fmt.Println("Error rebuilding: ", err) - os.Exit(1) - } - }() - }) - } + // Assign --filter to "" for all functions if we can't determine the + // changed function to direct the calls to build/push/deploy + filter = target - } + wg.Add(1) + go func() { + defer wg.Done() + if err := onChange(cmd, args); err != nil { + fmt.Println("Error on change: ", err) + } + }() + }) case err, ok := <-watcher.Errors: if !ok { @@ -193,18 +185,35 @@ func watchLoop(cmd *cobra.Command, args []string, onChange func(cmd *cobra.Comma } return err - case <-signalChannel: + case <-mainCtx.Done(): watcher.Close() return nil } } +} + +// shouldTrigger returns true if the event should trigger a rebuild. This currently +// includes create, write, remove, and rename events. +func shouldTrigger(event fsnotify.Event) (fs.FileInfo, bool) { + // skip temp and swap files + if strings.HasSuffix(event.Name, ".swp") || strings.HasSuffix(event.Name, "~") || strings.HasSuffix(event.Name, ".swx") { + return nil, false + } + + // only trigger for content changes, this skips chmod, chown, etc. + if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) { + info, err := os.Stat(event.Name) + if err != nil { + return nil, false + } - return nil + return info, true + } + + return nil, false } func addPath(watcher *fsnotify.Watcher, rootPath string) error { - debug := os.Getenv("FAAS_DEBUG") - return filepath.WalkDir(rootPath, func(subPath string, d os.DirEntry, err error) error { if err != nil { return err @@ -215,31 +224,10 @@ func addPath(watcher *fsnotify.Watcher, rootPath string) error { return fmt.Errorf("unable to watch %s: %s", subPath, err) } - if debug == "1" { - fmt.Printf("[Watch] added: %s\n", subPath) - } + logger.Debugf("[Watch] added: %s\n", subPath) } return nil }) } - -// Cancel is a struct to hold a reference to a context and -// cancellation function between closures -type Cancel struct { - cancel context.CancelFunc - ctx context.Context -} - -func (c *Cancel) Set(ctx context.Context, cancel context.CancelFunc) { - c.cancel = cancel - c.ctx = ctx -} - -func (c *Cancel) Cancel() { - if c.cancel != nil { - c.cancel() - } - -} diff --git a/contexts/signals.go b/contexts/signals.go new file mode 100644 index 00000000..c619ece0 --- /dev/null +++ b/contexts/signals.go @@ -0,0 +1,32 @@ +package contexts + +import ( + "context" + "log" + "os" + "os/signal" +) + +// WithSignals returns a context that is canceled when the process receives one of the given signals. +// When ctx is nil, a default Background context is used. +// When signals is empty, the context will be canceled by the default os.Interrupt signal. +func WithSignals(ctx context.Context, signals ...os.Signal) (context.Context, context.CancelFunc) { + if ctx == nil { + ctx = context.Background() + } + + if len(signals) == 0 { + signals = []os.Signal{os.Interrupt} + } + + ctx, cancel := context.WithCancel(ctx) + c := make(chan os.Signal, 1) + signal.Notify(c, signals...) + go func() { + sig := <-c + log.Printf("Received signal: %q. Terminating...", sig.String()) + cancel() + }() + + return ctx, cancel +} diff --git a/execute/exec.go b/execute/exec.go new file mode 100644 index 00000000..d7fb057e --- /dev/null +++ b/execute/exec.go @@ -0,0 +1,144 @@ +package execute + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" +) + +const ExitCodeCancelled = -1 + +type ExecTask struct { + Command string + Args []string + Shell bool + Env []string + Cwd string + + // Stdin connect a reader to stdin for the command + // being executed. + Stdin io.Reader + + // StreamStdio prints stdout and stderr directly to os.Stdout/err as + // the command runs. + StreamStdio bool + + // PrintCommand prints the command before executing + PrintCommand bool +} + +type ExecResult struct { + // Stdout contains the stdout content from the command + Stdout string + // Stderr contains the stderr content from the command + Stderr string + // ExitCode will be the exit code of the command, + // or -1 if the command never started or was cancelled. + ExitCode int + // Cancelled indicates if the command context was cancelled + // this can be used to interpret the ExitCode. + Cancelled bool +} + +func (et ExecTask) Execute(ctx context.Context) (ExecResult, error) { + argsSt := "" + if len(et.Args) > 0 { + argsSt = strings.Join(et.Args, " ") + } + + if et.PrintCommand { + fmt.Println("exec: ", et.Command, argsSt) + } + + var cmd *exec.Cmd + + if et.Shell { + var args []string + if len(et.Args) == 0 { + startArgs := strings.Split(et.Command, " ") + script := strings.Join(startArgs, " ") + args = append([]string{"-c"}, fmt.Sprintf("%s", script)) + + } else { + script := strings.Join(et.Args, " ") + args = append([]string{"-c"}, fmt.Sprintf("%s %s", et.Command, script)) + + } + + cmd = exec.CommandContext(ctx, "/bin/bash", args...) + } else { + if strings.Index(et.Command, " ") > 0 { + parts := strings.Split(et.Command, " ") + command := parts[0] + args := parts[1:] + cmd = exec.CommandContext(ctx, command, args...) + + } else { + cmd = exec.CommandContext(ctx, et.Command, et.Args...) + } + } + + cmd.Dir = et.Cwd + + if len(et.Env) > 0 { + overrides := map[string]bool{} + for _, env := range et.Env { + key := strings.Split(env, "=")[0] + overrides[key] = true + cmd.Env = append(cmd.Env, env) + } + + for _, env := range os.Environ() { + key := strings.Split(env, "=")[0] + + if _, ok := overrides[key]; !ok { + cmd.Env = append(cmd.Env, env) + } + } + } + if et.Stdin != nil { + cmd.Stdin = et.Stdin + } + + stdoutBuff := bytes.Buffer{} + stderrBuff := bytes.Buffer{} + + var stdoutWriters io.Writer + var stderrWriters io.Writer + + if et.StreamStdio { + stdoutWriters = io.MultiWriter(os.Stdout, &stdoutBuff) + stderrWriters = io.MultiWriter(os.Stderr, &stderrBuff) + } else { + stdoutWriters = &stdoutBuff + stderrWriters = &stderrBuff + } + + cmd.Stdout = stdoutWriters + cmd.Stderr = stderrWriters + + startErr := cmd.Start() + + if startErr != nil { + return ExecResult{}, startErr + } + + exitCode := 0 + execErr := cmd.Wait() + if execErr != nil { + if exitError, ok := execErr.(*exec.ExitError); ok { + exitCode = exitError.ExitCode() + } + } + + return ExecResult{ + Stdout: stdoutBuff.String(), + Stderr: stderrBuff.String(), + ExitCode: exitCode, + Cancelled: ctx.Err() == context.Canceled, + }, nil +} diff --git a/logger/logging.go b/logger/logging.go new file mode 100644 index 00000000..1c2445c5 --- /dev/null +++ b/logger/logging.go @@ -0,0 +1,36 @@ +// logger should immediately be replaced by https://pkg.go.dev/log/slog@master +// when the project upgrades to Go 1.21 +package logger + +import ( + "log" + "os" +) + +var debug = false + +func init() { + if os.Getenv("FAAS_DEBUG") == "1" { + debug = true + } +} + +func Debug(message string) { + if debug { + log.Println(message) + } +} + +func Debugf(format string, v ...interface{}) { + if debug { + log.Printf(format, v...) + } +} + +func Print(message string) { + log.Println(message) +} + +func Printf(format string, v ...interface{}) { + log.Printf(format, v...) +} diff --git a/proxy/logs.go b/proxy/logs.go index 17305122..a7b00109 100644 --- a/proxy/logs.go +++ b/proxy/logs.go @@ -9,10 +9,10 @@ import ( "log" "net/http" "net/url" - "os" "strconv" "time" + "github.com/openfaas/faas-cli/logger" "github.com/openfaas/faas-provider/logs" ) @@ -26,9 +26,7 @@ func (c *Client) GetLogs(ctx context.Context, params logs.Request) (<-chan logs. logRequest.URL.RawQuery = reqAsQueryValues(params).Encode() - if os.Getenv("FAAS_DEBUG") == "1" { - fmt.Printf("%s\n", logRequest.URL.RawQuery) - } + logger.Debugf("%s\n", logRequest.URL.RawQuery) res, err := c.doRequest(ctx, logRequest) if err != nil {