smplkit 3.0.84 → 3.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/smplkit/jobs/models.rb +408 -0
- data/lib/smplkit/management/client.rb +4 -1
- data/lib/smplkit/management/jobs.rb +226 -0
- data/lib/smplkit.rb +4 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ad7af7469c15e0dc8228442eac8974c02bccb72e087ccc26e6a622ad81ecffb
|
|
4
|
+
data.tar.gz: 060a8c1159094f6f06548fab3a5131a73059dfd01f5e36969a1c9a9ec9024295
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9aa6fff53a83ffb830457a221ddeb40f50f25c5f14926dea4dcae25a97e669121e3dd19191eb6c0786c0e05e2804c54b3cef6ff7bdb618253d38ba96f21ab441
|
|
7
|
+
data.tar.gz: 4925d4755f91c0e2efaa87ed19e356604d4eef5bd73d2a937a83103e7977c2fd545071ea4c4feb8612fc2e09eaacac3cac1482a2fe4ffaf001fa082768ab38fd
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
# Smpl Jobs surface — exposed through +mgmt.jobs.*+.
|
|
5
|
+
#
|
|
6
|
+
# Unlike Config/Flags/Logging, Jobs has no live "phone-home" agent — no
|
|
7
|
+
# environment registration, no WebSocket — so its entire surface lives on
|
|
8
|
+
# the management client rather than a runtime client. A {Job} is an active
|
|
9
|
+
# record: build it with +mgmt.jobs.new(...)+, set fields, and call {Job#save}
|
|
10
|
+
# (create when new, full-replace update when it already exists) or
|
|
11
|
+
# {Job#delete}. Runs are read-only views; run actions live on
|
|
12
|
+
# +mgmt.jobs.runs+.
|
|
13
|
+
module Jobs
|
|
14
|
+
# Wrap a generated-jobs-API call and translate +ApiError+ into the
|
|
15
|
+
# +Smplkit::Error+ hierarchy. Connection-level failures (no response
|
|
16
|
+
# code) become {Smplkit::ConnectionError}; status-coded failures route
|
|
17
|
+
# through {Smplkit::Errors.raise_for_status}, which emits
|
|
18
|
+
# +PaymentRequiredError+ / +NotFoundError+ / +ConflictError+ /
|
|
19
|
+
# +ValidationError+ / +Error+ depending on the JSON:API body.
|
|
20
|
+
def self.call_api
|
|
21
|
+
yield
|
|
22
|
+
rescue SmplkitGeneratedClient::Jobs::ApiError => e
|
|
23
|
+
raise Smplkit::ConnectionError, e.message.to_s if e.code.nil? || e.code.zero?
|
|
24
|
+
|
|
25
|
+
Smplkit::Errors.raise_for_status(e.code, e.response_body.to_s)
|
|
26
|
+
# raise_for_status only returns on 2xx; if we get here the generated
|
|
27
|
+
# layer raised on a 2xx (shouldn't happen) — re-raise the original so
|
|
28
|
+
# the caller can inspect.
|
|
29
|
+
raise
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# HTTP verb a job uses when it fires (ADR-049).
|
|
33
|
+
#
|
|
34
|
+
# Mirrors the jobs spec's method enum so a job's
|
|
35
|
+
# +configuration.method+ field is constrained to a known value instead
|
|
36
|
+
# of accepting any string. Members are declared in alphabetical order.
|
|
37
|
+
module HttpMethod
|
|
38
|
+
DELETE = "DELETE"
|
|
39
|
+
GET = "GET"
|
|
40
|
+
PATCH = "PATCH"
|
|
41
|
+
POST = "POST"
|
|
42
|
+
PUT = "PUT"
|
|
43
|
+
|
|
44
|
+
VALUES = [DELETE, GET, PATCH, POST, PUT].freeze
|
|
45
|
+
|
|
46
|
+
# Validate and normalize an input to a wire-format string.
|
|
47
|
+
#
|
|
48
|
+
# @param value [String, nil] a published constant or its literal string.
|
|
49
|
+
# @return [String, nil] the canonical wire value (or +nil+ when input is +nil+).
|
|
50
|
+
# @raise [ArgumentError] when +value+ is not a member of {VALUES}.
|
|
51
|
+
def self.coerce(value)
|
|
52
|
+
return nil if value.nil?
|
|
53
|
+
|
|
54
|
+
s = value.to_s
|
|
55
|
+
return s if VALUES.include?(s)
|
|
56
|
+
|
|
57
|
+
raise ArgumentError,
|
|
58
|
+
"Unknown HttpMethod #{value.inspect}; expected one of #{VALUES.inspect}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A single name/value HTTP header on the request a job performs.
|
|
63
|
+
#
|
|
64
|
+
# @!attribute [rw] name
|
|
65
|
+
# @return [String] Header name (e.g. +"Authorization"+, +"Content-Type"+).
|
|
66
|
+
# @!attribute [rw] value
|
|
67
|
+
# @return [String] Header value, plaintext on writes. The jobs service
|
|
68
|
+
# encrypts values at rest; reads return them redacted.
|
|
69
|
+
HttpHeader = Struct.new(:name, :value, keyword_init: true)
|
|
70
|
+
|
|
71
|
+
# The HTTP request a job performs when it fires (the +http+ configuration).
|
|
72
|
+
#
|
|
73
|
+
# Extends the shared forwarder shape with the two fields a scheduled job
|
|
74
|
+
# needs beyond a forwarder: a request +body+ and a per-run +timeout+.
|
|
75
|
+
#
|
|
76
|
+
# @!attribute [rw] method
|
|
77
|
+
# @return [String] HTTP verb used when the job fires. Defaults to {HttpMethod::POST}.
|
|
78
|
+
# @!attribute [rw] url
|
|
79
|
+
# @return [String] Destination URL the job requests on each run.
|
|
80
|
+
# @!attribute [rw] headers
|
|
81
|
+
# @return [Array<HttpHeader>] Headers attached to every request. Values
|
|
82
|
+
# are redacted on reads.
|
|
83
|
+
# @!attribute [rw] body
|
|
84
|
+
# @return [String, nil] Request body sent on each run. +nil+ (the default)
|
|
85
|
+
# sends an empty body, suitable for a connectivity ping. Sent verbatim —
|
|
86
|
+
# pair with a matching +Content-Type+ header.
|
|
87
|
+
# @!attribute [rw] success_status
|
|
88
|
+
# @return [String] Status the destination must return for the run to count
|
|
89
|
+
# as success — an exact code (+"200"+, +"204"+) or a class (+"2xx"+,
|
|
90
|
+
# +"4xx"+). Defaults to +"2xx"+.
|
|
91
|
+
# @!attribute [rw] timeout
|
|
92
|
+
# @return [Integer] Per-run timeout in seconds. A run that does not complete
|
|
93
|
+
# within this many seconds fails with reason +TIMEOUT+. Defaults to 30;
|
|
94
|
+
# bounded by your plan's maximum timeout.
|
|
95
|
+
# @!attribute [rw] tls_verify
|
|
96
|
+
# @return [Boolean] Whether to verify the destination's TLS certificate
|
|
97
|
+
# chain. Defaults to +true+; flip to +false+ only for short-lived
|
|
98
|
+
# testing against an untrusted certificate. Prefer pinning the CA via
|
|
99
|
+
# +ca_cert+.
|
|
100
|
+
# @!attribute [rw] ca_cert
|
|
101
|
+
# @return [String, nil] Optional PEM-encoded certificate (or bundle)
|
|
102
|
+
# trusted in addition to the system CA store. Ignored when +tls_verify+
|
|
103
|
+
# is +false+. +nil+ (the default) means "use system CAs only".
|
|
104
|
+
#
|
|
105
|
+
# rubocop:disable Lint/StructNewOverride -- ``:method`` matches the
|
|
106
|
+
# API attribute and shadowing Struct#method is the expected ergonomics.
|
|
107
|
+
HttpConfig = Struct.new(
|
|
108
|
+
:method, :url, :headers, :body, :success_status, :timeout, :tls_verify, :ca_cert,
|
|
109
|
+
keyword_init: true
|
|
110
|
+
) do
|
|
111
|
+
def initialize(
|
|
112
|
+
url: "", method: HttpMethod::POST, headers: nil, body: nil,
|
|
113
|
+
success_status: "2xx", timeout: 30, tls_verify: true, ca_cert: nil
|
|
114
|
+
)
|
|
115
|
+
super(
|
|
116
|
+
method: HttpMethod.coerce(method), url: url, headers: headers || [], body: body,
|
|
117
|
+
success_status: success_status, timeout: timeout, tls_verify: tls_verify, ca_cert: ca_cert
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.to_wire(src)
|
|
122
|
+
h = src.is_a?(Hash) ? new(**src) : src
|
|
123
|
+
SmplkitGeneratedClient::Jobs::JobHttpConfiguration.new(
|
|
124
|
+
method: HttpMethod.coerce(h.method),
|
|
125
|
+
url: h.url,
|
|
126
|
+
headers: (h.headers || []).map do |hdr|
|
|
127
|
+
name, value = if hdr.is_a?(Hash)
|
|
128
|
+
[hdr[:name] || hdr["name"], hdr[:value] || hdr["value"]]
|
|
129
|
+
else
|
|
130
|
+
[hdr.name, hdr.value]
|
|
131
|
+
end
|
|
132
|
+
SmplkitGeneratedClient::Jobs::HttpHeader.new(name: name, value: value)
|
|
133
|
+
end,
|
|
134
|
+
body: h.body,
|
|
135
|
+
success_status: h.success_status,
|
|
136
|
+
timeout: h.timeout,
|
|
137
|
+
tls_verify: h.tls_verify,
|
|
138
|
+
ca_cert: h.ca_cert
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.from_wire(src)
|
|
143
|
+
return new if src.nil?
|
|
144
|
+
|
|
145
|
+
# +url+, +success_status+, and +timeout+ are required non-nil on the
|
|
146
|
+
# generated jobs config, so they pass straight through. +method+,
|
|
147
|
+
# +tls_verify+, +headers+, +body+, and +ca_cert+ are nullable and get
|
|
148
|
+
# wrapper-side defaults when the wire omits them.
|
|
149
|
+
new(
|
|
150
|
+
method: src.method || HttpMethod::POST,
|
|
151
|
+
url: src.url,
|
|
152
|
+
headers: (src.headers || []).map { |h| HttpHeader.new(name: h.name, value: h.value) },
|
|
153
|
+
body: src.body,
|
|
154
|
+
success_status: src.success_status,
|
|
155
|
+
timeout: src.timeout,
|
|
156
|
+
# rubocop:disable Style/RedundantCondition -- nil and false are
|
|
157
|
+
# distinct: nil means "field absent on the wire" (default to true);
|
|
158
|
+
# explicit false means "verification disabled".
|
|
159
|
+
tls_verify: src.tls_verify.nil? ? true : src.tls_verify,
|
|
160
|
+
# rubocop:enable Style/RedundantCondition
|
|
161
|
+
ca_cert: src.ca_cert
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
# rubocop:enable Lint/StructNewOverride
|
|
166
|
+
|
|
167
|
+
# A scheduled unit of work: an HTTP request run on a schedule.
|
|
168
|
+
#
|
|
169
|
+
# Active-record style: instantiate via +mgmt.jobs.new(...)+, mutate fields
|
|
170
|
+
# directly, and call {#save} to persist or {#delete} to remove. Header
|
|
171
|
+
# values in +configuration.headers+ are returned redacted on reads —
|
|
172
|
+
# re-supply the real values before calling {#save}; the SDK does not cache
|
|
173
|
+
# them client-side.
|
|
174
|
+
class Job
|
|
175
|
+
# @return [String] Caller-supplied unique identifier for the job (the
|
|
176
|
+
# resource +id+). Unique within the account and immutable; the service
|
|
177
|
+
# returns 409 if another live job already uses this id.
|
|
178
|
+
attr_accessor :id
|
|
179
|
+
|
|
180
|
+
# @return [String] Human-readable name for the job.
|
|
181
|
+
attr_accessor :name
|
|
182
|
+
|
|
183
|
+
# @return [String, nil] Free-text description. +nil+ when unset.
|
|
184
|
+
attr_accessor :description
|
|
185
|
+
|
|
186
|
+
# @return [Boolean] Whether the job is scheduling runs. +false+ pauses
|
|
187
|
+
# without deleting.
|
|
188
|
+
attr_accessor :enabled
|
|
189
|
+
|
|
190
|
+
# @return [String] Job type. Only +"http"+ is supported today.
|
|
191
|
+
attr_accessor :type
|
|
192
|
+
|
|
193
|
+
# @return [String] When the job runs: an ISO-8601 datetime (a one-off
|
|
194
|
+
# run), a 5-field cron expression evaluated in UTC (recurring), or the
|
|
195
|
+
# literal +"now"+ (run once, as soon as possible). A datetime or +"now"+
|
|
196
|
+
# job disables itself after it fires.
|
|
197
|
+
attr_accessor :schedule
|
|
198
|
+
|
|
199
|
+
# @return [HttpConfig] The HTTP request to perform when the job fires.
|
|
200
|
+
attr_accessor :configuration
|
|
201
|
+
|
|
202
|
+
# @return [String] How overlapping runs are handled. +"ALLOW"+ (the only
|
|
203
|
+
# value) permits them.
|
|
204
|
+
attr_accessor :concurrency_policy
|
|
205
|
+
|
|
206
|
+
# @return [String, nil] The next scheduled fire time. +nil+ once a one-off
|
|
207
|
+
# job has fired.
|
|
208
|
+
attr_accessor :next_run_at
|
|
209
|
+
|
|
210
|
+
# @return [String, nil] ISO-8601 timestamp of first persist. +nil+ for an
|
|
211
|
+
# unsaved instance.
|
|
212
|
+
attr_accessor :created_at
|
|
213
|
+
|
|
214
|
+
# @return [String, nil] ISO-8601 timestamp of the most recent mutation.
|
|
215
|
+
attr_accessor :updated_at
|
|
216
|
+
|
|
217
|
+
# @return [String, nil] Soft-delete timestamp. +nil+ for live jobs.
|
|
218
|
+
attr_accessor :deleted_at
|
|
219
|
+
|
|
220
|
+
# @return [Integer, nil] Monotonic version counter, bumped on every
|
|
221
|
+
# server-side write.
|
|
222
|
+
attr_accessor :version
|
|
223
|
+
|
|
224
|
+
def initialize(client = nil, id:, name:, schedule:, configuration:,
|
|
225
|
+
description: nil, enabled: true, type: "http",
|
|
226
|
+
concurrency_policy: "ALLOW", next_run_at: nil,
|
|
227
|
+
created_at: nil, updated_at: nil, deleted_at: nil, version: nil)
|
|
228
|
+
@client = client
|
|
229
|
+
@id = id
|
|
230
|
+
@name = name
|
|
231
|
+
@description = description
|
|
232
|
+
@enabled = enabled
|
|
233
|
+
@type = type
|
|
234
|
+
@schedule = schedule
|
|
235
|
+
@configuration = configuration
|
|
236
|
+
@concurrency_policy = concurrency_policy
|
|
237
|
+
@next_run_at = next_run_at
|
|
238
|
+
@created_at = created_at
|
|
239
|
+
@updated_at = updated_at
|
|
240
|
+
@deleted_at = deleted_at
|
|
241
|
+
@version = version
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Create this job, or full-replace it if it already exists.
|
|
245
|
+
#
|
|
246
|
+
# Upsert behavior is driven by {#created_at}: a job with no +created_at+
|
|
247
|
+
# is created (POST); otherwise it's full-replace updated (PUT). After the
|
|
248
|
+
# call, every field is refreshed from the server response (including
|
|
249
|
+
# newly-assigned +created_at+, +version+, +next_run_at+).
|
|
250
|
+
#
|
|
251
|
+
# @return [self]
|
|
252
|
+
def save
|
|
253
|
+
raise "Job was constructed without a client; cannot save" if @client.nil?
|
|
254
|
+
|
|
255
|
+
updated = @created_at.nil? ? @client._create_job(self) : @client._update_job(self)
|
|
256
|
+
_apply(updated)
|
|
257
|
+
self
|
|
258
|
+
end
|
|
259
|
+
alias save! save
|
|
260
|
+
|
|
261
|
+
# Soft-delete this job on the server.
|
|
262
|
+
#
|
|
263
|
+
# @return [nil]
|
|
264
|
+
def delete
|
|
265
|
+
raise "Job was constructed without a client or id; cannot delete" if @client.nil? || @id.nil?
|
|
266
|
+
|
|
267
|
+
@client.delete(@id)
|
|
268
|
+
end
|
|
269
|
+
alias delete! delete
|
|
270
|
+
|
|
271
|
+
# @api private
|
|
272
|
+
def _apply(other)
|
|
273
|
+
@id = other.id
|
|
274
|
+
@name = other.name
|
|
275
|
+
@description = other.description
|
|
276
|
+
@enabled = other.enabled
|
|
277
|
+
@type = other.type
|
|
278
|
+
@schedule = other.schedule
|
|
279
|
+
@configuration = other.configuration
|
|
280
|
+
@concurrency_policy = other.concurrency_policy
|
|
281
|
+
@next_run_at = other.next_run_at
|
|
282
|
+
@created_at = other.created_at
|
|
283
|
+
@updated_at = other.updated_at
|
|
284
|
+
@deleted_at = other.deleted_at
|
|
285
|
+
@version = other.version
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def self.from_resource(resource, client: nil)
|
|
289
|
+
a = resource.attributes
|
|
290
|
+
new(
|
|
291
|
+
client,
|
|
292
|
+
id: resource.id,
|
|
293
|
+
name: a.name,
|
|
294
|
+
description: a.description,
|
|
295
|
+
enabled: a.enabled.nil? || a.enabled,
|
|
296
|
+
type: a.type || "http",
|
|
297
|
+
schedule: a.schedule,
|
|
298
|
+
configuration: HttpConfig.from_wire(a.configuration),
|
|
299
|
+
concurrency_policy: a.concurrency_policy || "ALLOW",
|
|
300
|
+
next_run_at: a.next_run_at,
|
|
301
|
+
created_at: a.created_at,
|
|
302
|
+
updated_at: a.updated_at,
|
|
303
|
+
deleted_at: a.deleted_at,
|
|
304
|
+
version: a.version
|
|
305
|
+
)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# A single execution of a job (read-only).
|
|
310
|
+
#
|
|
311
|
+
# Runs are created and mutated by the jobs service, not by clients; clients
|
|
312
|
+
# influence runs only through the +run+ / +cancel+ / +rerun+ actions on
|
|
313
|
+
# +mgmt.jobs+.
|
|
314
|
+
#
|
|
315
|
+
# @!attribute [rw] id
|
|
316
|
+
# @return [String] Server-assigned UUID for this run.
|
|
317
|
+
# @!attribute [rw] job
|
|
318
|
+
# @return [String] The id of the job this run belongs to.
|
|
319
|
+
# @!attribute [rw] job_version
|
|
320
|
+
# @return [Integer, nil] The job's version at the time the run executed.
|
|
321
|
+
# @!attribute [rw] trigger
|
|
322
|
+
# @return [String] Why the run exists: +SCHEDULE+, +MANUAL+ (run now), or +RERUN+.
|
|
323
|
+
# @!attribute [rw] rerun_of
|
|
324
|
+
# @return [String, nil] The source run's id; set only when +trigger+ is +RERUN+.
|
|
325
|
+
# @!attribute [rw] scheduled_for
|
|
326
|
+
# @return [String, nil] The intended fire time for a scheduled run; +nil+
|
|
327
|
+
# for manual / rerun runs.
|
|
328
|
+
# @!attribute [rw] status
|
|
329
|
+
# @return [String] Lifecycle state of the run.
|
|
330
|
+
# @!attribute [rw] started_at
|
|
331
|
+
# @return [String, nil] When execution started.
|
|
332
|
+
# @!attribute [rw] finished_at
|
|
333
|
+
# @return [String, nil] When execution finished.
|
|
334
|
+
# @!attribute [rw] pending_duration_ms
|
|
335
|
+
# @return [Integer, nil] Milliseconds the run waited as +PENDING+ before starting.
|
|
336
|
+
# @!attribute [rw] run_duration_ms
|
|
337
|
+
# @return [Integer, nil] Milliseconds the run spent executing.
|
|
338
|
+
# @!attribute [rw] total_duration_ms
|
|
339
|
+
# @return [Integer, nil] Milliseconds from enqueue to finish.
|
|
340
|
+
# @!attribute [rw] failure_reason
|
|
341
|
+
# @return [String, nil] Why a +FAILED+ run failed; +nil+ otherwise.
|
|
342
|
+
# @!attribute [rw] error
|
|
343
|
+
# @return [String, nil] Free-text failure detail, if any.
|
|
344
|
+
# @!attribute [rw] request
|
|
345
|
+
# @return [Hash, nil] Snapshot of the request that was sent (header values redacted).
|
|
346
|
+
# @!attribute [rw] result
|
|
347
|
+
# @return [Hash, nil] Outcome of the call (status, headers, body, ...).
|
|
348
|
+
# @!attribute [rw] created_at
|
|
349
|
+
# @return [String, nil] When the run was enqueued (became +PENDING+).
|
|
350
|
+
Run = Struct.new(
|
|
351
|
+
:id, :job, :job_version, :trigger, :rerun_of, :scheduled_for, :status,
|
|
352
|
+
:started_at, :finished_at, :pending_duration_ms, :run_duration_ms,
|
|
353
|
+
:total_duration_ms, :failure_reason, :error, :request, :result, :created_at,
|
|
354
|
+
keyword_init: true
|
|
355
|
+
) do
|
|
356
|
+
def self.from_resource(resource)
|
|
357
|
+
a = resource.attributes
|
|
358
|
+
new(
|
|
359
|
+
id: resource.id,
|
|
360
|
+
job: a.job,
|
|
361
|
+
job_version: a.job_version,
|
|
362
|
+
trigger: a.trigger,
|
|
363
|
+
rerun_of: a.rerun_of,
|
|
364
|
+
scheduled_for: a.scheduled_for,
|
|
365
|
+
status: a.status,
|
|
366
|
+
started_at: a.started_at,
|
|
367
|
+
finished_at: a.finished_at,
|
|
368
|
+
pending_duration_ms: a.pending_duration_ms,
|
|
369
|
+
run_duration_ms: a.run_duration_ms,
|
|
370
|
+
total_duration_ms: a.total_duration_ms,
|
|
371
|
+
failure_reason: a.failure_reason,
|
|
372
|
+
error: a.error,
|
|
373
|
+
request: a.request.nil? ? nil : Smplkit::Helpers.deep_stringify_keys(a.request),
|
|
374
|
+
result: a.result.nil? ? nil : Smplkit::Helpers.deep_stringify_keys(a.result),
|
|
375
|
+
created_at: a.created_at
|
|
376
|
+
)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Current-period usage against the account's plan allotments (read-only).
|
|
381
|
+
#
|
|
382
|
+
# @!attribute [rw] period
|
|
383
|
+
# @return [String] The usage period this report covers, as +YYYY-MM+ (UTC).
|
|
384
|
+
# @!attribute [rw] runs_used
|
|
385
|
+
# @return [Integer] Runs metered so far this period.
|
|
386
|
+
# @!attribute [rw] runs_included
|
|
387
|
+
# @return [Integer] Runs included in the plan this period (+-1+ means unlimited).
|
|
388
|
+
# @!attribute [rw] active_jobs
|
|
389
|
+
# @return [Integer] Number of currently-enabled jobs.
|
|
390
|
+
# @!attribute [rw] active_jobs_limit
|
|
391
|
+
# @return [Integer] Maximum enabled jobs the plan allows (+-1+ means unlimited).
|
|
392
|
+
Usage = Struct.new(
|
|
393
|
+
:period, :runs_used, :runs_included, :active_jobs, :active_jobs_limit,
|
|
394
|
+
keyword_init: true
|
|
395
|
+
) do
|
|
396
|
+
def self.from_resource(resource)
|
|
397
|
+
a = resource.attributes
|
|
398
|
+
new(
|
|
399
|
+
period: a.period,
|
|
400
|
+
runs_used: a.runs_used,
|
|
401
|
+
runs_included: a.runs_included,
|
|
402
|
+
active_jobs: a.active_jobs,
|
|
403
|
+
active_jobs_limit: a.active_jobs_limit
|
|
404
|
+
)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
@@ -35,7 +35,7 @@ module Smplkit
|
|
|
35
35
|
RUNTIME_PAGE_SIZE = 1000
|
|
36
36
|
|
|
37
37
|
attr_reader :contexts, :context_types, :environments, :services, :account_settings,
|
|
38
|
-
:config, :flags, :loggers, :log_groups, :audit
|
|
38
|
+
:config, :flags, :loggers, :log_groups, :audit, :jobs
|
|
39
39
|
|
|
40
40
|
def self.from_resolved(resolved, extra_headers: nil)
|
|
41
41
|
new(_resolved: resolved, extra_headers: extra_headers)
|
|
@@ -58,6 +58,7 @@ module Smplkit
|
|
|
58
58
|
@flags_api_client = build_api_client(SmplkitGeneratedClient::Flags, "flags", cfg)
|
|
59
59
|
@logging_api_client = build_api_client(SmplkitGeneratedClient::Logging, "logging", cfg)
|
|
60
60
|
@audit_api_client = build_api_client(SmplkitGeneratedClient::Audit, "audit", cfg)
|
|
61
|
+
@jobs_api_client = build_api_client(SmplkitGeneratedClient::Jobs, "jobs", cfg)
|
|
61
62
|
|
|
62
63
|
@contexts = ContextsNamespace.new(@app_api_client)
|
|
63
64
|
@context_types = ContextTypesNamespace.new(@app_api_client)
|
|
@@ -69,6 +70,7 @@ module Smplkit
|
|
|
69
70
|
@loggers = LoggersNamespace.new(@logging_api_client)
|
|
70
71
|
@log_groups = LogGroupsNamespace.new(@logging_api_client)
|
|
71
72
|
@audit = Management::AuditNamespace.new(@audit_api_client)
|
|
73
|
+
@jobs = Management::JobsNamespace.new(@jobs_api_client)
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def close
|
|
@@ -82,6 +84,7 @@ module Smplkit
|
|
|
82
84
|
def _flags_http = @flags_api_client
|
|
83
85
|
def _logging_http = @logging_api_client
|
|
84
86
|
def _audit_http = @audit_api_client
|
|
87
|
+
def _jobs_http = @jobs_api_client
|
|
85
88
|
|
|
86
89
|
SDK_OWNED_HEADERS = %w[authorization content-type user-agent].freeze
|
|
87
90
|
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
module Management
|
|
5
|
+
# Smpl Jobs management surface — accessed via +mgmt.jobs+.
|
|
6
|
+
#
|
|
7
|
+
# Unlike Config/Flags/Logging, Jobs has no live "phone-home" agent — no
|
|
8
|
+
# environment registration, no WebSocket — so its entire surface lives on
|
|
9
|
+
# the management client. Defining a job, triggering a run, and reading run
|
|
10
|
+
# history are all plain request/response calls here:
|
|
11
|
+
#
|
|
12
|
+
# mgmt.jobs.{new,get,list,delete,run,usage}
|
|
13
|
+
# mgmt.jobs.runs.{list,get,cancel,rerun}
|
|
14
|
+
# Job#{save,delete}
|
|
15
|
+
#
|
|
16
|
+
# The active-record entry point is {#new}: instantiate a draft, mutate
|
|
17
|
+
# fields, then call {Smplkit::Jobs::Job#save}. Run history and the
|
|
18
|
+
# cancel / rerun run actions live on {#runs}.
|
|
19
|
+
class JobsNamespace
|
|
20
|
+
# @return [RunsNamespace] Run history and run actions (+mgmt.jobs.runs+).
|
|
21
|
+
attr_reader :runs
|
|
22
|
+
|
|
23
|
+
def initialize(api_client)
|
|
24
|
+
@api = SmplkitGeneratedClient::Jobs::JobsApi.new(api_client)
|
|
25
|
+
@runs = RunsNamespace.new(
|
|
26
|
+
SmplkitGeneratedClient::Jobs::RunsApi.new(api_client)
|
|
27
|
+
)
|
|
28
|
+
@usage_api = SmplkitGeneratedClient::Jobs::UsageApi.new(api_client)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Construct an unsaved {Smplkit::Jobs::Job} bound to this namespace. Call
|
|
32
|
+
# +#save+ on the returned instance to create it.
|
|
33
|
+
#
|
|
34
|
+
# @param id [String] Caller-supplied unique identifier for the job.
|
|
35
|
+
# Unique within the account and immutable; the service returns 409 if
|
|
36
|
+
# another live job already uses this id.
|
|
37
|
+
# @param name [String] Human-readable name for the job.
|
|
38
|
+
# @param schedule [String] An ISO-8601 datetime, a 5-field UTC cron
|
|
39
|
+
# expression, or the literal +"now"+.
|
|
40
|
+
# @param configuration [Smplkit::Jobs::HttpConfig] The HTTP request the
|
|
41
|
+
# job performs.
|
|
42
|
+
# @param description [String, nil] Optional free-text description.
|
|
43
|
+
# @param enabled [Boolean] Whether the job schedules runs. Defaults +true+.
|
|
44
|
+
# @param concurrency_policy [String] How overlapping runs are handled.
|
|
45
|
+
# Defaults to +"ALLOW"+.
|
|
46
|
+
# @return [Smplkit::Jobs::Job]
|
|
47
|
+
def new(id, name:, schedule:, configuration:, description: nil,
|
|
48
|
+
enabled: true, concurrency_policy: "ALLOW")
|
|
49
|
+
Smplkit::Jobs::Job.new(
|
|
50
|
+
self,
|
|
51
|
+
id: id,
|
|
52
|
+
name: name,
|
|
53
|
+
schedule: schedule,
|
|
54
|
+
configuration: configuration,
|
|
55
|
+
description: description,
|
|
56
|
+
enabled: enabled,
|
|
57
|
+
concurrency_policy: concurrency_policy
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# List jobs for the authenticated account.
|
|
62
|
+
#
|
|
63
|
+
# @param enabled [Boolean, nil] Filter to jobs matching this enabled state.
|
|
64
|
+
# @param page_number [Integer, nil] 1-based page number to return.
|
|
65
|
+
# @param page_size [Integer, nil] Items per page.
|
|
66
|
+
# @return [Array<Smplkit::Jobs::Job>]
|
|
67
|
+
def list(enabled: nil, page_number: nil, page_size: nil)
|
|
68
|
+
opts = {}
|
|
69
|
+
opts[:filter_enabled] = enabled unless enabled.nil?
|
|
70
|
+
opts[:page_number] = page_number unless page_number.nil?
|
|
71
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
72
|
+
|
|
73
|
+
resp = Smplkit::Jobs.call_api { @api.list_jobs(opts) }
|
|
74
|
+
(resp.data || []).map { |r| Smplkit::Jobs::Job.from_resource(r, client: self) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Fetch a single job by id. The returned instance is bound to this
|
|
78
|
+
# namespace, so +job.save+ and +job.delete+ work.
|
|
79
|
+
#
|
|
80
|
+
# @param id [String]
|
|
81
|
+
# @return [Smplkit::Jobs::Job]
|
|
82
|
+
def get(id)
|
|
83
|
+
resp = Smplkit::Jobs.call_api { @api.get_job(id) }
|
|
84
|
+
Smplkit::Jobs::Job.from_resource(resp.data, client: self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Soft-delete a job.
|
|
88
|
+
#
|
|
89
|
+
# @param id [String]
|
|
90
|
+
# @return [nil]
|
|
91
|
+
def delete(id)
|
|
92
|
+
Smplkit::Jobs.call_api { @api.delete_job(id) }
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Trigger one immediate +MANUAL+ run of the job.
|
|
97
|
+
#
|
|
98
|
+
# @param id [String]
|
|
99
|
+
# @return [Smplkit::Jobs::Run]
|
|
100
|
+
def run(id)
|
|
101
|
+
resp = Smplkit::Jobs.call_api { @api.run_job_now(id) }
|
|
102
|
+
Smplkit::Jobs::Run.from_resource(resp.data)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Current-period usage counters for the account.
|
|
106
|
+
#
|
|
107
|
+
# @return [Smplkit::Jobs::Usage]
|
|
108
|
+
def usage
|
|
109
|
+
resp = Smplkit::Jobs.call_api { @usage_api.get_usage }
|
|
110
|
+
Smplkit::Jobs::Usage.from_resource(resp.data)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @api private — POST a new job. Called by {Smplkit::Jobs::Job#save} on
|
|
114
|
+
# unsaved instances. The jobs service requires a caller-supplied
|
|
115
|
+
# +data.id+ on create and 409s on conflict.
|
|
116
|
+
def _create_job(job)
|
|
117
|
+
raise ArgumentError, "Job.id is required on create (caller-supplied key)" if job.id.nil? || job.id.empty?
|
|
118
|
+
|
|
119
|
+
resp = Smplkit::Jobs.call_api { @api.create_job(build_create_body(job)) }
|
|
120
|
+
Smplkit::Jobs::Job.from_resource(resp.data, client: self)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @api private — Full-replace PUT for an existing job. Called by
|
|
124
|
+
# {Smplkit::Jobs::Job#save} on instances with +created_at+.
|
|
125
|
+
#
|
|
126
|
+
# Header values must be re-supplied as plaintext; the GET path redacts
|
|
127
|
+
# them, so a PUT body containing the redacted placeholder would persist
|
|
128
|
+
# that literal. Track real header values client-side and round-trip them.
|
|
129
|
+
def _update_job(job)
|
|
130
|
+
raise ArgumentError, "cannot update a Job with no id" if job.id.nil?
|
|
131
|
+
|
|
132
|
+
resp = Smplkit::Jobs.call_api { @api.update_job(job.id, build_body(job)) }
|
|
133
|
+
Smplkit::Jobs::Job.from_resource(resp.data, client: self)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def build_attrs(job)
|
|
139
|
+
SmplkitGeneratedClient::Jobs::Job.new(
|
|
140
|
+
name: job.name,
|
|
141
|
+
description: job.description,
|
|
142
|
+
enabled: job.enabled,
|
|
143
|
+
type: job.type,
|
|
144
|
+
schedule: job.schedule,
|
|
145
|
+
configuration: Smplkit::Jobs::HttpConfig.to_wire(job.configuration),
|
|
146
|
+
concurrency_policy: job.concurrency_policy
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def build_create_body(job)
|
|
151
|
+
# Create uses the distinct JobCreateRequest envelope; the jobs service
|
|
152
|
+
# requires data.id (the caller-supplied key) on create and 409s on
|
|
153
|
+
# conflict.
|
|
154
|
+
resource = SmplkitGeneratedClient::Jobs::JobCreateResource.new(
|
|
155
|
+
id: job.id.to_s,
|
|
156
|
+
type: "job",
|
|
157
|
+
attributes: build_attrs(job)
|
|
158
|
+
)
|
|
159
|
+
SmplkitGeneratedClient::Jobs::JobCreateRequest.new(data: resource)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def build_body(job)
|
|
163
|
+
# Update path uses the generic JobRequest envelope.
|
|
164
|
+
resource = SmplkitGeneratedClient::Jobs::JobResource.new(
|
|
165
|
+
id: job.id.to_s,
|
|
166
|
+
type: "job",
|
|
167
|
+
attributes: build_attrs(job)
|
|
168
|
+
)
|
|
169
|
+
SmplkitGeneratedClient::Jobs::JobRequest.new(data: resource)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# +mgmt.jobs.runs.*+ — read-only run history plus the cancel / rerun run
|
|
174
|
+
# actions.
|
|
175
|
+
class RunsNamespace
|
|
176
|
+
def initialize(api)
|
|
177
|
+
@api = api
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# List runs for the authenticated account, newest first. Cursor
|
|
181
|
+
# paginated (ADR-014): pass +page_size+ and the +after+ cursor from the
|
|
182
|
+
# prior page. Pass +job+ to scope to a single job's history.
|
|
183
|
+
#
|
|
184
|
+
# @param job [String, nil] Filter to a single job's run history, by job id.
|
|
185
|
+
# @param page_size [Integer, nil] Items per page (cursor pagination).
|
|
186
|
+
# @param after [String, nil] Opaque cursor token from a prior page.
|
|
187
|
+
# @return [Array<Smplkit::Jobs::Run>]
|
|
188
|
+
def list(job: nil, page_size: nil, after: nil)
|
|
189
|
+
opts = {}
|
|
190
|
+
opts[:filter_job] = job unless job.nil?
|
|
191
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
192
|
+
opts[:page_after] = after unless after.nil?
|
|
193
|
+
|
|
194
|
+
resp = Smplkit::Jobs.call_api { @api.list_runs(opts) }
|
|
195
|
+
(resp.data || []).map { |r| Smplkit::Jobs::Run.from_resource(r) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Fetch a single run by id.
|
|
199
|
+
#
|
|
200
|
+
# @param run_id [String]
|
|
201
|
+
# @return [Smplkit::Jobs::Run]
|
|
202
|
+
def get(run_id)
|
|
203
|
+
resp = Smplkit::Jobs.call_api { @api.get_run(run_id) }
|
|
204
|
+
Smplkit::Jobs::Run.from_resource(resp.data)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Cancel a pending run.
|
|
208
|
+
#
|
|
209
|
+
# @param run_id [String]
|
|
210
|
+
# @return [Smplkit::Jobs::Run]
|
|
211
|
+
def cancel(run_id)
|
|
212
|
+
resp = Smplkit::Jobs.call_api { @api.cancel_run(run_id) }
|
|
213
|
+
Smplkit::Jobs::Run.from_resource(resp.data)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Re-run a prior run, spawning a new +RERUN+ run.
|
|
217
|
+
#
|
|
218
|
+
# @param run_id [String]
|
|
219
|
+
# @return [Smplkit::Jobs::Run]
|
|
220
|
+
def rerun(run_id)
|
|
221
|
+
resp = Smplkit::Jobs.call_api { @api.rerun_run(run_id) }
|
|
222
|
+
Smplkit::Jobs::Run.from_resource(resp.data)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/smplkit.rb
CHANGED
|
@@ -16,7 +16,7 @@ require_relative "smplkit/version"
|
|
|
16
16
|
# +lib/smplkit/_generated/<svc>/lib+ uses internal +require+ paths like
|
|
17
17
|
# +smplkit_<svc>_client/api_client+, so the directory has to be on
|
|
18
18
|
# +$LOAD_PATH+ before its top-level entry is required.
|
|
19
|
-
%w[app audit config flags logging].each do |svc|
|
|
19
|
+
%w[app audit config flags jobs logging].each do |svc|
|
|
20
20
|
generated_lib = File.expand_path("smplkit/_generated/#{svc}/lib", __dir__)
|
|
21
21
|
$LOAD_PATH.unshift(generated_lib) if File.directory?(generated_lib)
|
|
22
22
|
end
|
|
@@ -28,7 +28,7 @@ module SmplkitGeneratedClient # rubocop:disable Style/OneClassPerFile
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
%w[smplkit_app_client smplkit_audit_client smplkit_config_client smplkit_flags_client
|
|
31
|
-
smplkit_logging_client].each do |gem_lib|
|
|
31
|
+
smplkit_jobs_client smplkit_logging_client].each do |gem_lib|
|
|
32
32
|
require gem_lib
|
|
33
33
|
rescue LoadError
|
|
34
34
|
# Generated tree may be intentionally absent in development snapshots —
|
|
@@ -65,10 +65,12 @@ require_relative "smplkit/audit/events"
|
|
|
65
65
|
require_relative "smplkit/audit/resource_types"
|
|
66
66
|
require_relative "smplkit/audit/event_types"
|
|
67
67
|
require_relative "smplkit/audit/client"
|
|
68
|
+
require_relative "smplkit/jobs/models"
|
|
68
69
|
require_relative "smplkit/management/types"
|
|
69
70
|
require_relative "smplkit/management/models"
|
|
70
71
|
require_relative "smplkit/management/buffer"
|
|
71
72
|
require_relative "smplkit/management/audit"
|
|
73
|
+
require_relative "smplkit/management/jobs"
|
|
72
74
|
require_relative "smplkit/management/client"
|
|
73
75
|
require_relative "smplkit/client"
|
|
74
76
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smplkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.85
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Smpl Solutions LLC
|
|
@@ -877,6 +877,7 @@ files:
|
|
|
877
877
|
- lib/smplkit/flags/types.rb
|
|
878
878
|
- lib/smplkit/generators/install_generator.rb
|
|
879
879
|
- lib/smplkit/helpers.rb
|
|
880
|
+
- lib/smplkit/jobs/models.rb
|
|
880
881
|
- lib/smplkit/log_level.rb
|
|
881
882
|
- lib/smplkit/logging/adapters/base.rb
|
|
882
883
|
- lib/smplkit/logging/adapters/semantic_logger_adapter.rb
|
|
@@ -891,6 +892,7 @@ files:
|
|
|
891
892
|
- lib/smplkit/management/audit.rb
|
|
892
893
|
- lib/smplkit/management/buffer.rb
|
|
893
894
|
- lib/smplkit/management/client.rb
|
|
895
|
+
- lib/smplkit/management/jobs.rb
|
|
894
896
|
- lib/smplkit/management/models.rb
|
|
895
897
|
- lib/smplkit/management/types.rb
|
|
896
898
|
- lib/smplkit/metrics.rb
|