• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

umputun / updater / 21417771743

27 Jan 2026 11:06PM UTC coverage: 84.783% (-0.3%) from 85.088%
21417771743

push

github

umputun
add release workflow, modernize goreleaser config

- add release.yml workflow using goreleaser-action
- update .goreleaser.yml to v2 format
- remove obsolete Dockerfile.artifacts
- simplify Makefile dist target to use goreleaser directly

195 of 230 relevant lines covered (84.78%)

3.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.58
/app/task/shell_runner.go
1
package task
2

3
import (
4
        "bytes"
5
        "context"
6
        "fmt"
7
        "io"
8
        "os"
9
        "os/exec"
10
        "strings"
11
        "sync"
12
        "time"
13

14
        log "github.com/go-pkgz/lgr"
15
        "github.com/hashicorp/go-multierror"
16
        "github.com/pkg/errors"
17
)
18

19
// ShellRunner executes commands with shell
20
type ShellRunner struct {
21
        BatchMode bool
22
        Limiter   sync.Locker
23
        TimeOut   time.Duration
24
}
25

26
// Run command in shell with provided logger
27
func (s *ShellRunner) Run(ctx context.Context, command string, logWriter io.Writer) error {
8✔
28
        if command == "" {
8✔
29
                return nil
×
30
        }
×
31

32
        if s.Limiter != nil {
8✔
33
                s.Limiter.Lock()
×
34
                defer s.Limiter.Unlock()
×
35
        }
×
36

37
        command = strings.TrimSpace(command)
8✔
38
        if s.BatchMode {
10✔
39
                batchFile, err := s.prepBatch(command)
2✔
40
                if err != nil {
2✔
41
                        return fmt.Errorf("can't prepare batch: %w", err)
×
42
                }
×
43
                return s.runBatch(batchFile, logWriter, s.TimeOut)
2✔
44
        }
45

46
        execCmd := func(command string) error {
15✔
47
                log.Printf("[INFO] execute %q", command)
9✔
48
                var suppressError bool
9✔
49
                if command[0] == '@' {
11✔
50
                        command = command[1:]
2✔
51
                        suppressError = true
2✔
52
                        log.Printf("[DEBUG] suppress error for %s", command)
2✔
53
                }
2✔
54
                cmd := exec.CommandContext(ctx, "sh", "-c", command)
9✔
55
                cmd.Stdout = logWriter
9✔
56
                cmd.Stderr = logWriter
9✔
57
                cmd.Stdin = os.Stdin
9✔
58
                if err := cmd.Run(); err != nil {
13✔
59
                        if suppressError {
6✔
60
                                log.Printf("[WARN] suppressed error executing %q, %v", command, err)
2✔
61
                                return nil
2✔
62
                        }
2✔
63
                        return fmt.Errorf("failed to execute %s: %w", command, err)
2✔
64
                }
65
                return nil
5✔
66
        }
67

68
        for c := range strings.SplitSeq(command, "\n") {
15✔
69
                if c = strings.TrimSpace(c); c == "" {
9✔
70
                        continue
×
71
                }
72
                if err := execCmd(c); err != nil {
11✔
73
                        return err
2✔
74
                }
2✔
75
        }
76

77
        return nil
4✔
78
}
79

80
func (s *ShellRunner) runBatch(batchFile string, logWriter io.Writer, timeout time.Duration) error {
2✔
81
        ctx, cancel := context.WithTimeout(context.Background(), timeout)
2✔
82
        defer func() {
4✔
83
                cancel()
2✔
84
                if e := os.Remove(batchFile); e != nil {
2✔
85
                        log.Printf("[WARN] can't remove temp batch file %s, %v", batchFile, e)
×
86
                }
×
87
        }()
88
        cmd := exec.CommandContext(ctx, "sh", batchFile)
2✔
89
        cmd.Stdout = logWriter
2✔
90
        cmd.Stderr = logWriter
2✔
91
        cmd.Stdin = os.Stdin
2✔
92
        log.Printf("[DEBUG] executing batch commands: %s", batchFile)
2✔
93

2✔
94
        return cmd.Run()
2✔
95
}
96

97
func (s *ShellRunner) prepBatch(cmd string) (batchFile string, err error) {
2✔
98
        lines := strings.Split(cmd, "\n")
2✔
99
        script := make([]string, 0, len(lines)+1)
2✔
100
        script = append(script, "#!/bin/sh")
2✔
101
        script = append(script, lines...)
2✔
102
        fh, e := os.CreateTemp("/tmp", "updater")
2✔
103
        if e != nil {
2✔
104
                return "", errors.Wrap(e, "failed to prep batch")
×
105
        }
×
106
        defer func() {
4✔
107
                errs := new(multierror.Error)
2✔
108
                fname := fh.Name()
2✔
109
                errs = multierror.Append(errs, fh.Sync())
2✔
110
                errs = multierror.Append(errs, fh.Close())
2✔
111
                errs = multierror.Append(errs, os.Chmod(fname, 0o755)) //nolint:gosec // batch file must be executable
2✔
112
                if errs.ErrorOrNil() != nil {
2✔
113
                        log.Printf("[WARN] can't properly close %s, %v", fname, errs.Error())
×
114
                }
×
115
        }()
116

117
        buff := bytes.NewBufferString(strings.Join(script, "\n"))
2✔
118
        _, err = io.Copy(fh, buff)
2✔
119
        return fh.Name(), errors.Wrapf(err, "failed to write to %s", fh.Name())
2✔
120
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc