-
Notifications
You must be signed in to change notification settings - Fork 8
/
output.go
95 lines (78 loc) · 2.24 KB
/
output.go
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package client
import (
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/gorilla/websocket"
)
// GetOutput streams build output for the provided buildID to w. The context controls the lifetime
// of the request.
func (c *Client) GetOutput(ctx context.Context, buildID string, w io.Writer) error {
u := c.baseURL.ResolveReference(&url.URL{
Path: "v1/build-ws/" + buildID,
})
wsScheme := "ws"
if c.baseURL.Scheme == "https" {
wsScheme = "wss"
}
u.Scheme = wsScheme
h := http.Header{}
c.setRequestHeaders(h)
// Clone default websocket dialer
dialer := *websocket.DefaultDialer
// Clone TLS configuration for websocket protocol such as to not interfere with http protocol TLS configuration
// (ref: https://github.com/gorilla/websocket/issues/601)
if tr, ok := c.httpClient.Transport.(*http.Transport); ok && tr.TLSClientConfig != nil {
tlsConfig := &tls.Config{
InsecureSkipVerify: tr.TLSClientConfig.InsecureSkipVerify,
RootCAs: tr.TLSClientConfig.RootCAs,
}
dialer.TLSClientConfig = tlsConfig.Clone()
}
ws, resp, err := dialer.DialContext(ctx, u.String(), h)
if err != nil {
return fmt.Errorf("failed to dial: %w", err)
}
defer resp.Body.Close()
defer ws.Close()
errChan := make(chan error)
go func() {
defer close(errChan)
errChan <- func() error {
for {
// Read from websocket
mt, r, err := ws.NextReader()
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
return nil
} else if err != nil {
return fmt.Errorf("failed to read output: %w", err)
}
if mt != websocket.TextMessage {
continue
}
if _, err := io.Copy(w, r); err != nil {
return fmt.Errorf("failed to copy output: %w", err)
}
}
}()
}()
select {
case <-ctx.Done():
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = c.Cancel(ctx, buildID) //nolint:contextcheck
ws.Close()
<-errChan
return nil
case err := <-errChan:
return err
}
}