Repository navigation
Expand file tree
/
Copy pathlog.go
More file actions
112 lines (100 loc) · 4.04 KB
/
Copy pathlog.go
File metadata and controls
112 lines (100 loc) · 4.04 KB
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package apify
import (
"context"
"io"
"net/http"
)
// LogClient is a client for accessing the log of an Actor build or run
// (/v2/logs/{buildOrRunId}, or the run/build-nested .../log).
type LogClient struct {
ctx *resourceContext
}
func newLogClient(hc *httpClient, baseURL, resourcePath, id string) *LogClient {
return &LogClient{ctx: newSingleContext(hc, baseURL, resourcePath, id)}
}
// newNestedLogClient creates a log client for a run's or build's nested log endpoint
// (e.g. /v2/actor-runs/{runId}/log).
func newNestedLogClient(hc *httpClient, base string) *LogClient {
return &LogClient{ctx: newCollectionContext(hc, base, "log")}
}
// LogOptions configures log retrieval/streaming.
type LogOptions struct {
// Raw, if true, returns the unprocessed log content (no platform post-processing).
Raw *bool
// Download, if true, sets Content-Disposition so the log is served as a download.
Download *bool
}
func (o LogOptions) apply(q *QueryParams) {
q.AddBool("raw", o.Raw).AddBool("download", o.Download)
}
// Get fetches the entire log as text, or ("", false, nil) if the log does not exist.
//
// A 404 resolves to "absent" only when this client names the log by its own id
// (client.Log(id)): a chained client without one, such as run.Log() or build.Log(), throws
// instead, since the run or build itself may be what is missing.
func (c *LogClient) Get(ctx context.Context) (string, bool, error) {
return c.GetWithOptions(ctx, LogOptions{})
}
// GetWithOptions fetches the log with explicit options (raw, download). See [LogClient.Get]
// for how a 404 is handled.
func (c *LogClient) GetWithOptions(ctx context.Context, options LogOptions) (string, bool, error) {
params := NewQueryParams()
options.apply(params)
resp, err := getRawRequired(ctx, c.ctx, "", params, c.ctx.longTimeout())
if err != nil {
if c.ctx.hasOwnID && isNotFound(err) {
return "", false, nil
}
return "", false, err
}
return string(resp.body), true, nil
}
// Stream opens a live, streaming connection to the log and returns a reader over the log
// bytes, or (nil, nil) if the log does not exist. The caller is responsible for closing the
// returned [io.ReadCloser].
//
// Unlike [LogClient.Get], this bypasses the buffered/retrying transport so the log can be
// followed in real time as the run produces it (the `stream=1` query parameter). Because the
// response is consumed incrementally, it is not retried; transient failures surface to the
// caller. This mirrors the reference clients' streamed-log behaviour used for log redirection.
//
// A 404 resolves to (nil, nil) only when this client names the log by its own id
// (client.Log(id)): a chained client without one, such as run.Log() or build.Log(), returns an
// error instead, since the run or build itself may be what is missing.
func (c *LogClient) Stream(ctx context.Context) (io.ReadCloser, error) {
return c.StreamWithOptions(ctx, LogOptions{})
}
// StreamWithOptions opens a live log stream with explicit options (raw, download). See
// [LogClient.Stream] for the streaming semantics and how a 404 is handled.
func (c *LogClient) StreamWithOptions(ctx context.Context, options LogOptions) (io.ReadCloser, error) {
if c.ctx.idErr != nil {
return nil, c.ctx.idErr
}
params := NewQueryParams()
streamOn := true
params.AddBool("stream", &streamOn)
options.apply(params)
url := c.ctx.mergedParams(params).applyToURL(c.ctx.subURL(""))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
// Reuse the shared auth/User-Agent header setup so the streaming path can never drift from
// the buffered request path.
c.ctx.http.applyAuthHeaders(req)
resp, err := c.ctx.http.backend.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode >= maxSuccessStatusCode {
// Consume and classify the error body, then close.
body, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
apiErr := buildAPIError(resp.StatusCode, body, 1, http.MethodGet, extractPath(url))
if c.ctx.hasOwnID && isNotFound(apiErr) {
return nil, nil
}
return nil, apiErr
}
return resp.Body, nil
}