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

mendersoftware / mender-artifact / 1884466719

23 Jun 2025 01:31PM UTC coverage: 76.598% (-0.08%) from 76.677%
1884466719

push

gitlab-ci

web-flow
Merge pull request #703 from michalkopczan/MEN-8382_new-mender-artifact_fails_on_calling_mender-update_show-provides

Men 8382 mender artifact fails on calling mender update show provides

0 of 157 new or added lines in 2 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

5921 of 7730 relevant lines covered (76.6%)

132.65 hits per line

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

0.0
/cli/util/ssh.go
1
// Copyright 2025 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package util
16

17
import (
18
        "bufio"
19
        "context"
20
        "io"
21
        "os"
22
        "os/exec"
23
        "os/signal"
24
        "strings"
25
        "syscall"
26
        "time"
27

28
        "github.com/pkg/errors"
29
        "github.com/urfave/cli"
30
)
31

32
type SSHCommand struct {
33
        Cmd     *exec.Cmd
34
        ctx     context.Context
35
        Stdout  io.ReadCloser
36
        cancel  context.CancelFunc
37
        sigChan chan os.Signal
38
        errChan chan error
39
}
40

41
func StartSSHCommand(c *cli.Context,
42
        _ctx context.Context,
43
        cancel context.CancelFunc,
44
        command string,
45
        sshConnectedToken string,
NEW
46
) (*SSHCommand, error) {
×
NEW
47

×
NEW
48
        var userAtHost string
×
NEW
49
        var sigChan chan os.Signal
×
NEW
50
        var errChan chan error
×
NEW
51
        port := "22"
×
NEW
52
        host := strings.TrimPrefix(c.String("file"), "ssh://")
×
NEW
53

×
NEW
54
        if remotePort := strings.Split(host, ":"); len(remotePort) == 2 {
×
NEW
55
                port = remotePort[1]
×
NEW
56
                userAtHost = remotePort[0]
×
NEW
57
        } else {
×
NEW
58
                userAtHost = host
×
NEW
59
        }
×
60

NEW
61
        args := c.StringSlice("ssh-args")
×
NEW
62
        // Check if port is specified explicitly with the --ssh-args flag
×
NEW
63
        addPort := true
×
NEW
64
        for _, arg := range args {
×
NEW
65
                if strings.Contains(arg, "-p") {
×
NEW
66
                        addPort = false
×
NEW
67
                        break
×
68
                }
69
        }
NEW
70
        if addPort {
×
NEW
71
                args = append(args, "-p", port)
×
NEW
72
        }
×
NEW
73
        args = append(args, userAtHost)
×
NEW
74
        args = append(
×
NEW
75
                args,
×
NEW
76
                "/bin/sh",
×
NEW
77
                "-c",
×
NEW
78
                command)
×
NEW
79

×
NEW
80
        cmd := exec.Command("ssh", args...)
×
NEW
81

×
NEW
82
        // Simply connect stdin/stderr
×
NEW
83
        cmd.Stdin = os.Stdin
×
NEW
84
        cmd.Stderr = os.Stderr
×
NEW
85
        stdout, err := cmd.StdoutPipe()
×
NEW
86
        if err != nil {
×
NEW
87
                return nil, errors.New("Error redirecting stdout on exec")
×
NEW
88
        }
×
89

90
        // Disable tty echo before starting
NEW
91
        term, err := DisableEcho(int(os.Stdin.Fd()))
×
NEW
92
        if err == nil {
×
NEW
93
                sigChan = make(chan os.Signal, 1)
×
NEW
94
                errChan = make(chan error, 1)
×
NEW
95
                // Make sure that echo is enabled if the process gets
×
NEW
96
                // interrupted
×
NEW
97
                signal.Notify(sigChan)
×
NEW
98
                go EchoSigHandler(_ctx, sigChan, errChan, term)
×
NEW
99
        } else if err != syscall.ENOTTY {
×
NEW
100
                return nil, err
×
NEW
101
        }
×
102

NEW
103
        if err := cmd.Start(); err != nil {
×
NEW
104
                return nil, err
×
NEW
105
        }
×
106

107
        // Wait for 120 seconds for ssh to establish connection
NEW
108
        err = waitForBufferSignal(stdout, os.Stdout, sshConnectedToken, 2*time.Minute)
×
NEW
109
        if err != nil {
×
NEW
110
                _ = cmd.Process.Kill()
×
NEW
111
                return nil, errors.Wrap(err,
×
NEW
112
                        "Error waiting for ssh session to be established.")
×
NEW
113
        }
×
NEW
114
        return &SSHCommand{
×
NEW
115
                ctx:     _ctx,
×
NEW
116
                Cmd:     cmd,
×
NEW
117
                Stdout:  stdout,
×
NEW
118
                cancel:  cancel,
×
NEW
119
                sigChan: sigChan,
×
NEW
120
                errChan: errChan,
×
NEW
121
        }, nil
×
122
}
123

NEW
124
func (s *SSHCommand) EndSSHCommand() error {
×
NEW
125
        if s.Cmd.ProcessState != nil && s.Cmd.ProcessState.Exited() {
×
NEW
126
                return errors.New("SSH session closed unexpectedly")
×
NEW
127
        }
×
128

NEW
129
        if err := s.Cmd.Wait(); err != nil {
×
NEW
130
                return errors.Wrap(err,
×
NEW
131
                        "SSH session closed with error")
×
NEW
132
        }
×
133

NEW
134
        if s.sigChan != nil {
×
NEW
135
                signal.Stop(s.sigChan)
×
NEW
136
                s.cancel()
×
NEW
137
                if err := <-s.errChan; err != nil {
×
NEW
138
                        return err
×
NEW
139
                }
×
NEW
140
        } else {
×
NEW
141
                s.cancel()
×
NEW
142
        }
×
143

NEW
144
        return nil
×
145
}
146

147
// Reads from src waiting for the string specified by signal, writing all other
148
// output appearing at src to sink. The function returns an error if occurs
149
// reading from the stream or the deadline exceeds.
150
func waitForBufferSignal(src io.Reader, sink io.Writer,
NEW
151
        signal string, deadline time.Duration) error {
×
NEW
152

×
NEW
153
        var err error
×
NEW
154
        errChan := make(chan error)
×
NEW
155

×
NEW
156
        go func() {
×
NEW
157
                stdoutRdr := bufio.NewReader(src)
×
NEW
158
                for {
×
NEW
159
                        line, err := stdoutRdr.ReadString('\n')
×
NEW
160
                        if err != nil {
×
NEW
161
                                errChan <- err
×
NEW
162
                                break
×
163
                        }
NEW
164
                        if strings.Contains(line, signal) {
×
NEW
165
                                errChan <- nil
×
NEW
166
                                break
×
167
                        }
NEW
168
                        _, err = sink.Write([]byte(line + "\n"))
×
NEW
169
                        if err != nil {
×
NEW
170
                                errChan <- err
×
NEW
171
                                break
×
172
                        }
173
                }
174
        }()
175

NEW
176
        select {
×
NEW
177
        case err = <-errChan:
×
178
                // Error from goroutine
NEW
179
        case <-time.After(deadline):
×
NEW
180
                err = errors.New("Input deadline exceeded")
×
181
        }
NEW
182
        return err
×
183
}
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