k9o 1.2.0 → 1.3.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 +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +90 -52
- data/lib/k9o/audit.rb +12 -14
- data/lib/k9o/client.rb +3 -5
- data/lib/k9o/compute.rb +32 -15
- data/lib/k9o/functions.rb +48 -9
- data/lib/k9o/gate.rb +9 -18
- data/lib/k9o/http_transport.rb +10 -5
- data/lib/k9o/kibo.rb +3 -3
- data/lib/k9o/me.rb +27 -87
- data/lib/k9o/org.rb +81 -68
- data/lib/k9o/orgs.rb +1 -0
- data/lib/k9o/policies.rb +25 -6
- data/lib/k9o/projects.rb +43 -8
- data/lib/k9o/realtime.rb +13 -35
- data/lib/k9o/region.rb +20 -18
- data/lib/k9o/studio.rb +21 -11
- data/lib/k9o/url_grammar.rb +46 -20
- data/lib/k9o/vault.rb +76 -80
- data/lib/k9o/version.rb +1 -1
- data/sig/k9o/gate.rbs +7 -6
- data/sig/k9o/region.rbs +14 -3
- metadata +2 -2
data/lib/k9o/http_transport.rb
CHANGED
|
@@ -68,10 +68,11 @@ module K9o
|
|
|
68
68
|
#
|
|
69
69
|
# `authenticated: false` sends NO credential: no api_key bearer, and any
|
|
70
70
|
# authorization / cookie header from `default_headers` or `headers:` is dropped.
|
|
71
|
-
def request(config:, method:, path:, body: nil, query: nil, headers: nil, authenticated: true, multipart: nil
|
|
71
|
+
def request(config:, method:, path:, body: nil, query: nil, headers: nil, authenticated: true, multipart: nil,
|
|
72
|
+
raw_body: nil)
|
|
72
73
|
retry_with_backoff(method, config.retry_config || RetryConfig.default) do
|
|
73
74
|
request_once(config: config, method: method, path: path, body: body, query: query, headers: headers,
|
|
74
|
-
authenticated: authenticated, multipart: multipart)
|
|
75
|
+
authenticated: authenticated, multipart: multipart, raw_body: raw_body)
|
|
75
76
|
end
|
|
76
77
|
end
|
|
77
78
|
|
|
@@ -116,7 +117,8 @@ module K9o
|
|
|
116
117
|
end
|
|
117
118
|
|
|
118
119
|
# Single-attempt request. `request` wraps this in retry-with-backoff.
|
|
119
|
-
def request_once(config:, method:, path:, body: nil, query: nil, headers: nil, authenticated: true, multipart: nil
|
|
120
|
+
def request_once(config:, method:, path:, body: nil, query: nil, headers: nil, authenticated: true, multipart: nil,
|
|
121
|
+
raw_body: nil)
|
|
120
122
|
url = "#{Region.resolve_origin(config).sub(%r{/\z}, '')}#{path}"
|
|
121
123
|
if query && !query.empty?
|
|
122
124
|
url += '?' + URI.encode_www_form(query)
|
|
@@ -149,7 +151,10 @@ module K9o
|
|
|
149
151
|
req.delete('cookie')
|
|
150
152
|
end
|
|
151
153
|
|
|
152
|
-
if
|
|
154
|
+
if raw_body
|
|
155
|
+
# Binary routes: the bytes go unchanged; the caller supplies the content-type in +headers+.
|
|
156
|
+
req.body = raw_body
|
|
157
|
+
elsif multipart
|
|
153
158
|
# multipart/form-data: the encoder sets the boundary; never force JSON.
|
|
154
159
|
data, content_type = Multipart.encode(multipart)
|
|
155
160
|
req['content-type'] = content_type
|
|
@@ -215,7 +220,7 @@ module K9o
|
|
|
215
220
|
# sentinel terminates the stream; non-2xx raises KError before any
|
|
216
221
|
# event is yielded.
|
|
217
222
|
#
|
|
218
|
-
#
|
|
223
|
+
# The Ruby async-iteration idiom is Enumerator — caller
|
|
219
224
|
# writes `client.compute.llms.invoke_stream(...).each { |chunk| ... }`.
|
|
220
225
|
def stream_sse(config:, method:, path:, body: nil, query: nil)
|
|
221
226
|
url = "#{Region.resolve_origin(config).sub(%r{/\z}, '')}#{path}"
|
data/lib/k9o/kibo.rb
CHANGED
|
@@ -97,14 +97,14 @@ module K9o
|
|
|
97
97
|
case e
|
|
98
98
|
when KiboKmsRestFfi::ZkFfiUniffiError::PostureViolation
|
|
99
99
|
K9o::KError.new(
|
|
100
|
-
code: e.code, message: e.
|
|
100
|
+
code: e.code, message: e.detail,
|
|
101
101
|
context: { dimension: e.dimension, requested: e.requested, declared: e.declared }
|
|
102
102
|
)
|
|
103
103
|
when KiboKmsRestFfi::ZkFfiUniffiError::Platform
|
|
104
|
-
K9o::KError.new(code: e.code, message: e.
|
|
104
|
+
K9o::KError.new(code: e.code, message: e.detail)
|
|
105
105
|
when KiboKmsRestFfi::ZkFfiUniffiError::UpstreamKms
|
|
106
106
|
# Customer's own KMS failed — no K9O_* code (outside the platform error namespace).
|
|
107
|
-
K9o::KError.new(code: 'UPSTREAM_KMS_ERROR', message: e.
|
|
107
|
+
K9o::KError.new(code: 'UPSTREAM_KMS_ERROR', message: e.detail)
|
|
108
108
|
else
|
|
109
109
|
K9o::KError.new(code: 'KIBO_FFI_ERROR', message: e.message)
|
|
110
110
|
end
|
data/lib/k9o/me.rb
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
|
-
# Operator-self namespace — k9o.me.*. Cross-org;
|
|
5
|
-
#
|
|
6
|
-
# api_tokens / notifications / audit_log / data_export.
|
|
4
|
+
# Operator-self namespace — k9o.me.*. Cross-org; org-plane routes under /me.
|
|
5
|
+
# Sub-namespaces: profile / sessions / api_tokens.
|
|
7
6
|
class Me
|
|
8
|
-
attr_reader :profile, :sessions, :api_tokens
|
|
7
|
+
attr_reader :profile, :sessions, :api_tokens
|
|
9
8
|
|
|
10
9
|
def initialize(config)
|
|
11
10
|
@profile = Profile.new(config)
|
|
12
11
|
@sessions = Sessions.new(config)
|
|
13
12
|
@api_tokens = ApiTokens.new(config)
|
|
14
|
-
@notifications = Notifications.new(config)
|
|
15
|
-
@audit_log = AuditLog.new(config)
|
|
16
|
-
@data_export = DataExport.new(config)
|
|
17
13
|
end
|
|
18
14
|
|
|
19
15
|
# k9o.me.profile.*
|
|
20
16
|
class Profile
|
|
17
|
+
PATCH_FIELDS = { display_name: 'displayName', locale: 'locale', timezone: 'timezone' }.freeze
|
|
18
|
+
|
|
21
19
|
def initialize(config)
|
|
22
20
|
@config = config
|
|
23
21
|
end
|
|
@@ -26,9 +24,17 @@ module K9o
|
|
|
26
24
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('me'))
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
# PATCH /me. Pass only the fields to change (display_name, locale, timezone); an explicit +nil+ is sent as
|
|
28
|
+
# null.
|
|
29
|
+
def update(**fields)
|
|
30
|
+
patch = {}
|
|
31
|
+
fields.each do |key, value|
|
|
32
|
+
wire = PATCH_FIELDS[key]
|
|
33
|
+
raise ArgumentError, "unknown profile field: #{key}" if wire.nil?
|
|
34
|
+
|
|
35
|
+
patch[wire] = value
|
|
36
|
+
end
|
|
37
|
+
HttpTransport.request(config: @config, method: 'PATCH', path: UrlGrammar.org_path('me'), body: patch)
|
|
32
38
|
end
|
|
33
39
|
end
|
|
34
40
|
|
|
@@ -42,13 +48,15 @@ module K9o
|
|
|
42
48
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('me/sessions'))
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
# POST /auth/sessions/<id>/revoke
|
|
45
52
|
def revoke(session_id)
|
|
46
|
-
HttpTransport.request(config: @config, method: '
|
|
47
|
-
path: UrlGrammar.org_path("
|
|
53
|
+
HttpTransport.request(config: @config, method: 'POST',
|
|
54
|
+
path: UrlGrammar.org_path("auth/sessions/#{enc(session_id)}/revoke"))
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
# POST /me/sessions/revoke-all
|
|
50
58
|
def revoke_all
|
|
51
|
-
HttpTransport.request(config: @config, method: '
|
|
59
|
+
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('me/sessions/revoke-all'))
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
private
|
|
@@ -68,10 +76,12 @@ module K9o
|
|
|
68
76
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('me/api-tokens'))
|
|
69
77
|
end
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
# POST /me/api-tokens { label, scopeManifest, ttlSeconds? }. +scope_manifest+ is a Hash with the gateway's keys
|
|
80
|
+
# (each may be nil for "no restriction"): 'operations' (read / write / admin), 'projects', 'resourceClasses',
|
|
81
|
+
# 'collections', 'cidrs'. The response carries the token's plaintext once.
|
|
82
|
+
def create(label:, scope_manifest:, ttl_seconds: nil)
|
|
83
|
+
body = { 'label' => label, 'scopeManifest' => scope_manifest }
|
|
84
|
+
body['ttlSeconds'] = ttl_seconds unless ttl_seconds.nil?
|
|
75
85
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('me/api-tokens'), body: body)
|
|
76
86
|
end
|
|
77
87
|
|
|
@@ -86,75 +96,5 @@ module K9o
|
|
|
86
96
|
::URI.encode_www_form_component(s)
|
|
87
97
|
end
|
|
88
98
|
end
|
|
89
|
-
|
|
90
|
-
# k9o.me.notifications.*
|
|
91
|
-
class Notifications
|
|
92
|
-
def initialize(config)
|
|
93
|
-
@config = config
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def list(unread_only: nil, limit: nil, cursor: nil)
|
|
97
|
-
q = {}
|
|
98
|
-
q['unreadOnly'] = unread_only unless unread_only.nil?
|
|
99
|
-
q['limit'] = limit if limit
|
|
100
|
-
q['cursor'] = cursor unless cursor.nil?
|
|
101
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('me/notifications'), query: q)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def mark_read(notification_id)
|
|
105
|
-
HttpTransport.request(config: @config, method: 'PATCH',
|
|
106
|
-
path: UrlGrammar.org_path("me/notifications/#{enc(notification_id)}"), body: { 'read' => true })
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def mark_all_read
|
|
110
|
-
HttpTransport.request(config: @config, method: 'PATCH', path: UrlGrammar.org_path('me/notifications'),
|
|
111
|
-
body: { 'read' => true })
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
def enc(s)
|
|
117
|
-
::URI.encode_www_form_component(s)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# k9o.me.audit_log.*
|
|
122
|
-
class AuditLog
|
|
123
|
-
def initialize(config)
|
|
124
|
-
@config = config
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def query(since: nil, until_: nil, type: nil, limit: nil, cursor: nil)
|
|
128
|
-
q = {}
|
|
129
|
-
q['since'] = since if since
|
|
130
|
-
q['until'] = until_ if until_
|
|
131
|
-
q['type'] = type if type
|
|
132
|
-
q['limit'] = limit if limit
|
|
133
|
-
q['cursor'] = cursor if cursor
|
|
134
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('me/audit-log'), query: q)
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# k9o.me.data_export.*
|
|
139
|
-
class DataExport
|
|
140
|
-
def initialize(config)
|
|
141
|
-
@config = config
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def request(format: 'json')
|
|
145
|
-
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('me/data-export'),
|
|
146
|
-
body: { 'format' => format })
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def status(job_id)
|
|
150
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("me/data-export/#{enc(job_id)}"))
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
private
|
|
154
|
-
|
|
155
|
-
def enc(s)
|
|
156
|
-
::URI.encode_www_form_component(s)
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
99
|
end
|
|
160
100
|
end
|
data/lib/k9o/org.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
|
-
# Org-plane bound namespace — k9o.org.*. Singular
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Org-plane URL grammar routes against the org subdomain
|
|
8
|
-
# without the @<projectSlug>/ prefix.
|
|
4
|
+
# Org-plane bound namespace — k9o.org.*. Singular bound-org namespace (distinct from the k9o.orgs collection):
|
|
5
|
+
# members / groups / smtp / audit_log / gate (roles + idp_providers) + default_llm.
|
|
6
|
+
# Org-plane routes live on the org host without the @<projectSlug>/ prefix.
|
|
9
7
|
class Org
|
|
10
8
|
attr_reader :members, :groups, :smtp, :audit_log, :gate, :default_llm
|
|
11
9
|
|
|
@@ -19,7 +17,7 @@ module K9o
|
|
|
19
17
|
@default_llm = DefaultLlm.new(config)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
#
|
|
20
|
+
# GET /org — the current org (slug + name + status).
|
|
23
21
|
def get
|
|
24
22
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('org'))
|
|
25
23
|
end
|
|
@@ -34,64 +32,57 @@ module K9o
|
|
|
34
32
|
@config = config
|
|
35
33
|
end
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
# GET /members. +status+ is the stored membership status ('active' or 'suspended'); +acceptance_status+ is
|
|
36
|
+
# 'accepted' or 'pending'.
|
|
37
|
+
def list(user_id: nil, role: nil, acceptance_status: nil, status: nil, state: nil)
|
|
38
|
+
q = {}
|
|
39
|
+
q['userId'] = user_id unless user_id.nil?
|
|
40
|
+
q['role'] = role unless role.nil?
|
|
41
|
+
q['acceptanceStatus'] = acceptance_status unless acceptance_status.nil?
|
|
42
|
+
q['status'] = status unless status.nil?
|
|
43
|
+
q['state'] = state unless state.nil?
|
|
39
44
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('members'), query: q)
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
# POST /members { email, role }
|
|
42
48
|
def invite(email:, role:)
|
|
43
49
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('members'),
|
|
44
50
|
body: { 'email' => email, 'role' => role })
|
|
45
51
|
end
|
|
46
52
|
|
|
47
|
-
def get(user_id)
|
|
48
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("members/#{Org.enc(user_id)}"))
|
|
49
|
-
end
|
|
50
|
-
|
|
51
53
|
def remove(user_id)
|
|
52
54
|
HttpTransport.request(config: @config, method: 'DELETE', path: UrlGrammar.org_path("members/#{Org.enc(user_id)}"))
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
# k9o.org.groups.*
|
|
58
|
+
# k9o.org.groups.* (read-only: the gateway has no group create or delete)
|
|
57
59
|
class Groups
|
|
58
60
|
def initialize(config)
|
|
59
61
|
@config = config
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def create(name:)
|
|
67
|
-
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('groups'), body: { 'name' => name })
|
|
64
|
+
# GET /groups — optionally only the groups +user_id+ belongs to.
|
|
65
|
+
def list(user_id: nil)
|
|
66
|
+
q = user_id.nil? ? nil : { 'userId' => user_id }
|
|
67
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('groups'), query: q)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def get(group_id)
|
|
71
71
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("groups/#{Org.enc(group_id)}"))
|
|
72
72
|
end
|
|
73
|
-
|
|
74
|
-
def delete(group_id)
|
|
75
|
-
HttpTransport.request(config: @config, method: 'DELETE', path: UrlGrammar.org_path("groups/#{Org.enc(group_id)}"))
|
|
76
|
-
end
|
|
77
73
|
end
|
|
78
74
|
|
|
79
|
-
# k9o.org.audit_log.* (org-plane; distinct from project Audit).
|
|
75
|
+
# k9o.org.audit_log.* (org-plane; distinct from project Audit). The org audit route reads only +type+ (exact
|
|
76
|
+
# event type) and +limit+ (1 to 500, default 100).
|
|
80
77
|
class AuditLog
|
|
81
78
|
def initialize(config)
|
|
82
79
|
@config = config
|
|
83
80
|
end
|
|
84
81
|
|
|
85
|
-
def query(
|
|
82
|
+
def query(type: nil, limit: nil)
|
|
86
83
|
q = {}
|
|
87
|
-
q['
|
|
88
|
-
q['
|
|
89
|
-
q['type'] = type if type
|
|
90
|
-
q['actorId'] = actor_id if actor_id
|
|
91
|
-
q['targetKind'] = target_kind if target_kind
|
|
92
|
-
q['targetId'] = target_id if target_id
|
|
93
|
-
q['limit'] = limit if limit
|
|
94
|
-
q['cursor'] = cursor if cursor
|
|
84
|
+
q['type'] = type unless type.nil?
|
|
85
|
+
q['limit'] = limit unless limit.nil?
|
|
95
86
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('audit-log'), query: q)
|
|
96
87
|
end
|
|
97
88
|
end
|
|
@@ -105,88 +96,110 @@ module K9o
|
|
|
105
96
|
@idp_providers = IdpProviders.new(config)
|
|
106
97
|
end
|
|
107
98
|
|
|
108
|
-
# k9o.org.gate.roles.*
|
|
99
|
+
# k9o.org.gate.roles.* — org role grants.
|
|
109
100
|
class Roles
|
|
110
101
|
def initialize(config)
|
|
111
102
|
@config = config
|
|
112
103
|
end
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
q['role'] = role if role
|
|
118
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('org-role-grants'), query: q)
|
|
105
|
+
# GET /role-grants (active grants).
|
|
106
|
+
def list
|
|
107
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('role-grants'))
|
|
119
108
|
end
|
|
120
109
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
# POST /role-grants { userId, role, decisionReason? }
|
|
111
|
+
def grant(user_id:, role:, decision_reason: nil)
|
|
112
|
+
body = { 'userId' => user_id, 'role' => role }
|
|
113
|
+
body['decisionReason'] = decision_reason unless decision_reason.nil?
|
|
114
|
+
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('role-grants'), body: body)
|
|
124
115
|
end
|
|
125
116
|
|
|
126
117
|
def get(grant_id)
|
|
127
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("
|
|
118
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("role-grants/#{Org.enc(grant_id)}"))
|
|
128
119
|
end
|
|
129
120
|
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
# DELETE /role-grants/<id> with an optional { decisionReason } body.
|
|
122
|
+
def revoke(grant_id, decision_reason: nil)
|
|
123
|
+
body = decision_reason.nil? ? nil : { 'decisionReason' => decision_reason }
|
|
124
|
+
HttpTransport.request(config: @config, method: 'DELETE',
|
|
125
|
+
path: UrlGrammar.org_path("role-grants/#{Org.enc(grant_id)}"), body: body)
|
|
132
126
|
end
|
|
133
127
|
end
|
|
134
128
|
|
|
135
|
-
# k9o.org.gate.idp_providers.*
|
|
129
|
+
# k9o.org.gate.idp_providers.* — the org's identity providers (/identity-providers).
|
|
136
130
|
class IdpProviders
|
|
131
|
+
CREATE_OPTIONAL = {
|
|
132
|
+
redirect_uri: 'redirectUri', metadata_url: 'metadataUrl', attribute_map: 'attributeMap', enabled: 'enabled'
|
|
133
|
+
}.freeze
|
|
134
|
+
|
|
137
135
|
def initialize(config)
|
|
138
136
|
@config = config
|
|
139
137
|
end
|
|
140
138
|
|
|
141
139
|
def list
|
|
142
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('
|
|
140
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('identity-providers'))
|
|
143
141
|
end
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
# POST /identity-providers { idpProvider, displayName, clientId, clientSecretRef, tierGating, ... }
|
|
144
|
+
# idp_provider: google github microsoft oidc saml scim ldap active_directory apple facebook twitter
|
|
145
|
+
# tier_gating: 'free_tier_ok' or 'ridge_plus'
|
|
146
|
+
# client_secret_ref: a reference to the stored client secret, never the secret itself
|
|
147
|
+
def create(idp_provider:, display_name:, client_id:, client_secret_ref:, tier_gating:, **opts)
|
|
148
|
+
body = { 'idpProvider' => idp_provider, 'displayName' => display_name, 'clientId' => client_id,
|
|
149
|
+
'clientSecretRef' => client_secret_ref, 'tierGating' => tier_gating }
|
|
150
|
+
opts.each do |key, value|
|
|
151
|
+
wire = CREATE_OPTIONAL[key]
|
|
152
|
+
raise ArgumentError, "unknown idp_providers.create option: #{key}" if wire.nil?
|
|
153
|
+
|
|
154
|
+
body[wire] = value
|
|
155
|
+
end
|
|
156
|
+
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('identity-providers'), body: body)
|
|
148
157
|
end
|
|
149
158
|
|
|
150
159
|
def get(idp_id)
|
|
151
|
-
HttpTransport.request(config: @config, method: 'GET',
|
|
160
|
+
HttpTransport.request(config: @config, method: 'GET',
|
|
161
|
+
path: UrlGrammar.org_path("identity-providers/#{Org.enc(idp_id)}"))
|
|
152
162
|
end
|
|
153
163
|
|
|
164
|
+
# PATCH /identity-providers/<id>. +patch+ keys are the gateway's: displayName, clientId, clientSecretRef,
|
|
165
|
+
# redirectUri, metadataUrl, attributeMap, enabled.
|
|
154
166
|
def configure(idp_id, patch)
|
|
155
167
|
HttpTransport.request(config: @config, method: 'PATCH',
|
|
156
|
-
path: UrlGrammar.org_path("
|
|
168
|
+
path: UrlGrammar.org_path("identity-providers/#{Org.enc(idp_id)}"), body: patch)
|
|
157
169
|
end
|
|
158
170
|
|
|
159
171
|
def delete(idp_id)
|
|
160
|
-
HttpTransport.request(config: @config, method: 'DELETE',
|
|
172
|
+
HttpTransport.request(config: @config, method: 'DELETE',
|
|
173
|
+
path: UrlGrammar.org_path("identity-providers/#{Org.enc(idp_id)}"))
|
|
161
174
|
end
|
|
162
175
|
end
|
|
163
176
|
end
|
|
164
177
|
|
|
165
|
-
# k9o.org.smtp.*
|
|
178
|
+
# k9o.org.smtp.* — the org's SMTP relay, stored in the org record (PATCH /org { smtpConfig }).
|
|
166
179
|
class Smtp
|
|
167
180
|
def initialize(config)
|
|
168
181
|
@config = config
|
|
169
182
|
end
|
|
170
183
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def set(host:, port:, username:, password:, from_email:, from_name:, encryption:, reply_to_email: nil)
|
|
184
|
+
# PATCH /org { smtpConfig: { host, port, username, passwordRef, fromAddress, fromName? } }.
|
|
185
|
+
# +password_ref+ references the stored SMTP password; it is never the password itself.
|
|
186
|
+
def set(host:, port:, username:, password_ref:, from_address:, from_name: nil)
|
|
176
187
|
smtp = {
|
|
177
|
-
'host' => host, 'port' => port, 'username' => username, '
|
|
178
|
-
'
|
|
188
|
+
'host' => host, 'port' => port, 'username' => username, 'passwordRef' => password_ref,
|
|
189
|
+
'fromAddress' => from_address
|
|
179
190
|
}
|
|
180
|
-
smtp['
|
|
191
|
+
smtp['fromName'] = from_name unless from_name.nil?
|
|
181
192
|
HttpTransport.request(config: @config, method: 'PATCH', path: UrlGrammar.org_path('org'), body: { 'smtpConfig' => smtp })
|
|
182
193
|
end
|
|
183
194
|
|
|
184
|
-
|
|
195
|
+
# POST /org/smtp/test-email { to, subject? }
|
|
196
|
+
def test(to:, subject: nil)
|
|
185
197
|
body = { 'to' => to }
|
|
186
|
-
body['
|
|
198
|
+
body['subject'] = subject unless subject.nil?
|
|
187
199
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('org/smtp/test-email'), body: body)
|
|
188
200
|
end
|
|
189
201
|
|
|
202
|
+
# PATCH /org { smtpConfig: null }
|
|
190
203
|
def unset
|
|
191
204
|
HttpTransport.request(config: @config, method: 'PATCH', path: UrlGrammar.org_path('org'), body: { 'smtpConfig' => nil })
|
|
192
205
|
end
|
|
@@ -198,15 +211,15 @@ module K9o
|
|
|
198
211
|
@config = config
|
|
199
212
|
end
|
|
200
213
|
|
|
201
|
-
#
|
|
214
|
+
# GET /default-llm
|
|
202
215
|
def get
|
|
203
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('
|
|
216
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('default-llm'))
|
|
204
217
|
end
|
|
205
218
|
|
|
206
|
-
#
|
|
219
|
+
# PATCH /default-llm { modelId }
|
|
207
220
|
def set(model_id)
|
|
208
221
|
HttpTransport.request(config: @config, method: 'PATCH',
|
|
209
|
-
path: UrlGrammar.org_path('
|
|
222
|
+
path: UrlGrammar.org_path('default-llm'), body: { 'modelId' => model_id })
|
|
210
223
|
end
|
|
211
224
|
end
|
|
212
225
|
end
|
data/lib/k9o/orgs.rb
CHANGED
data/lib/k9o/policies.rb
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
4
|
# Policy bundle lifecycle namespace — k9o.policies.*.
|
|
5
|
+
#
|
|
6
|
+
# The policy document is a Hash of sections keyed by resource kind ('collections', 'storage', 'tools',
|
|
7
|
+
# 'functions', 'secrets', 'streams', 'analytics'; other keys are allowed). Only authoring roles write policy and
|
|
8
|
+
# only custodian roles publish; +validate+ is read-only.
|
|
5
9
|
class Policies
|
|
10
|
+
EDITOR_MODES = %w[visual json unspecified].freeze
|
|
11
|
+
|
|
6
12
|
def initialize(config)
|
|
7
13
|
@config = config
|
|
8
14
|
end
|
|
9
15
|
|
|
16
|
+
# GET /@<slug>/policy — the currently active policy (data is nil when none is published).
|
|
10
17
|
def current
|
|
11
18
|
slug = require_project_slug
|
|
12
19
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'policy'))
|
|
@@ -18,16 +25,18 @@ module K9o
|
|
|
18
25
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'policies'), query: q)
|
|
19
26
|
end
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
# POST /@<slug>/policies — create a draft bundle from a policy document.
|
|
29
|
+
def create(policy:, rules: nil, editor_mode: nil)
|
|
22
30
|
slug = require_project_slug
|
|
23
31
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.project_path(slug, 'policies'),
|
|
24
|
-
body:
|
|
32
|
+
body: policy_body(policy, rules, editor_mode))
|
|
25
33
|
end
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
# POST /@<slug>/policies/validate — lint a candidate policy document without persisting.
|
|
36
|
+
def validate(policy:, rules: nil)
|
|
28
37
|
slug = require_project_slug
|
|
29
38
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.project_path(slug, 'policies/validate'),
|
|
30
|
-
body:
|
|
39
|
+
body: policy_body(policy, rules, nil))
|
|
31
40
|
end
|
|
32
41
|
|
|
33
42
|
def get(bundle_id)
|
|
@@ -35,12 +44,15 @@ module K9o
|
|
|
35
44
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, "policies/#{enc(bundle_id)}"))
|
|
36
45
|
end
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
# PATCH /@<slug>/policies/<id> — replace a draft bundle's policy document. Published bundles cannot be patched.
|
|
48
|
+
def update(bundle_id, policy:, rules: nil, editor_mode: nil)
|
|
39
49
|
slug = require_project_slug
|
|
40
50
|
HttpTransport.request(config: @config, method: 'PATCH',
|
|
41
|
-
path: UrlGrammar.project_path(slug, "policies/#{enc(bundle_id)}"),
|
|
51
|
+
path: UrlGrammar.project_path(slug, "policies/#{enc(bundle_id)}"),
|
|
52
|
+
body: policy_body(policy, rules, editor_mode))
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
# Delete a draft bundle. Published bundles cannot be deleted.
|
|
44
56
|
def delete(bundle_id)
|
|
45
57
|
slug = require_project_slug
|
|
46
58
|
HttpTransport.request(config: @config, method: 'DELETE', path: UrlGrammar.project_path(slug, "policies/#{enc(bundle_id)}"))
|
|
@@ -54,6 +66,13 @@ module K9o
|
|
|
54
66
|
|
|
55
67
|
private
|
|
56
68
|
|
|
69
|
+
def policy_body(policy, rules, editor_mode)
|
|
70
|
+
body = { 'policy' => policy }
|
|
71
|
+
body['rules'] = rules unless rules.nil?
|
|
72
|
+
body['editorMode'] = editor_mode unless editor_mode.nil?
|
|
73
|
+
body
|
|
74
|
+
end
|
|
75
|
+
|
|
57
76
|
def require_project_slug
|
|
58
77
|
Studio.require_project_slug(@config, 'policies')
|
|
59
78
|
end
|
data/lib/k9o/projects.rb
CHANGED
|
@@ -2,32 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
module K9o
|
|
4
4
|
# Project lifecycle namespace — k9o.projects.* (Ruby snake_case).
|
|
5
|
+
#
|
|
6
|
+
# All four operations are org-plane: the client needs an org context (an
|
|
7
|
+
# +org_slug+ or a bearer token that carries one). Option names are snake_case
|
|
8
|
+
# spellings of the gateway's body property names.
|
|
5
9
|
class Projects
|
|
10
|
+
CREATE_FIELDS = {
|
|
11
|
+
slug: 'slug', product_scope: 'productScope', region: 'region',
|
|
12
|
+
default_project_region: 'defaultProjectRegion', custody_mode: 'custodyMode',
|
|
13
|
+
processing_contexts: 'processingContexts', exec_seam_confined: 'execSeamConfined',
|
|
14
|
+
showroom_vertical: 'showroomVertical', persona: 'persona', showroom_locale: 'showroomLocale',
|
|
15
|
+
idempotency_key: 'idempotencyKey'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
UPDATE_FIELDS = {
|
|
19
|
+
slug: 'slug', display_name: 'displayName', description: 'description', region: 'region',
|
|
20
|
+
product_scope: 'productScope', soft_delete_retention_days: 'softDeleteRetentionDays',
|
|
21
|
+
processing_contexts: 'processingContexts'
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
6
24
|
def initialize(config)
|
|
7
25
|
@config = config
|
|
8
26
|
end
|
|
9
27
|
|
|
28
|
+
# GET /projects
|
|
10
29
|
def list
|
|
11
30
|
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path('projects'))
|
|
12
31
|
end
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
# POST /projects. +display_name+ is required (1 to 128 characters); the gateway derives a slug from it when
|
|
34
|
+
# +slug+ is omitted. +custody_mode+ is one of PLATFORM_MANAGED, SKR, BYOK, HYOK. +idempotency_key+ makes a
|
|
35
|
+
# retried create return the first result instead of a second project.
|
|
36
|
+
def create(display_name:, **opts)
|
|
37
|
+
body = { 'displayName' => display_name }
|
|
38
|
+
opts.each do |key, value|
|
|
39
|
+
wire = CREATE_FIELDS[key]
|
|
40
|
+
raise ArgumentError, "unknown projects.create option: #{key}" if wire.nil?
|
|
41
|
+
|
|
42
|
+
body[wire] = value unless value.nil?
|
|
43
|
+
end
|
|
44
|
+
UrlGrammar.validate_slug(opts[:slug], 'project') unless opts[:slug].nil?
|
|
18
45
|
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.org_path('projects'), body: body)
|
|
19
46
|
end
|
|
20
47
|
|
|
48
|
+
# GET /projects/<slug>
|
|
21
49
|
def get(slug)
|
|
22
50
|
UrlGrammar.validate_slug(slug, 'project')
|
|
23
|
-
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.
|
|
51
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.org_path("projects/#{slug}"))
|
|
24
52
|
end
|
|
25
53
|
|
|
26
|
-
|
|
54
|
+
# PATCH /projects/<slug>. Pass only the fields to change; an explicit +nil+ is sent as null
|
|
55
|
+
# (for +description+ that clears it).
|
|
56
|
+
def update(slug, **fields)
|
|
27
57
|
UrlGrammar.validate_slug(slug, 'project')
|
|
28
58
|
patch = {}
|
|
29
|
-
|
|
30
|
-
|
|
59
|
+
fields.each do |key, value|
|
|
60
|
+
wire = UPDATE_FIELDS[key]
|
|
61
|
+
raise ArgumentError, "unknown projects.update field: #{key}" if wire.nil?
|
|
62
|
+
|
|
63
|
+
patch[wire] = value
|
|
64
|
+
end
|
|
65
|
+
HttpTransport.request(config: @config, method: 'PATCH', path: UrlGrammar.org_path("projects/#{slug}"), body: patch)
|
|
31
66
|
end
|
|
32
67
|
end
|
|
33
68
|
end
|