From 5d7027dc3fc6a16e2023ffa1ebac93f1c667786e Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 22 Mar 2022 20:26:10 +0100 Subject: [PATCH] feat: add bug-report flag (#1056) * feat: add bug-report flag * fix: use docker host CPU count * feat: add config files to bug-report Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- cmd/root.go | 98 +++++++++++++++++++++++++++++++++++-- pkg/container/docker_run.go | 21 ++++++-- pkg/runner/runner.go | 16 ++++-- 3 files changed, 124 insertions(+), 11 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ac575955..9567a422 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "bufio" "context" + "fmt" "os" "path/filepath" "regexp" @@ -19,6 +20,7 @@ import ( "github.com/nektos/act/pkg/artifacts" "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/model" "github.com/nektos/act/pkg/runner" ) @@ -39,6 +41,8 @@ func Execute(ctx context.Context, version string) { rootCmd.Flags().BoolP("list", "l", false, "list workflows") rootCmd.Flags().BoolP("graph", "g", false, "draw workflows") rootCmd.Flags().StringP("job", "j", "", "run job") + rootCmd.Flags().BoolP("bug-report", "", false, "Display system information for bug report") + rootCmd.Flags().StringArrayVarP(&input.secrets, "secret", "s", []string{}, "secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)") rootCmd.Flags().StringArrayVarP(&input.envs, "env", "", []string{}, "env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)") rootCmd.Flags().StringArrayVarP(&input.platforms, "platform", "P", []string{}, "custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)") @@ -105,14 +109,94 @@ func args() []string { args := make([]string, 0) for _, f := range actrc { - args = append(args, readArgsFile(f)...) + args = append(args, readArgsFile(f, true)...) } args = append(args, os.Args[1:]...) return args } -func readArgsFile(file string) []string { +func bugReport(ctx context.Context, version string) error { + var commonSocketPaths = []string{ + "/var/run/docker.sock", + "/var/run/podman/podman.sock", + "$HOME/.colima/docker.sock", + "$XDG_RUNTIME_DIR/docker.sock", + `\\.\pipe\docker_engine`, + } + + sprintf := func(key, val string) string { + return fmt.Sprintf("%-24s%s\n", key, val) + } + + report := sprintf("act version:", version) + report += sprintf("GOOS:", runtime.GOOS) + report += sprintf("GOARCH:", runtime.GOARCH) + report += sprintf("NumCPU:", fmt.Sprint(runtime.NumCPU())) + + var dockerHost string + if dockerHost = os.Getenv("DOCKER_HOST"); dockerHost == "" { + dockerHost = "DOCKER_HOST environment variable is unset/empty." + } + + report += sprintf("Docker host:", dockerHost) + report += fmt.Sprintln("Sockets found:") + for _, p := range commonSocketPaths { + if strings.HasPrefix(p, `$`) { + v := strings.Split(p, `/`)[0] + p = strings.Replace(p, v, os.Getenv(strings.TrimPrefix(v, `$`)), 1) + } + if _, err := os.Stat(p); err != nil { + continue + } else { + report += fmt.Sprintf("\t%s\n", p) + } + } + + info, err := container.GetHostInfo(ctx) + if err != nil { + fmt.Println(report) + return err + } + + report += sprintf("Config files:", "") + for _, c := range configLocations() { + args := readArgsFile(c, false) + if len(args) > 0 { + report += fmt.Sprintf("\t%s:\n", c) + for _, l := range args { + report += fmt.Sprintf("\t\t%s\n", l) + } + } + } + + report += fmt.Sprintln("Docker Engine:") + + report += sprintf("\tEngine version:", info.ServerVersion) + report += sprintf("\tEngine runtime:", info.DefaultRuntime) + report += sprintf("\tCgroup version:", info.CgroupVersion) + report += sprintf("\tCgroup driver:", info.CgroupDriver) + report += sprintf("\tStorage driver:", info.Driver) + report += sprintf("\tRegistry URI:", info.IndexServerAddress) + + report += sprintf("\tOS:", info.OperatingSystem) + report += sprintf("\tOS type:", info.OSType) + report += sprintf("\tOS version:", info.OSVersion) + report += sprintf("\tOS arch:", info.Architecture) + report += sprintf("\tOS kernel:", info.KernelVersion) + report += sprintf("\tOS CPU:", fmt.Sprint(info.NCPU)) + report += sprintf("\tOS memory:", fmt.Sprintf("%d MB", info.MemTotal/1024/1024)) + + report += fmt.Sprintln("\tSecurity options:") + for _, secopt := range info.SecurityOptions { + report += fmt.Sprintf("\t\t%s\n", secopt) + } + + fmt.Println(report) + return nil +} + +func readArgsFile(file string, split bool) []string { args := make([]string, 0) f, err := os.Open(file) if err != nil { @@ -127,8 +211,10 @@ func readArgsFile(file string) []string { scanner := bufio.NewScanner(f) for scanner.Scan() { arg := strings.TrimSpace(scanner.Text()) - if strings.HasPrefix(arg, "-") { + if strings.HasPrefix(arg, "-") && split { args = append(args, regexp.MustCompile(`\s`).Split(arg, 2)...) + } else if !split { + args = append(args, arg) } } return args @@ -162,6 +248,10 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str log.SetFormatter(&log.JSONFormatter{}) } + if ok, _ := cmd.Flags().GetBool("bug-report"); ok { + return bugReport(ctx, cmd.Version) + } + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && input.containerArchitecture == "" { l := log.New() l.SetFormatter(&log.TextFormatter{ @@ -256,7 +346,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str if err := defaultImageSurvey(cfgLocations[0]); err != nil { log.Fatal(err) } - input.platforms = readArgsFile(cfgLocations[0]) + input.platforms = readArgsFile(cfgLocations[0], true) } } diff --git a/pkg/container/docker_run.go b/pkg/container/docker_run.go index 9fb8123e..cc8335dc 100644 --- a/pkg/container/docker_run.go +++ b/pkg/container/docker_run.go @@ -201,10 +201,7 @@ type containerReference struct { input *NewContainerInput } -func GetDockerClient(ctx context.Context) (*client.Client, error) { - var err error - var cli *client.Client - +func GetDockerClient(ctx context.Context) (cli *client.Client, err error) { // TODO: this should maybe need to be a global option, not hidden in here? // though i'm not sure how that works out when there's another Executor :D // I really would like something that works on OSX native for eg @@ -232,6 +229,22 @@ func GetDockerClient(ctx context.Context) (*client.Client, error) { return cli, err } +func GetHostInfo(ctx context.Context) (info types.Info, err error) { + var cli *client.Client + cli, err = GetDockerClient(ctx) + if err != nil { + return info, err + } + defer cli.Close() + + info, err = cli.Info(ctx) + if err != nil { + return info, err + } + + return info, nil +} + func (cr *containerReference) connect() common.Executor { return func(ctx context.Context) error { if cr.cli != nil { diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 5c9174c6..105227b2 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -9,9 +9,11 @@ import ( "runtime" "strings" - "github.com/nektos/act/pkg/common" - "github.com/nektos/act/pkg/model" log "github.com/sirupsen/logrus" + + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/container" + "github.com/nektos/act/pkg/model" ) // Runner provides capabilities to run GitHub actions @@ -171,7 +173,15 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor { } pipeline = append(pipeline, common.NewParallelExecutor(maxParallel, stageExecutor...)) } - return common.NewParallelExecutor(runtime.NumCPU(), pipeline...)(ctx) + var ncpu int + info, err := container.GetHostInfo(ctx) + if err != nil { + log.Errorf("failed to obtain container engine info: %s", err) + ncpu = 1 // sane default? + } else { + ncpu = info.NCPU + } + return common.NewParallelExecutor(ncpu, pipeline...)(ctx) }) }