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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76d48d080f70c89c2f8d1af67d4fb6df32f0490a200351ed53a5f733c8a4a082
4
- data.tar.gz: d31418b27a7853f9da10dbc65cee816330db331b69ea139f514c7b8dd12521a6
3
+ metadata.gz: 2ad7af7469c15e0dc8228442eac8974c02bccb72e087ccc26e6a622ad81ecffb
4
+ data.tar.gz: 060a8c1159094f6f06548fab3a5131a73059dfd01f5e36969a1c9a9ec9024295
5
5
  SHA512:
6
- metadata.gz: af2675a5bb4a3d8d068370d054bf289617ebb6f7f412fee8fc2770c4b34cc00ce8d5cbc517c80501cfdaf3567af2e021f713d0b2e95fe88413fce14ceaffb5f6
7
- data.tar.gz: 5ca474a7489922e6bcad50b77342498a77ff5180d551e6bbcd1a344d68fbd5b42a01ebec939509e2cd86a824659d82c0fe014a1405436266f2c70b5fa70e14ea
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.84
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