k8s-rails 0.2.0 → 0.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 +34 -0
- data/README.md +135 -47
- data/docs/design.md +68 -34
- data/lib/k8s_rails/client.rb +90 -64
- data/lib/k8s_rails/configuration.rb +6 -6
- data/lib/k8s_rails/resource.rb +49 -51
- data/lib/k8s_rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e99e17a8bbceb09aac19b96621477b798646b95104223a5603dd5a4785866f3
|
|
4
|
+
data.tar.gz: a1ae1cb4c96639738a320d533b71ffbdbfda6a07d67f87065c2ac395bbee691a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae8df68ba2ed241704cf052df3a9b4a0e74ad407136f7d79d1a0573e985b2ff884df1dfb1dd5d808b7488304a6424080b224ad1b72f504112afe718c6be57360
|
|
7
|
+
data.tar.gz: fae4996c36d5f3e24546750f132b0efb6018826c4c56eea13a1465423d96f2717015be3d701546a83427bdb2fc379a4739a16983aefffdf69159067e64e21464
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
`k8s-rails` の全 notable な変更はこのファイルに記録する。
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- **対応リソースの拡大: core v1 built-in(Pod / Service / ConfigMap / Node 等)に対応**。
|
|
8
|
+
従来、transport は kruby の `CustomObjectsApi`(`/apis/{group}/...` 固定パス)
|
|
9
|
+
に依存しており、`group: ""` の core v1 リソース(`/api/v1/...`)だけは 404
|
|
10
|
+
で到達できなかった(named built-in は従来から動作)。transport を
|
|
11
|
+
`Kubernetes::ApiClient#call_api` 上の**統一 REST 層**に改訂し、
|
|
12
|
+
core v1 / named built-in / CRD の全てを宣言座標からのパス構築で到達させる。
|
|
13
|
+
named 系・CRD の既存宣言の挙動は不変。
|
|
14
|
+
- **`delete` / `delete_cluster` を追加**(CRUD 完成)。readonly ゲートは
|
|
15
|
+
create / patch と同型(`readonly: true` 宣言で呼ぶと `ReadOnlyError`)。
|
|
16
|
+
K8s は削除成功時に Status オブジェクト(`{"kind":"Status","status":"Success"}`)を返す。
|
|
17
|
+
- **テスト注入スタブの契約変更(テストコードへの影響)**: `config.api_client`
|
|
18
|
+
が実装すべきメソッドは CustomObjectsApi の 8 メソッドから
|
|
19
|
+
`call_api(http_method, path, opts)` の 1 メソッドに統一
|
|
20
|
+
(戻り値 `[data, status_code, headers]`、http_method は
|
|
21
|
+
`:GET` / `:POST` / `:PATCH` / `:DELETE`)。既存スタブは `call_api` に
|
|
22
|
+
置き換える必要がある(実クラスタ接続の production コードには影響なし)。
|
|
23
|
+
- `k8s-rails.request` notification の `operation` に `:delete` を追加。
|
|
24
|
+
- 実クラスタ(microk8s v1.33.13)で Pod list / Deployment find /
|
|
25
|
+
ConfigMap create・patch・delete / Node list_cluster を E2E 検証済み。
|
|
26
|
+
|
|
27
|
+
## 0.2.1
|
|
28
|
+
|
|
29
|
+
- **Ruby 3.5 / 4.0 対応の宣言**(宣言範囲拡大のみ・コード・挙動変更なし):
|
|
30
|
+
2026-09-22 に Ruby 4.0.7(stable・v4.0.7、2026-09-15 リリース)と
|
|
31
|
+
Ruby 3.5.0-preview1(3.5 系で公開済みの唯一のリリース)で全
|
|
32
|
+
rspec / rubocop を実行し緑を確認したため(kruby 1.36.4.1 は両方で
|
|
33
|
+
install / 動作確認)、`required_ruby_version` を `>= 3.3, < 4.0` から
|
|
34
|
+
**`>= 3.3, < 4.1`** に拡大。上限は検証済みの 4.0 系に設定し、
|
|
35
|
+
CI matrix は 3.3.0 / 3.3.8 / 3.4.10 / 3.5.0-preview1 / 4.0.7 の 5 系統で
|
|
36
|
+
範囲内の公開済み Ruby を全カバー(宣言範囲 = 検証範囲)。
|
|
37
|
+
設計書 KBR-DESIGN-001 v0.1.13(案)へ更新(§6 / §10 / §13 / §14)。
|
|
38
|
+
|
|
5
39
|
## 0.2.0
|
|
6
40
|
|
|
7
41
|
- **cluster-scoped CRD サポート**(#17): `K8sRails.crd` に `scope: :namespaced`
|
data/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# k8s-rails
|
|
2
|
-
A Kubernetes API
|
|
2
|
+
A Kubernetes API access convention layer for Rails applications.
|
|
3
3
|
|
|
4
|
-
`k8s-rails` provides the **connection,
|
|
5
|
-
layer** for Rails applications that talk to the Kubernetes API.
|
|
4
|
+
`k8s-rails` provides the **connection, resource access, and error-handling
|
|
5
|
+
convention layer** for Rails applications that talk to the Kubernetes API. It
|
|
6
|
+
covers every resource kruby can address: **core v1** built-ins (Pod, Service,
|
|
7
|
+
ConfigMap, ...), **named-group** built-ins (Deployment, Ingress, ...), and
|
|
8
|
+
**CRDs**.
|
|
6
9
|
|
|
7
10
|
[](https://github.com/doridoridoriand/k8s-rails/actions/workflows/test.yml)
|
|
8
11
|
[](https://rubygems.org/gems/k8s-rails)
|
|
@@ -13,21 +16,21 @@ For the design rationale, see the [design document (docs/design.md)](docs/design
|
|
|
13
16
|
|
|
14
17
|
| Item | Supported range | Notes |
|
|
15
18
|
|------|-----------------|-------|
|
|
16
|
-
| Ruby | `>= 3.3, < 4.
|
|
19
|
+
| Ruby | `>= 3.3, < 4.1` | Floor: kruby 1.36.x requires Ruby 3.3. Ruby 4.0 support verified on Ruby 4.0.7 (2026-09-22); Ruby 3.5 verified on 3.5.0-preview1 (the only released 3.5, 2026-09-22). CI verifies the declared range with a 3.3.0 / 3.3.8 / 3.4.10 / 3.5.0-preview1 / 4.0.7 matrix — every released Ruby in the range |
|
|
17
20
|
| kruby | `~> 1.36.0` | The official Kubernetes OpenAPI client |
|
|
18
|
-
| Kubernetes server | **Verified on v1.33.x** (
|
|
21
|
+
| Kubernetes server | **Verified on v1.33.x** (real cluster, microk8s v1.33.13, 2026-09-21) | The transport speaks the generic Kubernetes REST API, so newer servers (1.36, etc.) are expected to work but are not yet verified on a real cluster |
|
|
19
22
|
| Dependencies | **kruby only** at runtime | ActiveSupport is used only for optional [instrumentation](#instrumentation-optional-activesupport) (no-op when absent) |
|
|
20
23
|
|
|
21
24
|
## Installation
|
|
22
25
|
|
|
23
26
|
```ruby
|
|
24
27
|
# Gemfile
|
|
25
|
-
gem "k8s-rails", "~> 0.
|
|
28
|
+
gem "k8s-rails", "~> 0.3"
|
|
26
29
|
```
|
|
27
30
|
|
|
28
31
|
```ruby
|
|
29
32
|
require "k8s-rails"
|
|
30
|
-
K8sRails::VERSION # => "0.
|
|
33
|
+
K8sRails::VERSION # => "0.3.0"
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
`require` is side-effect-free and needs no cluster. kruby itself is loaded
|
|
@@ -37,30 +40,96 @@ reachable.
|
|
|
37
40
|
|
|
38
41
|
## Quick start
|
|
39
42
|
|
|
43
|
+
`k8s-rails` reaches **every resource the Kubernetes API exposes** — core v1
|
|
44
|
+
built-ins (Pod, Service, ConfigMap, ...), named-group built-ins (Deployment,
|
|
45
|
+
Ingress, ...), and CRDs — through one declaration API. The transport speaks
|
|
46
|
+
the generic REST API: a core v1 resource is declared with an **empty group**
|
|
47
|
+
(`group: ""` → `/api/v1/...`), any other resource with its API group
|
|
48
|
+
(`/apis/{group}/{version}/...`).
|
|
49
|
+
|
|
50
|
+
### 1. Configure (once, in an initializer)
|
|
51
|
+
|
|
40
52
|
```ruby
|
|
41
53
|
# config/initializers/k8s-rails.rb
|
|
42
54
|
K8sRails.configure do |config|
|
|
43
55
|
config.namespace = "team-a" # default namespace (can be overridden per declaration or per call)
|
|
44
56
|
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Declare the resource you work with
|
|
60
|
+
|
|
61
|
+
Coordinates are declared explicitly — group / version / plural / kind are
|
|
62
|
+
**never guessed**. A core v1 resource (Pods) uses an empty group:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
Pod = K8sRails.crd(
|
|
66
|
+
group: "", # core v1 (the "/api/v1" API group)
|
|
67
|
+
version: "v1",
|
|
68
|
+
plural: "pods",
|
|
69
|
+
kind: "Pod",
|
|
70
|
+
readonly: false, # default true; only an explicit false enables create/patch/delete
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A named-group built-in (Deployments) is identical except for the group:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
Deployment = K8sRails.crd(
|
|
78
|
+
group: "apps", # "/apis/apps/v1"
|
|
79
|
+
version: "v1",
|
|
80
|
+
plural: "deployments",
|
|
81
|
+
kind: "Deployment",
|
|
82
|
+
readonly: false, # enables create/patch/delete in the example below
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Read and write (CRUD)
|
|
45
87
|
|
|
46
|
-
|
|
88
|
+
All return values are string-keyed hashes:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
Pod.list # => [{"metadata" => {"name" => "..."}, "spec" => {...}}, ...]
|
|
92
|
+
Pod.find("api-7d9f8b6c5-xk2lt") # the full object; raises K8sRails::NotFound when absent
|
|
93
|
+
Pod.find_or_nil("missing") # same, but returns nil instead of raising
|
|
94
|
+
|
|
95
|
+
# readonly: false only
|
|
96
|
+
Pod.create({
|
|
97
|
+
apiVersion: "v1",
|
|
98
|
+
kind: "Pod",
|
|
99
|
+
metadata: { name: "busybox" },
|
|
100
|
+
spec: { containers: [{ name: "busybox", image: "busybox" }] }
|
|
101
|
+
})
|
|
102
|
+
Pod.patch("busybox", [{ op: "replace", path: "/spec/containers/0/image", value: "busybox:1.36" }])
|
|
103
|
+
Pod.delete("busybox") # => {"kind" => "Status", "status" => "Success", ...}
|
|
104
|
+
|
|
105
|
+
# Deployment (named group) is used exactly the same way
|
|
106
|
+
Deployment.list # all Deployments in the default namespace
|
|
107
|
+
Deployment.find("web")
|
|
108
|
+
Deployment.delete("web") # readonly: false only
|
|
109
|
+
|
|
110
|
+
K8sRails.connected? # true on success; raises K8sRails::Unavailable / ApiError on failure
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Example: Argo Workflows (CRD)
|
|
114
|
+
|
|
115
|
+
The same declaration and operations work for any CRD — here, the Argo
|
|
116
|
+
Workflows `Workflow` CRD:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
47
119
|
Workflow = K8sRails.crd(
|
|
48
120
|
group: "argoproj.io",
|
|
49
121
|
version: "v1alpha1",
|
|
50
122
|
plural: "workflows",
|
|
51
123
|
kind: "Workflow",
|
|
52
|
-
readonly: false, # default true; false enables create/patch
|
|
124
|
+
readonly: false, # default true; false enables create/patch/delete
|
|
53
125
|
)
|
|
54
|
-
```
|
|
55
126
|
|
|
56
|
-
|
|
57
|
-
Workflow.list # => [{"name" => "...", "labels" => {...}}, ...]
|
|
127
|
+
Workflow.list # => [{"metadata" => {"name" => "...", ...}}, ...]
|
|
58
128
|
Workflow.find("wf-1") # same shape / raises K8sRails::NotFound when absent
|
|
59
129
|
Workflow.find_or_nil("wf-1") # same, but returns nil instead of raising
|
|
60
130
|
Workflow.create({ metadata: { name: "wf-1" } }) # readonly: false only
|
|
61
131
|
Workflow.patch("wf-1", [{ op: "replace", path: "/spec/a", value: 2 }]) # readonly: false only
|
|
62
|
-
|
|
63
|
-
K8sRails.connected? # true on success; raises on failure
|
|
132
|
+
Workflow.delete("wf-1") # readonly: false only
|
|
64
133
|
```
|
|
65
134
|
|
|
66
135
|
## Configuration
|
|
@@ -90,28 +159,34 @@ end
|
|
|
90
159
|
in-cluster. Note in-cluster is tried **last**, after the file-based
|
|
91
160
|
sources — re-verify `kruby`'s loader when upgrading kruby).
|
|
92
161
|
|
|
93
|
-
##
|
|
162
|
+
## Resource declaration and access
|
|
94
163
|
|
|
95
|
-
|
|
96
|
-
|
|
164
|
+
The same `K8sRails.crd` API covers core v1 built-ins, named-group built-ins,
|
|
165
|
+
and CRDs. `plural` / `kind` are **never guessed** (many resources do not
|
|
166
|
+
follow the obvious naming convention). Re-declaring the same kind raises
|
|
97
167
|
`K8sRails::RedeclarationError` (configuration-mistake detection).
|
|
98
168
|
|
|
169
|
+
- **core v1** resources (Pod, Service, ConfigMap, ...): `group: ""`
|
|
170
|
+
- **named-group** resources (Deployment = `apps`, Ingress = `networking.k8s.io`,
|
|
171
|
+
any CRD group): the resource's API group
|
|
172
|
+
- **cluster-scoped** resources (Node, ClusterIssuer, ...): `scope: :cluster`
|
|
173
|
+
and no `namespace:` (see [Cluster-scoped resources](#cluster-scoped-resources))
|
|
174
|
+
|
|
99
175
|
Return values are **always string-keyed hashes**. kruby returns symbol keys,
|
|
100
176
|
but Rails-side JSON/views work with string keys, so the gem normalizes
|
|
101
177
|
internally in pure Ruby (no ActiveSupport dependency). Every namespaced
|
|
102
178
|
method accepts a `namespace:` argument to override the namespace from the
|
|
103
|
-
declaration (the `*_cluster` methods take no `namespace:`
|
|
104
|
-
[Cluster-scoped CRDs](#cluster-scoped-crds)).
|
|
179
|
+
declaration (the `*_cluster` methods take no `namespace:`).
|
|
105
180
|
|
|
106
181
|
`readonly` must be an **explicit boolean** (`nil` or other values raise
|
|
107
|
-
`ArgumentError`). Writes
|
|
108
|
-
missing flag can never fail open.
|
|
182
|
+
`ArgumentError`). Writes (`create` / `patch` / `delete`) are enabled **only**
|
|
183
|
+
by `readonly: false`, so a missing flag can never fail open.
|
|
109
184
|
|
|
110
|
-
### Cluster-scoped
|
|
185
|
+
### Cluster-scoped resources
|
|
111
186
|
|
|
112
|
-
Both namespaced and cluster-scoped
|
|
113
|
-
cluster-scoped
|
|
114
|
-
`scope: :cluster` and **without** `namespace:` (combining the two raises
|
|
187
|
+
Both namespaced and cluster-scoped resources are supported. Declare a
|
|
188
|
+
cluster-scoped resource (Node, ClusterIssuer, ClusterWorkflowTemplate, ...)
|
|
189
|
+
with `scope: :cluster` and **without** `namespace:` (combining the two raises
|
|
115
190
|
`ArgumentError`):
|
|
116
191
|
|
|
117
192
|
```ruby
|
|
@@ -128,13 +203,22 @@ ClusterIssuer.find_cluster("letsencrypt")
|
|
|
128
203
|
ClusterIssuer.find_or_nil_cluster("x")
|
|
129
204
|
ClusterIssuer.create_cluster({ ... }) # readonly: false only
|
|
130
205
|
ClusterIssuer.patch_cluster("letsencrypt", [{ ... }]) # readonly: false only
|
|
206
|
+
ClusterIssuer.delete_cluster("letsencrypt") # readonly: false only
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Core v1 has cluster-scoped resources too (Node, Namespace):
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
Node = K8sRails.crd(group: "", version: "v1", plural: "nodes", kind: "Node", scope: :cluster)
|
|
213
|
+
Node.list_cluster # all nodes
|
|
214
|
+
Node.find_cluster("n1")
|
|
131
215
|
```
|
|
132
216
|
|
|
133
217
|
On a `scope: :namespaced` declaration (the default) the `*_cluster` methods
|
|
134
|
-
raise `ArgumentError` — the
|
|
135
|
-
404 anyway. For a cluster-scoped declaration, use the `*_cluster`
|
|
136
|
-
(the plain `list`/`find`/... would call the namespaced endpoints and
|
|
137
|
-
they raise `ArgumentError` there as well).
|
|
218
|
+
raise `ArgumentError` — the resource is namespaced, so the cluster endpoints
|
|
219
|
+
would 404 anyway. For a cluster-scoped declaration, use the `*_cluster`
|
|
220
|
+
methods (the plain `list`/`find`/... would call the namespaced endpoints and
|
|
221
|
+
404, so they raise `ArgumentError` there as well).
|
|
138
222
|
|
|
139
223
|
## Connectivity check
|
|
140
224
|
|
|
@@ -159,8 +243,8 @@ K8sRails::Error < StandardError
|
|
|
159
243
|
├── K8sRails::Unavailable # transport-layer failure (DNS/timeout/connection refused; kruby 1.36.x reports it as ApiError code 0)
|
|
160
244
|
├── K8sRails::NotFound # HTTP 404
|
|
161
245
|
├── K8sRails::ApiError # other API errors (401/403/409/422/5xx); holds #code and #response
|
|
162
|
-
├── K8sRails::ReadOnlyError # create/patch called on a readonly: true declaration
|
|
163
|
-
└── K8sRails::RedeclarationError # re-declaration of an already-declared
|
|
246
|
+
├── K8sRails::ReadOnlyError # create/patch/delete called on a readonly: true declaration
|
|
247
|
+
└── K8sRails::RedeclarationError # re-declaration of an already-declared kind
|
|
164
248
|
```
|
|
165
249
|
|
|
166
250
|
Recommended caller pattern:
|
|
@@ -198,27 +282,31 @@ end
|
|
|
198
282
|
## Testing
|
|
199
283
|
|
|
200
284
|
The test suite needs **no cluster**. Tests inject a transport stub via
|
|
201
|
-
`config.api_client`. The
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
285
|
+
`config.api_client`. The transport speaks kruby's `Kubernetes::ApiClient#call_api`
|
|
286
|
+
protocol, so a stub just implements that one method — `call_api(http_method,
|
|
287
|
+
path, opts)` returning `[data, status_code, headers]`. `http_method` is one of
|
|
288
|
+
`:GET` / `:POST` / `:PATCH` / `:DELETE`, and `path` is the fully-built API
|
|
289
|
+
path (the gem builds it from the declaration — core v1 as `/api/v1/...`,
|
|
290
|
+
everything else as `/apis/{group}/{version}/...`):
|
|
205
291
|
|
|
206
292
|
```ruby
|
|
207
293
|
class StubTransport
|
|
208
|
-
def
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def patch_cluster_custom_object(group, version, plural, name, body) = {}
|
|
294
|
+
def call_api(method, path, opts = {})
|
|
295
|
+
case method
|
|
296
|
+
when :GET then { items: [] }, 200, {} # ... or a single object
|
|
297
|
+
when :POST then { metadata: {} }, 200, {}
|
|
298
|
+
when :PATCH then { metadata: {} }, 200, {}
|
|
299
|
+
when :DELETE then { kind: "Status", status: "Success" }, 200, {}
|
|
300
|
+
end
|
|
301
|
+
end
|
|
217
302
|
end
|
|
218
303
|
|
|
219
304
|
K8sRails.configure { |c| c.api_client = StubTransport.new }
|
|
220
305
|
```
|
|
221
306
|
|
|
307
|
+
A realistic stub routes on `path` (and `opts[:body]` for writes). Responses
|
|
308
|
+
may use symbol keys — the gem stringifies them.
|
|
309
|
+
|
|
222
310
|
With a transport injected, `K8sRails.connected?` returns `true` without
|
|
223
311
|
network I/O (see [Connectivity check](#connectivity-check)).
|
|
224
312
|
|
|
@@ -244,10 +332,10 @@ through k8s-rails are unaffected (it does not overwrite an already-set
|
|
|
244
332
|
kruby's own configuration behavior (outside this gem), check this token-key
|
|
245
333
|
issue first.
|
|
246
334
|
|
|
247
|
-
## Roadmap (v0.
|
|
335
|
+
## Roadmap (v0.4+)
|
|
248
336
|
|
|
249
|
-
-
|
|
250
|
-
|
|
337
|
+
- Update the CI matrix when Ruby 3.5 reaches a stable release (3.5 is
|
|
338
|
+
verified today on 3.5.0-preview1, the only released 3.5)
|
|
251
339
|
- CI-based E2E tests using kind
|
|
252
340
|
- watch (streaming) support, under consideration
|
|
253
341
|
|
data/docs/design.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# k8s-rails 設計書
|
|
2
2
|
|
|
3
3
|
- 文書番号: KBR-DESIGN-001
|
|
4
|
-
- 版: 0.1.
|
|
4
|
+
- 版: 0.1.15(案)
|
|
5
5
|
- 日付: 2026-09-15
|
|
6
6
|
- 対象リポジトリ: k8s-rails(本設計の実装先)
|
|
7
7
|
- ライセンス: MIT(LICENSE は main に既存)
|
|
@@ -40,8 +40,8 @@ K8s CRD を扱えるようにし、(b) 実際の consumer アプリをこの gem
|
|
|
40
40
|
|
|
41
41
|
### 2.2 非スコープ(本設計の外)
|
|
42
42
|
|
|
43
|
-
- core v1
|
|
44
|
-
- **watch(ストリーム)** — v0.1 では非対応。kruby の watch は get/list より成熟度が低く、初版の API 保証範囲から外す(§10 で v0.
|
|
43
|
+
- **core v1 リソースの CustomObjects 経路でのアクセス** — 2026-09-22 に**解消済み**(unified REST transport 導入、§5.2 改訂)。以前は core v1(Pod / Service / ConfigMap / Node 等)が `/api/v1/...`(group 区間なし)のため `CustomObjectsApi` では扱えず、`group: ""` 宣言は 404 になった。**named-group の built-in(apps/v1 の Deployment 等)は CustomObjects 経路でも動作していた**(generic な `/apis/{group}/...` クライアントのため)。現在の transport は `Kubernetes::ApiClient#call_api` 上の統一 REST 層で、core v1(`group: ""` → `/api/v1/...`)・named built-in・CRD を 1 つの経路で扱う
|
|
44
|
+
- **watch(ストリーム)** — v0.1 では非対応。kruby の watch は get/list より成熟度が低く、初版の API 保証範囲から外す(§10 で v0.4 対象)
|
|
45
45
|
- アプリの**デプロイ**(helm / kustomize 生成等) — `kuby-core` の領域
|
|
46
46
|
- RBAC 権限の付与・管理 — 呼び出しアプリ側の ClusterRole/Role の責務
|
|
47
47
|
- 複数クラスタ同時接続 — v0.1 は単一クラスタ前提(§11 展望)
|
|
@@ -106,7 +106,7 @@ k8s-rails/
|
|
|
106
106
|
├── lib/
|
|
107
107
|
│ ├── k8s-rails.rb # エントリ。require 集 + モジュール定義
|
|
108
108
|
│ └── k8s_rails/
|
|
109
|
-
│ ├── version.rb # VERSION = "0.
|
|
109
|
+
│ ├── version.rb # VERSION = "0.3.0"
|
|
110
110
|
│ ├── configuration.rb # Config: namespace, connection, 計測 ON/OFF
|
|
111
111
|
│ ├── client.rb # 接続解決・BearerToken 橋渡し・計測ラップ
|
|
112
112
|
│ ├── crd.rb # K8sRails.crd 宣言 → Resource 生成
|
|
@@ -143,7 +143,7 @@ end
|
|
|
143
143
|
|---|---|---|
|
|
144
144
|
| `namespace` | `"default"` | CRD 宣言が namespace 未指定時のデフォルト |
|
|
145
145
|
| `connection` | `nil`(自動検出) | `Kubernetes::Configuration` インスタンス。認証を上書きする場合に指定。省略時の探索順序(kruby 1.36.x の loader 実装順): **KUBECONFIG → `~/.kube/config` → in-cluster**(in-cluster はファイル系が両方無効な場合の**最後**。kruby 上げ替え時に loader を再確認すること — §7) |
|
|
146
|
-
| `api_client` | `nil` | **テスト専用**: kruby の `
|
|
146
|
+
| `api_client` | `nil` | **テスト専用**: kruby の `Kubernetes::ApiClient#call_api` と同型の 1 メソッド `call_api(http_method, path, opts)`(戻り値 `[data, status_code, headers]`。`http_method` は `:GET` / `:POST` / `:PATCH` / `:DELETE`、`path` は宣言の座標から構築された REST パス)を実装する素のオブジェクト。指定時は `Client.build` が接続解決をスキープして `StringKeyedAdapter` で包んで使う(§5.2・§9) |
|
|
147
147
|
| `instrumentation` | `true` | `ActiveSupport::Notifications` で計測する(§8) |
|
|
148
148
|
|
|
149
149
|
- 設定は `K8sRails.configure` で**一度だけ**。再実行は警告(`Warning`)+ 無視。
|
|
@@ -159,7 +159,7 @@ end
|
|
|
159
159
|
### 5.2 接続
|
|
160
160
|
|
|
161
161
|
```ruby
|
|
162
|
-
K8sRails::Client.build # →
|
|
162
|
+
K8sRails::Client.build # → 統一 REST transport(kruby ApiClient を包む。lazy。初回呼び出し時に接続)
|
|
163
163
|
K8sRails.connected? # → 成功時は true。失敗は K8sRails::Unavailable / ApiError を raise
|
|
164
164
|
# (false を返す経路なし)。/version 相当の軽量確認
|
|
165
165
|
# (kruby 1.36.x の VersionApi#get_code(GET /version/)1 回)
|
|
@@ -167,33 +167,55 @@ K8sRails.connected? # → 成功時は true。失敗は K8sRails::Unavailab
|
|
|
167
167
|
# 注入が接続面そのものだから。Resource 操作と整合させる)
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
`Client.build` が内部で行うこと(
|
|
170
|
+
`Client.build` が内部で行うこと(2026-09-22 に unified REST transport に改訂 —
|
|
171
|
+
旧実装は `CustomObjectsApi` 経由で CRD / named-group のみ、core v1 は 404):
|
|
171
172
|
|
|
172
173
|
0. `config.api_client` があれば(テスト注入、§5.1)それを直接返し、以降の接続解決をスキップ
|
|
173
174
|
1. `config.connection` があればそれ、なければ `Kubernetes::Configuration.default_config`
|
|
174
175
|
2. **K1 橋渡し**: `api_key['authorization']` が `api_key['BearerToken']` に書かれていなければ複製
|
|
175
|
-
3. `Kubernetes::ApiClient`
|
|
176
|
+
3. `Kubernetes::ApiClient` を生成し、`StringKeyedAdapter` で包む。**パス構築**: 宣言の座標から REST パスを構築して `call_api` で実行 — core v1(`group == ""`)は `/api/{version}[/namespaces/{ns}]/{plural}[/{name}]`、named(CRD・named built-in)は `/apis/{group}/{version}[/namespaces/{ns}]/{plural}[/{name}]`。`return_type: "Object"` で kruby が JSON をパース(symbol キー)したオブジェクトを返し、**文字列キー化**(K2)を API レスポンス後に行う(`K8sRails::Normalizer`、ActiveSupport 非依存)
|
|
176
177
|
4. **接続レベルの失敗**(DNS 失敗 / タイムアウト / 接続拒否等)は `K8sRails::Unavailable` に変換して `raise`(リトライはしない)。**kruby 1.36.x ではこれらの転送失敗は HTTP ステータスが無いため `Kubernetes::ApiError`(`code == 0`)として surfacing する**(§5.4 の変換表参照)。認可失敗(401/403)は §5.4 により `K8sRails::ApiError`
|
|
177
178
|
|
|
179
|
+
**対応リソース**: kruby が到達できる全リソースを 1 つの経路で扱う —
|
|
180
|
+
- core v1 built-in(Pod / Service / ConfigMap / Node 等): `group: ""`
|
|
181
|
+
- named-group built-in(Deployment = `apps` / Ingress = `networking.k8s.io` 等): 各 API group(**旧実装の `CustomObjectsApi` 経路でもこれらは `/apis/{group}/...` generic クライアントとして動作していた**)
|
|
182
|
+
- CRD: 各 CRD の group
|
|
183
|
+
|
|
178
184
|
`connected?` の解決順序:
|
|
179
185
|
|
|
180
186
|
0. `config.api_client` があれば(テスト注入、§5.1)**I/O なしで `true` を返す**。注入されたトランスポートが接続面そのもののため、実エンドポイントへのプローブは同一注入下の Resource 操作と矛盾する(#18 対応)。クラスタ到達性の真の確認が必要な場合は注入を解除した環境で行う
|
|
181
187
|
1. 以下 `Client.build` と同一(`config.connection` → `default_config` → K1 橋渡し → VersionApi プローブ)
|
|
182
188
|
|
|
183
|
-
### 5.3 CRD
|
|
189
|
+
### 5.3 リソース宣言(CRD / built-in 共通)
|
|
190
|
+
|
|
191
|
+
`K8sRails.crd` は core v1 built-in・named built-in・CRD の全てに同一の API(2026-09-22 の unified REST transport 導入で core v1 が有効化):
|
|
184
192
|
|
|
185
193
|
```ruby
|
|
186
194
|
# config/initializers/k8s-rails_crd.rb(またはアプリケーションクラス内)
|
|
195
|
+
# core v1 built-in(group が空文字)
|
|
196
|
+
Pod = K8sRails.crd(
|
|
197
|
+
group: "", # core v1(/api/v1 経路)
|
|
198
|
+
version: "v1",
|
|
199
|
+
plural: "pods",
|
|
200
|
+
kind: "Pod",
|
|
201
|
+
namespace: K8sRails.config.namespace, # 省略可
|
|
202
|
+
readonly: false, # 既定 true。false で create/patch/delete 有効化(K4)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# named-group built-in(apps/v1 の Deployment 等)も同形
|
|
206
|
+
Deployment = K8sRails.crd(group: "apps", version: "v1", plural: "deployments", kind: "Deployment")
|
|
207
|
+
|
|
208
|
+
# CRD(従来の形・不変)
|
|
187
209
|
Workflow = K8sRails.crd(
|
|
188
210
|
group: "argoproj.io",
|
|
189
211
|
version: "v1alpha1",
|
|
190
212
|
plural: "workflows",
|
|
191
213
|
kind: "Workflow",
|
|
192
214
|
namespace: K8sRails.config.namespace, # 省略可
|
|
193
|
-
readonly: false, # 既定 true。false で create/patch 有効化(K4)
|
|
215
|
+
readonly: false, # 既定 true。false で create/patch/delete 有効化(K4)
|
|
194
216
|
)
|
|
195
217
|
|
|
196
|
-
# cluster-scoped
|
|
218
|
+
# cluster-scoped(ClusterIssuer / Node / ClusterWorkflowTemplate 等)は
|
|
197
219
|
# scope: :cluster を付け、namespace: は省略する(併用は ArgumentError)
|
|
198
220
|
ClusterIssuer = K8sRails.crd(
|
|
199
221
|
group: "cert-manager.io",
|
|
@@ -210,19 +232,21 @@ ClusterIssuer = K8sRails.crd(
|
|
|
210
232
|
|---|---|---|---|
|
|
211
233
|
| `list` | `{}` | `[{"name" => "...", "labels" => {}, "spec" => {}, "status" => {}}, ...]`(**文字列キー**) | 常に有効 |
|
|
212
234
|
| `find(name)` | 必須 | 同型 or `K8sRails::NotFound`(raise) | 常に有効 |
|
|
213
|
-
| `create(attributes)` |
|
|
235
|
+
| `create(attributes)` | body hash | 作成済みオブジェクト(文字列キー) | `readonly: false` のみ |
|
|
214
236
|
| `patch(name, operations)` | JSON Patch 操作配列 | 更新済みオブジェクト | `readonly: false` のみ |
|
|
237
|
+
| `delete(name)` | 必須 | API の Status オブジェクト(文字列キー。`{"kind" => "Status", "status" => "Success", ...}`) | `readonly: false` のみ |
|
|
215
238
|
| `list_cluster` | `{}` | 同上(クラスタ横断。`namespace:` なし) | 常に有効 |
|
|
216
239
|
| `find_cluster(name)` | 必須 | 同上 or `K8sRails::NotFound` | 常に有効 |
|
|
217
|
-
| `create_cluster(attributes)` |
|
|
240
|
+
| `create_cluster(attributes)` | body hash | 作成済みオブジェクト | `readonly: false` のみ |
|
|
218
241
|
| `patch_cluster(name, operations)` | JSON Patch 操作配列 | 更新済みオブジェクト | `readonly: false` のみ |
|
|
242
|
+
| `delete_cluster(name)` | 必須 | API の Status オブジェクト(文字列キー) | `readonly: false` のみ |
|
|
219
243
|
|
|
220
244
|
スコープの契約(#17 対応):
|
|
221
245
|
|
|
222
246
|
- `scope: :namespaced`(既定)の宣言では `*_cluster` メソッドは `ArgumentError`(CRD が namespaced のためクラスタ endpoint を呼んでも 404 になるだけ)。
|
|
223
247
|
- `scope: :cluster` の宣言では素の `list` / `find` / `create` / `patch` は `ArgumentError`(namespaced endpoint を呼ぶと 404 になるだけ。`namespace:` 引数は意味を持たない)。
|
|
224
248
|
- 宣言時に `scope: :cluster` と `namespace:` を併用した場合は `ArgumentError`(設定ミスの fail fast)。
|
|
225
|
-
- transport は kruby の
|
|
249
|
+
- transport は kruby の `ApiClient#call_api` 上の統一 REST 層で、namespaced / cluster の区別はパス構築(`/namespaces/{ns}` の有無)のみ。テスト注入スタブは `call_api` の 1 メソッドを実装する(§9)。
|
|
226
250
|
|
|
227
251
|
- **戻り値は常に文字列キーの Hash**(K2 の規約を API 契約として固定)。
|
|
228
252
|
`find` は存在しない場合 `K8sRails::NotFound` を raise(consumer アプリ側が `return nil` にしていたのは
|
|
@@ -239,7 +263,7 @@ K8sRails::Error < StandardError
|
|
|
239
263
|
├── K8sRails::Unavailable # 接続不能・タイムアウト・DNS 失敗等(クラスタ起因)
|
|
240
264
|
├── K8sRails::NotFound # リソース不在(HTTP 404)
|
|
241
265
|
├── K8sRails::ApiError # その他の API エラー(401/403/409/422 等)。#code と #response を保持
|
|
242
|
-
├── K8sRails::ReadOnlyError # readonly: true 宣言で create/patch が呼ばれた(設定ミス)
|
|
266
|
+
├── K8sRails::ReadOnlyError # readonly: true 宣言で create/patch/delete が呼ばれた(設定ミス)
|
|
243
267
|
└── K8sRails::RedeclarationError # 同名 CRD の再宣言(設定ミス)
|
|
244
268
|
```
|
|
245
269
|
|
|
@@ -251,7 +275,7 @@ K8sRails::Error < StandardError
|
|
|
251
275
|
| その他の `StandardError`(プログラミング/設定ミス、例: `NoMethodError`) | そのまま伝播(変換せず隠さない) |
|
|
252
276
|
| `Kubernetes::ApiError` code 404 | `NotFound` |
|
|
253
277
|
| `Kubernetes::ApiError` その他 | `ApiError`(code / response body を保持) |
|
|
254
|
-
| 宣言時に `readonly: true` で create/patch を呼ばれた | `K8sRails::ReadOnlyError`(**設定ミス**なので raise せずには済ませない) |
|
|
278
|
+
| 宣言時に `readonly: true` で create/patch/delete を呼ばれた | `K8sRails::ReadOnlyError`(**設定ミス**なので raise せずには済ませない) |
|
|
255
279
|
|
|
256
280
|
呼び出しアプリ(Rails)側の推奨パターン:
|
|
257
281
|
|
|
@@ -269,8 +293,8 @@ end
|
|
|
269
293
|
|
|
270
294
|
| 依存 | 制約 | 理由 |
|
|
271
295
|
|---|---|---|
|
|
272
|
-
| Ruby | `>= 3.3, < 4.
|
|
273
|
-
| `kruby` | `~> 1.36.0` | consumer アプリと同一 pin。`~> 1.36.0` は 1.36.x のみ許可(`~> 1.36` 形式は 1.37 以降も許容してしまうため使用しない)。新しめの kruby に対応する場合は §7 の確認事項(client.rb
|
|
296
|
+
| Ruby | `>= 3.3, < 4.1` | 下限: kruby 1.36.x が `required_ruby_version ">= 3.3"` を宣言(RubyGems API で実測 2026-09-21、1.36.0.1〜1.36.4.1 全バージョン)。上限: 「宣言した minor を必ず CI で検証する」方針 — 2026-09-22 に Ruby 4.0.7(stable)と Ruby 3.5.0-preview1(3.5 系で公開済みの唯一のリリース)で全 suite / rubocop を実行し緑を確認(matrix に 4.0.7 / 3.5.0-preview1 追加・v0.2.1)。この範囲で公開済みの Ruby は 3.3.x / 3.4.x / 3.5.0-preview1 / 4.0.x のみ(ruby/ruby タグ実測・4.1 系リリースなし)のため宣言範囲 = 検証範囲。RubyGems は version requirement で集合和を表現できないため未検証 minor を区間で挟み込む形(`< 5.0` 等)は取らない。3.5 stable / 4.1 系がリリースされたら matrix を追加して検証(§10 / §13) |
|
|
297
|
+
| `kruby` | `~> 1.36.0` | consumer アプリと同一 pin。`~> 1.36.0` は 1.36.x のみ許可(`~> 1.36` 形式は 1.37 以降も許容してしまうため使用しない)。新しめの kruby に対応する場合は §7 の確認事項(client.rb の `call_api` 依存箇所・K1 橋渡し・`default_config` 探索順序)を済ませてから明示的に上げ替える |
|
|
274
298
|
| `activesupport` | **任意**(`>= 7.0`) | `defined?(ActiveSupport::Notifications)` でガード(計測のみ、§8)。Rails 無し環境(Cron スクリプト等)でも動作する必要がある — レスポンスの文字列キー化(K2)はこれに依存せず、gem 内部の純 Ruby 変換で担う(§5.2) |
|
|
275
299
|
| `rspec` / `rubocop` | 開発依存 | spec / lint |
|
|
276
300
|
|
|
@@ -281,13 +305,14 @@ end
|
|
|
281
305
|
|
|
282
306
|
- `lib/k8s_rails/client.rb` **のみ**が `require "kubernetes"` してよい。
|
|
283
307
|
他のファイルは kruby 定数・クラスを参照しない。
|
|
284
|
-
- kruby の
|
|
285
|
-
|
|
286
|
-
|
|
308
|
+
- kruby の API 呼び出しは `client.rb` 内の `Kubernetes::ApiClient#call_api`
|
|
309
|
+
1 メソッドに集約する(unified REST transport。パスは `build_path` が宣言の
|
|
310
|
+
座標から構築 — core v1 は `/api/{version}...`、named は `/apis/{group}/{version}...`)。
|
|
287
311
|
`resource.rb` は
|
|
288
312
|
`K8sRails.client.get(group, version, ns, plural, name)` のような **gem 内部 API** だけを使う。
|
|
289
|
-
- kruby 上げ替え時の作業は (1)
|
|
290
|
-
(2) K1 橋渡しの要否確認、
|
|
313
|
+
- kruby 上げ替え時の作業は (1) `ApiClient#call_api` のシグネチャ・`opts` 形式(header_params /
|
|
314
|
+
query_params / body / auth_names / return_type)の確認、(2) K1 橋渡しの要否確認、
|
|
315
|
+
(3) **`Kubernetes::Configuration.default_config` の探索順序確認**
|
|
291
316
|
(kruby 1.36.x の loader 実装順は **KUBECONFIG → `~/.kube/config` → in-cluster** で in-cluster が
|
|
292
317
|
最後。README / 設計書 / 設定コメントがこの順序を明記しているため、loader が変わった場合は
|
|
293
318
|
全箇所を同期する — #19 対応)に収まることをテスト(§9)で担保する。
|
|
@@ -298,7 +323,7 @@ end
|
|
|
298
323
|
|
|
299
324
|
```
|
|
300
325
|
k8s-rails.request payload: { operation: :list, group:, version:, plural:, namespace:
|
|
301
|
-
# operation は symbol(:list / :find / :create / :patch)
|
|
326
|
+
# operation は symbol(:list / :find / :create / :patch / :delete)
|
|
302
327
|
duration_ms: # float(ミリ秒・小数点 2 桁)
|
|
303
328
|
status: "ok" | "unavailable" | "api_error" }
|
|
304
329
|
```
|
|
@@ -316,11 +341,11 @@ k8s-rails.request payload: { operation: :list, group:, version:, plural:, names
|
|
|
316
341
|
|
|
317
342
|
| レイヤー | 手法 | 対象 |
|
|
318
343
|
|---|---|---|
|
|
319
|
-
| ユニット | `K8sRails.config.api_client` に**スタブ**(kruby `
|
|
344
|
+
| ユニット | `K8sRails.config.api_client` に**スタブ**(kruby `ApiClient#call_api` と同型の 1 メソッド `call_api(http_method, path, opts)` を実装する素のオブジェクト。`http_method` は `:GET` / `:POST` / `:PATCH` / `:DELETE`、`path` は宣言座標から構築された REST パス(core v1 `/api/v1/...` / named `/apis/{group}/{version}/...`)、戻り値 `[data, status_code, headers]`。`StringKeyedAdapter` がこの形式を呼ぶ)を注入 | client(橋渡し・例外変換・パス構築)、resource(整形・readonly 制限・スコープ制限)、crd(メソッド生成・scope 検証) |
|
|
320
345
|
| 設定 | spec 間で `K8sRails.reset!` | 宣言の破棄・再接続 |
|
|
321
346
|
| 集積(任意) | GitHub Actions で **kind**(または既存 microk8s に接続するジョブ)で実クラスタ E2E | v0.1 の必須ではない。**推奨**: consumer アプリ移行時の検証を兼ねる |
|
|
322
347
|
|
|
323
|
-
- 本設計では CI は `rspec` + `rubocop` のみを必須とし、kind E2E は v0.
|
|
348
|
+
- 本設計では CI は `rspec` + `rubocop` のみを必須とし、kind E2E は v0.4 で
|
|
324
349
|
GitHub Actions の追加として扱う(実クラスタへの接続 CI はネットワーク依存のため採用しない)。
|
|
325
350
|
|
|
326
351
|
## 10. リリース計画
|
|
@@ -329,8 +354,9 @@ k8s-rails.request payload: { operation: :list, group:, version:, plural:, names
|
|
|
329
354
|
|---|---|---|
|
|
330
355
|
| **v0.1** | §5 の公開 API(CRD 宣言 / list / find / create / patch / 例外 / 計測 / スタブテスト)+ README | rspec 全緑 + **consumer アプリの K8s サービスを `k8s-rails` に移行して動作確認**(§12) |
|
|
331
356
|
| **v0.2** | #16–#19 の公開 API 拡充・修正(cluster-scoped CRD `scope:` + `*_cluster` メソッド、`configure` のアトミック契約、`connected?` の注入契約、kruby loader 探索順序の修正)。設計書 v0.1.12(案) | rspec 全緑(クラスタ不要)+ 公開 gem push(v0.1.0 と同導線) |
|
|
332
|
-
| v0.3 |
|
|
333
|
-
| v0.4 |
|
|
357
|
+
| **v0.3** | unified REST transport(`ApiClient#call_api` 上の統一 REST 層)で**対応リソースを全リソース(core v1 built-in 含)に拡大**+`delete` / `delete_cluster` 追加(CRUD 完成)+README QuickStart 改稿(Pod / Deployment CRUD を主例)。設計書 v0.1.15(案) | rspec 全緑(クラスタ不要)+実クラスタ E2E(Pod / Deployment / Node 読み取り・ConfigMap create/patch/delete・readonly ゲート)+公開 gem push(v0.1.0 と同導線) |
|
|
358
|
+
| v0.4 | watch(`watch` メソッド、kruby の watch サポート上)、kind E2E の CI 化、Ruby 3.5 stable / 4.1 系の matrix 追加(リリースされたら検証の上追加。Ruby 3.5 preview / 4.0 は v0.2.1 で対応済み)、リトライポリシー | v0.3 運用のフィードバック |
|
|
359
|
+
| v0.5 | (展望)複数クラスタ(ネームスペース化された client 集合) | — |
|
|
334
360
|
|
|
335
361
|
v0.1 の milestone 分割(開発セッション向けのタスク単位目安):
|
|
336
362
|
|
|
@@ -397,15 +423,20 @@ PR の差分を最小化)。
|
|
|
397
423
|
公開した gem のバージョンがどのコミットに基づくかを追跡できる。
|
|
398
424
|
tag 名と gemspec の `VERSION` は一致させる
|
|
399
425
|
- テスト CI(`.github/workflows/test.yml`)は push / PR 時に rspec + rubocop を実行。
|
|
400
|
-
gemspec の宣言範囲(`>= 3.3, < 4.
|
|
426
|
+
gemspec の宣言範囲(`>= 3.3, < 4.1`)で公開済みの Ruby を matrix で検証:
|
|
401
427
|
3.3.0(下限・kruby 1.36.x の `>= 3.3`)・3.3.8(開発)・
|
|
402
|
-
3.4.10(3.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
428
|
+
3.4.10(3.4 系の最新 stable)・3.5.0-preview1(3.5 系の唯一の公開
|
|
429
|
+
リリース・2026-09-22 にローカルで全 suite / rubocop 検証済み)・
|
|
430
|
+
4.0.7(4.0 系の最新 stable・2026-09-22 にローカルで全 suite /
|
|
431
|
+
rubocop 検証済み)。
|
|
432
|
+
**宣言範囲を常に matrix がカバーする**こと(`< 4.1` 上限により
|
|
433
|
+
4.1 系以降のリリースは宣言範囲外。RubyGems は version requirement で
|
|
434
|
+
集合和を表現できないため、未検証 minor を区間で挟み込む形
|
|
435
|
+
(`< 5.0` 等)は取らない — matrix 追加と範囲拡大をセットで行う。
|
|
436
|
+
3.5 stable / 4.1 系等 stable 化された新リリースが出たら
|
|
406
437
|
matrix への追加を忘れないこと)。
|
|
407
438
|
テストはクラスタ不要(§9・スタブ注入)のため v0.1 は runner 上のユニットのみ。
|
|
408
|
-
kind / 実クラスタ E2E の CI 化は v0.
|
|
439
|
+
kind / 実クラスタ E2E の CI 化は v0.4 対象(§9・§10)
|
|
409
440
|
- `README` に「kruby pin」「対応 k8s バージョン(実測 v1.33.x で検証済み)」「K1 橋渡しの背景」
|
|
410
441
|
を明記する(検索でヒットする重要な注意点のため)
|
|
411
442
|
|
|
@@ -426,3 +457,6 @@ PR の差分を最小化)。
|
|
|
426
457
|
| 0.1.10 | 2026-09-21 | PR #12 レビュー対応(Copilot): §13 の公開手順で tag の **remote への push**(`git push origin v<VERSION>`)が欠落しており、GitHub 上のリリースコミットとの対応付け(追溯性)が確保できないとの指摘を反映 | 実装反映済み |
|
|
427
458
|
| 0.1.11 | 2026-09-22 | Issue #16–#19 対応: ①#16 `configure` の例外送出時は設定済みフラグをリセット(「一度だけ」はブロック正常終了時にのみ成立)。例外前に書かれた属性は残存することを契約として明文化(§5.1)②#17 `scope: :namespaced`(既定)/ `:cluster` を宣言 API に追加。cluster 系 4 メソッド(`list_cluster` / `find_cluster` / `find_or_nil_cluster` / `create_cluster` / `patch_cluster`)と双方向の ArgumentError 契約(§5.3)。transport は kruby の `*_cluster_custom_object` 4 メソッドを新たに使用③#18 `connected?` は `config.api_client` 注入時に I/O なしで `true`(§5.2)。注入下で Resource 操作と接続確認の挙動を一致させる④#19 kruby 1.36.x の loader 実装順(**KUBECONFIG → `~/.kube/config` → in-cluster**)を README / 設計書 / 設定コメントに明記し、§7 の上げ替え確認事項に探索順序の再確認を追加(in-cluster は最後。従来の「in-cluster → KUBECONFIG」記述は誤り) | 実装反映済み |
|
|
428
459
|
| 0.1.12 | 2026-09-22 | PR #20 レビュー対応(Codex P2 + Copilot M/L 4 系統): ①#16 のリセット範囲を `rescue StandardError` から**任意の異常終了**(`LoadError` / `ScriptError` / `throw` / non-local return 等)に拡大(成功マーカー + `ensure` で実装、spec 2 件追加)。§5.1 / README の契約文言も「任意の異常終了」に修正②README の「`namespace:` 引数を受け取る」記述を namespaced メソッドに限定(`*_cluster` は受け付けない)③§6 の kruby 上げ替え確認事項を 8 メソッド + 探索順序に同期④`api_client` 注入スタブの契約を namespaced 4 + cluster 4 の 8 メソッドに統一(§5.1 表 / configuration.rb コメント / §9) | 実装反映済み |
|
|
460
|
+
| 0.1.13 | 2026-09-22 | v0.2.1 リリース準備 — Ruby 対応範囲の拡大・検証: ①Ruby 4.0.7(stable・v4.0.7)上で全 rspec / rubocop を実行し動作確認(kruby 1.36.4.1 の install / 動作を含む)②宣言範囲を **`>= 3.3, < 4.1`** に拡大(gemspec / §6 / README / test.yml)。上限は「宣言した minor を必ず CI で検証する」方針で検証済みの 4.0 系に設定(`< 5.0` 等未検証 minor を区間で挟み込む形は RubyGems が集合和を表現できないため不採用・§13 に明記)③RubyGems は `3.5.0-preview1` を範囲内に満たすため 3.5 も実際に対応・検証(3.5.0-preview1 で全 suite / rubocop 緑確認)。matrix は 3.3.0 / 3.3.8 / 3.4.10 / 3.5.0-preview1 / 4.0.7 の 5 系統で範囲内公開済み Ruby を全カバー(§13) | 実装反映済み |
|
|
461
|
+
| 0.1.14 | 2026-09-22 | **対応リソースの拡大: unified REST transport 導入で core v1 built-in(Pod / Service / ConfigMap / Node 等)に対応**(旧 `CustomObjectsApi` 経路では `group: ""` が 404 だった唯一のギャップ。named built-in は従来から動作)。①transport を `Kubernetes::ApiClient#call_api` 上の統一 REST 層に改訂(`client.rb`。パス構築: core `/api/v1/...` / named `/apis/{group}/{version}/...`・CGI escape・json-patch Content-Type 維持)②`delete` / `delete_cluster` を追加(CRUD 完成。readonly ゲート同型。Status オブジェクトを文字列キーで返す)③テスト注入スタブの契約を `call_api` の 1 メソッドに統一(§5.1 表 / configuration.rb コメント / §9)④実クラスタ(v1.33.13)で Pod list / Deployment find / ConfigMap create・patch・delete / Node list_cluster を E2E 検証 | 実装反映済み |
|
|
462
|
+
| 0.1.15 | 2026-09-22 | v0.3.0 リリース準備: ①`VERSION` を 0.2.1 → **0.3.0**(version.rb + version spec)②CHANGELOG の `Unreleased` を **0.3.0** に確定③README の install pin(`~> 0.2` → `~> 0.3`)・`VERSION` 例・Roadmap 見出し(v0.3+ → v0.4+)を同期④§10 リリース計画表で v0.3 行を「実際にリリースした内容」に書き換え(unified REST transport / delete / README 改稿)、旧 v0.3 予定内容(watch / kind E2E / matrix 追加)を v0.4 へ、リトライポリシーを v0.4 へ、複数クラスタ展望を v0.5 へ移動⑤§2.2 / §9 / §13 の旧「v0.3 対象」参照を v0.4 に修正 | 実装反映済み |
|
data/lib/k8s_rails/client.rb
CHANGED
|
@@ -4,25 +4,35 @@
|
|
|
4
4
|
# 他のファイルは kruby の定数・クラスを参照しない。
|
|
5
5
|
# Typhoeus は kruby が require するため転送層例外もここに閉じ込める。
|
|
6
6
|
require "kubernetes"
|
|
7
|
+
require "cgi"
|
|
7
8
|
|
|
8
9
|
module K8sRails
|
|
9
|
-
# Resolves a connection to the Kubernetes API and exposes the
|
|
10
|
-
#
|
|
10
|
+
# Resolves a connection to the Kubernetes API and exposes the read/write
|
|
11
|
+
# operations as a small internal REST transport (design §5.2 / §7).
|
|
11
12
|
#
|
|
12
13
|
# api = K8sRails::Client.build
|
|
13
14
|
# api.list(group, version, namespace, plural)
|
|
14
15
|
# api.get(group, version, namespace, plural, name)
|
|
15
16
|
# api.create(group, version, namespace, plural, body)
|
|
16
17
|
# api.patch(group, version, namespace, plural, name, body)
|
|
18
|
+
# api.delete(group, version, namespace, plural, name)
|
|
17
19
|
# # cluster-scoped (namespace argument omitted, design §5.3)
|
|
18
20
|
# api.list_cluster(group, version, plural)
|
|
19
21
|
# api.get_cluster(group, version, plural, name)
|
|
20
22
|
# api.create_cluster(group, version, plural, body)
|
|
21
23
|
# api.patch_cluster(group, version, plural, name, body)
|
|
24
|
+
# api.delete_cluster(group, version, plural, name)
|
|
25
|
+
#
|
|
26
|
+
# The transport is a uniform REST layer over kruby's `Kubernetes::ApiClient`
|
|
27
|
+
# (`call_api`), NOT the `CustomObjectsApi`. A single path builder maps the
|
|
28
|
+
# declaration coordinates (group / version / scope / namespace / plural /
|
|
29
|
+
# name) onto the API path, so it reaches EVERY resource kruby can address:
|
|
30
|
+
# core v1 (`group: ""` → `/api/v1/...`), named built-in groups
|
|
31
|
+
# (`apps/v1` → `/apis/apps/v1/...`), and CRDs (`/apis/{group}/{version}/...`).
|
|
22
32
|
#
|
|
23
33
|
# Connection is LAZY: `build` does no network I/O — it only resolves a
|
|
24
|
-
# `Kubernetes::Configuration` and builds an in-memory `
|
|
25
|
-
#
|
|
34
|
+
# `Kubernetes::Configuration` and builds an in-memory `ApiClient`. The first
|
|
35
|
+
# real call is where DNS/timeout/TLS can fail.
|
|
26
36
|
class Client
|
|
27
37
|
class << self
|
|
28
38
|
# Return the shared transport, building it on first call (lazy connect).
|
|
@@ -34,16 +44,15 @@ module K8sRails
|
|
|
34
44
|
# 2. K1 bridge: duplicate `api_key['authorization']` into
|
|
35
45
|
# `api_key['BearerToken']` (kruby 1.36 in-cluster/KUBECONFIG write the
|
|
36
46
|
# token under 'authorization' but auth_settings reads 'BearerToken').
|
|
37
|
-
# 3. Build `ApiClient`
|
|
47
|
+
# 3. Build `ApiClient` (in-memory, no I/O).
|
|
38
48
|
# 4. Wrap in a StringKeyedAdapter that normalizes responses (K2) and
|
|
39
49
|
# converts kruby errors to K8sRails exceptions.
|
|
40
50
|
def build
|
|
41
|
-
@build ||=
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
51
|
+
@build ||= if (injected = K8sRails.config.api_client)
|
|
52
|
+
StringKeyedAdapter.new(injected)
|
|
53
|
+
else
|
|
54
|
+
StringKeyedAdapter.new(build_api_client)
|
|
55
|
+
end
|
|
47
56
|
end
|
|
48
57
|
|
|
49
58
|
# Lightweight connectivity probe (design §5.2). Performs one lightweight
|
|
@@ -95,12 +104,11 @@ module K8sRails
|
|
|
95
104
|
kruby_error.message.to_s.sub(/\nHTTP status code:.*\z/, "").strip
|
|
96
105
|
end
|
|
97
106
|
|
|
98
|
-
# Build a lazy in-memory
|
|
99
|
-
def
|
|
107
|
+
# Build a lazy in-memory ApiClient from the resolved configuration.
|
|
108
|
+
def build_api_client
|
|
100
109
|
config = build_configuration
|
|
101
110
|
bridge_bearer_token(config)
|
|
102
|
-
|
|
103
|
-
Kubernetes::CustomObjectsApi.new(api_client)
|
|
111
|
+
Kubernetes::ApiClient.new(config)
|
|
104
112
|
end
|
|
105
113
|
|
|
106
114
|
def build_configuration
|
|
@@ -120,95 +128,113 @@ module K8sRails
|
|
|
120
128
|
end
|
|
121
129
|
end
|
|
122
130
|
|
|
123
|
-
# Wraps
|
|
131
|
+
# Wraps an ApiClient (or an injected test double) so that
|
|
124
132
|
# - every response is deep-stringified (K2), and
|
|
125
133
|
# - kruby errors are converted to K8sRails exceptions (K3).
|
|
126
134
|
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
135
|
+
# This is the ONLY place kruby response shapes / errors are touched, so a
|
|
136
|
+
# kruby upgrade is a one-file change (§7). It speaks kruby's `call_api`
|
|
137
|
+
# protocol directly, so it reaches core v1, named built-in groups, and
|
|
138
|
+
# CRDs uniformly.
|
|
129
139
|
class StringKeyedAdapter
|
|
130
140
|
def initialize(transport)
|
|
131
141
|
@transport = transport
|
|
132
142
|
end
|
|
133
143
|
|
|
144
|
+
# --- namespaced ------------------------------------------------------
|
|
145
|
+
|
|
134
146
|
def list(group, version, namespace, plural)
|
|
135
|
-
|
|
136
|
-
Normalizer.stringify(
|
|
137
|
-
@transport.list_namespaced_custom_object(group, version, namespace, plural)
|
|
138
|
-
)
|
|
139
|
-
end
|
|
147
|
+
request(:GET, build_path(:namespaced, group, version, namespace, plural))
|
|
140
148
|
end
|
|
141
149
|
|
|
142
150
|
def get(group, version, namespace, plural, name)
|
|
143
|
-
|
|
144
|
-
Normalizer.stringify(
|
|
145
|
-
@transport.get_namespaced_custom_object(group, version, namespace, plural, name)
|
|
146
|
-
)
|
|
147
|
-
end
|
|
151
|
+
request(:GET, build_path(:namespaced, group, version, namespace, plural, name))
|
|
148
152
|
end
|
|
149
153
|
|
|
150
154
|
def create(group, version, namespace, plural, body)
|
|
151
|
-
|
|
152
|
-
Normalizer.stringify(
|
|
153
|
-
@transport.create_namespaced_custom_object(group, version, namespace, plural, body)
|
|
154
|
-
)
|
|
155
|
-
end
|
|
155
|
+
request(:POST, build_path(:namespaced, group, version, namespace, plural), body: body)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def patch(group, version, namespace, plural, name, body)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
request(
|
|
160
|
+
:PATCH,
|
|
161
|
+
build_path(:namespaced, group, version, namespace, plural, name),
|
|
162
|
+
body: body,
|
|
163
|
+
content_type: "application/json-patch+json"
|
|
164
|
+
)
|
|
164
165
|
end
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
def delete(group, version, namespace, plural, name)
|
|
168
|
+
request(:DELETE, build_path(:namespaced, group, version, namespace, plural, name))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# --- cluster-scoped --------------------------------------------------
|
|
170
172
|
|
|
171
173
|
def list_cluster(group, version, plural)
|
|
172
|
-
|
|
173
|
-
Normalizer.stringify(
|
|
174
|
-
@transport.list_cluster_custom_object(group, version, plural)
|
|
175
|
-
)
|
|
176
|
-
end
|
|
174
|
+
request(:GET, build_path(:cluster, group, version, nil, plural))
|
|
177
175
|
end
|
|
178
176
|
|
|
179
177
|
def get_cluster(group, version, plural, name)
|
|
180
|
-
|
|
181
|
-
Normalizer.stringify(
|
|
182
|
-
@transport.get_cluster_custom_object(group, version, plural, name)
|
|
183
|
-
)
|
|
184
|
-
end
|
|
178
|
+
request(:GET, build_path(:cluster, group, version, nil, plural, name))
|
|
185
179
|
end
|
|
186
180
|
|
|
187
181
|
def create_cluster(group, version, plural, body)
|
|
188
|
-
|
|
189
|
-
Normalizer.stringify(
|
|
190
|
-
@transport.create_cluster_custom_object(group, version, plural, body)
|
|
191
|
-
)
|
|
192
|
-
end
|
|
182
|
+
request(:POST, build_path(:cluster, group, version, nil, plural), body: body)
|
|
193
183
|
end
|
|
194
184
|
|
|
195
185
|
def patch_cluster(group, version, plural, name, body)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
186
|
+
request(
|
|
187
|
+
:PATCH,
|
|
188
|
+
build_path(:cluster, group, version, nil, plural, name),
|
|
189
|
+
body: body,
|
|
190
|
+
content_type: "application/json-patch+json"
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def delete_cluster(group, version, plural, name)
|
|
195
|
+
request(:DELETE, build_path(:cluster, group, version, nil, plural, name))
|
|
201
196
|
end
|
|
202
197
|
|
|
203
198
|
private
|
|
204
199
|
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
# Map the declaration coordinates to the API path (mirrors kubectl's
|
|
201
|
+
# discovery-based routing):
|
|
202
|
+
# - core (group == ""): /api/v1[/namespaces/{ns}]/{plural}[/{name}]
|
|
203
|
+
# - named (group != ""): /apis/{group}/{version}[/namespaces/{ns}]/{plural}[/{name}]
|
|
204
|
+
# `scope` :namespaced takes a namespace; :cluster passes nil (no
|
|
205
|
+
# namespace in the path). `name` (when present) is CGI-escaped.
|
|
206
|
+
def build_path(scope, group, version, namespace, plural, name = nil)
|
|
207
|
+
prefix = group.to_s.empty? ? "/api/#{version}" : "/apis/#{group}/#{version}"
|
|
208
|
+
middle = scope == :cluster ? "" : "/namespaces/#{CGI.escape(namespace.to_s)}"
|
|
209
|
+
path = "#{prefix}#{middle}/#{CGI.escape(plural.to_s)}"
|
|
210
|
+
name.nil? ? path : "#{path}/#{CGI.escape(name.to_s)}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Issue one request through kruby's call_api and normalize the result.
|
|
214
|
+
# `return_type "Object"` makes kruby hand back the parsed JSON (symbol
|
|
215
|
+
# keys); the Normalizer deep-stringifies it (K2). kruby errors are
|
|
216
|
+
# converted to the K8sRails hierarchy (K3).
|
|
217
|
+
def request(method, path, body: nil, content_type: "application/json")
|
|
218
|
+
data, _status_code, _headers = @transport.call_api(method, path, call_opts(body, content_type))
|
|
219
|
+
Normalizer.stringify(data)
|
|
207
220
|
rescue Kubernetes::ApiError => e
|
|
208
221
|
raise Client.convert_api_error(e)
|
|
209
222
|
rescue Kubernetes::ConfigError, Typhoeus::Errors::TyphoeusError => e
|
|
210
223
|
raise Unavailable, "K8s に接続できません: #{e.message}"
|
|
211
224
|
end
|
|
225
|
+
|
|
226
|
+
# The kruby call_api options hash (mirrors how kruby's generated API
|
|
227
|
+
# methods assemble their request opts).
|
|
228
|
+
def call_opts(body, content_type)
|
|
229
|
+
{
|
|
230
|
+
operation: :k8s_rails_request,
|
|
231
|
+
header_params: { "Content-Type" => content_type },
|
|
232
|
+
query_params: {},
|
|
233
|
+
body: body,
|
|
234
|
+
auth_names: ["BearerToken"],
|
|
235
|
+
return_type: "Object"
|
|
236
|
+
}
|
|
237
|
+
end
|
|
212
238
|
end
|
|
213
239
|
|
|
214
240
|
# One-shot /version probe backed by VersionApi#get_code (the kruby 1.36.x
|
|
@@ -18,12 +18,12 @@ module K8sRails
|
|
|
18
18
|
# 認証を上書きする場合に指定する。
|
|
19
19
|
attr_accessor :connection
|
|
20
20
|
|
|
21
|
-
# テスト専用(§5.1)。
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
21
|
+
# テスト専用(§5.1)。kruby の `Kubernetes::ApiClient#call_api` と同型の
|
|
22
|
+
# `call_api(http_method, path, opts)` メソッドを実装した素のオブジェクトを
|
|
23
|
+
# 指定すると、Client.build は接続解決をスキープしてこれを内部トランスポート
|
|
24
|
+
# として使う(`StringKeyedAdapter` が `call_api` を呼ぶ)。path は宣言の
|
|
25
|
+
# 座標から構築された API パス(core v1 は /api/v1/...、named グループは
|
|
26
|
+
# /apis/{group}/{version}/...)。
|
|
27
27
|
attr_accessor :api_client
|
|
28
28
|
|
|
29
29
|
# ActiveSupport::Notifications での計測の ON/OFF(§8。M3 で実装)。
|
data/lib/k8s_rails/resource.rb
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module K8sRails
|
|
4
|
-
# Base for generated CRD access classes (design §5.3).
|
|
4
|
+
# Base for generated CRD / built-in access classes (design §5.3).
|
|
5
5
|
#
|
|
6
6
|
# A `K8sRails.crd` declaration returns a `Class.new(Resource)` with the
|
|
7
|
-
# declared coordinates bound as class methods. All operations go through
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# touches kruby itself (§7).
|
|
11
|
-
#
|
|
12
|
-
# Workflow = K8sRails.crd(group: "argoproj.io", version: "v1alpha1",
|
|
13
|
-
# plural: "workflows", kind: "Workflow")
|
|
7
|
+
# declared coordinates bound as class methods. All operations go through the
|
|
8
|
+
# shared transport (`K8sRails.client`), which handles kruby error conversion
|
|
9
|
+
# (K3) and response stringification (K2) — this class never touches kruby (§7).
|
|
14
10
|
class Resource
|
|
15
|
-
# Bind declared coordinates to a fresh anonymous subclass. `namespace`
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# declarations must not set `namespace`.
|
|
11
|
+
# Bind declared coordinates to a fresh anonymous subclass. `namespace` may
|
|
12
|
+
# be nil (resolved from `K8sRails.config.namespace` at call time); `scope`
|
|
13
|
+
# is :namespaced (default) or :cluster (cluster declarations: no namespace).
|
|
19
14
|
def self.declare(group:, version:, plural:, kind:, namespace: nil, readonly: true, scope: :namespaced)
|
|
20
15
|
Class.new(self) do
|
|
21
16
|
define_singleton_method(:group_name) { group }
|
|
@@ -29,20 +24,16 @@ module K8sRails
|
|
|
29
24
|
end
|
|
30
25
|
|
|
31
26
|
class << self
|
|
32
|
-
# All objects in the namespace
|
|
33
|
-
# cluster-scoped declarations raise ArgumentError). Returns an array
|
|
34
|
-
# of string-keyed Hashes (design §5.3: `[{"name" => "...", ...}]`).
|
|
27
|
+
# All objects in the namespace; returns an array of string-keyed Hashes.
|
|
35
28
|
def list(namespace: resolved_namespace)
|
|
36
|
-
|
|
37
|
-
K8sRails.instrument(:list, instrument_meta(namespace)) do
|
|
29
|
+
run_operation(:list, :namespaced, namespace: namespace) do
|
|
38
30
|
transport.list(group_name, version_name, namespace, plural_name)["items"] || []
|
|
39
31
|
end
|
|
40
32
|
end
|
|
41
33
|
|
|
42
34
|
# One object by name. Raises K8sRails::NotFound when absent.
|
|
43
35
|
def find(name, namespace: resolved_namespace)
|
|
44
|
-
|
|
45
|
-
K8sRails.instrument(:find, instrument_meta(namespace)) do
|
|
36
|
+
run_operation(:find, :namespaced, namespace: namespace) do
|
|
46
37
|
transport.get(group_name, version_name, namespace, plural_name, name)
|
|
47
38
|
end
|
|
48
39
|
end
|
|
@@ -54,40 +45,39 @@ module K8sRails
|
|
|
54
45
|
nil
|
|
55
46
|
end
|
|
56
47
|
|
|
57
|
-
# Create from a
|
|
58
|
-
# K8sRails::ReadOnlyError (K4).
|
|
48
|
+
# Create from a body hash. `readonly: true` declarations raise ReadOnlyError.
|
|
59
49
|
def create(attributes, namespace: resolved_namespace)
|
|
60
|
-
|
|
61
|
-
assert_writable
|
|
62
|
-
K8sRails.instrument(:create, instrument_meta(namespace)) do
|
|
50
|
+
run_operation(:create, :namespaced, namespace: namespace, writable: true) do
|
|
63
51
|
transport.create(group_name, version_name, namespace, plural_name, attributes)
|
|
64
52
|
end
|
|
65
53
|
end
|
|
66
54
|
|
|
67
55
|
# JSON Patch a named object. Same readonly restriction as `create`.
|
|
68
56
|
def patch(name, operations, namespace: resolved_namespace)
|
|
69
|
-
|
|
70
|
-
assert_writable
|
|
71
|
-
K8sRails.instrument(:patch, instrument_meta(namespace)) do
|
|
57
|
+
run_operation(:patch, :namespaced, namespace: namespace, writable: true) do
|
|
72
58
|
transport.patch(group_name, version_name, namespace, plural_name, name, operations)
|
|
73
59
|
end
|
|
74
60
|
end
|
|
75
61
|
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
# Delete a named object; returns the API status object. Same readonly
|
|
63
|
+
# restriction; raises NotFound when already gone.
|
|
64
|
+
def delete(name, namespace: resolved_namespace)
|
|
65
|
+
run_operation(:delete, :namespaced, namespace: namespace, writable: true) do
|
|
66
|
+
transport.delete(group_name, version_name, namespace, plural_name, name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Cluster-scoped variants (design §5.3); a namespaced declaration raises
|
|
71
|
+
# ArgumentError (its endpoints 404 on the cluster path anyway).
|
|
80
72
|
def list_cluster
|
|
81
|
-
|
|
82
|
-
K8sRails.instrument(:list, instrument_meta(nil)) do
|
|
73
|
+
run_operation(:list, :cluster) do
|
|
83
74
|
transport.list_cluster(group_name, version_name, plural_name)["items"] || []
|
|
84
75
|
end
|
|
85
76
|
end
|
|
86
77
|
|
|
87
|
-
# One cluster-scoped object by name. Raises
|
|
78
|
+
# One cluster-scoped object by name. Raises NotFound when absent.
|
|
88
79
|
def find_cluster(name)
|
|
89
|
-
|
|
90
|
-
K8sRails.instrument(:find, instrument_meta(nil)) do
|
|
80
|
+
run_operation(:find, :cluster) do
|
|
91
81
|
transport.get_cluster(group_name, version_name, plural_name, name)
|
|
92
82
|
end
|
|
93
83
|
end
|
|
@@ -101,43 +91,51 @@ module K8sRails
|
|
|
101
91
|
|
|
102
92
|
# Create a cluster-scoped object. Same readonly restriction as `create`.
|
|
103
93
|
def create_cluster(attributes)
|
|
104
|
-
|
|
105
|
-
assert_writable
|
|
106
|
-
K8sRails.instrument(:create, instrument_meta(nil)) do
|
|
94
|
+
run_operation(:create, :cluster, writable: true) do
|
|
107
95
|
transport.create_cluster(group_name, version_name, plural_name, attributes)
|
|
108
96
|
end
|
|
109
97
|
end
|
|
110
98
|
|
|
111
99
|
# JSON Patch a cluster-scoped object. Same readonly restriction.
|
|
112
100
|
def patch_cluster(name, operations)
|
|
113
|
-
|
|
114
|
-
assert_writable
|
|
115
|
-
K8sRails.instrument(:patch, instrument_meta(nil)) do
|
|
101
|
+
run_operation(:patch, :cluster, writable: true) do
|
|
116
102
|
transport.patch_cluster(group_name, version_name, plural_name, name, operations)
|
|
117
103
|
end
|
|
118
104
|
end
|
|
119
105
|
|
|
106
|
+
# Delete a cluster-scoped object. Same readonly restriction as `delete`.
|
|
107
|
+
def delete_cluster(name)
|
|
108
|
+
run_operation(:delete, :cluster, writable: true) do
|
|
109
|
+
transport.delete_cluster(group_name, version_name, plural_name, name)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
120
113
|
private
|
|
121
114
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
# Scope gate + readonly gate (§5.3) + §8 instrumentation for every
|
|
116
|
+
# operation. The block value (and any K8sRails exception) flows through
|
|
117
|
+
# unchanged. Cluster-scoped operations carry no namespace.
|
|
118
|
+
def run_operation(operation, scope, namespace: nil, writable: false, &block)
|
|
119
|
+
scope == :cluster ? assert_cluster_scoped : assert_namespaced
|
|
120
|
+
assert_writable if writable
|
|
121
|
+
ns = scope == :cluster ? nil : namespace
|
|
122
|
+
K8sRails.instrument(operation, instrument_meta(ns)) { block.call }
|
|
125
123
|
end
|
|
126
124
|
|
|
125
|
+
# Notification metadata for design §8 (`k8s-rails.request`).
|
|
126
|
+
def instrument_meta(namespace) = { group: group_name, version: version_name, plural: plural_name, namespace: }
|
|
127
|
+
|
|
127
128
|
# Declared namespace wins; otherwise the gem default (resolved at call
|
|
128
129
|
# time so `K8sRails.reset!` + reconfigure works in tests).
|
|
129
|
-
def resolved_namespace
|
|
130
|
-
declared_namespace || K8sRails.config.namespace
|
|
131
|
-
end
|
|
130
|
+
def resolved_namespace = declared_namespace || K8sRails.config.namespace
|
|
132
131
|
|
|
133
|
-
# Endless method:
|
|
134
|
-
# delegating to the shared transport (design §5.2).
|
|
132
|
+
# Endless method: delegates to the shared transport (design §5.2).
|
|
135
133
|
def transport = K8sRails.client
|
|
136
134
|
|
|
137
135
|
def assert_writable
|
|
138
136
|
return unless readonly?
|
|
139
137
|
|
|
140
|
-
raise ReadOnlyError, "#{kind_name} is declared readonly — create/patch are disabled (K4)"
|
|
138
|
+
raise ReadOnlyError, "#{kind_name} is declared readonly — create/patch/delete are disabled (K4)"
|
|
141
139
|
end
|
|
142
140
|
|
|
143
141
|
def assert_cluster_scoped
|
data/lib/k8s_rails/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: k8s-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dorian - Takahiro Ishida
|
|
@@ -116,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
116
116
|
version: '3.3'
|
|
117
117
|
- - "<"
|
|
118
118
|
- !ruby/object:Gem::Version
|
|
119
|
-
version: '4.
|
|
119
|
+
version: '4.1'
|
|
120
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
122
|
- - ">="
|