File: output.go

package info (click to toggle)
singularity-container 4.0.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,672 kB
  • sloc: asm: 3,857; sh: 2,125; ansic: 1,677; awk: 414; makefile: 110; python: 99
file content (95 lines) | stat: -rw-r--r-- 2,293 bytes parent folder | download | duplicates (2)
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
	}
}