-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrun.go
More file actions
230 lines (204 loc) · 8.98 KB
/
Copy pathrun.go
File metadata and controls
230 lines (204 loc) · 8.98 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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package apify
import (
"context"
"io"
"math/rand"
"net/http"
"strconv"
"time"
)
// RunResurrectOptions configures [RunClient.Resurrect].
type RunResurrectOptions struct {
// Build is the tag or number of the build to resurrect with.
Build *string
// MemoryMbytes is the memory in megabytes to allocate.
MemoryMbytes *int64
// TimeoutSecs is the run timeout in seconds.
TimeoutSecs *int64
// MaxItems is the maximum number of dataset items to charge (pay-per-result Actors).
MaxItems *int64
// MaxTotalChargeUsd is the maximum total charge in USD (pay-per-event Actors).
MaxTotalChargeUsd *float64
// RestartOnError, if true, restarts the run if it fails.
RestartOnError *bool
}
func (o RunResurrectOptions) apply(q *QueryParams) {
q.AddString("build", o.Build).
AddInt("memory", o.MemoryMbytes).
AddInt("timeout", o.TimeoutSecs).
AddInt("maxItems", o.MaxItems).
AddFloat("maxTotalChargeUsd", o.MaxTotalChargeUsd).
AddBool("restartOnError", o.RestartOnError)
}
// RunChargeOptions configures [RunClient.Charge].
type RunChargeOptions struct {
// EventName is the name of the event to charge for. Required.
EventName string
// Count is the number of times to charge the event (defaults to 1).
Count *int64
// IdempotencyKey deduplicates the charge across retries. If empty, one is auto-generated
// as "{runId}-{eventName}-{timestampMillis}-{random}", matching the reference client.
IdempotencyKey string
}
// RunClient is a client for a specific Actor run.
//
// It provides CRUD methods plus convenience helpers (abort, metamorph, reboot, resurrect,
// charge, wait-for-finish) and accessors for the run's default storages and log.
type RunClient struct {
root *ApifyClient
ctx *resourceContext
id string
}
func newRunClient(root *ApifyClient, hc *httpClient, baseURL, resourcePath, id string) *RunClient {
return &RunClient{
root: root,
ctx: newSingleContext(hc, baseURL, resourcePath, id),
id: id,
}
}
// LastRunOptions filters which "last" run the ActorClient.LastRunWithOptions /
// TaskClient.LastRunWithOptions accessors resolve to. An empty field leaves that filter unset.
//
// Origin is threaded to the runs/last endpoint as a documented query parameter of that endpoint in
// the OpenAPI spec, mirroring the reference client (lastRun({ origin })).
type LastRunOptions struct {
// Status filters by run status (e.g. "SUCCEEDED", "FAILED", "RUNNING").
Status string
// Origin filters by how the run was started (e.g. "DEVELOPMENT", "WEB", "API", "SCHEDULER").
Origin string
}
// setLastRunParams pins the `status` and/or `origin` query parameters inherited by all calls on
// this client. Empty values are skipped so they leave the corresponding filter unset.
func (c *RunClient) setLastRunParams(options LastRunOptions) {
if options.Status != "" {
c.ctx.baseParams.addRaw("status", options.Status)
}
if options.Origin != "" {
c.ctx.baseParams.addRaw("origin", options.Origin)
}
}
// Get fetches the run object. The bool reports whether it exists.
func (c *RunClient) Get(ctx context.Context) (ActorRun, bool, error) {
return c.GetWithWait(ctx, nil)
}
// GetWithWait fetches the run, optionally asking the API to wait up to waitForFinishSecs
// seconds (max 60) for the run to reach a terminal state before responding. Pass nil for an
// immediate fetch. Mirrors the reference client's get({ waitForFinish }).
func (c *RunClient) GetWithWait(ctx context.Context, waitForFinishSecs *int64) (ActorRun, bool, error) {
params := NewQueryParams()
params.AddInt("waitForFinish", waitForFinishSecs)
return getResource[ActorRun](ctx, c.ctx, "", params)
}
// Update updates the run with the given fields and returns the updated object.
func (c *RunClient) Update(ctx context.Context, newFields any) (ActorRun, error) {
return updateResource[ActorRun](ctx, c.ctx, "", newFields)
}
// Delete deletes the run.
func (c *RunClient) Delete(ctx context.Context) error {
return deleteResource(ctx, c.ctx, "")
}
// Abort aborts the run. If gracefully points to true, the run is sent a signal so it can
// finish the current request before terminating; if false it is aborted immediately. Pass
// nil to omit the parameter entirely and let the server apply its default (immediate abort),
// matching the reference client's optional `gracefully` option.
func (c *RunClient) Abort(ctx context.Context, gracefully *bool) (ActorRun, error) {
params := NewQueryParams()
params.AddBool("gracefully", gracefully)
return postWithBody[ActorRun](ctx, c.ctx, "abort", params, nil, "")
}
// MetamorphOptions configures [RunClient.Metamorph].
type MetamorphOptions struct {
// Build optionally pins the target Actor's build (empty for default).
Build string
// ContentType is the content type of the input body. Defaults to application/json.
ContentType string
}
// Metamorph transforms the run into a run of another Actor with a new input.
//
// targetActorID is the Actor to metamorph into. input is the new input (nil for none).
func (c *RunClient) Metamorph(ctx context.Context, targetActorID string, input any, options MetamorphOptions) (ActorRun, error) {
params := NewQueryParams()
params.AddString("targetActorId", &targetActorID)
if options.Build != "" {
params.AddString("build", &options.Build)
}
body, err := marshalInput(input)
if err != nil {
return ActorRun{}, err
}
contentType := options.ContentType
if contentType == "" {
contentType = contentTypeJSON
}
return postWithBody[ActorRun](ctx, c.ctx, "metamorph", params, body, contentType)
}
// Reboot reboots the run (restarts its container while keeping the same run).
func (c *RunClient) Reboot(ctx context.Context) (ActorRun, error) {
return postWithBody[ActorRun](ctx, c.ctx, "reboot", NewQueryParams(), nil, "")
}
// Resurrect resurrects a finished run, starting it again from the beginning.
func (c *RunClient) Resurrect(ctx context.Context, options RunResurrectOptions) (ActorRun, error) {
params := NewQueryParams()
options.apply(params)
return postWithBody[ActorRun](ctx, c.ctx, "resurrect", params, nil, "")
}
// Charge charges for a pay-per-event Actor run: it records occurrences of a named event.
// Only meaningful for runs of pay-per-event Actors.
//
// An idempotency key is always sent (auto-generated if not provided), so a charge that is
// retried by the transport is applied at most once, matching the reference client.
func (c *RunClient) Charge(ctx context.Context, options RunChargeOptions) error {
count := int64(1)
if options.Count != nil {
count = *options.Count
}
idempotencyKey := options.IdempotencyKey
if idempotencyKey == "" {
idempotencyKey = c.generateIdempotencyKey(options.EventName)
}
body := mustMarshal(map[string]any{"eventName": options.EventName, "count": count})
url := c.ctx.subURL("charge")
headers := map[string]string{chargeIdempotencyHeader: idempotencyKey}
_, err := c.ctx.http.callWithHeaders(ctx, http.MethodPost, url, body, contentTypeJSON, headers, defaultRequestTimeout)
return err
}
// chargeIdempotencyHeader is the header the API uses to deduplicate charge requests.
const chargeIdempotencyHeader = "idempotency-key"
// generateIdempotencyKey builds a per-charge idempotency key of the form
// "{runId}-{eventName}-{timestampMillis}-{random}", matching the reference client. It need
// not be cryptographically secure — only unique enough to avoid collisions within a request.
func (c *RunClient) generateIdempotencyKey(eventName string) string {
return c.id + "-" + eventName + "-" +
strconv.FormatInt(time.Now().UnixMilli(), 10) + "-" +
strconv.FormatInt(rand.Int63n(1_000_000), 10)
}
// WaitForFinish polls until the run reaches a terminal state or waitSecs elapses (nil waits
// indefinitely). It returns the latest run.
func (c *RunClient) WaitForFinish(ctx context.Context, waitSecs *int64) (ActorRun, error) {
return waitForFinish[ActorRun](ctx, c.ctx, waitSecs, "run", func(r *ActorRun) bool { return r.IsTerminal() })
}
// Dataset returns a client for this run's default dataset.
func (c *RunClient) Dataset() *DatasetClient {
return newDatasetNestedClient(c.ctx.http, c.ctx.subURL(""), "dataset")
}
// KeyValueStore returns a client for this run's default key-value store.
func (c *RunClient) KeyValueStore() *KeyValueStoreClient {
return newKeyValueStoreNestedClient(c.ctx.http, c.ctx.subURL(""), "key-value-store")
}
// RequestQueue returns a client for this run's default request queue.
func (c *RunClient) RequestQueue() *RequestQueueClient {
return newRequestQueueNestedClient(c.ctx.http, c.ctx.subURL(""), "request-queue")
}
// Log returns a client for accessing this run's log.
func (c *RunClient) Log() *LogClient {
return newNestedLogClient(c.ctx.http, c.ctx.subURL(""))
}
// GetStreamedLog opens a live stream of this run's raw log, for convenient log redirection.
//
// It is a convenience wrapper over Log().StreamWithOptions with raw=true (matching the
// reference client's getStreamedLog, which streams raw log content). The caller must close
// the returned reader.
func (c *RunClient) GetStreamedLog(ctx context.Context) (io.ReadCloser, error) {
raw := true
return c.Log().StreamWithOptions(ctx, LogOptions{Raw: &raw})
}