reactor_sdk 0.1.0
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 +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +281 -0
- data/lib/reactor_sdk/authentication.rb +137 -0
- data/lib/reactor_sdk/client.rb +186 -0
- data/lib/reactor_sdk/configuration.rb +102 -0
- data/lib/reactor_sdk/connection.rb +342 -0
- data/lib/reactor_sdk/endpoints/app_configurations.rb +42 -0
- data/lib/reactor_sdk/endpoints/audit_events.rb +64 -0
- data/lib/reactor_sdk/endpoints/base_endpoint.rb +207 -0
- data/lib/reactor_sdk/endpoints/builds.rb +62 -0
- data/lib/reactor_sdk/endpoints/callbacks.rb +38 -0
- data/lib/reactor_sdk/endpoints/companies.rb +42 -0
- data/lib/reactor_sdk/endpoints/data_elements.rb +251 -0
- data/lib/reactor_sdk/endpoints/environments.rb +174 -0
- data/lib/reactor_sdk/endpoints/extension_package_usage_authorizations.rb +51 -0
- data/lib/reactor_sdk/endpoints/extension_packages.rb +63 -0
- data/lib/reactor_sdk/endpoints/extensions.rb +181 -0
- data/lib/reactor_sdk/endpoints/hosts.rb +101 -0
- data/lib/reactor_sdk/endpoints/libraries.rb +872 -0
- data/lib/reactor_sdk/endpoints/notes.rb +11 -0
- data/lib/reactor_sdk/endpoints/profiles.rb +14 -0
- data/lib/reactor_sdk/endpoints/properties.rb +123 -0
- data/lib/reactor_sdk/endpoints/revisions.rb +102 -0
- data/lib/reactor_sdk/endpoints/rule_components.rb +218 -0
- data/lib/reactor_sdk/endpoints/rules.rb +240 -0
- data/lib/reactor_sdk/endpoints/search.rb +23 -0
- data/lib/reactor_sdk/endpoints/secrets.rb +76 -0
- data/lib/reactor_sdk/error.rb +115 -0
- data/lib/reactor_sdk/library_comparison_builder.rb +74 -0
- data/lib/reactor_sdk/library_snapshot_builder.rb +66 -0
- data/lib/reactor_sdk/paginator.rb +92 -0
- data/lib/reactor_sdk/rate_limiter.rb +96 -0
- data/lib/reactor_sdk/reference_extractor.rb +34 -0
- data/lib/reactor_sdk/resource_metadata.rb +73 -0
- data/lib/reactor_sdk/resource_normalizer.rb +90 -0
- data/lib/reactor_sdk/resources/app_configuration.rb +20 -0
- data/lib/reactor_sdk/resources/audit_event.rb +45 -0
- data/lib/reactor_sdk/resources/base_resource.rb +181 -0
- data/lib/reactor_sdk/resources/build.rb +64 -0
- data/lib/reactor_sdk/resources/callback.rb +16 -0
- data/lib/reactor_sdk/resources/company.rb +38 -0
- data/lib/reactor_sdk/resources/comprehensive_data_element.rb +28 -0
- data/lib/reactor_sdk/resources/comprehensive_extension.rb +30 -0
- data/lib/reactor_sdk/resources/comprehensive_resource.rb +31 -0
- data/lib/reactor_sdk/resources/comprehensive_rule.rb +26 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain.rb +50 -0
- data/lib/reactor_sdk/resources/comprehensive_upstream_chain_entry.rb +34 -0
- data/lib/reactor_sdk/resources/data_element.rb +108 -0
- data/lib/reactor_sdk/resources/environment.rb +45 -0
- data/lib/reactor_sdk/resources/extension.rb +66 -0
- data/lib/reactor_sdk/resources/extension_package.rb +49 -0
- data/lib/reactor_sdk/resources/extension_package_usage_authorization.rb +26 -0
- data/lib/reactor_sdk/resources/host.rb +68 -0
- data/lib/reactor_sdk/resources/library.rb +67 -0
- data/lib/reactor_sdk/resources/library_comparison.rb +72 -0
- data/lib/reactor_sdk/resources/library_comparison_entry.rb +144 -0
- data/lib/reactor_sdk/resources/library_snapshot.rb +118 -0
- data/lib/reactor_sdk/resources/library_snapshot_extension_index.rb +70 -0
- data/lib/reactor_sdk/resources/library_snapshot_index.rb +169 -0
- data/lib/reactor_sdk/resources/library_with_resources.rb +194 -0
- data/lib/reactor_sdk/resources/note.rb +37 -0
- data/lib/reactor_sdk/resources/profile.rb +22 -0
- data/lib/reactor_sdk/resources/property.rb +44 -0
- data/lib/reactor_sdk/resources/revision.rb +156 -0
- data/lib/reactor_sdk/resources/rule.rb +44 -0
- data/lib/reactor_sdk/resources/rule_component.rb +101 -0
- data/lib/reactor_sdk/resources/search_results.rb +28 -0
- data/lib/reactor_sdk/resources/secret.rb +17 -0
- data/lib/reactor_sdk/resources/upstream_chain.rb +80 -0
- data/lib/reactor_sdk/resources/upstream_chain_entry.rb +55 -0
- data/lib/reactor_sdk/response_parser.rb +160 -0
- data/lib/reactor_sdk/version.rb +5 -0
- data/lib/reactor_sdk.rb +79 -0
- data/reactor_sdk.gemspec +70 -0
- data/sig/reactor_sdk.rbs +346 -0
- metadata +293 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0aa1ebc8313c5d522d17ac36860404dddb917ff6b92df673f1b6161b28a78523
|
|
4
|
+
data.tar.gz: 9ffc87d3a9413abfffdfd0b15b9e3a3d8b42721f7d80d0c2eaf481fec0ac93ee
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 67970e3f8a09bd53c7654fb71b104e76ca12318f0dec8745aa4a84a0c1f62217fdd4f91f199c0218f613ffe05a25153d91f885d9c1ebb21d5fd49ce2260142b8
|
|
7
|
+
data.tar.gz: 65a4ecb927896f6bddd075916ed172b1e1653446c87c41120524614303d975646cff5416725e5828543a7f310ddc64cfa39f1a2700d5928701dfbb8f273aa156
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## [0.1.0] - 2026-04-01
|
|
2
|
+
|
|
3
|
+
- Added support for app configurations, callbacks, secrets, extension packages,
|
|
4
|
+
extension package usage authorizations, profile lookup, search, and direct
|
|
5
|
+
note lookup to align the SDK with Adobe's current Reactor endpoint families.
|
|
6
|
+
- Added missing relationship and maintenance operations across properties,
|
|
7
|
+
extensions, libraries, builds, rules, rule components, and notes-bearing
|
|
8
|
+
resources.
|
|
9
|
+
- Corrected audit event listing to use Adobe's current global `/audit_events`
|
|
10
|
+
endpoint while keeping `list_for_property` as a backward-compatible wrapper.
|
|
11
|
+
- Added multipart extension package upload support and coverage for the new
|
|
12
|
+
endpoint surface.
|
|
13
|
+
- Added contributor and security documentation for open source maintenance.
|
|
14
|
+
- Added a pinned `.ruby-version` to align local development with CI.
|
|
15
|
+
- Expanded CI and core infrastructure test coverage.
|
|
16
|
+
- Aligned gem metadata and install documentation with the published gem name.
|
|
17
|
+
- Documented current Adobe Reactor API coverage and multi-client usage patterns.
|
|
18
|
+
- Added test coverage proving separate client instances keep credentials isolated.
|
|
19
|
+
- Initial public release.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dhairya Gabhawala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src=".github/assets/reactor-sdk-lockup.svg" alt="ReactorSDK" width="520">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/dhairyagabha/reactor_sdk/actions/workflows/main.yml">
|
|
7
|
+
<img src="https://github.com/dhairyagabha/reactor_sdk/actions/workflows/main.yml/badge.svg" alt="CI">
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://rubygems.org/gems/reactor_sdk">
|
|
10
|
+
<img src="https://img.shields.io/gem/v/reactor_sdk.svg" alt="RubyGems version">
|
|
11
|
+
</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://reactor-sdk.dhairyagabhawala.com">Documentation</a>
|
|
16
|
+
·
|
|
17
|
+
<a href="https://github.com/dhairyagabha/reactor_sdk/blob/main/CHANGELOG.md">Changelog</a>
|
|
18
|
+
·
|
|
19
|
+
<a href="https://rubygems.org/gems/reactor_sdk">RubyGems</a>
|
|
20
|
+
·
|
|
21
|
+
<a href="https://github.com/dhairyagabha/reactor_sdk">GitHub</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
# ReactorSDK
|
|
25
|
+
|
|
26
|
+
`reactor_sdk` is a Ruby SDK for the Adobe Launch / Data Collection Reactor API. It handles OAuth Server-to-Server authentication, JSON:API parsing, pagination, retries, rate limiting, revision snapshots, and review-friendly comparison helpers so application code can work with typed Ruby objects instead of raw HTTP payloads.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- OAuth Server-to-Server authentication with automatic token refresh
|
|
31
|
+
- Typed resource objects across companies, properties, rules, data elements, extensions, libraries, builds, revisions, notes, and more
|
|
32
|
+
- Automatic pagination for list endpoints
|
|
33
|
+
- Consistent error classes and retry behavior around Adobe API failures
|
|
34
|
+
- Library-aware review helpers for rules, data elements, and extensions
|
|
35
|
+
- Upstream-chain lookup for resource review across Development, Staging, and Production
|
|
36
|
+
- Library-to-library comparison output with status, revision IDs, and normalized review payloads
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Add the gem to your application:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
gem "reactor_sdk"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then install it:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or install directly:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gem install reactor_sdk
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication and Configuration
|
|
59
|
+
|
|
60
|
+
ReactorSDK uses Adobe OAuth Server-to-Server credentials. JWT / Service Account credentials are not supported.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
require "reactor_sdk"
|
|
64
|
+
|
|
65
|
+
client = ReactorSDK::Client.new(
|
|
66
|
+
client_id: ENV.fetch("ADOBE_CLIENT_ID"),
|
|
67
|
+
client_secret: ENV.fetch("ADOBE_CLIENT_SECRET"),
|
|
68
|
+
org_id: ENV.fetch("ADOBE_IMS_ORG_ID")
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Optional configuration is passed to the same client constructor:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
client = ReactorSDK::Client.new(
|
|
76
|
+
client_id: "your-client-id",
|
|
77
|
+
client_secret: "your-client-secret",
|
|
78
|
+
org_id: "your-org-id@AdobeOrg",
|
|
79
|
+
base_url: "https://reactor.adobe.io",
|
|
80
|
+
ims_token_url: "https://ims-na1.adobelogin.com/ims/token/v3",
|
|
81
|
+
timeout: 30,
|
|
82
|
+
logger: Logger.new($stdout),
|
|
83
|
+
auto_refresh_token: true
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick Start
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
require "reactor_sdk"
|
|
91
|
+
|
|
92
|
+
client = ReactorSDK::Client.new(
|
|
93
|
+
client_id: ENV.fetch("ADOBE_CLIENT_ID"),
|
|
94
|
+
client_secret: ENV.fetch("ADOBE_CLIENT_SECRET"),
|
|
95
|
+
org_id: ENV.fetch("ADOBE_IMS_ORG_ID")
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# List companies available to the credential set.
|
|
99
|
+
companies = client.companies.list
|
|
100
|
+
company = companies.first
|
|
101
|
+
|
|
102
|
+
# List properties inside the company.
|
|
103
|
+
properties = client.properties.list_for_company(company.id)
|
|
104
|
+
property = properties.first
|
|
105
|
+
|
|
106
|
+
# List rules in the property.
|
|
107
|
+
rules = client.rules.list_for_property(property.id)
|
|
108
|
+
puts rules.first&.name
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Review and Promotion Workflows
|
|
112
|
+
|
|
113
|
+
ReactorSDK includes higher-level helpers for review tooling and release workflows, not just CRUD wrappers.
|
|
114
|
+
|
|
115
|
+
### Work with a Library Snapshot
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
# LB_DEV = development library ID.
|
|
119
|
+
# PR123 = property ID.
|
|
120
|
+
snapshot = client.libraries.find_snapshot("LB_DEV", property_id: "PR123")
|
|
121
|
+
|
|
122
|
+
# Point-in-time rule components associated with the rule inside this library snapshot.
|
|
123
|
+
snapshot.rule_components_for_rule("RL123").map(&:id)
|
|
124
|
+
|
|
125
|
+
# Snapshot-scoped impact analysis for a data element.
|
|
126
|
+
snapshot.impacted_rules_for("DE123").map(&:name)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Build Comprehensive Review Objects
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
rule_review = client.rules.find_comprehensive(
|
|
133
|
+
"RL123",
|
|
134
|
+
library_id: "LB_DEV",
|
|
135
|
+
property_id: "PR123"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
data_element_review = client.data_elements.find_comprehensive(
|
|
139
|
+
"DE123",
|
|
140
|
+
library_id: "LB_DEV",
|
|
141
|
+
property_id: "PR123"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
extension_review = client.extensions.find_comprehensive(
|
|
145
|
+
"EX123",
|
|
146
|
+
library_id: "LB_DEV",
|
|
147
|
+
property_id: "PR123"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
puts rule_review.normalized_json
|
|
151
|
+
puts data_element_review.normalized_json
|
|
152
|
+
puts extension_review.normalized_json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
These comprehensive review objects include the resource plus the associated records a reviewer usually needs:
|
|
156
|
+
|
|
157
|
+
- Rules include rule components
|
|
158
|
+
- Data elements include referenced data elements and impacted rules
|
|
159
|
+
- Extensions include dependent data elements, rule components, and rules
|
|
160
|
+
|
|
161
|
+
### Resolve Upstream Versions
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
chain = client.rules.comprehensive_upstream_chain(
|
|
165
|
+
"RL123",
|
|
166
|
+
library_id: "LB_DEV",
|
|
167
|
+
property_id: "PR123"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
staging_entry = chain.entries.find { |entry| entry.stage == "staging" }
|
|
171
|
+
|
|
172
|
+
puts chain.target_revision_id
|
|
173
|
+
puts staging_entry&.revision_id
|
|
174
|
+
puts staging_entry&.normalized_json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Compare Two Libraries
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# Compare Development against Staging.
|
|
181
|
+
comparison = client.libraries.compare(
|
|
182
|
+
"LB_DEV",
|
|
183
|
+
baseline_library_id: "LB_STG",
|
|
184
|
+
property_id: "PR123"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
comparison.entries.each do |entry|
|
|
188
|
+
puts "#{entry.resource_type} #{entry.resource_id} #{entry.status}"
|
|
189
|
+
puts entry.current_revision_id
|
|
190
|
+
puts entry.baseline_revision_id
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Inspect normalized output for one entry.
|
|
194
|
+
entry = comparison.entries.first
|
|
195
|
+
puts entry.current_normalized_json
|
|
196
|
+
puts entry.baseline_normalized_json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Sample comparison output:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
# Example status rows you might print from comparison.entries:
|
|
203
|
+
# rules RL100 modified
|
|
204
|
+
# current revision = RE_CUR_RULE
|
|
205
|
+
# baseline revision = RE_BASE_RULE
|
|
206
|
+
#
|
|
207
|
+
# rules RL200 added
|
|
208
|
+
# current revision = RE_ADDED
|
|
209
|
+
# baseline revision = nil
|
|
210
|
+
#
|
|
211
|
+
# data_elements DE200 removed
|
|
212
|
+
# current revision = nil
|
|
213
|
+
# baseline revision = RE_REMOVED
|
|
214
|
+
#
|
|
215
|
+
# extensions EX100 unchanged
|
|
216
|
+
# current revision = RE_EX
|
|
217
|
+
# baseline revision = RE_EX
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Comparison is performed across the library's top-level rules, data elements, and extensions. Rule components are still included in the comprehensive rule payload so rule diffs remain readable.
|
|
221
|
+
|
|
222
|
+
## Endpoint Coverage
|
|
223
|
+
|
|
224
|
+
The SDK currently exposes these endpoint families:
|
|
225
|
+
|
|
226
|
+
- Companies
|
|
227
|
+
- Properties
|
|
228
|
+
- App configurations
|
|
229
|
+
- Callbacks
|
|
230
|
+
- Secrets
|
|
231
|
+
- Environments
|
|
232
|
+
- Hosts
|
|
233
|
+
- Rules
|
|
234
|
+
- Rule components
|
|
235
|
+
- Data elements
|
|
236
|
+
- Extensions
|
|
237
|
+
- Extension packages
|
|
238
|
+
- Extension package usage authorizations
|
|
239
|
+
- Libraries
|
|
240
|
+
- Builds
|
|
241
|
+
- Revisions
|
|
242
|
+
- Notes
|
|
243
|
+
- Audit events
|
|
244
|
+
- Profiles
|
|
245
|
+
- Search
|
|
246
|
+
|
|
247
|
+
## Documentation
|
|
248
|
+
|
|
249
|
+
Full guides, API reference pages, example payloads, and review-workflow documentation are available at:
|
|
250
|
+
|
|
251
|
+
- https://reactor-sdk.dhairyagabhawala.com
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
Install dependencies:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
bundle install
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Run the test suite:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
bundle exec rspec
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Run RuboCop:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
bundle exec rubocop
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Build the gem:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
gem build reactor_sdk.gemspec
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Contributing
|
|
280
|
+
|
|
281
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and development guidance.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file authentication.rb
|
|
5
|
+
# @description Handles Adobe IMS OAuth Server-to-Server authentication.
|
|
6
|
+
#
|
|
7
|
+
# Fetches and caches access tokens using the client_credentials grant.
|
|
8
|
+
# Tokens are refreshed automatically when within REFRESH_BUFFER_SECONDS
|
|
9
|
+
# of expiry. Thread-safe via Mutex.
|
|
10
|
+
#
|
|
11
|
+
# Adobe deprecated JWT (Service Account) authentication on January 1 2025.
|
|
12
|
+
# This implementation uses OAuth Server-to-Server only.
|
|
13
|
+
#
|
|
14
|
+
# The token URL is read from config rather than hardcoded so that tests
|
|
15
|
+
# can override it via Configuration's ims_token_url parameter without
|
|
16
|
+
# any monkey-patching or global state changes.
|
|
17
|
+
#
|
|
18
|
+
# @domain Infrastructure
|
|
19
|
+
# @depends ReactorSDK::Configuration
|
|
20
|
+
#
|
|
21
|
+
# @see https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
module ReactorSDK
|
|
25
|
+
class Authentication
|
|
26
|
+
# Adobe IMS token endpoint — single global endpoint for all regions
|
|
27
|
+
# Read by Configuration as its default value for ims_token_url
|
|
28
|
+
IMS_TOKEN_URL = 'https://ims-na1.adobelogin.com/ims/token/v3'
|
|
29
|
+
|
|
30
|
+
# Refresh the token this many seconds before actual expiry.
|
|
31
|
+
# Prevents edge cases where a token expires between check and use.
|
|
32
|
+
REFRESH_BUFFER_SECONDS = 300
|
|
33
|
+
|
|
34
|
+
# Required OAuth scopes for full Reactor API access
|
|
35
|
+
REACTOR_SCOPE = [
|
|
36
|
+
'openid',
|
|
37
|
+
'AdobeID',
|
|
38
|
+
'read_organizations',
|
|
39
|
+
'additional_info.projectedProductContext'
|
|
40
|
+
].join(',').freeze
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @param config [ReactorSDK::Configuration] SDK configuration instance
|
|
44
|
+
#
|
|
45
|
+
def initialize(config)
|
|
46
|
+
@config = config
|
|
47
|
+
@token = nil
|
|
48
|
+
@token_expiry = nil
|
|
49
|
+
@mutex = Mutex.new
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Returns a valid access token, fetching or refreshing if necessary.
|
|
54
|
+
# Thread-safe — uses a Mutex to prevent parallel token fetches in
|
|
55
|
+
# multi-threaded environments such as Puma.
|
|
56
|
+
#
|
|
57
|
+
# @return [String] Valid Adobe IMS Bearer access token
|
|
58
|
+
# @raise [ReactorSDK::AuthenticationError] if the token request fails
|
|
59
|
+
#
|
|
60
|
+
def access_token
|
|
61
|
+
@mutex.synchronize do
|
|
62
|
+
fetch_token if token_expired?
|
|
63
|
+
@token
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Returns true if the token is missing or within the refresh buffer window.
|
|
71
|
+
#
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
#
|
|
74
|
+
def token_expired?
|
|
75
|
+
@token.nil? ||
|
|
76
|
+
Time.now.utc >= (@token_expiry - REFRESH_BUFFER_SECONDS)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Fetches a fresh access token from Adobe IMS using client_credentials grant.
|
|
81
|
+
# Uses @config.ims_token_url so tests can intercept without hitting Adobe.
|
|
82
|
+
#
|
|
83
|
+
# @raise [ReactorSDK::AuthenticationError] if the IMS request fails
|
|
84
|
+
# @sideeffect Sets @token and @token_expiry
|
|
85
|
+
#
|
|
86
|
+
def fetch_token
|
|
87
|
+
response = Faraday.post(@config.ims_token_url, token_request_params)
|
|
88
|
+
|
|
89
|
+
unless response.success?
|
|
90
|
+
raise AuthenticationError.new(
|
|
91
|
+
"Adobe IMS token request failed (HTTP #{response.status}). " \
|
|
92
|
+
'Check your client_id and client_secret.',
|
|
93
|
+
status: response.status
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
parse_token_response(response.body)
|
|
98
|
+
rescue Faraday::Error => e
|
|
99
|
+
raise AuthenticationError.new(
|
|
100
|
+
"Network error during token fetch: #{e.message}",
|
|
101
|
+
cause: e
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# Builds the POST body parameters for the IMS token request.
|
|
107
|
+
#
|
|
108
|
+
# @return [Hash] Form-encoded parameters for the IMS endpoint
|
|
109
|
+
#
|
|
110
|
+
def token_request_params
|
|
111
|
+
{
|
|
112
|
+
grant_type: 'client_credentials',
|
|
113
|
+
client_id: @config.client_id,
|
|
114
|
+
client_secret: @config.client_secret,
|
|
115
|
+
scope: REACTOR_SCOPE
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# Parses the JSON response and stores the token and its expiry time.
|
|
121
|
+
#
|
|
122
|
+
# @param body [String] Raw JSON response body from Adobe IMS
|
|
123
|
+
# @raise [ReactorSDK::AuthenticationError] if body is not valid JSON
|
|
124
|
+
# @sideeffect Sets @token and @token_expiry
|
|
125
|
+
#
|
|
126
|
+
def parse_token_response(body)
|
|
127
|
+
data = JSON.parse(body)
|
|
128
|
+
@token = data.fetch('access_token')
|
|
129
|
+
@token_expiry = Time.now.utc + data.fetch('expires_in').to_i
|
|
130
|
+
rescue JSON::ParserError, KeyError => e
|
|
131
|
+
raise AuthenticationError.new(
|
|
132
|
+
'Could not parse Adobe IMS token response',
|
|
133
|
+
cause: e
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# @file client.rb
|
|
5
|
+
# @description Main entry point for the ReactorSDK gem.
|
|
6
|
+
#
|
|
7
|
+
# Instantiate one client per Adobe org. The client wires together all
|
|
8
|
+
# infrastructure and exposes every endpoint group as a named method.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# client = ReactorSDK::Client.new(
|
|
12
|
+
# client_id: ENV["ADOBE_CLIENT_ID"],
|
|
13
|
+
# client_secret: ENV["ADOBE_CLIENT_SECRET"],
|
|
14
|
+
# org_id: ENV["ADOBE_IMS_ORG_ID"]
|
|
15
|
+
# )
|
|
16
|
+
#
|
|
17
|
+
# # Fetch hosts before creating an environment
|
|
18
|
+
# hosts = client.hosts.list_for_property("PR123")
|
|
19
|
+
# client.environments.create(
|
|
20
|
+
# property_id: "PR123",
|
|
21
|
+
# name: "jsmith-dev",
|
|
22
|
+
# stage: "development",
|
|
23
|
+
# host_id: hosts.first.id
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
module ReactorSDK
|
|
28
|
+
class Client
|
|
29
|
+
ENDPOINT_CLASSES = {
|
|
30
|
+
companies: Endpoints::Companies,
|
|
31
|
+
properties: Endpoints::Properties,
|
|
32
|
+
app_configurations: Endpoints::AppConfigurations,
|
|
33
|
+
callbacks: Endpoints::Callbacks,
|
|
34
|
+
secrets: Endpoints::Secrets,
|
|
35
|
+
environments: Endpoints::Environments,
|
|
36
|
+
hosts: Endpoints::Hosts,
|
|
37
|
+
rules: Endpoints::Rules,
|
|
38
|
+
rule_components: Endpoints::RuleComponents,
|
|
39
|
+
data_elements: Endpoints::DataElements,
|
|
40
|
+
extensions: Endpoints::Extensions,
|
|
41
|
+
extension_packages: Endpoints::ExtensionPackages,
|
|
42
|
+
extension_package_usage_authorizations: Endpoints::ExtensionPackageUsageAuthorizations,
|
|
43
|
+
libraries: Endpoints::Libraries,
|
|
44
|
+
builds: Endpoints::Builds,
|
|
45
|
+
audit_events: Endpoints::AuditEvents,
|
|
46
|
+
revisions: Endpoints::Revisions,
|
|
47
|
+
profiles: Endpoints::Profiles,
|
|
48
|
+
search: Endpoints::Search,
|
|
49
|
+
notes: Endpoints::Notes
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
# @return [ReactorSDK::Endpoints::Companies]
|
|
53
|
+
attr_reader :companies
|
|
54
|
+
|
|
55
|
+
# @return [ReactorSDK::Endpoints::Properties]
|
|
56
|
+
attr_reader :properties
|
|
57
|
+
|
|
58
|
+
# @return [ReactorSDK::Endpoints::AppConfigurations]
|
|
59
|
+
attr_reader :app_configurations
|
|
60
|
+
|
|
61
|
+
# @return [ReactorSDK::Endpoints::Callbacks]
|
|
62
|
+
attr_reader :callbacks
|
|
63
|
+
|
|
64
|
+
# @return [ReactorSDK::Endpoints::Secrets]
|
|
65
|
+
attr_reader :secrets
|
|
66
|
+
|
|
67
|
+
# @return [ReactorSDK::Endpoints::Environments]
|
|
68
|
+
attr_reader :environments
|
|
69
|
+
|
|
70
|
+
# @return [ReactorSDK::Endpoints::Hosts]
|
|
71
|
+
attr_reader :hosts
|
|
72
|
+
|
|
73
|
+
# @return [ReactorSDK::Endpoints::Rules]
|
|
74
|
+
attr_reader :rules
|
|
75
|
+
|
|
76
|
+
# @return [ReactorSDK::Endpoints::RuleComponents]
|
|
77
|
+
attr_reader :rule_components
|
|
78
|
+
|
|
79
|
+
# @return [ReactorSDK::Endpoints::DataElements]
|
|
80
|
+
attr_reader :data_elements
|
|
81
|
+
|
|
82
|
+
# @return [ReactorSDK::Endpoints::Extensions]
|
|
83
|
+
attr_reader :extensions
|
|
84
|
+
|
|
85
|
+
# @return [ReactorSDK::Endpoints::ExtensionPackages]
|
|
86
|
+
attr_reader :extension_packages
|
|
87
|
+
|
|
88
|
+
# @return [ReactorSDK::Endpoints::ExtensionPackageUsageAuthorizations]
|
|
89
|
+
attr_reader :extension_package_usage_authorizations
|
|
90
|
+
|
|
91
|
+
# @return [ReactorSDK::Endpoints::Libraries]
|
|
92
|
+
attr_reader :libraries
|
|
93
|
+
|
|
94
|
+
# @return [ReactorSDK::Endpoints::Builds]
|
|
95
|
+
attr_reader :builds
|
|
96
|
+
|
|
97
|
+
# @return [ReactorSDK::Endpoints::AuditEvents]
|
|
98
|
+
attr_reader :audit_events
|
|
99
|
+
|
|
100
|
+
# @return [ReactorSDK::Endpoints::Revisions]
|
|
101
|
+
attr_reader :revisions
|
|
102
|
+
|
|
103
|
+
# @return [ReactorSDK::Endpoints::Profiles]
|
|
104
|
+
attr_reader :profiles
|
|
105
|
+
|
|
106
|
+
# @return [ReactorSDK::Endpoints::Search]
|
|
107
|
+
attr_reader :search
|
|
108
|
+
|
|
109
|
+
# @return [ReactorSDK::Endpoints::Notes]
|
|
110
|
+
attr_reader :notes
|
|
111
|
+
|
|
112
|
+
# @return [ReactorSDK::Configuration]
|
|
113
|
+
attr_reader :config
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Initializes the client and all infrastructure dependencies.
|
|
117
|
+
#
|
|
118
|
+
# @param client_id [String] Adobe Developer Console client ID
|
|
119
|
+
# @param client_secret [String] Adobe Developer Console client secret
|
|
120
|
+
# @param org_id [String] Adobe IMS organisation ID
|
|
121
|
+
# @param base_url [String] Override Reactor API base URL (optional)
|
|
122
|
+
# @param ims_token_url [String] Override IMS token URL — for testing (optional)
|
|
123
|
+
# @param timeout [Integer] HTTP timeout in seconds (optional)
|
|
124
|
+
# @param logger [Logger] Custom logger (optional)
|
|
125
|
+
# @param auto_refresh_token [Boolean] Auto-refresh token before expiry (optional)
|
|
126
|
+
# @raise [ReactorSDK::ConfigurationError] if any required credential is blank
|
|
127
|
+
#
|
|
128
|
+
def initialize(
|
|
129
|
+
client_id:,
|
|
130
|
+
client_secret:,
|
|
131
|
+
org_id:,
|
|
132
|
+
base_url: Configuration::DEFAULT_BASE_URL,
|
|
133
|
+
ims_token_url: Authentication::IMS_TOKEN_URL,
|
|
134
|
+
timeout: Configuration::DEFAULT_TIMEOUT,
|
|
135
|
+
logger: nil,
|
|
136
|
+
auto_refresh_token: true
|
|
137
|
+
)
|
|
138
|
+
@config = Configuration.new(
|
|
139
|
+
client_id: client_id,
|
|
140
|
+
client_secret: client_secret,
|
|
141
|
+
org_id: org_id,
|
|
142
|
+
base_url: base_url,
|
|
143
|
+
ims_token_url: ims_token_url,
|
|
144
|
+
timeout: timeout,
|
|
145
|
+
logger: logger,
|
|
146
|
+
auto_refresh_token: auto_refresh_token
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
build_infrastructure
|
|
150
|
+
build_endpoints
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Instantiates all infrastructure objects in dependency order.
|
|
157
|
+
#
|
|
158
|
+
# @sideeffect Sets @auth, @rate_limiter, @connection, @paginator, @parser
|
|
159
|
+
#
|
|
160
|
+
def build_infrastructure
|
|
161
|
+
@auth = Authentication.new(@config)
|
|
162
|
+
@rate_limiter = RateLimiter.new
|
|
163
|
+
@connection = Connection.new(@config, @auth, @rate_limiter)
|
|
164
|
+
@paginator = Paginator.new(@connection)
|
|
165
|
+
@parser = ResponseParser.new
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
##
|
|
169
|
+
# Instantiates all endpoint group objects.
|
|
170
|
+
# Add new endpoint groups here as they are implemented.
|
|
171
|
+
#
|
|
172
|
+
# @sideeffect Sets all endpoint attr_reader values
|
|
173
|
+
#
|
|
174
|
+
def build_endpoints
|
|
175
|
+
deps = {
|
|
176
|
+
connection: @connection,
|
|
177
|
+
paginator: @paginator,
|
|
178
|
+
parser: @parser
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ENDPOINT_CLASSES.each do |name, endpoint_class|
|
|
182
|
+
instance_variable_set(:"@#{name}", endpoint_class.new(**deps))
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|