k8s-rails 0.1.0 → 0.2.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 +28 -0
- data/README.md +65 -10
- data/docs/design.md +64 -19
- data/lib/k8s-rails.rb +32 -4
- data/lib/k8s_rails/client.rb +50 -0
- data/lib/k8s_rails/configuration.rb +8 -4
- data/lib/k8s_rails/crd.rb +18 -2
- data/lib/k8s_rails/resource.rb +74 -9
- data/lib/k8s_rails/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 708bfa5afaf656a7fb64a0ab781ef429eaa3f643d049d209941260178d6859d8
|
|
4
|
+
data.tar.gz: e9a0184c46ad0e5afd2f66bfdb1857d7f170c9a573825363a6910087ff4b8995
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e54a31eee779538219001aec14d679ca11ff2ec2334f7f97a4f6bcb77d274b4b477a032e092daa85333f318656dc2f00feb45d1f163aeb76e36196dfd985ba20
|
|
7
|
+
data.tar.gz: 77ccd48603ee30f6145855fb6808001042bc5dd0cd74f6172a247fa6e2acef6bcde07a1ce21f497b9d823bf9b50f036ceb83aaa36daaabe5c85c07175e6b70ef
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
`k8s-rails` の全 notable な変更はこのファイルに記録する。
|
|
4
4
|
|
|
5
|
+
## 0.2.0
|
|
6
|
+
|
|
7
|
+
- **cluster-scoped CRD サポート**(#17): `K8sRails.crd` に `scope: :namespaced`
|
|
8
|
+
(既定)/ `:cluster` を追加。cluster-scoped CRD(ClusterIssuer 等)は
|
|
9
|
+
`scope: :cluster`(`namespace:` 併用不可)で宣言し、`list_cluster` /
|
|
10
|
+
`find_cluster` / `find_or_nil_cluster` / `create_cluster` /
|
|
11
|
+
`patch_cluster` を使う。スコープと非対称な呼び出し(namespaced 宣言で
|
|
12
|
+
`*_cluster` / cluster 宣言で素のメソッド)は `ArgumentError`。
|
|
13
|
+
transport は kruby の `*_cluster_custom_object` 4 メソッドを新たに利用。
|
|
14
|
+
- **`configure` のアトミック契約**(#16): 「一度だけ」はブロックが
|
|
15
|
+
正常終了した場合のみ成立。ブロックが異常終了した(任意の例外 —
|
|
16
|
+
`LoadError` / `ScriptError` を含む — / `throw` / non-local return 等)
|
|
17
|
+
場合は設定済みフラグがリセットされ、後続の `configure` は通常どおり
|
|
18
|
+
実行される。異常終了前に書き込まれた属性は残存する(再実行ブロックは
|
|
19
|
+
依存する属性を全て設定する責務を負う。ロールバックはしない)。
|
|
20
|
+
- **`connected?` の注入契約**(#18): `config.api_client` 注入時は
|
|
21
|
+
I/O なしで `true`(注入トランスポートが接続面そのもの)。
|
|
22
|
+
非注入時は従来どおり VersionApi プローブ。
|
|
23
|
+
- **接続設定探索順序の修正**(#19): README / 設計書 / 設定コメントの
|
|
24
|
+
自動検出順序を kruby 1.36.x の loader 実装順
|
|
25
|
+
(**KUBECONFIG → `~/.kube/config` → in-cluster**、in-cluster は最後)
|
|
26
|
+
に修正(従来の「in-cluster → KUBECONFIG」記述は誤り)。kruby 上げ替え
|
|
27
|
+
時の再確認手順を設計書 §7 に追加。
|
|
28
|
+
- テスト注入スタブ(`config.api_client`)の契約: namespaced 4 メソッド +
|
|
29
|
+
cluster 4 メソッド(namespaced 宣言のみ使う場合は前者 4 メソッドで足りる)。
|
|
30
|
+
- 設計書 KBR-DESIGN-001 v0.1.12(案)へ更新(§5.1 / §5.2 / §5.3 / §6 /
|
|
31
|
+
§7 / §9 / §14)。
|
|
32
|
+
|
|
5
33
|
## 0.1.0
|
|
6
34
|
|
|
7
35
|
- **M0**: gem 骨子(gemspec / Gemfile / Rakefile / version / require 構造)
|
data/README.md
CHANGED
|
@@ -22,12 +22,12 @@ For the design rationale, see the [design document (docs/design.md)](docs/design
|
|
|
22
22
|
|
|
23
23
|
```ruby
|
|
24
24
|
# Gemfile
|
|
25
|
-
gem "k8s-rails", "~> 0.
|
|
25
|
+
gem "k8s-rails", "~> 0.2"
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
```ruby
|
|
29
29
|
require "k8s-rails"
|
|
30
|
-
K8sRails::VERSION # => "0.
|
|
30
|
+
K8sRails::VERSION # => "0.2.0"
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
`require` is side-effect-free and needs no cluster. kruby itself is loaded
|
|
@@ -74,12 +74,21 @@ K8sRails.configure do |config|
|
|
|
74
74
|
end
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
- `configure` is effective **only once
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
- `configure` is effective **only once** — but only when the block returns
|
|
78
|
+
normally. If the block exits abnormally (any exception, including
|
|
79
|
+
`LoadError`; `throw`; non-local return), the "configured" flag is reset,
|
|
80
|
+
so a later `configure` runs normally. Note that attribute writes made
|
|
81
|
+
before the abnormal exit **remain** on the shared configuration (a
|
|
82
|
+
partially applied state is possible); a re-run block should set every
|
|
83
|
+
attribute it depends on.
|
|
84
|
+
A second call on an already-configured gem prints a warning and is ignored
|
|
85
|
+
(use `K8sRails.reset!` to reset the configuration, the cached transport, and
|
|
86
|
+
declared CRDs — primarily for tests).
|
|
80
87
|
- Connection resolution order: `config.api_client` (test injection) →
|
|
81
88
|
`config.connection` → `Kubernetes::Configuration.default_config`
|
|
82
|
-
(automatic in
|
|
89
|
+
(automatic detection in kruby 1.36.x: `KUBECONFIG` → `~/.kube/config` →
|
|
90
|
+
in-cluster. Note in-cluster is tried **last**, after the file-based
|
|
91
|
+
sources — re-verify `kruby`'s loader when upgrading kruby).
|
|
83
92
|
|
|
84
93
|
## CRD declaration and access
|
|
85
94
|
|
|
@@ -89,13 +98,44 @@ naming convention). Re-declaring the same kind raises
|
|
|
89
98
|
|
|
90
99
|
Return values are **always string-keyed hashes**. kruby returns symbol keys,
|
|
91
100
|
but Rails-side JSON/views work with string keys, so the gem normalizes
|
|
92
|
-
internally in pure Ruby (no ActiveSupport dependency). Every
|
|
93
|
-
`namespace:` argument to override the namespace from the
|
|
101
|
+
internally in pure Ruby (no ActiveSupport dependency). Every namespaced
|
|
102
|
+
method accepts a `namespace:` argument to override the namespace from the
|
|
103
|
+
declaration (the `*_cluster` methods take no `namespace:` — see
|
|
104
|
+
[Cluster-scoped CRDs](#cluster-scoped-crds)).
|
|
94
105
|
|
|
95
106
|
`readonly` must be an **explicit boolean** (`nil` or other values raise
|
|
96
107
|
`ArgumentError`). Writes are enabled **only** by `readonly: false`, so a
|
|
97
108
|
missing flag can never fail open.
|
|
98
109
|
|
|
110
|
+
### Cluster-scoped CRDs
|
|
111
|
+
|
|
112
|
+
Both namespaced and cluster-scoped CRDs are supported. Declare a
|
|
113
|
+
cluster-scoped CRD (ClusterIssuer, ClusterWorkflowTemplate, ...) with
|
|
114
|
+
`scope: :cluster` and **without** `namespace:` (combining the two raises
|
|
115
|
+
`ArgumentError`):
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
ClusterIssuer = K8sRails.crd(
|
|
119
|
+
group: "cert-manager.io",
|
|
120
|
+
version: "v1",
|
|
121
|
+
plural: "clusterissuers",
|
|
122
|
+
kind: "ClusterIssuer",
|
|
123
|
+
scope: :cluster, # cluster-scoped endpoints (no namespace)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
ClusterIssuer.list_cluster # all objects cluster-wide
|
|
127
|
+
ClusterIssuer.find_cluster("letsencrypt")
|
|
128
|
+
ClusterIssuer.find_or_nil_cluster("x")
|
|
129
|
+
ClusterIssuer.create_cluster({ ... }) # readonly: false only
|
|
130
|
+
ClusterIssuer.patch_cluster("letsencrypt", [{ ... }]) # readonly: false only
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
On a `scope: :namespaced` declaration (the default) the `*_cluster` methods
|
|
134
|
+
raise `ArgumentError` — the CRD is namespaced, so the cluster endpoints would
|
|
135
|
+
404 anyway. For a cluster-scoped declaration, use the `*_cluster` methods
|
|
136
|
+
(the plain `list`/`find`/... would call the namespaced endpoints and 404, so
|
|
137
|
+
they raise `ArgumentError` there as well).
|
|
138
|
+
|
|
99
139
|
## Connectivity check
|
|
100
140
|
|
|
101
141
|
```ruby
|
|
@@ -107,6 +147,11 @@ It never returns `false` — a connection failure surfaces as an exception
|
|
|
107
147
|
(handle it with `rescue`). The actual API connection is established lazily on
|
|
108
148
|
the first API call.
|
|
109
149
|
|
|
150
|
+
When a test transport is injected via `config.api_client`, `connected?`
|
|
151
|
+
returns `true` without any network I/O — the injected transport **is** the
|
|
152
|
+
connection surface, so probing a real endpoint would contradict the Resource
|
|
153
|
+
operations that the same injection serves.
|
|
154
|
+
|
|
110
155
|
## Exception hierarchy
|
|
111
156
|
|
|
112
157
|
```
|
|
@@ -154,7 +199,9 @@ end
|
|
|
154
199
|
|
|
155
200
|
The test suite needs **no cluster**. Tests inject a transport stub via
|
|
156
201
|
`config.api_client`. The stub is wrapped internally by an adapter, so it just
|
|
157
|
-
implements the same
|
|
202
|
+
implements the same methods as kruby's `CustomObjectsApi` — the four
|
|
203
|
+
`*_namespaced_custom_object` methods, plus (for cluster-scoped declarations)
|
|
204
|
+
the four `*_cluster_custom_object` methods:
|
|
158
205
|
|
|
159
206
|
```ruby
|
|
160
207
|
class StubTransport
|
|
@@ -162,11 +209,19 @@ class StubTransport
|
|
|
162
209
|
def get_namespaced_custom_object(group, version, namespace, plural, name) = {}
|
|
163
210
|
def create_namespaced_custom_object(group, version, namespace, plural, body) = {}
|
|
164
211
|
def patch_namespaced_custom_object(group, version, namespace, plural, name, body) = {}
|
|
212
|
+
|
|
213
|
+
def list_cluster_custom_object(group, version, plural) = { items: [] }
|
|
214
|
+
def get_cluster_custom_object(group, version, plural, name) = {}
|
|
215
|
+
def create_cluster_custom_object(group, version, plural, body) = {}
|
|
216
|
+
def patch_cluster_custom_object(group, version, plural, name, body) = {}
|
|
165
217
|
end
|
|
166
218
|
|
|
167
219
|
K8sRails.configure { |c| c.api_client = StubTransport.new }
|
|
168
220
|
```
|
|
169
221
|
|
|
222
|
+
With a transport injected, `K8sRails.connected?` returns `true` without
|
|
223
|
+
network I/O (see [Connectivity check](#connectivity-check)).
|
|
224
|
+
|
|
170
225
|
## Development
|
|
171
226
|
|
|
172
227
|
```
|
|
@@ -189,7 +244,7 @@ through k8s-rails are unaffected (it does not overwrite an already-set
|
|
|
189
244
|
kruby's own configuration behavior (outside this gem), check this token-key
|
|
190
245
|
issue first.
|
|
191
246
|
|
|
192
|
-
## Roadmap (v0.
|
|
247
|
+
## Roadmap (v0.3+)
|
|
193
248
|
|
|
194
249
|
- Ruby 3.5 / 4.0 support (after verifying against the stable releases, then
|
|
195
250
|
widening the declared range and the CI matrix)
|
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.12(案)
|
|
5
5
|
- 日付: 2026-09-15
|
|
6
6
|
- 対象リポジトリ: k8s-rails(本設計の実装先)
|
|
7
7
|
- ライセンス: MIT(LICENSE は main に既存)
|
|
@@ -41,7 +41,7 @@ K8s CRD を扱えるようにし、(b) 実際の consumer アプリをこの gem
|
|
|
41
41
|
### 2.2 非スコープ(本設計の外)
|
|
42
42
|
|
|
43
43
|
- core v1 リソース(Pod / Deployment / Service 等)のフルサポート — CRD 中心の gem。core v1 は built-in リソースであり CustomObjects API では扱えない(`group=""`/`version="v1"` の宣言は**無効**)ため、別途 core API 経路が必要。v0.1 では対象外(将来候補、§11)
|
|
44
|
-
- **watch(ストリーム)** — v0.1 では非対応。kruby の watch は get/list より成熟度が低く、初版の API 保証範囲から外す(§
|
|
44
|
+
- **watch(ストリーム)** — v0.1 では非対応。kruby の watch は get/list より成熟度が低く、初版の API 保証範囲から外す(§10 で v0.3 対象)
|
|
45
45
|
- アプリの**デプロイ**(helm / kustomize 生成等) — `kuby-core` の領域
|
|
46
46
|
- RBAC 権限の付与・管理 — 呼び出しアプリ側の ClusterRole/Role の責務
|
|
47
47
|
- 複数クラスタ同時接続 — v0.1 は単一クラスタ前提(§11 展望)
|
|
@@ -80,7 +80,8 @@ K8s CRD を扱えるようにし、(b) 実際の consumer アプリをこの gem
|
|
|
80
80
|
│ │
|
|
81
81
|
┌──────────────────────────┴────────────────┴────────┐
|
|
82
82
|
│ kruby (~> 1.36.0) → Kubernetes API Server │
|
|
83
|
-
│ (in-cluster SA token
|
|
83
|
+
│ (KUBECONFIG | ~/.kube/config | in-cluster SA token) │
|
|
84
|
+
│ ※kruby 1.36.x の探索順序は左→右(in-cluster は最後) │
|
|
84
85
|
└────────────────────────────────────────────────────┘
|
|
85
86
|
```
|
|
86
87
|
|
|
@@ -105,7 +106,7 @@ k8s-rails/
|
|
|
105
106
|
├── lib/
|
|
106
107
|
│ ├── k8s-rails.rb # エントリ。require 集 + モジュール定義
|
|
107
108
|
│ └── k8s_rails/
|
|
108
|
-
│ ├── version.rb # VERSION = "0.
|
|
109
|
+
│ ├── version.rb # VERSION = "0.2.0"
|
|
109
110
|
│ ├── configuration.rb # Config: namespace, connection, 計測 ON/OFF
|
|
110
111
|
│ ├── client.rb # 接続解決・BearerToken 橋渡し・計測ラップ
|
|
111
112
|
│ ├── crd.rb # K8sRails.crd 宣言 → Resource 生成
|
|
@@ -129,7 +130,8 @@ k8s-rails/
|
|
|
129
130
|
# config/initializers/k8s-rails.rb
|
|
130
131
|
K8sRails.configure do |config|
|
|
131
132
|
config.namespace = ENV.fetch("K8S_NAMESPACE", "default")
|
|
132
|
-
# 任意上書き(省略時は default_config
|
|
133
|
+
# 任意上書き(省略時は default_config の自動検出。kruby 1.36.x の探索順序は
|
|
134
|
+
# KUBECONFIG → ~/.kube/config → in-cluster で、in-cluster が最後)
|
|
133
135
|
# config.connection = Kubernetes::Configuration.default_config
|
|
134
136
|
# config.instrumentation = true # 既定 true(ActiveSupport 存在時のみ有効)
|
|
135
137
|
end
|
|
@@ -140,11 +142,18 @@ end
|
|
|
140
142
|
| キー | 既定 | 説明 |
|
|
141
143
|
|---|---|---|
|
|
142
144
|
| `namespace` | `"default"` | CRD 宣言が namespace 未指定時のデフォルト |
|
|
143
|
-
| `connection` | `nil`(自動検出) | `Kubernetes::Configuration`
|
|
144
|
-
| `api_client` | `nil` | **テスト専用**: kruby の `CustomObjectsApi`
|
|
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 の `CustomObjectsApi` と同型のメソッド(namespaced 4 メソッド `get_namespaced_custom_object` / `list_namespaced_custom_object` / `create_namespaced_custom_object` / `patch_namespaced_custom_object` + cluster 4 メソッド `get_cluster_custom_object` / `list_cluster_custom_object` / `create_cluster_custom_object` / `patch_cluster_custom_object`)を実装する素のオブジェクト(namespaced 宣言のみを使う場合は namespaced 4 メソッドで足りる)。指定時は `Client.build` が接続解決をスキープして `StringKeyedAdapter` で包んで使う(§5.2・§9) |
|
|
145
147
|
| `instrumentation` | `true` | `ActiveSupport::Notifications` で計測する(§8) |
|
|
146
148
|
|
|
147
149
|
- 設定は `K8sRails.configure` で**一度だけ**。再実行は警告(`Warning`)+ 無視。
|
|
150
|
+
**「一度だけ」はブロックが正常終了した場合に限る**: ブロックが異常終了した
|
|
151
|
+
(任意の例外送出 — `LoadError` / `ScriptError` を含む — / `throw` /
|
|
152
|
+
non-local return 等)場合は設定済みフラグがリセットされ、後続の `configure`
|
|
153
|
+
は通常どおり実行される。ただし異常終了前に書き込まれた属性は共有 Configuration
|
|
154
|
+
に**残存する**(部分的な設定状態になり得るため、再実行ブロックは依存する属性を
|
|
155
|
+
全て設定する責務を負う。アトミックなロールバックは行わない — Configuration
|
|
156
|
+
は純データで 4 属性のみのため、複写+スワップの複雑さに見合わない)。
|
|
148
157
|
- `K8sRails.reset!`(テスト用)で接続キャッシュ・宣言済 CRD を破棄できる。
|
|
149
158
|
|
|
150
159
|
### 5.2 接続
|
|
@@ -154,6 +163,8 @@ K8sRails::Client.build # → Kubernetes::CustomObjectsApi(lazy。初回呼
|
|
|
154
163
|
K8sRails.connected? # → 成功時は true。失敗は K8sRails::Unavailable / ApiError を raise
|
|
155
164
|
# (false を返す経路なし)。/version 相当の軽量確認
|
|
156
165
|
# (kruby 1.36.x の VersionApi#get_code(GET /version/)1 回)
|
|
166
|
+
# `config.api_client` 注入時は I/O なしで true(§5.1 の
|
|
167
|
+
# 注入が接続面そのものだから。Resource 操作と整合させる)
|
|
157
168
|
```
|
|
158
169
|
|
|
159
170
|
`Client.build` が内部で行うこと(consumer アプリの K8s サービスの custom objects 生成部を移設):
|
|
@@ -164,6 +175,11 @@ K8sRails.connected? # → 成功時は true。失敗は K8sRails::Unavailab
|
|
|
164
175
|
3. `Kubernetes::ApiClient` → `Kubernetes::CustomObjectsApi` を生成し、**文字列キー化**(K2)を API レスポンス後に行う。ActiveSupport 非依存の gem 内部の純 Ruby 再帰変換(`K8sRails::Normalizer`)を使う(v0.1.1 以降: 常に Normalizer。`deep_stringify_keys` 経路は廃止)
|
|
165
176
|
4. **接続レベルの失敗**(DNS 失敗 / タイムアウト / 接続拒否等)は `K8sRails::Unavailable` に変換して `raise`(リトライはしない)。**kruby 1.36.x ではこれらの転送失敗は HTTP ステータスが無いため `Kubernetes::ApiError`(`code == 0`)として surfacing する**(§5.4 の変換表参照)。認可失敗(401/403)は §5.4 により `K8sRails::ApiError`
|
|
166
177
|
|
|
178
|
+
`connected?` の解決順序:
|
|
179
|
+
|
|
180
|
+
0. `config.api_client` があれば(テスト注入、§5.1)**I/O なしで `true` を返す**。注入されたトランスポートが接続面そのもののため、実エンドポイントへのプローブは同一注入下の Resource 操作と矛盾する(#18 対応)。クラスタ到達性の真の確認が必要な場合は注入を解除した環境で行う
|
|
181
|
+
1. 以下 `Client.build` と同一(`config.connection` → `default_config` → K1 橋渡し → VersionApi プローブ)
|
|
182
|
+
|
|
167
183
|
### 5.3 CRD 宣言
|
|
168
184
|
|
|
169
185
|
```ruby
|
|
@@ -176,9 +192,19 @@ Workflow = K8sRails.crd(
|
|
|
176
192
|
namespace: K8sRails.config.namespace, # 省略可
|
|
177
193
|
readonly: false, # 既定 true。false で create/patch 有効化(K4)
|
|
178
194
|
)
|
|
195
|
+
|
|
196
|
+
# cluster-scoped CRD(ClusterIssuer / ClusterWorkflowTemplate 等)は
|
|
197
|
+
# scope: :cluster を付け、namespace: は省略する(併用は ArgumentError)
|
|
198
|
+
ClusterIssuer = K8sRails.crd(
|
|
199
|
+
group: "cert-manager.io",
|
|
200
|
+
version: "v1",
|
|
201
|
+
plural: "clusterissuers",
|
|
202
|
+
kind: "ClusterIssuer",
|
|
203
|
+
scope: :cluster,
|
|
204
|
+
)
|
|
179
205
|
```
|
|
180
206
|
|
|
181
|
-
宣言で生成されるメソッド(全て class
|
|
207
|
+
宣言で生成されるメソッド(全て class メソッド。namespaced 系は任意の `namespace:` 引数を受け取り、宣言時のデフォルト namespace を上書き可能):
|
|
182
208
|
|
|
183
209
|
| メソッド | 引数 | 戻り値 | readonly 制限 |
|
|
184
210
|
|---|---|---|---|
|
|
@@ -186,6 +212,17 @@ Workflow = K8sRails.crd(
|
|
|
186
212
|
| `find(name)` | 必須 | 同型 or `K8sRails::NotFound`(raise) | 常に有効 |
|
|
187
213
|
| `create(attributes)` | CRD body hash | 作成済みオブジェクト(文字列キー) | `readonly: false` のみ |
|
|
188
214
|
| `patch(name, operations)` | JSON Patch 操作配列 | 更新済みオブジェクト | `readonly: false` のみ |
|
|
215
|
+
| `list_cluster` | `{}` | 同上(クラスタ横断。`namespace:` なし) | 常に有効 |
|
|
216
|
+
| `find_cluster(name)` | 必須 | 同上 or `K8sRails::NotFound` | 常に有効 |
|
|
217
|
+
| `create_cluster(attributes)` | CRD body hash | 作成済みオブジェクト | `readonly: false` のみ |
|
|
218
|
+
| `patch_cluster(name, operations)` | JSON Patch 操作配列 | 更新済みオブジェクト | `readonly: false` のみ |
|
|
219
|
+
|
|
220
|
+
スコープの契約(#17 対応):
|
|
221
|
+
|
|
222
|
+
- `scope: :namespaced`(既定)の宣言では `*_cluster` メソッドは `ArgumentError`(CRD が namespaced のためクラスタ endpoint を呼んでも 404 になるだけ)。
|
|
223
|
+
- `scope: :cluster` の宣言では素の `list` / `find` / `create` / `patch` は `ArgumentError`(namespaced endpoint を呼ぶと 404 になるだけ。`namespace:` 引数は意味を持たない)。
|
|
224
|
+
- 宣言時に `scope: :cluster` と `namespace:` を併用した場合は `ArgumentError`(設定ミスの fail fast)。
|
|
225
|
+
- transport は kruby の `*_cluster_custom_object` 4 メソッド(`namespace` 非持参の endpoint)を使う。テスト注入スタブは 2 セット 8 メソッドを実装する(§9)。
|
|
189
226
|
|
|
190
227
|
- **戻り値は常に文字列キーの Hash**(K2 の規約を API 契約として固定)。
|
|
191
228
|
`find` は存在しない場合 `K8sRails::NotFound` を raise(consumer アプリ側が `return nil` にしていたのは
|
|
@@ -232,8 +269,8 @@ end
|
|
|
232
269
|
|
|
233
270
|
| 依存 | 制約 | 理由 |
|
|
234
271
|
|---|---|---|
|
|
235
|
-
| Ruby | `>= 3.3, < 4.0` | 下限: kruby 1.36.x が `required_ruby_version ">= 3.3"` を宣言(RubyGems API で実測 2026-09-21、1.36.0.1〜1.36.4.1 全バージョン)。上限: 「宣言した Ruby minor を必ず CI で検証する」方針(レビュー対応・2026-09-21)— 2026-09-21 時点で Ruby 4.0 は stable(v4.0.7)だが未検証、3.5 は preview(v3_5_0_preview1)のため、宣言範囲を 3.x に限定。4.0 / 3.5 対応は v0.
|
|
236
|
-
| `kruby` | `~> 1.36.0` | consumer アプリと同一 pin。`~> 1.36.0` は 1.36.x のみ許可(`~> 1.36` 形式は 1.37 以降も許容してしまうため使用しない)。新しめの kruby に対応する場合は §7 の確認事項(client.rb 4
|
|
272
|
+
| Ruby | `>= 3.3, < 4.0` | 下限: kruby 1.36.x が `required_ruby_version ">= 3.3"` を宣言(RubyGems API で実測 2026-09-21、1.36.0.1〜1.36.4.1 全バージョン)。上限: 「宣言した Ruby minor を必ず CI で検証する」方針(レビュー対応・2026-09-21)— 2026-09-21 時点で Ruby 4.0 は stable(v4.0.7)だが未検証、3.5 は preview(v3_5_0_preview1)のため、宣言範囲を 3.x に限定。4.0 / 3.5 対応は v0.3 で検証の上宣言に含める(§10) |
|
|
273
|
+
| `kruby` | `~> 1.36.0` | consumer アプリと同一 pin。`~> 1.36.0` は 1.36.x のみ許可(`~> 1.36` 形式は 1.37 以降も許容してしまうため使用しない)。新しめの kruby に対応する場合は §7 の確認事項(client.rb 8 メソッド(namespaced 4 + cluster 4)・K1 橋渡し・`default_config` 探索順序)を済ませてから明示的に上げ替える |
|
|
237
274
|
| `activesupport` | **任意**(`>= 7.0`) | `defined?(ActiveSupport::Notifications)` でガード(計測のみ、§8)。Rails 無し環境(Cron スクリプト等)でも動作する必要がある — レスポンスの文字列キー化(K2)はこれに依存せず、gem 内部の純 Ruby 変換で担う(§5.2) |
|
|
238
275
|
| `rspec` / `rubocop` | 開発依存 | spec / lint |
|
|
239
276
|
|
|
@@ -245,10 +282,15 @@ end
|
|
|
245
282
|
- `lib/k8s_rails/client.rb` **のみ**が `require "kubernetes"` してよい。
|
|
246
283
|
他のファイルは kruby 定数・クラスを参照しない。
|
|
247
284
|
- kruby の `CustomObjectsApi` メソッド呼び出しは `client.rb` 内の
|
|
248
|
-
`*_namespaced_custom_object` の 4 メソッド(`get_namespaced_custom_object`
|
|
285
|
+
`*_namespaced_custom_object` の 4 メソッド(`get_namespaced_custom_object` 等)と
|
|
286
|
+
`*_cluster_custom_object` の 4 メソッド(`get_cluster_custom_object` 等)に集約する。
|
|
287
|
+
`resource.rb` は
|
|
249
288
|
`K8sRails.client.get(group, version, ns, plural, name)` のような **gem 内部 API** だけを使う。
|
|
250
|
-
- kruby 上げ替え時の作業は (1) client.rb 4
|
|
251
|
-
(2) K1
|
|
289
|
+
- kruby 上げ替え時の作業は (1) client.rb 8 メソッド(namespaced 4 + cluster 4)のシグネチャ確認、
|
|
290
|
+
(2) K1 橋渡しの要否確認、(3) **`Kubernetes::Configuration.default_config` の探索順序確認**
|
|
291
|
+
(kruby 1.36.x の loader 実装順は **KUBECONFIG → `~/.kube/config` → in-cluster** で in-cluster が
|
|
292
|
+
最後。README / 設計書 / 設定コメントがこの順序を明記しているため、loader が変わった場合は
|
|
293
|
+
全箇所を同期する — #19 対応)に収まることをテスト(§9)で担保する。
|
|
252
294
|
|
|
253
295
|
## 8. 計測(ActiveSupport 任意)
|
|
254
296
|
|
|
@@ -274,11 +316,11 @@ k8s-rails.request payload: { operation: :list, group:, version:, plural:, names
|
|
|
274
316
|
|
|
275
317
|
| レイヤー | 手法 | 対象 |
|
|
276
318
|
|---|---|---|
|
|
277
|
-
| ユニット | `K8sRails.config.api_client` に**スタブ**(kruby `CustomObjectsApi`
|
|
319
|
+
| ユニット | `K8sRails.config.api_client` に**スタブ**(kruby `CustomObjectsApi` と同型のメソッド。namespaced 4 メソッド `get_namespaced_custom_object` / `list_namespaced_custom_object` / `create_namespaced_custom_object` / `patch_namespaced_custom_object` + cluster 4 メソッド `get_cluster_custom_object` / `list_cluster_custom_object` / `create_cluster_custom_object` / `patch_cluster_custom_object` を実装する素のオブジェクト。`StringKeyedAdapter` がこの形式を呼ぶ)を注入 | client(橋渡し・例外変換)、resource(整形・readonly 制限・スコープ制限)、crd(メソッド生成・scope 検証) |
|
|
278
320
|
| 設定 | spec 間で `K8sRails.reset!` | 宣言の破棄・再接続 |
|
|
279
321
|
| 集積(任意) | GitHub Actions で **kind**(または既存 microk8s に接続するジョブ)で実クラスタ E2E | v0.1 の必須ではない。**推奨**: consumer アプリ移行時の検証を兼ねる |
|
|
280
322
|
|
|
281
|
-
- 本設計では CI は `rspec` + `rubocop` のみを必須とし、kind E2E は v0.
|
|
323
|
+
- 本設計では CI は `rspec` + `rubocop` のみを必須とし、kind E2E は v0.3 で
|
|
282
324
|
GitHub Actions の追加として扱う(実クラスタへの接続 CI はネットワーク依存のため採用しない)。
|
|
283
325
|
|
|
284
326
|
## 10. リリース計画
|
|
@@ -286,8 +328,9 @@ k8s-rails.request payload: { operation: :list, group:, version:, plural:, names
|
|
|
286
328
|
| バージョン | 内容 | 出口基準 |
|
|
287
329
|
|---|---|---|
|
|
288
330
|
| **v0.1** | §5 の公開 API(CRD 宣言 / list / find / create / patch / 例外 / 計測 / スタブテスト)+ README | rspec 全緑 + **consumer アプリの K8s サービスを `k8s-rails` に移行して動作確認**(§12) |
|
|
289
|
-
| v0.2 |
|
|
290
|
-
| v0.3 |
|
|
331
|
+
| **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 | watch(`watch` メソッド、kruby の watch サポート上)、core v1 built-in リソース対応(CustomObjects API では不可なため別途 core API 経路、§2.2)、kind E2E の CI 化、Ruby 3.5 / 4.0 対応(検証の上宣言範囲・matrix を拡大) | v0.2 運用のフィードバック |
|
|
333
|
+
| v0.4 | (展望)複数クラスタ(ネームスペース化された client 集合)、リトライポリシー | — |
|
|
291
334
|
|
|
292
335
|
v0.1 の milestone 分割(開発セッション向けのタスク単位目安):
|
|
293
336
|
|
|
@@ -357,12 +400,12 @@ PR の差分を最小化)。
|
|
|
357
400
|
gemspec の宣言範囲(`>= 3.3, < 4.0`)を matrix で検証:
|
|
358
401
|
3.3.0(下限・kruby 1.36.x の `>= 3.3`)・3.3.8(開発)・
|
|
359
402
|
3.4.10(3.x 系の最新 stable・2026-09-21 時点。3.5 は preview、
|
|
360
|
-
4.0 は宣言範囲外のため未検証・v0.
|
|
403
|
+
4.0 は宣言範囲外のため未検証・v0.3 で検討)。
|
|
361
404
|
**宣言範囲を常に matrix がカバーする**こと(`< 4.0` 上限により、
|
|
362
405
|
4.x のリリースは宣言範囲外。stable 化された新 3.x minor が出たら
|
|
363
406
|
matrix への追加を忘れないこと)。
|
|
364
407
|
テストはクラスタ不要(§9・スタブ注入)のため v0.1 は runner 上のユニットのみ。
|
|
365
|
-
kind / 実クラスタ E2E の CI 化は v0.
|
|
408
|
+
kind / 実クラスタ E2E の CI 化は v0.3 対象(§9・§10)
|
|
366
409
|
- `README` に「kruby pin」「対応 k8s バージョン(実測 v1.33.x で検証済み)」「K1 橋渡しの背景」
|
|
367
410
|
を明記する(検索でヒットする重要な注意点のため)
|
|
368
411
|
|
|
@@ -381,3 +424,5 @@ PR の差分を最小化)。
|
|
|
381
424
|
| 0.1.8 | 2026-09-21 | PR #11 レビュー第 4 波対応(Copilot ×3): 指摘(「Ruby 3.5 が stable 化したため matrix に追加せよ」)を検証した結果 **3.5 は preview であり claim は誤り**(ruby/ruby タグ `v3_5_0_preview1`・2026-09-21 実測)と判明。ただし指摘の根本(宣言と検証範囲のズレ)は**Ruby 4.0 が stable(v4.0.7)だったため**実際に存在した。対策として宣言範囲を **`>= 3.3, < 4.0` に改訂**(gemspec / §6 / README / CHANGELOG)し、宣言範囲 = matrix 検証範囲(3.3.0 / 3.3.8 / 3.4.10)を一致。4.0 / 3.5 対応は v0.2 以降で検証の上宣言に含める方針 | 実装反映済み |
|
|
382
425
|
| 0.1.9 | 2026-09-21 | public リポジトリ化の準備: ①公開導線を**ローカル PC から手動 `gem push`** に変更(kruby と同様の運用方針・CI 自動公開は廃止、publish workflow を削除、test workflow の push/PR テストのみ残す)②§13 公開手順の手動化(tag は追溯性のため推奨)③内部 consumer アプリの名称・構造への言及を §1〜§14 全箇所から除去し「consumer アプリ」に一般化 | 実装反映済み |
|
|
383
426
|
| 0.1.10 | 2026-09-21 | PR #12 レビュー対応(Copilot): §13 の公開手順で tag の **remote への push**(`git push origin v<VERSION>`)が欠落しており、GitHub 上のリリースコミットとの対応付け(追溯性)が確保できないとの指摘を反映 | 実装反映済み |
|
|
427
|
+
| 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
|
+
| 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) | 実装反映済み |
|
data/lib/k8s-rails.rb
CHANGED
|
@@ -23,14 +23,22 @@ module K8sRails
|
|
|
23
23
|
|
|
24
24
|
# Configure the gem. Runs once — a second call warns and is ignored
|
|
25
25
|
# (design §5.1). Yields the Configuration object.
|
|
26
|
+
#
|
|
27
|
+
# Atomicity contract: "runs once" holds only when the block returns
|
|
28
|
+
# normally. If the block exits abnormally (any exception — including
|
|
29
|
+
# LoadError/ScriptError — throw, non-local return, ...), the flag is
|
|
30
|
+
# reset, so a later `configure` call re-runs normally. However,
|
|
31
|
+
# attribute writes made before the exit REMAIN on the shared
|
|
32
|
+
# Configuration — a failed block may leave a partially applied state;
|
|
33
|
+
# the re-run block is responsible for setting every attribute it
|
|
34
|
+
# depends on (documented in design §5.1).
|
|
26
35
|
def configure
|
|
27
36
|
if @configured
|
|
28
37
|
warn "[K8sRails] K8sRails.configure called more than once; ignoring the second call."
|
|
29
38
|
return config
|
|
30
39
|
end
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
yield config
|
|
41
|
+
run_configure_block { yield config }
|
|
34
42
|
config
|
|
35
43
|
end
|
|
36
44
|
|
|
@@ -57,9 +65,10 @@ module K8sRails
|
|
|
57
65
|
# Declare a CRD and return its Resource class (design §5.3, K5).
|
|
58
66
|
# Workflow = K8sRails.crd(group: "argoproj.io", version: "v1alpha1",
|
|
59
67
|
# plural: "workflows", kind: "Workflow")
|
|
68
|
+
# Cluster-scoped CRDs: pass scope: :cluster (and no namespace:).
|
|
60
69
|
# Re-declaring the same kind raises K8sRails::RedeclarationError.
|
|
61
|
-
def crd(group:, version:, plural:, kind:, namespace: nil, readonly: true)
|
|
62
|
-
CRD.declare(group:, version:, plural:, kind:, namespace:, readonly:)
|
|
70
|
+
def crd(group:, version:, plural:, kind:, namespace: nil, readonly: true, scope: :namespaced)
|
|
71
|
+
CRD.declare(group:, version:, plural:, kind:, namespace:, readonly:, scope:)
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
# Design §8: run an API call inside a `k8s-rails.request` notification.
|
|
@@ -106,6 +115,25 @@ module K8sRails
|
|
|
106
115
|
def instrumentation_enabled?
|
|
107
116
|
config.instrumentation && defined?(ActiveSupport::Notifications)
|
|
108
117
|
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# Runs the configure block with the configured-once contract (§5.1):
|
|
122
|
+
# the flag is set only if the block returns normally. ANY abnormal exit
|
|
123
|
+
# (StandardError, LoadError/ScriptError, throw, non-local return, other
|
|
124
|
+
# Exception subclasses) resets it so a later `configure` re-runs — but
|
|
125
|
+
# attribute writes made before the exit remain on the shared
|
|
126
|
+
# Configuration.
|
|
127
|
+
def run_configure_block
|
|
128
|
+
@configured = true
|
|
129
|
+
ok = false
|
|
130
|
+
begin
|
|
131
|
+
yield
|
|
132
|
+
ok = true
|
|
133
|
+
ensure
|
|
134
|
+
@configured = false unless ok
|
|
135
|
+
end
|
|
136
|
+
end
|
|
109
137
|
end
|
|
110
138
|
|
|
111
139
|
# Whether the kruby-dependent client file has actually been required yet
|
data/lib/k8s_rails/client.rb
CHANGED
|
@@ -14,6 +14,11 @@ module K8sRails
|
|
|
14
14
|
# api.get(group, version, namespace, plural, name)
|
|
15
15
|
# api.create(group, version, namespace, plural, body)
|
|
16
16
|
# api.patch(group, version, namespace, plural, name, body)
|
|
17
|
+
# # cluster-scoped (namespace argument omitted, design §5.3)
|
|
18
|
+
# api.list_cluster(group, version, plural)
|
|
19
|
+
# api.get_cluster(group, version, plural, name)
|
|
20
|
+
# api.create_cluster(group, version, plural, body)
|
|
21
|
+
# api.patch_cluster(group, version, plural, name, body)
|
|
17
22
|
#
|
|
18
23
|
# Connection is LAZY: `build` does no network I/O — it only resolves a
|
|
19
24
|
# `Kubernetes::Configuration` and builds an in-memory `CustomObjectsApi`.
|
|
@@ -44,7 +49,15 @@ module K8sRails
|
|
|
44
49
|
# Lightweight connectivity probe (design §5.2). Performs one lightweight
|
|
45
50
|
# `/version` call via VersionApi and returns true on success. Raises
|
|
46
51
|
# K8sRails::Unavailable / ApiError on failure (the app may rescue).
|
|
52
|
+
#
|
|
53
|
+
# Injection contract: when `config.api_client` is set (test injection),
|
|
54
|
+
# the injected transport IS the connection surface — there is no real
|
|
55
|
+
# network to probe, so this returns true without I/O. This keeps
|
|
56
|
+
# `connected?` consistent with Resource operations under injection
|
|
57
|
+
# (design §5.2).
|
|
47
58
|
def connected?
|
|
59
|
+
return true if K8sRails.config.api_client
|
|
60
|
+
|
|
48
61
|
config = build_configuration
|
|
49
62
|
# The probe also authenticates — apply the K1 bridge or the Authorization
|
|
50
63
|
# header would be empty on clusters where /version requires auth.
|
|
@@ -150,6 +163,43 @@ module K8sRails
|
|
|
150
163
|
end
|
|
151
164
|
end
|
|
152
165
|
|
|
166
|
+
# Cluster-scoped variants (design §5.3): the same four operations on
|
|
167
|
+
# kruby's *_cluster_custom_object endpoints (no namespace in the path).
|
|
168
|
+
# An injected test double must implement both the *_namespaced_* and
|
|
169
|
+
# *_cluster_* quadruples.
|
|
170
|
+
|
|
171
|
+
def list_cluster(group, version, plural)
|
|
172
|
+
handle do
|
|
173
|
+
Normalizer.stringify(
|
|
174
|
+
@transport.list_cluster_custom_object(group, version, plural)
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def get_cluster(group, version, plural, name)
|
|
180
|
+
handle do
|
|
181
|
+
Normalizer.stringify(
|
|
182
|
+
@transport.get_cluster_custom_object(group, version, plural, name)
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def create_cluster(group, version, plural, body)
|
|
188
|
+
handle do
|
|
189
|
+
Normalizer.stringify(
|
|
190
|
+
@transport.create_cluster_custom_object(group, version, plural, body)
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def patch_cluster(group, version, plural, name, body)
|
|
196
|
+
handle do
|
|
197
|
+
Normalizer.stringify(
|
|
198
|
+
@transport.patch_cluster_custom_object(group, version, plural, name, body)
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
153
203
|
private
|
|
154
204
|
|
|
155
205
|
def handle
|
|
@@ -13,13 +13,17 @@ module K8sRails
|
|
|
13
13
|
attr_accessor :namespace
|
|
14
14
|
|
|
15
15
|
# Kubernetes::Configuration インスタンス。省略時は default_config の自動
|
|
16
|
-
# 検出(in-cluster
|
|
16
|
+
# 検出(kruby 1.36.x の実装順: KUBECONFIG → ~/.kube/config → in-cluster。
|
|
17
|
+
# kruby 更新時にこの順序が変更されていないか loader を再確認すること)。
|
|
18
|
+
# 認証を上書きする場合に指定する。
|
|
17
19
|
attr_accessor :connection
|
|
18
20
|
|
|
19
|
-
# テスト専用(§5.1)。CustomObjectsApi と同型の
|
|
20
|
-
#
|
|
21
|
+
# テスト専用(§5.1)。CustomObjectsApi と同型の 8 メソッド
|
|
22
|
+
# (namespaced 4 メソッド `get_namespaced_custom_object` 等 + cluster 4
|
|
23
|
+
# メソッド `get_cluster_custom_object` 等)を実装した素の
|
|
21
24
|
# オブジェクトを指定すると、Client.build は接続解決をスキープしてこれを
|
|
22
|
-
#
|
|
25
|
+
# 内部トランスポートとして使う(namespaced 宣言だけを使う場合は
|
|
26
|
+
# namespaced 4 メソッドのみの実装で足りる)。
|
|
23
27
|
attr_accessor :api_client
|
|
24
28
|
|
|
25
29
|
# ActiveSupport::Notifications での計測の ON/OFF(§8。M3 で実装)。
|
data/lib/k8s_rails/crd.rb
CHANGED
|
@@ -12,6 +12,9 @@ module K8sRails
|
|
|
12
12
|
# readonly: false, # default true
|
|
13
13
|
# )
|
|
14
14
|
#
|
|
15
|
+
# Cluster-scoped CRDs (ClusterIssuer, ClusterWorkflowTemplate, ...) use
|
|
16
|
+
# `scope: :cluster` — `namespace` must be omitted (design §5.3).
|
|
17
|
+
#
|
|
15
18
|
# `crd` returns a `Resource` subclass with the coordinates bound; the same
|
|
16
19
|
# class is registered under its kind name so a second declaration of the
|
|
17
20
|
# same kind raises K8sRails::RedeclarationError (config-mistake detection).
|
|
@@ -30,12 +33,24 @@ module K8sRails
|
|
|
30
33
|
# kind are explicit — never guessed). `readonly` must be an explicit
|
|
31
34
|
# boolean: mutations are enabled ONLY by `readonly: false` (design §5.3 /
|
|
32
35
|
# K4), so nil/other values fail fast instead of silently allowing writes.
|
|
33
|
-
|
|
36
|
+
# `scope` is :namespaced (default) or :cluster; `namespace` and
|
|
37
|
+
# `scope: :cluster` are mutually exclusive (a cluster CRD has no
|
|
38
|
+
# namespace to bind).
|
|
39
|
+
def self.declare(group:, version:, plural:, kind:, namespace: nil, readonly: true, scope: :namespaced)
|
|
34
40
|
unless [true, false].include?(readonly)
|
|
35
41
|
raise ArgumentError,
|
|
36
42
|
"readonly must be true or false (got #{readonly.inspect}) — mutations require an explicit readonly: false"
|
|
37
43
|
end
|
|
38
44
|
|
|
45
|
+
unless %i[namespaced cluster].include?(scope)
|
|
46
|
+
raise ArgumentError, "scope must be :namespaced or :cluster (got #{scope.inspect})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if scope == :cluster && !namespace.nil?
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"namespace cannot be combined with scope: :cluster — cluster-scoped CRDs have no namespace"
|
|
52
|
+
end
|
|
53
|
+
|
|
39
54
|
if registered.key?(kind)
|
|
40
55
|
raise RedeclarationError, "CRD kind #{kind} is already declared — re-declaration is a configuration mistake"
|
|
41
56
|
end
|
|
@@ -46,7 +61,8 @@ module K8sRails
|
|
|
46
61
|
plural: plural,
|
|
47
62
|
kind: kind,
|
|
48
63
|
namespace: namespace,
|
|
49
|
-
readonly: readonly
|
|
64
|
+
readonly: readonly,
|
|
65
|
+
scope: scope
|
|
50
66
|
)
|
|
51
67
|
registered[kind] = resource
|
|
52
68
|
resource
|
data/lib/k8s_rails/resource.rb
CHANGED
|
@@ -10,13 +10,13 @@ module K8sRails
|
|
|
10
10
|
# touches kruby itself (§7).
|
|
11
11
|
#
|
|
12
12
|
# Workflow = K8sRails.crd(group: "argoproj.io", version: "v1alpha1",
|
|
13
|
-
#
|
|
14
|
-
# Workflow.list
|
|
15
|
-
# Workflow.find("wf-1")
|
|
13
|
+
# plural: "workflows", kind: "Workflow")
|
|
16
14
|
class Resource
|
|
17
15
|
# Bind declared coordinates to a fresh anonymous subclass. `namespace`
|
|
18
16
|
# may be nil — resolved from `K8sRails.config.namespace` at call time.
|
|
19
|
-
|
|
17
|
+
# `scope` is :namespaced (default) or :cluster (design §5.3); cluster
|
|
18
|
+
# declarations must not set `namespace`.
|
|
19
|
+
def self.declare(group:, version:, plural:, kind:, namespace: nil, readonly: true, scope: :namespaced)
|
|
20
20
|
Class.new(self) do
|
|
21
21
|
define_singleton_method(:group_name) { group }
|
|
22
22
|
define_singleton_method(:version_name) { version }
|
|
@@ -24,13 +24,16 @@ module K8sRails
|
|
|
24
24
|
define_singleton_method(:kind_name) { kind }
|
|
25
25
|
define_singleton_method(:declared_namespace) { namespace }
|
|
26
26
|
define_singleton_method(:readonly?) { readonly }
|
|
27
|
+
define_singleton_method(:cluster_scoped?) { scope == :cluster }
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
class << self
|
|
31
|
-
# All objects in the namespace
|
|
32
|
-
#
|
|
32
|
+
# All objects in the namespace (namespaced declarations only —
|
|
33
|
+
# cluster-scoped declarations raise ArgumentError). Returns an array
|
|
34
|
+
# of string-keyed Hashes (design §5.3: `[{"name" => "...", ...}]`).
|
|
33
35
|
def list(namespace: resolved_namespace)
|
|
36
|
+
assert_namespaced
|
|
34
37
|
K8sRails.instrument(:list, instrument_meta(namespace)) do
|
|
35
38
|
transport.list(group_name, version_name, namespace, plural_name)["items"] || []
|
|
36
39
|
end
|
|
@@ -38,6 +41,7 @@ module K8sRails
|
|
|
38
41
|
|
|
39
42
|
# One object by name. Raises K8sRails::NotFound when absent.
|
|
40
43
|
def find(name, namespace: resolved_namespace)
|
|
44
|
+
assert_namespaced
|
|
41
45
|
K8sRails.instrument(:find, instrument_meta(namespace)) do
|
|
42
46
|
transport.get(group_name, version_name, namespace, plural_name, name)
|
|
43
47
|
end
|
|
@@ -53,6 +57,7 @@ module K8sRails
|
|
|
53
57
|
# Create from a CRD body hash. `readonly: true` declarations raise
|
|
54
58
|
# K8sRails::ReadOnlyError (K4).
|
|
55
59
|
def create(attributes, namespace: resolved_namespace)
|
|
60
|
+
assert_namespaced
|
|
56
61
|
assert_writable
|
|
57
62
|
K8sRails.instrument(:create, instrument_meta(namespace)) do
|
|
58
63
|
transport.create(group_name, version_name, namespace, plural_name, attributes)
|
|
@@ -61,12 +66,57 @@ module K8sRails
|
|
|
61
66
|
|
|
62
67
|
# JSON Patch a named object. Same readonly restriction as `create`.
|
|
63
68
|
def patch(name, operations, namespace: resolved_namespace)
|
|
69
|
+
assert_namespaced
|
|
64
70
|
assert_writable
|
|
65
71
|
K8sRails.instrument(:patch, instrument_meta(namespace)) do
|
|
66
72
|
transport.patch(group_name, version_name, namespace, plural_name, name, operations)
|
|
67
73
|
end
|
|
68
74
|
end
|
|
69
75
|
|
|
76
|
+
# Cluster-scoped variants (design §5.3). Available on EVERY declared
|
|
77
|
+
# class; a `scope: :namespaced` declaration raises ArgumentError (its
|
|
78
|
+
# CRD is namespaced, so cluster endpoints 404 anyway).
|
|
79
|
+
# All objects cluster-wide. Returns an array of string-keyed Hashes.
|
|
80
|
+
def list_cluster
|
|
81
|
+
assert_cluster_scoped
|
|
82
|
+
K8sRails.instrument(:list, instrument_meta(nil)) do
|
|
83
|
+
transport.list_cluster(group_name, version_name, plural_name)["items"] || []
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# One cluster-scoped object by name. Raises K8sRails::NotFound when absent.
|
|
88
|
+
def find_cluster(name)
|
|
89
|
+
assert_cluster_scoped
|
|
90
|
+
K8sRails.instrument(:find, instrument_meta(nil)) do
|
|
91
|
+
transport.get_cluster(group_name, version_name, plural_name, name)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Like `find_cluster`, but returns nil instead of raising on NotFound.
|
|
96
|
+
def find_or_nil_cluster(name)
|
|
97
|
+
find_cluster(name)
|
|
98
|
+
rescue NotFound
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Create a cluster-scoped object. Same readonly restriction as `create`.
|
|
103
|
+
def create_cluster(attributes)
|
|
104
|
+
assert_cluster_scoped
|
|
105
|
+
assert_writable
|
|
106
|
+
K8sRails.instrument(:create, instrument_meta(nil)) do
|
|
107
|
+
transport.create_cluster(group_name, version_name, plural_name, attributes)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# JSON Patch a cluster-scoped object. Same readonly restriction.
|
|
112
|
+
def patch_cluster(name, operations)
|
|
113
|
+
assert_cluster_scoped
|
|
114
|
+
assert_writable
|
|
115
|
+
K8sRails.instrument(:patch, instrument_meta(nil)) do
|
|
116
|
+
transport.patch_cluster(group_name, version_name, plural_name, name, operations)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
70
120
|
private
|
|
71
121
|
|
|
72
122
|
# Notification metadata for design §8 (`k8s-rails.request`).
|
|
@@ -80,15 +130,30 @@ module K8sRails
|
|
|
80
130
|
declared_namespace || K8sRails.config.namespace
|
|
81
131
|
end
|
|
82
132
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
# Endless method: keeps the class under the ClassLength budget while
|
|
134
|
+
# delegating to the shared transport (design §5.2).
|
|
135
|
+
def transport = K8sRails.client
|
|
86
136
|
|
|
87
137
|
def assert_writable
|
|
88
138
|
return unless readonly?
|
|
89
139
|
|
|
90
140
|
raise ReadOnlyError, "#{kind_name} is declared readonly — create/patch are disabled (K4)"
|
|
91
141
|
end
|
|
142
|
+
|
|
143
|
+
def assert_cluster_scoped
|
|
144
|
+
return if cluster_scoped?
|
|
145
|
+
|
|
146
|
+
raise ArgumentError,
|
|
147
|
+
"#{kind_name} is namespaced — *_cluster methods require a scope: :cluster declaration"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def assert_namespaced
|
|
151
|
+
return unless cluster_scoped?
|
|
152
|
+
|
|
153
|
+
raise ArgumentError,
|
|
154
|
+
"#{kind_name} is declared scope: :cluster — use the *_cluster methods " \
|
|
155
|
+
"(there is no namespace endpoint for a cluster-scoped CRD)"
|
|
156
|
+
end
|
|
92
157
|
end
|
|
93
158
|
end
|
|
94
159
|
end
|
data/lib/k8s_rails/version.rb
CHANGED