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

mendersoftware / mender-artifact / 1814505553

13 May 2025 07:50AM UTC coverage: 76.562% (-0.08%) from 76.641%
1814505553

Pull #701

gitlab-ci

michalkopczan
fix: Mender-artifact fails on calling mender-update show-provides

Ticket: MEN-8282
Changelog: None

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>
Pull Request #701: fix: Mender-artifact fails on calling mender-update show-provides

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

3 existing lines in 1 file now uncovered.

5919 of 7731 relevant lines covered (76.56%)

132.8 hits per line

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

0.0
/cli/util/ssh.go
1
// Copyright 2021 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

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

×
NEW
43
        var userAtHost string
×
NEW
44
        var sigChan chan os.Signal
×
NEW
45
        var errChan chan error
×
NEW
46
        port := "22"
×
NEW
47
        host := strings.TrimPrefix(c.String("file"), "ssh://")
×
NEW
48

×
NEW
49
        if remotePort := strings.Split(host, ":"); len(remotePort) == 2 {
×
NEW
50
                port = remotePort[1]
×
NEW
51
                userAtHost = remotePort[0]
×
NEW
52
        } else {
×
NEW
53
                userAtHost = host
×
NEW
54
        }
×
55

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

×
NEW
77
        cmd := exec.Command("ssh", args...)
×
NEW
78

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

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

NEW
100
        if err := cmd.Start(); err != nil {
×
NEW
101
                return nil, errChan, err
×
NEW
102
        }
×
103

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

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

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

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

NEW
141
        return nil
×
142
}
143

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

×
NEW
150
        var err error
×
NEW
151
        errChan := make(chan error)
×
NEW
152

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

NEW
173
        select {
×
NEW
174
        case err = <-errChan:
×
175
                // Error from goroutine
NEW
176
        case <-time.After(deadline):
×
NEW
177
                err = errors.New("Input deadline exceeded")
×
178
        }
NEW
179
        return err
×
180
}
181

NEW
182
func WaitForEchoRestore(errChan chan error) {
×
NEW
183
        <-errChan
×
NEW
184
}
×
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

© 2025 Coveralls, Inc