smplkit 3.0.95 → 3.0.97
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/account/client.rb +128 -0
- data/lib/smplkit/account/models.rb +71 -0
- data/lib/smplkit/api_support.rb +91 -0
- data/lib/smplkit/audit/buffer.rb +3 -1
- data/lib/smplkit/audit/categories.rb +21 -10
- data/lib/smplkit/audit/client.rb +18 -9
- data/lib/smplkit/audit/event_types.rb +26 -10
- data/lib/smplkit/audit/events.rb +93 -17
- data/lib/smplkit/{management/audit.rb → audit/forwarders.rb} +93 -85
- data/lib/smplkit/audit/models.rb +86 -32
- data/lib/smplkit/audit/resource_types.rb +21 -9
- data/lib/smplkit/buffers.rb +250 -0
- data/lib/smplkit/client.rb +161 -70
- data/lib/smplkit/config/client.rb +874 -186
- data/lib/smplkit/config/helpers.rb +44 -6
- data/lib/smplkit/config/models.rb +114 -7
- data/lib/smplkit/config_resolution.rb +17 -9
- data/lib/smplkit/errors.rb +14 -3
- data/lib/smplkit/flags/client.rb +602 -116
- data/lib/smplkit/flags/models.rb +110 -8
- data/lib/smplkit/flags/types.rb +8 -9
- data/lib/smplkit/jobs/client.rb +306 -0
- data/lib/smplkit/jobs/models.rb +47 -18
- data/lib/smplkit/logging/client.rb +755 -191
- data/lib/smplkit/logging/helpers.rb +5 -1
- data/lib/smplkit/logging/levels.rb +3 -1
- data/lib/smplkit/logging/models.rb +163 -6
- data/lib/smplkit/logging/normalize.rb +3 -1
- data/lib/smplkit/logging/resolution.rb +4 -4
- data/lib/smplkit/logging/sources.rb +1 -1
- data/lib/smplkit/platform/client.rb +597 -0
- data/lib/smplkit/platform/models.rb +282 -0
- data/lib/smplkit/{management → platform}/types.rb +21 -4
- data/lib/smplkit/transport.rb +103 -0
- data/lib/smplkit/ws.rb +1 -1
- data/lib/smplkit.rb +18 -6
- metadata +11 -7
- data/lib/smplkit/management/buffer.rb +0 -198
- data/lib/smplkit/management/client.rb +0 -1074
- data/lib/smplkit/management/jobs.rb +0 -226
- data/lib/smplkit/management/models.rb +0 -178
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c317a0f573d990742547e60f7e17bba0bd65a6abc13dae8bcf8ab13ca7e167e3
|
|
4
|
+
data.tar.gz: c02f2385189d409d3f1a8263156f0a659e55ae762d4c2f9940bdf6a51d61f19d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33f03cf3bde2e7a38b9856b558be13546c374ceddec2c02ec9cb5d454922f373d46f39188e86ffbbba194421318b9d04bba1a966ac2930ad088c6cf46f6d7cf5
|
|
7
|
+
data.tar.gz: 91337cdc5b3da03eae931e8b67b7454d0d04925e667446b49ba003ecd0c6bcd091f319f15df58d2c926f818a563a5ab4648ef769261ffacc9422636f125dd3a6
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Smplkit
|
|
7
|
+
module Account
|
|
8
|
+
# Resolve the (app_base_url, api_key, extra_headers) for the settings client.
|
|
9
|
+
#
|
|
10
|
+
# +base_url+/+api_key+ are used directly when both are supplied (the path
|
|
11
|
+
# the top-level client takes after it has already resolved them); otherwise
|
|
12
|
+
# the config resolver fills in whatever is missing.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
def self.resolve_account_target(api_key:, base_url:, profile:, base_domain:, scheme:, debug:, extra_headers:)
|
|
16
|
+
cfg = ConfigResolution.resolve_client_config(
|
|
17
|
+
profile: profile, api_key: api_key, base_domain: base_domain, scheme: scheme, debug: debug
|
|
18
|
+
)
|
|
19
|
+
resolved_key = api_key.nil? ? cfg.api_key : api_key
|
|
20
|
+
app_url = base_url.nil? ? ConfigResolution.service_url(cfg.scheme, "app", cfg.base_domain) : base_url
|
|
21
|
+
headers = {}
|
|
22
|
+
headers.merge!(cfg.extra_headers || {})
|
|
23
|
+
headers.merge!(extra_headers || {})
|
|
24
|
+
[app_url.sub(%r{/+\z}, ""), resolved_key, headers]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Sync account-settings get/save (+client.account.settings+).
|
|
28
|
+
#
|
|
29
|
+
# The endpoint isn't JSON:API — body is a raw JSON object — so we use an
|
|
30
|
+
# HTTP client directly rather than going through a generated client.
|
|
31
|
+
class SettingsClient
|
|
32
|
+
SETTINGS_PATH = "/api/v1/accounts/current/settings"
|
|
33
|
+
|
|
34
|
+
def initialize(app_base_url, api_key, extra_headers = nil)
|
|
35
|
+
@base_url = app_base_url
|
|
36
|
+
@headers = {
|
|
37
|
+
"Authorization" => "Bearer #{api_key}",
|
|
38
|
+
"Content-Type" => "application/json"
|
|
39
|
+
}.merge(extra_headers || {})
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fetch the authenticated account's current settings.
|
|
43
|
+
#
|
|
44
|
+
# @return [Smplkit::Account::AccountSettings] An active record. Mutate its
|
|
45
|
+
# fields and call +save+ to persist the changes.
|
|
46
|
+
def get
|
|
47
|
+
resp = connection.get(SETTINGS_PATH)
|
|
48
|
+
Errors.raise_for_status(resp.status, resp.body.to_s)
|
|
49
|
+
AccountSettings.new(self, data: parse_body(resp.body))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @api private
|
|
53
|
+
def _save(data)
|
|
54
|
+
resp = connection.put(SETTINGS_PATH) { |req| req.body = JSON.generate(data) }
|
|
55
|
+
Errors.raise_for_status(resp.status, resp.body.to_s)
|
|
56
|
+
AccountSettings.new(self, data: parse_body(resp.body))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# A short-lived connection per call — mirrors the Python settings client
|
|
62
|
+
# opening and closing its own HTTP client each time.
|
|
63
|
+
def connection
|
|
64
|
+
Faraday.new(url: @base_url, headers: @headers, request: { timeout: 30 })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse_body(body)
|
|
68
|
+
return {} if body.nil? || body.to_s.empty?
|
|
69
|
+
|
|
70
|
+
parsed = JSON.parse(body)
|
|
71
|
+
parsed.is_a?(Hash) ? parsed : {}
|
|
72
|
+
rescue JSON::ParserError
|
|
73
|
+
{}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The Smpl Account client (sync).
|
|
78
|
+
#
|
|
79
|
+
# Exposes the authenticated account's own configuration, reachable as
|
|
80
|
+
# +client.account+ (+Smplkit::Client+) or constructed directly:
|
|
81
|
+
#
|
|
82
|
+
# account = Smplkit::AccountClient.new(api_key: "sk_...")
|
|
83
|
+
# settings = account.settings.get
|
|
84
|
+
# settings.environment_order = ["production", "staging"]
|
|
85
|
+
# settings.save
|
|
86
|
+
#
|
|
87
|
+
# Sub-client: +settings+ (get/save). Pure CRUD — no +install+ required.
|
|
88
|
+
#
|
|
89
|
+
# @param api_key [String, nil] API key. When omitted, resolved from
|
|
90
|
+
# +SMPLKIT_API_KEY+ or +~/.smplkit+.
|
|
91
|
+
# @param base_url [String, nil] Full app-service base URL. Usually resolved
|
|
92
|
+
# from +base_domain+/+scheme+; supplied directly by the top-level clients
|
|
93
|
+
# which have already computed it.
|
|
94
|
+
# @param profile [String, nil] Named +~/.smplkit+ profile section.
|
|
95
|
+
# @param base_domain [String, nil] Base domain for API requests (default
|
|
96
|
+
# +"smplkit.com"+).
|
|
97
|
+
# @param scheme [String, nil] URL scheme (default +"https"+).
|
|
98
|
+
# @param debug [Boolean, nil] Enable SDK debug logging.
|
|
99
|
+
# @param extra_headers [Hash, nil] Extra headers attached to every request.
|
|
100
|
+
class AccountClient
|
|
101
|
+
attr_reader :settings
|
|
102
|
+
|
|
103
|
+
def initialize(api_key: nil, base_url: nil, profile: nil, base_domain: nil,
|
|
104
|
+
scheme: nil, debug: nil, extra_headers: nil)
|
|
105
|
+
app_url, resolved_key, headers = Account.resolve_account_target(
|
|
106
|
+
api_key: api_key, base_url: base_url, profile: profile,
|
|
107
|
+
base_domain: base_domain, scheme: scheme, debug: debug, extra_headers: extra_headers
|
|
108
|
+
)
|
|
109
|
+
@settings = SettingsClient.new(app_url, resolved_key, headers.empty? ? nil : headers)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# No-op — the settings client opens a short-lived HTTP client per call.
|
|
113
|
+
def close; end
|
|
114
|
+
|
|
115
|
+
# Construct, yield to the block, and close on exit.
|
|
116
|
+
def self.open(**kwargs)
|
|
117
|
+
client = new(**kwargs)
|
|
118
|
+
begin
|
|
119
|
+
yield client
|
|
120
|
+
ensure
|
|
121
|
+
client.close
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
AccountClient = Account::AccountClient
|
|
128
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
module Account
|
|
5
|
+
# Active-record account-settings model.
|
|
6
|
+
#
|
|
7
|
+
# The wire format is opaque JSON. Documented keys are exposed as typed
|
|
8
|
+
# properties; unknown keys live in +raw+. +save+ writes the full settings
|
|
9
|
+
# object back.
|
|
10
|
+
class AccountSettings
|
|
11
|
+
def initialize(client = nil, data: nil)
|
|
12
|
+
@client = client
|
|
13
|
+
@data = data ? data.dup : {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The full settings dict. Mutations are persisted on save.
|
|
17
|
+
#
|
|
18
|
+
# @return [Hash] The full settings dict.
|
|
19
|
+
def raw
|
|
20
|
+
@data
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Replace the full settings dict.
|
|
24
|
+
#
|
|
25
|
+
# @param value [Hash] The new settings dict.
|
|
26
|
+
# @return [void]
|
|
27
|
+
def raw=(value)
|
|
28
|
+
@data = value.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Canonical ordering of STANDARD environments. Empty list if unset.
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<String>] The environment ids in canonical order.
|
|
34
|
+
def environment_order
|
|
35
|
+
Array(@data["environment_order"] || [])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set the canonical ordering of STANDARD environments.
|
|
39
|
+
#
|
|
40
|
+
# @param value [Array<String>] The environment ids in canonical order.
|
|
41
|
+
# @return [void]
|
|
42
|
+
def environment_order=(value)
|
|
43
|
+
@data["environment_order"] = value.to_a
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Write the full settings object back to the account.
|
|
47
|
+
#
|
|
48
|
+
# @return [AccountSettings] +self+, updated with the server response.
|
|
49
|
+
# @raise [RuntimeError] If this model was constructed without a client
|
|
50
|
+
# (e.g. built by hand rather than returned from +get+).
|
|
51
|
+
def save
|
|
52
|
+
raise "AccountSettings was constructed without a client; cannot save" if @client.nil?
|
|
53
|
+
|
|
54
|
+
other = @client._save(@data)
|
|
55
|
+
_apply(other)
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
alias save! save
|
|
59
|
+
|
|
60
|
+
def to_s
|
|
61
|
+
"AccountSettings(#{@data.inspect})"
|
|
62
|
+
end
|
|
63
|
+
alias inspect to_s
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def _apply(other)
|
|
67
|
+
@data = other.raw.dup
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smplkit
|
|
4
|
+
# Ruby-internal adapters bridging the generated client layer to the wrapper.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module ApiSupport
|
|
8
|
+
# Default page[size] the runtime asks for when walking a list endpoint to
|
|
9
|
+
# completion. The platform caps page[size] at 1000; using the same value
|
|
10
|
+
# here makes the minimum number of round-trips per exhaustive fetch.
|
|
11
|
+
RUNTIME_PAGE_SIZE = 1000
|
|
12
|
+
|
|
13
|
+
# Wraps a generated-API call and converts any +ApiError+ raised by the
|
|
14
|
+
# generated layer into the +Smplkit::Error+ hierarchy. Connection-level
|
|
15
|
+
# failures (no response from the server) become +Smplkit::ConnectionError+;
|
|
16
|
+
# status-coded failures route through +Errors.raise_for_status+ which emits
|
|
17
|
+
# +NotFoundError+ / +ConflictError+ / +ValidationError+ / +Error+ depending
|
|
18
|
+
# on the JSON:API body.
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
module ErrorMapping
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
yield
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
raise unless generated_api_error?(e)
|
|
28
|
+
|
|
29
|
+
raise Smplkit::ConnectionError, e.message.to_s if e.code.nil? || e.code.zero?
|
|
30
|
+
|
|
31
|
+
Smplkit::Errors.raise_for_status(e.code, e.response_body.to_s)
|
|
32
|
+
# raise_for_status only returns on 2xx; if we get here the generated
|
|
33
|
+
# layer raised on a 2xx (shouldn't happen) so re-raise the original.
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generated_api_error?(err)
|
|
38
|
+
klass_name = err.class.name.to_s
|
|
39
|
+
klass_name.start_with?("SmplkitGeneratedClient::") && klass_name.end_with?("::ApiError")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Walk a generated paginated list endpoint to completion.
|
|
44
|
+
#
|
|
45
|
+
# The block receives a per-page +opts+ hash with +page_number+ and
|
|
46
|
+
# +page_size+ filled in, calls the generated list method through
|
|
47
|
+
# {ErrorMapping.call}, and returns the response object. Pages stop when the
|
|
48
|
+
# server returns fewer rows than requested — the platform's standard
|
|
49
|
+
# last-page signal across every offset-paginated list endpoint. Returns the
|
|
50
|
+
# concatenated +response.data+ rows.
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
module PaginatedFetch
|
|
54
|
+
module_function
|
|
55
|
+
|
|
56
|
+
def collect(page_size: RUNTIME_PAGE_SIZE)
|
|
57
|
+
rows = []
|
|
58
|
+
page_number = 1
|
|
59
|
+
loop do
|
|
60
|
+
opts = { page_number: page_number, page_size: page_size }
|
|
61
|
+
response = ErrorMapping.call { yield(opts) }
|
|
62
|
+
page = response.data || []
|
|
63
|
+
rows.concat(page)
|
|
64
|
+
break if page.length < page_size
|
|
65
|
+
|
|
66
|
+
page_number += 1
|
|
67
|
+
end
|
|
68
|
+
rows
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Deep-stringify Hash keys so resources returned by generated +to_hash+
|
|
73
|
+
# (symbol-keyed) match what the wrapper helpers expect (string-keyed).
|
|
74
|
+
#
|
|
75
|
+
# @api private
|
|
76
|
+
module ResourceShim
|
|
77
|
+
module_function
|
|
78
|
+
|
|
79
|
+
def stringify(value)
|
|
80
|
+
Smplkit::Helpers.deep_stringify_keys(value)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Convenience: produce a string-keyed Hash from a generated model.
|
|
84
|
+
def from_model(model)
|
|
85
|
+
return {} if model.nil?
|
|
86
|
+
|
|
87
|
+
stringify(model.to_hash)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/smplkit/audit/buffer.rb
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
module Smplkit
|
|
4
4
|
module Audit
|
|
5
5
|
# Bounded in-memory queue + worker thread for fire-and-forget audit
|
|
6
|
-
# emits
|
|
6
|
+
# emits.
|
|
7
7
|
#
|
|
8
8
|
# +#enqueue+ returns immediately. The worker drains on either a
|
|
9
9
|
# periodic tick or once depth crosses the high-water mark, retries
|
|
10
10
|
# transient failures with exponential backoff, drops permanent 4xx
|
|
11
11
|
# (other than 429), and evicts the oldest item under sustained
|
|
12
12
|
# back-pressure.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
13
15
|
class EventBuffer
|
|
14
16
|
MAX_BUFFER_SIZE = 1000
|
|
15
17
|
WATERMARK = 50
|
|
@@ -5,21 +5,32 @@ module Smplkit
|
|
|
5
5
|
# +client.audit.categories.list+ — distinct +category+ values seen for
|
|
6
6
|
# the account.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# time is independent of how many years of events the account has
|
|
11
|
-
# accumulated. Sorted alphabetically; offset pagination (+page_number+ /
|
|
12
|
-
# +page_size+) per ADR-014.
|
|
8
|
+
# Response time is independent of how many years of events the account has
|
|
9
|
+
# accumulated. Sorted alphabetically; offset paginated.
|
|
13
10
|
class Categories
|
|
14
11
|
def initialize(api)
|
|
15
12
|
@api = api
|
|
16
13
|
end
|
|
17
14
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# the
|
|
21
|
-
#
|
|
22
|
-
# the
|
|
15
|
+
# List the distinct +category+ values seen in the account.
|
|
16
|
+
#
|
|
17
|
+
# +environments+ scopes the listing to a set of environments: pass an
|
|
18
|
+
# array of environment keys and/or the reserved +"smplkit"+ control-plane
|
|
19
|
+
# bucket; the values are comma-joined into +filter[environment]+. Omitting
|
|
20
|
+
# it (or passing an empty array) leaves the filter off entirely.
|
|
21
|
+
#
|
|
22
|
+
# @param page_number [Integer, nil] 1-based page index. Omit for the first
|
|
23
|
+
# page.
|
|
24
|
+
# @param page_size [Integer, nil] Maximum number of categories to return in
|
|
25
|
+
# this page.
|
|
26
|
+
# @param meta_total [Boolean, nil] When +true+, populate +total+ and
|
|
27
|
+
# +total_pages+ in the returned page's +pagination+ block (costs an extra
|
|
28
|
+
# count server-side). Omit to skip it.
|
|
29
|
+
# @param environments [Array<String>, nil] Environment keys and/or the
|
|
30
|
+
# reserved +"smplkit"+ control-plane bucket to scope the listing to. Omit
|
|
31
|
+
# to leave the filter off entirely.
|
|
32
|
+
# @return [Smplkit::Audit::CategoryListPage] A page of the matching
|
|
33
|
+
# category values.
|
|
23
34
|
def list(page_number: nil, page_size: nil, meta_total: nil, environments: nil)
|
|
24
35
|
opts = {}
|
|
25
36
|
opts[:page_number] = page_number if page_number
|
data/lib/smplkit/audit/client.rb
CHANGED
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module Smplkit
|
|
4
4
|
module Audit
|
|
5
|
-
# Audit
|
|
5
|
+
# The Smpl Audit client — accessed via +client.audit+.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# +categories+)
|
|
11
|
-
#
|
|
7
|
+
# One client exposes the full surface — event recording and reads,
|
|
8
|
+
# distinct-value discovery, and SIEM forwarder CRUD. Owns fire-and-forget
|
|
9
|
+
# +#events.record+, plus the audit-log +list+ / +get+ and the distinct-value
|
|
10
|
+
# listings (+resource_types+, +event_types+, +categories+), plus SIEM
|
|
11
|
+
# forwarder CRUD on +#forwarders+.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
13
|
+
# @param api_key [String] API key used to authenticate every request.
|
|
14
|
+
# @param base_url [String] Full audit-service base URL.
|
|
15
|
+
# @param environment [String, nil] Deployment environment to scope recording
|
|
16
|
+
# and reads to. Optional — forwarder CRUD and discovery are
|
|
17
|
+
# environment-agnostic, and reads accept an explicit +environments: [...]+
|
|
18
|
+
# filter.
|
|
19
|
+
# @param timeout [Float] Per-request timeout, in seconds. Defaults to +10.0+.
|
|
20
|
+
# @param extra_headers [Hash{String => String}, nil] Extra headers attached
|
|
21
|
+
# to every request. SDK-owned headers (authorization, content-type,
|
|
22
|
+
# user-agent) cannot be overridden.
|
|
15
23
|
class AuditClient
|
|
16
|
-
attr_reader :events, :resource_types, :event_types, :categories
|
|
24
|
+
attr_reader :events, :resource_types, :event_types, :categories, :forwarders
|
|
17
25
|
|
|
18
26
|
SDK_OWNED_HEADERS = %w[authorization content-type user-agent].freeze
|
|
19
27
|
|
|
@@ -41,6 +49,7 @@ module Smplkit
|
|
|
41
49
|
@resource_types = ResourceTypes.new(SmplkitGeneratedClient::Audit::ResourceTypesApi.new(api_client))
|
|
42
50
|
@event_types = EventTypes.new(SmplkitGeneratedClient::Audit::EventTypesApi.new(api_client))
|
|
43
51
|
@categories = Categories.new(SmplkitGeneratedClient::Audit::CategoriesApi.new(api_client))
|
|
52
|
+
@forwarders = ForwardersClient.new(SmplkitGeneratedClient::Audit::ForwardersApi.new(api_client))
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
def _close
|
|
@@ -7,22 +7,38 @@ module Smplkit
|
|
|
7
7
|
#
|
|
8
8
|
# Without +filter_resource_type+, returns one row per distinct
|
|
9
9
|
# event type — an event type recorded with multiple resource_types appears
|
|
10
|
-
# once. With the filter, returns the event types seen with that
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# once. With the filter, returns the event types seen with that specific
|
|
11
|
+
# resource_type, which supports building a cascading resource-type-then-
|
|
12
|
+
# event-type filter.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
# (+page_number+ / +page_size+) per ADR-014.
|
|
14
|
+
# Sorted alphabetically; offset paginated.
|
|
16
15
|
class EventTypes
|
|
17
16
|
def initialize(api)
|
|
18
17
|
@api = api
|
|
19
18
|
end
|
|
20
19
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# the
|
|
24
|
-
#
|
|
25
|
-
# the
|
|
20
|
+
# List the distinct +event_type+ slugs seen in the account.
|
|
21
|
+
#
|
|
22
|
+
# +environments+ scopes the listing to a set of environments: pass an
|
|
23
|
+
# array of environment keys and/or the reserved +"smplkit"+ control-plane
|
|
24
|
+
# bucket; the values are comma-joined into +filter[environment]+. Omitting
|
|
25
|
+
# it (or passing an empty array) leaves the filter off entirely.
|
|
26
|
+
#
|
|
27
|
+
# @param filter_resource_type [String, nil] Restrict the listing to
|
|
28
|
+
# event_types seen with this +resource_type+. Omit to list every distinct
|
|
29
|
+
# event_type.
|
|
30
|
+
# @param page_number [Integer, nil] 1-based page index. Omit for the first
|
|
31
|
+
# page.
|
|
32
|
+
# @param page_size [Integer, nil] Maximum number of slugs to return in this
|
|
33
|
+
# page.
|
|
34
|
+
# @param meta_total [Boolean, nil] When +true+, populate +total+ and
|
|
35
|
+
# +total_pages+ in the returned page's +pagination+ block (costs an extra
|
|
36
|
+
# count server-side). Omit to skip it.
|
|
37
|
+
# @param environments [Array<String>, nil] Environment keys and/or the
|
|
38
|
+
# reserved +"smplkit"+ control-plane bucket to scope the listing to. Omit
|
|
39
|
+
# to leave the filter off entirely.
|
|
40
|
+
# @return [Smplkit::Audit::EventTypeListPage] A page of the matching
|
|
41
|
+
# event-type slugs.
|
|
26
42
|
def list(filter_resource_type: nil, page_number: nil, page_size: nil, meta_total: nil, environments: nil)
|
|
27
43
|
opts = {}
|
|
28
44
|
opts[:filter_resource_type] = filter_resource_type if filter_resource_type
|
data/lib/smplkit/audit/events.rb
CHANGED
|
@@ -4,9 +4,9 @@ module Smplkit
|
|
|
4
4
|
module Audit
|
|
5
5
|
# Audit events surface — accessed via +client.audit.events+.
|
|
6
6
|
#
|
|
7
|
-
# +#record+ is fire-and-forget
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# +#record+ is fire-and-forget — the call enqueues the event onto an
|
|
8
|
+
# in-memory bounded buffer and returns immediately. +#list+ and +#get+
|
|
9
|
+
# are synchronous reads.
|
|
10
10
|
class Events
|
|
11
11
|
def initialize(api)
|
|
12
12
|
@api = api
|
|
@@ -15,18 +15,53 @@ module Smplkit
|
|
|
15
15
|
|
|
16
16
|
# Enqueue an audit event for asynchronous delivery.
|
|
17
17
|
#
|
|
18
|
+
# Returns immediately — the buffer's worker thread performs the actual
|
|
19
|
+
# POST with retry on transient failures.
|
|
20
|
+
#
|
|
18
21
|
# Actor attribution (+actor_type+, +actor_id+, +actor_label+) is
|
|
19
22
|
# customer-supplied and free-form. The audit service stores
|
|
20
23
|
# whatever the caller passed and never backfills from the request
|
|
21
24
|
# credential — supply the fields explicitly when you want the
|
|
22
25
|
# event attributed.
|
|
23
26
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
+
# @param event_type [String] What happened (e.g. +"invoice.created"+).
|
|
28
|
+
# Any non-empty string.
|
|
29
|
+
# @param resource_type [String] Kind of resource the event is about (e.g.
|
|
30
|
+
# +"invoice"+). Any non-empty string. Customer events must NOT use the
|
|
31
|
+
# +smpl.+ prefix — that namespace is reserved for smplkit-emitted events
|
|
32
|
+
# and the server rejects customer attempts with a 403 (the buffer logs
|
|
33
|
+
# and drops permanent failures).
|
|
34
|
+
# @param resource_id [String] Identifier of the affected resource.
|
|
35
|
+
# @param occurred_at [Time, DateTime, String, nil] When the event happened
|
|
36
|
+
# in the originating system. Defaults to +now+ server-side if omitted.
|
|
37
|
+
# @param actor_type [String, nil] Free-form label for the kind of actor
|
|
38
|
+
# that caused the event (e.g. +"USER"+, +"API_KEY"+, +"SYSTEM"+, or any
|
|
39
|
+
# custom value). The audit service never backfills this from the request
|
|
40
|
+
# credential — supply it explicitly when you want the event attributed.
|
|
41
|
+
# @param actor_id [String, nil] Free-form identifier of the actor that
|
|
42
|
+
# caused the event. Any string scheme is accepted.
|
|
43
|
+
# @param actor_label [String, nil] Human-readable label for the actor
|
|
44
|
+
# (e.g. an email address or API key name).
|
|
45
|
+
# @param category [String, nil] Optional free-form bucket label for the
|
|
46
|
+
# event (e.g. +"auth"+, +"billing"+, +"config-change"+). Stored exactly
|
|
47
|
+
# as supplied; powers the audit log's category filter and the
|
|
48
|
+
# +categories+ discovery listing ({Smplkit::Audit::AuditClient#categories}).
|
|
49
|
+
# Omit it to leave the event uncategorized.
|
|
50
|
+
# @param data [Hash, nil] Free-form contextual JSON. To record a resource
|
|
51
|
+
# snapshot, place it inside +data+ — smplkit's own convention nests it at
|
|
52
|
+
# +data["snapshot"]+ for consistency, but the shape is unconstrained.
|
|
53
|
+
# @param idempotency_key [String, nil] Optional caller-supplied idempotency
|
|
54
|
+
# key. If omitted, the server derives one from event content (account_id
|
|
55
|
+
# + event_type + resource_type + resource_id + occurred_at + actor_* +
|
|
56
|
+
# data).
|
|
57
|
+
# @param do_not_forward [Boolean] When +true+, the audit service records
|
|
58
|
+
# the event normally but does NOT POST it through any configured SIEM
|
|
59
|
+
# forwarder. A +skipped_do_not_forward+ delivery row is recorded for each
|
|
60
|
+
# enabled forwarder so the skip is visible in the forwarder delivery log.
|
|
61
|
+
# @return [void]
|
|
27
62
|
def record(event_type:, resource_type:, resource_id:,
|
|
28
63
|
occurred_at: nil, actor_type: nil, actor_id: nil,
|
|
29
|
-
actor_label: nil, data: nil, idempotency_key: nil,
|
|
64
|
+
actor_label: nil, category: nil, data: nil, idempotency_key: nil,
|
|
30
65
|
do_not_forward: false)
|
|
31
66
|
raise ArgumentError, "event_type is required" if event_type.nil? || event_type.to_s.empty?
|
|
32
67
|
raise ArgumentError, "resource_type is required" if resource_type.nil? || resource_type.to_s.empty?
|
|
@@ -55,6 +90,7 @@ module Smplkit
|
|
|
55
90
|
actor_type: actor_type,
|
|
56
91
|
actor_id: actor_id,
|
|
57
92
|
actor_label: actor_label,
|
|
93
|
+
category: category,
|
|
58
94
|
data: data || {},
|
|
59
95
|
do_not_forward: do_not_forward
|
|
60
96
|
)
|
|
@@ -69,22 +105,55 @@ module Smplkit
|
|
|
69
105
|
|
|
70
106
|
# Single-event retrieval.
|
|
71
107
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
108
|
+
# @param event_id [String] The event's UUID, as a string parseable as one.
|
|
109
|
+
# @return [Smplkit::Audit::AuditEvent] The matching event.
|
|
110
|
+
# @raise [Smplkit::NotFoundError] If no event with that id exists in the
|
|
111
|
+
# caller's account.
|
|
74
112
|
def get(event_id)
|
|
75
113
|
resp = Smplkit::Audit.call_api { @api.get_event(event_id) }
|
|
76
114
|
AuditEvent.from_resource(resp.data)
|
|
77
115
|
end
|
|
78
116
|
|
|
79
|
-
# List events
|
|
80
|
-
#
|
|
81
|
-
#
|
|
117
|
+
# List audit events for the authenticated account.
|
|
118
|
+
#
|
|
119
|
+
# Filters apply server-side. +actor_id+ is matched as a literal string
|
|
120
|
+
# against whatever the recording call stored. Pagination uses an opaque
|
|
121
|
+
# cursor (+page_after+); the returned page exposes +#next_cursor+ when
|
|
122
|
+
# more pages are available.
|
|
123
|
+
#
|
|
124
|
+
# +search+ is an optional free-text filter: pass a string to return only
|
|
125
|
+
# events whose +resource_id+ or +description+ contains it as a
|
|
126
|
+
# case-insensitive substring; omit it (the default) to disable text
|
|
127
|
+
# filtering. A +search+ filter must be scoped — combine it with
|
|
128
|
+
# +occurred_at_range+, or with both +resource_type+ and +resource_id+ —
|
|
129
|
+
# or the request is rejected.
|
|
82
130
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
131
|
+
# @param event_type [String, nil] Return only events with this
|
|
132
|
+
# +event_type+. Omit to match any.
|
|
133
|
+
# @param resource_type [String, nil] Return only events about this
|
|
134
|
+
# +resource_type+. Omit to match any.
|
|
135
|
+
# @param resource_id [String, nil] Return only events about this resource
|
|
136
|
+
# id. Omit to match any.
|
|
137
|
+
# @param actor_type [String, nil] Return only events whose +actor_type+
|
|
138
|
+
# equals this value. Omit to match any.
|
|
139
|
+
# @param actor_id [String, nil] Return only events whose +actor_id+
|
|
140
|
+
# matches this value as a literal string. Omit to match any.
|
|
141
|
+
# @param occurred_at_range [String, nil] Restrict to events whose
|
|
142
|
+
# +occurred_at+ falls in this range. Omit to leave the time window open.
|
|
143
|
+
# @param search [String, nil] Optional free-text filter — returns only
|
|
144
|
+
# events whose +resource_id+ or +description+ contains it as a
|
|
145
|
+
# case-insensitive substring. Must be scoped (combine with
|
|
146
|
+
# +occurred_at_range+, or with both +resource_type+ and +resource_id+)
|
|
147
|
+
# or the request is rejected. Omit to disable text filtering.
|
|
148
|
+
# @param environments [Array<String>, nil] Environment keys (and/or the
|
|
149
|
+
# reserved +"smplkit"+ control-plane bucket) to scope the read to. Omit
|
|
150
|
+
# to leave the filter off entirely.
|
|
151
|
+
# @param page_size [Integer, nil] Maximum number of events to return in
|
|
152
|
+
# this page.
|
|
153
|
+
# @param page_after [String, nil] Opaque cursor from a previous page's
|
|
154
|
+
# +next_cursor+. Omit for the first page.
|
|
155
|
+
# @return [Smplkit::Audit::ListEventsPage] A page of the matching events;
|
|
156
|
+
# its +#next_cursor+ is set when more pages are available.
|
|
88
157
|
def list(event_type: nil, resource_type: nil, resource_id: nil,
|
|
89
158
|
actor_type: nil, actor_id: nil, occurred_at_range: nil,
|
|
90
159
|
search: nil, environments: nil, page_size: nil, page_after: nil)
|
|
@@ -112,6 +181,13 @@ module Smplkit
|
|
|
112
181
|
end
|
|
113
182
|
|
|
114
183
|
# Block until the in-memory buffer is drained or the timeout elapses.
|
|
184
|
+
#
|
|
185
|
+
# Useful for draining buffered events at process shutdown or after a batch
|
|
186
|
+
# of fire-and-forget records.
|
|
187
|
+
#
|
|
188
|
+
# @param timeout [Float, nil] Upper bound on the blocking flush, in
|
|
189
|
+
# seconds. +nil+ blocks indefinitely. Defaults to +5.0+.
|
|
190
|
+
# @return [void]
|
|
115
191
|
def flush(timeout: 5.0)
|
|
116
192
|
@buffer.flush(timeout: timeout)
|
|
117
193
|
end
|