smplkit 3.0.95 → 3.0.96
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 +121 -0
- data/lib/smplkit/account/models.rb +53 -0
- data/lib/smplkit/api_support.rb +83 -0
- data/lib/smplkit/audit/client.rb +9 -10
- data/lib/smplkit/{management/audit.rb → audit/forwarders.rb} +73 -76
- data/lib/smplkit/audit/models.rb +40 -1
- data/lib/smplkit/buffers.rb +235 -0
- data/lib/smplkit/client.rb +126 -67
- data/lib/smplkit/config/client.rb +617 -182
- data/lib/smplkit/config_resolution.rb +11 -5
- data/lib/smplkit/errors.rb +8 -0
- data/lib/smplkit/flags/client.rb +472 -114
- data/lib/smplkit/flags/types.rb +6 -7
- data/lib/smplkit/{management/jobs.rb → jobs/client.rb} +148 -89
- data/lib/smplkit/logging/client.rb +647 -192
- data/lib/smplkit/logging/helpers.rb +1 -0
- data/lib/smplkit/logging/models.rb +92 -1
- data/lib/smplkit/logging/sources.rb +1 -1
- data/lib/smplkit/platform/client.rb +472 -0
- data/lib/smplkit/platform/models.rb +182 -0
- data/lib/smplkit/{management → platform}/types.rb +7 -4
- data/lib/smplkit/transport.rb +99 -0
- 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/models.rb +0 -178
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
module Smplkit
|
|
4
4
|
module Logging
|
|
5
|
+
# Per-environment configuration on a logger or log group.
|
|
6
|
+
#
|
|
7
|
+
# Lives at +logger.environments[env_name]+ (a +Hash{String => LoggerEnvironment}+).
|
|
8
|
+
# Frozen — mutate the override via +logger.set_level(level, environment: "...")+
|
|
9
|
+
# or remove it via +logger.clear_level(environment: "...")+.
|
|
10
|
+
#
|
|
11
|
+
# Attributes:
|
|
12
|
+
# - level: Per-environment level override (+nil+ means no override).
|
|
13
|
+
LoggerEnvironment = Struct.new(:level, keyword_init: true) do
|
|
14
|
+
def initialize(level: nil)
|
|
15
|
+
super
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Coerce a dict input into +Hash{String => LoggerEnvironment}+.
|
|
21
|
+
#
|
|
22
|
+
# Accepts both pre-built +LoggerEnvironment+ instances and the wire-shaped
|
|
23
|
+
# +{env_id => {"level" => "ERROR"}}+ dicts.
|
|
24
|
+
def self.convert_environments(value)
|
|
25
|
+
return {} if value.nil? || value.empty?
|
|
26
|
+
|
|
27
|
+
value.each_with_object({}) do |(env_id, env_data), out|
|
|
28
|
+
out[env_id] = build_logger_environment(env_data)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.build_logger_environment(env_data)
|
|
33
|
+
return env_data if env_data.is_a?(LoggerEnvironment)
|
|
34
|
+
return LoggerEnvironment.new unless env_data.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
level_str = env_data["level"] || env_data[:level]
|
|
37
|
+
return LoggerEnvironment.new if level_str.nil?
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
LoggerEnvironment.new(level: LogLevel.coerce(level_str))
|
|
41
|
+
rescue ArgumentError
|
|
42
|
+
LoggerEnvironment.new
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Convert a typed environments dict to the wire-shaped dict for sending.
|
|
47
|
+
# Entries with +level=nil+ are skipped (no override to send).
|
|
48
|
+
def self.environments_to_wire(environments)
|
|
49
|
+
environments.each_with_object({}) do |(env_id, env), out|
|
|
50
|
+
out[env_id] = { "level" => env.level.to_s } unless env.level.nil?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
5
54
|
# A logger resource managed by the smplkit Logging service.
|
|
6
55
|
#
|
|
7
56
|
# Attributes:
|
|
@@ -12,13 +61,15 @@ module Smplkit
|
|
|
12
61
|
# - service, environment: provenance
|
|
13
62
|
# - log_group_id: parent log group, if any
|
|
14
63
|
# - managed: whether the SDK should apply server-driven level changes
|
|
64
|
+
# - environments: per-environment level overrides, keyed by environment
|
|
15
65
|
class SmplLogger
|
|
16
66
|
attr_accessor :id, :name, :resolved_level, :level, :service, :environment,
|
|
17
67
|
:log_group_id, :managed, :created_at, :updated_at, :description
|
|
18
68
|
|
|
19
69
|
def initialize(client = nil, name:, resolved_level:, id: nil, level: nil,
|
|
20
70
|
service: nil, environment: nil, log_group_id: nil,
|
|
21
|
-
managed: true, description: nil,
|
|
71
|
+
managed: true, description: nil, environments: nil,
|
|
72
|
+
created_at: nil, updated_at: nil)
|
|
22
73
|
@client = client
|
|
23
74
|
@id = id
|
|
24
75
|
@name = name
|
|
@@ -29,12 +80,51 @@ module Smplkit
|
|
|
29
80
|
@log_group_id = log_group_id
|
|
30
81
|
@managed = managed
|
|
31
82
|
@description = description
|
|
83
|
+
@environments = Logging.convert_environments(environments)
|
|
32
84
|
@created_at = created_at
|
|
33
85
|
@updated_at = updated_at
|
|
34
86
|
end
|
|
35
87
|
|
|
36
88
|
def managed? = !!@managed
|
|
37
89
|
|
|
90
|
+
# Read-only view of per-environment level overrides. Mutate via
|
|
91
|
+
# +set_level+ / +clear_level+ (with +environment: "..."+).
|
|
92
|
+
def environments
|
|
93
|
+
@environments.dup
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set the log level.
|
|
97
|
+
#
|
|
98
|
+
# With +environment: nil+ (the default), sets the base log level used when
|
|
99
|
+
# no environment-specific override applies. With +environment: "..."+,
|
|
100
|
+
# sets the per-environment override. Changes are local until +save+.
|
|
101
|
+
def set_level(level, environment: nil)
|
|
102
|
+
if environment.nil?
|
|
103
|
+
@level = level
|
|
104
|
+
else
|
|
105
|
+
@environments[environment] = LoggerEnvironment.new(level: level)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Remove a log level.
|
|
110
|
+
#
|
|
111
|
+
# With +environment: nil+ (the default), removes the base log level (the
|
|
112
|
+
# logger then inherits from its group / ancestor / system default). With
|
|
113
|
+
# +environment: "..."+, removes the per-environment override only. Changes
|
|
114
|
+
# are local until +save+.
|
|
115
|
+
def clear_level(environment: nil)
|
|
116
|
+
if environment.nil?
|
|
117
|
+
@level = nil
|
|
118
|
+
else
|
|
119
|
+
@environments.delete(environment)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Remove all per-environment level overrides.
|
|
124
|
+
def clear_all_environment_levels
|
|
125
|
+
@environments = {}
|
|
126
|
+
end
|
|
127
|
+
|
|
38
128
|
def save
|
|
39
129
|
raise "SmplLogger was constructed without a client; cannot save" if @client.nil?
|
|
40
130
|
|
|
@@ -61,6 +151,7 @@ module Smplkit
|
|
|
61
151
|
@log_group_id = other.log_group_id
|
|
62
152
|
@managed = other.managed
|
|
63
153
|
@description = other.description
|
|
154
|
+
@environments = other.environments
|
|
64
155
|
@created_at = other.created_at
|
|
65
156
|
@updated_at = other.updated_at
|
|
66
157
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Smplkit
|
|
4
4
|
module Logging
|
|
5
|
-
# Describes a logger to register via +
|
|
5
|
+
# Describes a logger to register via +client.logging.loggers.register+.
|
|
6
6
|
#
|
|
7
7
|
# Used both for buffered runtime discovery (called by +Smplkit::Client+ as
|
|
8
8
|
# adapters discover loggers) and for explicit registration from setup
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The Smpl Platform client — cross-cutting CRUD on +client.platform+.
|
|
4
|
+
#
|
|
5
|
+
# +PlatformClient+ groups the account-wide configuration resources that aren't
|
|
6
|
+
# owned by a single product, mirroring the product UI's Platform area:
|
|
7
|
+
#
|
|
8
|
+
# - +platform.environments+ — environment CRUD
|
|
9
|
+
# - +platform.services+ — service CRUD
|
|
10
|
+
# - +platform.contexts+ — evaluation-context registration + read/delete
|
|
11
|
+
# - +platform.context_types+ — context-type CRUD
|
|
12
|
+
#
|
|
13
|
+
# All four are pure CRUD — no +install+ gate. Every sub-client speaks to the
|
|
14
|
+
# app service, so the client needs exactly one app transport (plus the
|
|
15
|
+
# context-registration buffer that +contexts+ drains).
|
|
16
|
+
#
|
|
17
|
+
# The client supports two construction shapes:
|
|
18
|
+
#
|
|
19
|
+
# * *Wired* into +Smplkit::Client+ — borrows the parent's app transport and an
|
|
20
|
+
# externally-supplied context buffer. This is the common path;
|
|
21
|
+
# +client.flags+ borrows +client.platform.contexts+ as its evaluation-context
|
|
22
|
+
# registration seam.
|
|
23
|
+
# * *Standalone* — +PlatformClient.new(api_key: ..., base_url: ..., ...)+ builds
|
|
24
|
+
# and owns its own app transport and buffer. +close+ tears down only the
|
|
25
|
+
# owned transport.
|
|
26
|
+
module Smplkit
|
|
27
|
+
module Platform
|
|
28
|
+
# Resolve the two-arg or composite-id form to +[type, key]+.
|
|
29
|
+
def self.split_context_id(id_or_type, key)
|
|
30
|
+
return [id_or_type, key] unless key.nil?
|
|
31
|
+
|
|
32
|
+
unless id_or_type.include?(":")
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"context id must be 'type:key' (got #{id_or_type.inspect}); " \
|
|
35
|
+
"alternatively pass type and key as separate args"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
id_or_type.split(":", 2)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Build a standalone app transport from resolved config.
|
|
42
|
+
#
|
|
43
|
+
# +base_url+/+api_key+ are used directly when supplied (the path the
|
|
44
|
+
# top-level client takes after it has already resolved them); otherwise the
|
|
45
|
+
# management config resolver fills in whatever is missing (+~/.smplkit+ /
|
|
46
|
+
# env vars / defaults).
|
|
47
|
+
def self.app_transport(api_key:, base_url:, profile:, base_domain:, scheme:, debug:, extra_headers:)
|
|
48
|
+
cfg = ConfigResolution.resolve_management_config(
|
|
49
|
+
profile: profile, api_key: api_key, base_domain: base_domain, scheme: scheme, debug: debug
|
|
50
|
+
)
|
|
51
|
+
resolved_key = api_key.nil? ? cfg.api_key : api_key
|
|
52
|
+
merged = {}
|
|
53
|
+
merged.merge!(cfg.extra_headers || {})
|
|
54
|
+
merged.merge!(extra_headers || {})
|
|
55
|
+
tcfg = ConfigResolution::ResolvedManagementConfig.new(
|
|
56
|
+
api_key: resolved_key, base_domain: cfg.base_domain, scheme: cfg.scheme,
|
|
57
|
+
debug: cfg.debug, extra_headers: merged
|
|
58
|
+
)
|
|
59
|
+
Transport.build_api_client(SmplkitGeneratedClient::App, "app", tcfg, base_url: base_url)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# -----------------------------------------------------------------------
|
|
63
|
+
# Environments
|
|
64
|
+
# -----------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
# Sync environment CRUD (+client.platform.environments+).
|
|
67
|
+
class EnvironmentsClient
|
|
68
|
+
def initialize(app_http)
|
|
69
|
+
@api = SmplkitGeneratedClient::App::EnvironmentsApi.new(app_http)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Return an unsaved +Environment+. Call +.save+ to persist.
|
|
73
|
+
def new(id, name:, color: nil, classification: EnvironmentClassification::STANDARD)
|
|
74
|
+
Environment.new(self, id: id, name: name, color: color, classification: classification)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def list(page_number: nil, page_size: nil)
|
|
78
|
+
opts = {}
|
|
79
|
+
opts[:page_number] = page_number unless page_number.nil?
|
|
80
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
81
|
+
response = ApiSupport::ErrorMapping.call { @api.list_environments(opts) }
|
|
82
|
+
(response.data || []).map { |r| from_resource(ApiSupport::ResourceShim.from_model(r)) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def get(id)
|
|
86
|
+
response = ApiSupport::ErrorMapping.call { @api.get_environment(id) }
|
|
87
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def delete(id)
|
|
91
|
+
ApiSupport::ErrorMapping.call { @api.delete_environment(id) }
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def _create(env)
|
|
96
|
+
response = ApiSupport::ErrorMapping.call { @api.create_environment(body_for(env)) }
|
|
97
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def _update(env)
|
|
101
|
+
raise "cannot update an Environment with no id" if env.id.nil?
|
|
102
|
+
|
|
103
|
+
response = ApiSupport::ErrorMapping.call { @api.update_environment(env.id, body_for(env)) }
|
|
104
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def body_for(env)
|
|
110
|
+
SmplkitGeneratedClient::App::EnvironmentRequest.new(
|
|
111
|
+
data: SmplkitGeneratedClient::App::EnvironmentResource.new(
|
|
112
|
+
type: "environment",
|
|
113
|
+
id: env.id,
|
|
114
|
+
attributes: SmplkitGeneratedClient::App::Environment.new(
|
|
115
|
+
name: env.name,
|
|
116
|
+
color: env.color&.hex,
|
|
117
|
+
classification: env.classification
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def from_resource(resource)
|
|
124
|
+
attrs = resource["attributes"] || {}
|
|
125
|
+
classification =
|
|
126
|
+
attrs["classification"] == "AD_HOC" ? EnvironmentClassification::AD_HOC : EnvironmentClassification::STANDARD
|
|
127
|
+
Environment.new(
|
|
128
|
+
self,
|
|
129
|
+
id: resource["id"], name: attrs["name"], color: attrs["color"],
|
|
130
|
+
classification: classification,
|
|
131
|
+
created_at: attrs["created_at"], updated_at: attrs["updated_at"]
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# -----------------------------------------------------------------------
|
|
137
|
+
# Services
|
|
138
|
+
# -----------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
# Sync service CRUD (+client.platform.services+).
|
|
141
|
+
class ServicesClient
|
|
142
|
+
def initialize(app_http)
|
|
143
|
+
@api = SmplkitGeneratedClient::App::ServicesApi.new(app_http)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Return an unsaved +Service+. Call +.save+ to persist.
|
|
147
|
+
def new(id, name:)
|
|
148
|
+
Service.new(self, id: id, name: name)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def list(page_number: nil, page_size: nil)
|
|
152
|
+
opts = {}
|
|
153
|
+
opts[:page_number] = page_number unless page_number.nil?
|
|
154
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
155
|
+
response = ApiSupport::ErrorMapping.call { @api.list_services(opts) }
|
|
156
|
+
(response.data || []).map { |r| from_resource(ApiSupport::ResourceShim.from_model(r)) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def get(id)
|
|
160
|
+
response = ApiSupport::ErrorMapping.call { @api.get_service(id) }
|
|
161
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def delete(id)
|
|
165
|
+
ApiSupport::ErrorMapping.call { @api.delete_service(id) }
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def _create(svc)
|
|
170
|
+
response = ApiSupport::ErrorMapping.call { @api.create_service(create_body_for(svc)) }
|
|
171
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def _update(svc)
|
|
175
|
+
raise "cannot update a Service with no id" if svc.id.nil?
|
|
176
|
+
|
|
177
|
+
response = ApiSupport::ErrorMapping.call { @api.update_service(svc.id, body_for(svc)) }
|
|
178
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def body_for(svc)
|
|
184
|
+
SmplkitGeneratedClient::App::ServiceRequest.new(
|
|
185
|
+
data: SmplkitGeneratedClient::App::ServiceResource.new(
|
|
186
|
+
type: "service",
|
|
187
|
+
id: svc.id,
|
|
188
|
+
attributes: SmplkitGeneratedClient::App::Service.new(name: svc.name)
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def create_body_for(svc)
|
|
194
|
+
SmplkitGeneratedClient::App::ServiceCreateRequest.new(
|
|
195
|
+
data: SmplkitGeneratedClient::App::ServiceCreateResource.new(
|
|
196
|
+
type: "service",
|
|
197
|
+
id: svc.id,
|
|
198
|
+
attributes: SmplkitGeneratedClient::App::Service.new(name: svc.name)
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def from_resource(resource)
|
|
204
|
+
attrs = resource["attributes"] || {}
|
|
205
|
+
Service.new(
|
|
206
|
+
self,
|
|
207
|
+
id: resource["id"], name: attrs["name"],
|
|
208
|
+
created_at: attrs["created_at"], updated_at: attrs["updated_at"]
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# -----------------------------------------------------------------------
|
|
214
|
+
# Context Types
|
|
215
|
+
# -----------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
# Sync context-type CRUD (+client.platform.context_types+).
|
|
218
|
+
class ContextTypesClient
|
|
219
|
+
def initialize(app_http)
|
|
220
|
+
@api = SmplkitGeneratedClient::App::ContextTypesApi.new(app_http)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def new(id, name: nil, attributes: nil)
|
|
224
|
+
ContextType.new(self, id: id, name: name || id, attributes: attributes || {})
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def list(page_number: nil, page_size: nil)
|
|
228
|
+
opts = {}
|
|
229
|
+
opts[:page_number] = page_number unless page_number.nil?
|
|
230
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
231
|
+
response = ApiSupport::ErrorMapping.call { @api.list_context_types(opts) }
|
|
232
|
+
(response.data || []).map { |r| from_resource(ApiSupport::ResourceShim.from_model(r)) }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def get(id)
|
|
236
|
+
response = ApiSupport::ErrorMapping.call { @api.get_context_type(id) }
|
|
237
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def delete(id)
|
|
241
|
+
ApiSupport::ErrorMapping.call { @api.delete_context_type(id) }
|
|
242
|
+
nil
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def _create(ct)
|
|
246
|
+
response = ApiSupport::ErrorMapping.call { @api.create_context_type(body_for(ct)) }
|
|
247
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def _update(ct)
|
|
251
|
+
raise "cannot update a ContextType with no id" if ct.id.nil?
|
|
252
|
+
|
|
253
|
+
response = ApiSupport::ErrorMapping.call { @api.update_context_type(ct.id, body_for(ct)) }
|
|
254
|
+
from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def body_for(ct)
|
|
260
|
+
SmplkitGeneratedClient::App::ContextTypeRequest.new(
|
|
261
|
+
data: SmplkitGeneratedClient::App::ContextTypeResource.new(
|
|
262
|
+
type: "context_type",
|
|
263
|
+
id: ct.id,
|
|
264
|
+
attributes: SmplkitGeneratedClient::App::ContextType.new(name: ct.name, attributes: ct.attributes)
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def from_resource(resource)
|
|
270
|
+
attrs = resource["attributes"] || {}
|
|
271
|
+
raw_meta = attrs["attributes"]
|
|
272
|
+
attribute_metadata = raw_meta.is_a?(Hash) ? raw_meta : {}
|
|
273
|
+
ContextType.new(
|
|
274
|
+
self,
|
|
275
|
+
id: resource["id"], name: attrs["name"], attributes: attribute_metadata,
|
|
276
|
+
created_at: attrs["created_at"], updated_at: attrs["updated_at"]
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# -----------------------------------------------------------------------
|
|
282
|
+
# Contexts
|
|
283
|
+
# -----------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
# Sync context registration + read/delete (+client.platform.contexts+).
|
|
286
|
+
class ContextsClient
|
|
287
|
+
def initialize(app_http, buffer)
|
|
288
|
+
@api = SmplkitGeneratedClient::App::ContextsApi.new(app_http)
|
|
289
|
+
@buffer = buffer
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Buffer contexts for registration; optionally flush immediately.
|
|
293
|
+
def register(items, flush: false)
|
|
294
|
+
batch = items.is_a?(Array) ? items : [items]
|
|
295
|
+
@buffer.observe(batch)
|
|
296
|
+
if flush
|
|
297
|
+
self.flush
|
|
298
|
+
return
|
|
299
|
+
end
|
|
300
|
+
return unless @buffer.pending_count >= CONTEXT_BATCH_FLUSH_SIZE
|
|
301
|
+
|
|
302
|
+
Thread.new { threshold_flush }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Send any pending observations to the server.
|
|
306
|
+
def flush
|
|
307
|
+
batch = @buffer.drain
|
|
308
|
+
return if batch.empty?
|
|
309
|
+
|
|
310
|
+
body = build_bulk_register_body(batch)
|
|
311
|
+
ApiSupport::ErrorMapping.call { @api.bulk_register_contexts(body) }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Number of observations queued and awaiting flush.
|
|
315
|
+
def pending_count
|
|
316
|
+
@buffer.pending_count
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# List all contexts of a given type.
|
|
320
|
+
def list(type, page_number: nil, page_size: nil)
|
|
321
|
+
opts = { filter_context_type: type }
|
|
322
|
+
opts[:page_number] = page_number unless page_number.nil?
|
|
323
|
+
opts[:page_size] = page_size unless page_size.nil?
|
|
324
|
+
response = ApiSupport::ErrorMapping.call { @api.list_contexts(opts) }
|
|
325
|
+
(response.data || []).map { |r| context_from_resource(ApiSupport::ResourceShim.from_model(r)) }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def get(id_or_type, key = nil)
|
|
329
|
+
ctx_type, ctx_key = Platform.split_context_id(id_or_type, key)
|
|
330
|
+
response = ApiSupport::ErrorMapping.call { @api.get_context("#{ctx_type}:#{ctx_key}") }
|
|
331
|
+
context_from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def delete(id_or_type, key = nil)
|
|
335
|
+
ctx_type, ctx_key = Platform.split_context_id(id_or_type, key)
|
|
336
|
+
ApiSupport::ErrorMapping.call { @api.delete_context("#{ctx_type}:#{ctx_key}") }
|
|
337
|
+
nil
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def _save_context(ctx)
|
|
341
|
+
body = ctx_to_resource(ctx)
|
|
342
|
+
response = ApiSupport::ErrorMapping.call { @api.update_context(ctx.id, body) }
|
|
343
|
+
context_from_resource(ApiSupport::ResourceShim.from_model(response.data))
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
private
|
|
347
|
+
|
|
348
|
+
def threshold_flush
|
|
349
|
+
flush
|
|
350
|
+
rescue StandardError => e
|
|
351
|
+
Smplkit.debug("registration", "context registration flush failed: #{e.class}: #{e.message}")
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def build_bulk_register_body(items)
|
|
355
|
+
bulk = items.map do |item|
|
|
356
|
+
SmplkitGeneratedClient::App::ContextBulkItem.new(
|
|
357
|
+
type: item["type"], key: item["key"], attributes: item["attributes"] || {}
|
|
358
|
+
)
|
|
359
|
+
end
|
|
360
|
+
SmplkitGeneratedClient::App::ContextBulkRegister.new(contexts: bulk)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def ctx_to_resource(ctx)
|
|
364
|
+
SmplkitGeneratedClient::App::ContextResponse.new(
|
|
365
|
+
data: SmplkitGeneratedClient::App::ContextResource.new(
|
|
366
|
+
type: "context",
|
|
367
|
+
id: ctx.id,
|
|
368
|
+
attributes: SmplkitGeneratedClient::App::Context.new(
|
|
369
|
+
name: ctx.name, context_type: ctx.type, attributes: ctx.attributes
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def context_from_resource(resource)
|
|
376
|
+
attrs = resource["attributes"] || {}
|
|
377
|
+
Smplkit::Context.new(
|
|
378
|
+
attrs["context_type"] || attrs["type"] || resource["id"].to_s.split(":").first,
|
|
379
|
+
attrs["key"] || resource["id"].to_s.split(":", 2).last,
|
|
380
|
+
attrs["attributes"] || {},
|
|
381
|
+
name: attrs["name"],
|
|
382
|
+
created_at: attrs["created_at"],
|
|
383
|
+
updated_at: attrs["updated_at"]
|
|
384
|
+
)._bind_client(self)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# -----------------------------------------------------------------------
|
|
389
|
+
# PlatformClient (client.platform)
|
|
390
|
+
# -----------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
# The Smpl Platform client (sync).
|
|
393
|
+
#
|
|
394
|
+
# Groups the account-wide CRUD resources that aren't owned by a single
|
|
395
|
+
# product, reachable as +client.platform+ (+Smplkit::Client+) or
|
|
396
|
+
# constructed directly:
|
|
397
|
+
#
|
|
398
|
+
# platform = Smplkit::PlatformClient.new(api_key: "sk_...")
|
|
399
|
+
# prod = platform.environments.new("production", name: "Production")
|
|
400
|
+
# prod.save
|
|
401
|
+
# platform.services.list.each { |svc| ... }
|
|
402
|
+
#
|
|
403
|
+
# Sub-clients: +environments+, +services+, +contexts+, +context_types+.
|
|
404
|
+
# Pure CRUD — no +install+ required.
|
|
405
|
+
#
|
|
406
|
+
# @param api_key [String, nil] API key. When omitted, resolved from
|
|
407
|
+
# +SMPLKIT_API_KEY+ or +~/.smplkit+.
|
|
408
|
+
# @param base_url [String, nil] Full app-service base URL. Usually resolved
|
|
409
|
+
# from +base_domain+/+scheme+; supplied directly by the top-level clients
|
|
410
|
+
# which have already computed it.
|
|
411
|
+
# @param profile [String, nil] Named +~/.smplkit+ profile section.
|
|
412
|
+
# @param base_domain [String, nil] Base domain for API requests (default
|
|
413
|
+
# +"smplkit.com"+).
|
|
414
|
+
# @param scheme [String, nil] URL scheme (default +"https"+).
|
|
415
|
+
# @param debug [Boolean, nil] Enable SDK debug logging.
|
|
416
|
+
# @param extra_headers [Hash, nil] Extra headers attached to every request.
|
|
417
|
+
# @param app_transport Internal — a pre-built app transport supplied by a
|
|
418
|
+
# top-level client so the platform surface shares one connection pool.
|
|
419
|
+
# Not for direct use.
|
|
420
|
+
# @param context_buffer Internal — the shared context-registration buffer.
|
|
421
|
+
# Not for direct use.
|
|
422
|
+
class PlatformClient
|
|
423
|
+
attr_reader :environments, :services, :contexts, :context_types
|
|
424
|
+
|
|
425
|
+
def initialize(api_key: nil, base_url: nil, profile: nil, base_domain: nil,
|
|
426
|
+
scheme: nil, debug: nil, extra_headers: nil,
|
|
427
|
+
app_transport: nil, context_buffer: nil)
|
|
428
|
+
if app_transport.nil?
|
|
429
|
+
@app_http = Platform.app_transport(
|
|
430
|
+
api_key: api_key, base_url: base_url, profile: profile,
|
|
431
|
+
base_domain: base_domain, scheme: scheme, debug: debug, extra_headers: extra_headers
|
|
432
|
+
)
|
|
433
|
+
@owns_transport = true
|
|
434
|
+
else
|
|
435
|
+
@app_http = app_transport
|
|
436
|
+
@owns_transport = false
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
buffer = context_buffer || ContextRegistrationBuffer.new
|
|
440
|
+
@context_buffer = buffer
|
|
441
|
+
|
|
442
|
+
@environments = EnvironmentsClient.new(@app_http)
|
|
443
|
+
@services = ServicesClient.new(@app_http)
|
|
444
|
+
@contexts = ContextsClient.new(@app_http, buffer)
|
|
445
|
+
@context_types = ContextTypesClient.new(@app_http)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Close the app transport — only when this client owns it.
|
|
449
|
+
#
|
|
450
|
+
# A wired client borrows the parent's app transport and closes nothing.
|
|
451
|
+
def close
|
|
452
|
+
return unless @owns_transport
|
|
453
|
+
|
|
454
|
+
# The generated ApiClient owns Faraday connections that release on GC;
|
|
455
|
+
# there is no explicit shutdown to call.
|
|
456
|
+
nil
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Construct, yield to the block, and close on exit.
|
|
460
|
+
def self.open(**kwargs)
|
|
461
|
+
client = new(**kwargs)
|
|
462
|
+
begin
|
|
463
|
+
yield client
|
|
464
|
+
ensure
|
|
465
|
+
client.close
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
PlatformClient = Platform::PlatformClient
|
|
472
|
+
end
|