~bigbes/ci-cacher

ref: v0.2.0 ci-cacher/cmd/exec.go -rw-r--r-- 2.1 KiB
f26ddc0e — Eugene Blikh Bump VERSION to 0.2.0 for tag 11 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package cmd

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"

	"github.com/spf13/cobra"
)

// Shared by every download subcommand. Only one command runs per
// process, so package-level flag vars are safe (same convention as
// flagForce).
var (
	flagExec     string
	flagOptional bool
)

// addExecFlag attaches --exec. produces describes what the script is
// expected to leave behind, so each subcommand's help text is concrete.
func addExecFlag(c *cobra.Command, produces string) {
	// The backquoted word sets the value placeholder cobra prints in
	// --help ("--exec script"), so keep exactly one of them.
	c.Flags().StringVar(&flagExec, "exec", "",
		"on cache miss, run this `script` through sh -c (it must produce "+produces+") and seed the cache with the result")
}

// addOptionalFlag attaches --optional: a miss stops being an error, so
// `set -e` scripts don't need a trailing `|| echo "cache miss"`.
func addOptionalFlag(c *cobra.Command) {
	c.Flags().BoolVar(&flagOptional, "optional", false,
		"treat a cache miss as success (exit 0) instead of an error")
}

// runExecFallback runs the --exec script through `sh -c` with stdio
// inherited, so its output lands in the CI log in place, unbuffered and
// in order. A non-zero exit is propagated verbatim by exitCodeFor —
// the script's own status is more informative than cacher's.
func runExecFallback(ctx context.Context, w *cobra.Command, script string) error {
	fmt.Fprintf(w.ErrOrStderr(), "Running  — sh -c %q\n", script)
	c := exec.CommandContext(ctx, "sh", "-c", script)
	c.Stdin = os.Stdin
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	if err := c.Run(); err != nil {
		var ee *exec.ExitError
		if errors.As(err, &ee) {
			return &execError{code: ee.ExitCode(), err: err}
		}
		return fmt.Errorf("--exec: %w", err)
	}
	return nil
}

// execError carries the --exec script's own exit status up to Execute.
type execError struct {
	code int
	err  error
}

func (e *execError) Error() string { return fmt.Sprintf("--exec script failed: %v", e.err) }
func (e *execError) Unwrap() error { return e.err }
func (e *execError) ExitCode() int { return e.code }