@elinpf/dsh-ops-access-k8s 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +4 -0
- package/README.md +39 -0
- package/README.zh.md +39 -0
- package/cordis.patch.yml +1 -0
- package/lib/index.d.ts +46 -0
- package/lib/index.js +149 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.js +27 -0
- package/lib/types.d.ts +12 -0
- package/lib/types.js +8 -0
- package/package.json +61 -0
package/README.i18n.yaml
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @elinpf/dsh-ops-access-k8s
|
|
2
|
+
|
|
3
|
+
The Kubernetes credential-kind provider for the ops-access capability seam — validates `k8s` registry entries, expands kubeconfig paths, and probes real permissions at save time.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
One provider per credential kind is the ops-access split rule: this package carries everything Kubernetes-specific and nothing else. It registers an `AccessProvider` (`kind: 'k8s'`) into `ctx.opsAccess` via the core package's `registerAccessProvider` helper — an effect, so fiber disposal/HMR unregisters it.
|
|
8
|
+
|
|
9
|
+
- **Zod entry schema**: `{ kubeconfig }` — one path per profile.
|
|
10
|
+
- **Field processing**: `~` expansion; the resolved field is `kubeconfigPath` (paths only — secret material never passes through any service).
|
|
11
|
+
- **Save-time paste guard** (`validateContent`): structural YAML validation — clusters/contexts/users present, and `current-context` must name a defined context (the ops tools never pass `--context`, so a stale current-context breaks every call at runtime; catch it at save time).
|
|
12
|
+
- **Save-time capability probe** (`kubectl auth can-i`): read = `get pods`, write = `create deployments`. `ro` verifies when reading works and writing is denied; `rw` verifies when both work. Unreachable cluster or missing kubectl degrades to `unverifiable`, never a silent "no". Facet checks (`services/proxy`, `pods/exec`) annotate the result but never gate it — subresource can-i verdicts can be wrong.
|
|
13
|
+
- **Agent-facing docs**: `fieldsDoc` and `derivationDoc` feed `list_access` help — the latter is the ro self-derivation recipe (ServiceAccount `<id>-ro` + view ClusterRole + long-lived token, verified in both directions).
|
|
14
|
+
|
|
15
|
+
## Design notes
|
|
16
|
+
|
|
17
|
+
- The provider is a plain data object (`provider` export); `apply` only binds the configured probe timeouts and registers it. The pure verdict function `assessK8sTier` is exported and unit-tested directly, so the can-i matrix logic never needs a live cluster in tests.
|
|
18
|
+
- kubectl stderr is never surfaced — it echoes the kubeconfig path in its errors.
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
- id: ops-access-k8s
|
|
24
|
+
name: '@elinpf/dsh-ops-access-k8s'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Key | Default | Meaning |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `probeTimeoutMs` | `10000` | Timeout per `kubectl auth can-i` call (ms). Slow clusters may need more. |
|
|
30
|
+
| `probeNamespace` | `default` | Namespace the can-i probe checks permissions in. |
|
|
31
|
+
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm run build
|
|
36
|
+
npx vitest run
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Unit tests cover schema accept/reject, `~` expansion, registration/disposal through a mock `opsAccess` context (effect cleanup actually removes the provider), the paste guard, and the pure `assessK8sTier` verdict matrix. The one live-kubectl test asserts degradation to `unverifiable` against a nonexistent kubeconfig.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @elinpf/dsh-ops-access-k8s
|
|
2
|
+
|
|
3
|
+
ops-access 能力缝的 Kubernetes 凭证种类 provider — 校验 `k8s` 注册表条目、展开 kubeconfig 路径,并在保存时探测真实权限。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
按 ops-access 的拆分规则,一种凭证一个 provider:本包只装 Kubernetes 专属的东西。它通过 core 包的 `registerAccessProvider` 帮助函数向 `ctx.opsAccess` 注册一个 `AccessProvider`(`kind: 'k8s'`)——注册走 effect,fiber 销毁 / HMR 时自动卸载。
|
|
8
|
+
|
|
9
|
+
- **zod 条目 schema**:`{ kubeconfig }` — 一个 profile 一个路径。
|
|
10
|
+
- **字段处理**:`~` 展开;解析后输出 `kubeconfigPath`(只有路径 — 机密材料从不经过任何 service)。
|
|
11
|
+
- **保存时粘贴守卫**(`validateContent`):结构化 YAML 校验 — clusters/contexts/users 必须存在,且 `current-context` 必须指向已定义的 context(ops 工具从不传 `--context`,current-context 失效会让运行时每次调用都挂,必须在保存时拦住)。
|
|
12
|
+
- **保存时能力探测**(`kubectl auth can-i`):读 = `get pods`,写 = `create deployments`。`ro` 在能读且不能写时验证通过;`rw` 在两者都行时通过。集群不可达或没有 kubectl 时降级为 `unverifiable`,绝不静默当作 "no"。facet 检查(`services/proxy`、`pods/exec`)只做标注不做门槛 — 子资源的 can-i 判定可能不准。
|
|
13
|
+
- **面向 agent 的文档**:`fieldsDoc` 和 `derivationDoc` 喂给 `list_access` 的 help — 后者是 ro 自助派生配方(ServiceAccount `<id>-ro` + view ClusterRole + 长效 token,双向验证)。
|
|
14
|
+
|
|
15
|
+
## 设计要点
|
|
16
|
+
|
|
17
|
+
- provider 是纯数据对象(`provider` 导出);`apply` 只做一件事:绑定配置里的探测超时并注册。纯判定函数 `assessK8sTier` 单独导出、直接单测,因此 can-i 矩阵逻辑在测试里不需要真实集群。
|
|
18
|
+
- kubectl 的 stderr 从不外抛 — 它的报错里会带出 kubeconfig 路径。
|
|
19
|
+
|
|
20
|
+
## 配置项
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
- id: ops-access-k8s
|
|
24
|
+
name: '@elinpf/dsh-ops-access-k8s'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| 键 | 默认值 | 含义 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `probeTimeoutMs` | `10000` | 每次 `kubectl auth can-i` 调用的超时(毫秒)。慢集群可调大。 |
|
|
30
|
+
| `probeNamespace` | `default` | can-i 探测权限所用的 namespace。 |
|
|
31
|
+
|
|
32
|
+
## 测试方式
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm run build
|
|
36
|
+
npx vitest run
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
单测覆盖:schema 接受/拒绝、`~` 展开、经 mock `opsAccess` 上下文的注册/卸载(effect 清理会真正把 provider 移除)、粘贴守卫、以及纯函数 `assessK8sTier` 的判定矩阵。唯一的 live-kubectl 测试断言对不存在的 kubeconfig 会降级为 `unverifiable`。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ops access provider for Kubernetes.
|
|
3
|
+
*
|
|
4
|
+
* Validates `k8s` registry entries (`{ kubeconfig }`) and expands the
|
|
5
|
+
* kubeconfig path, exposing it as `kubeconfigPath`. Registers into the
|
|
6
|
+
* ops-access capability seam (ctx.opsAccess).
|
|
7
|
+
*
|
|
8
|
+
* @module @elinpf/dsh-ops-access-k8s
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import z from '@deepseek-ai/schemastery';
|
|
12
|
+
import { z as zod } from 'zod';
|
|
13
|
+
import type { AccessProvider } from '@elinpf/dsh-ops-access';
|
|
14
|
+
export declare const name = "ops-access-k8s";
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
export declare const Config: z<Schemastery.ObjectS<{
|
|
17
|
+
/** Save-time probe: timeout per `kubectl auth can-i` call (ms). Slow clusters may need more. */
|
|
18
|
+
probeTimeoutMs: z<number, number>;
|
|
19
|
+
/** Namespace the can-i probe checks permissions in. */
|
|
20
|
+
probeNamespace: z<string, string>;
|
|
21
|
+
}>, Schemastery.ObjectT<{
|
|
22
|
+
/** Save-time probe: timeout per `kubectl auth can-i` call (ms). Slow clusters may need more. */
|
|
23
|
+
probeTimeoutMs: z<number, number>;
|
|
24
|
+
/** Namespace the can-i probe checks permissions in. */
|
|
25
|
+
probeNamespace: z<string, string>;
|
|
26
|
+
}>>;
|
|
27
|
+
/** Zod schema for one k8s registry entry (excluding name and envelope fields). */
|
|
28
|
+
export declare const entrySchema: zod.ZodObject<{
|
|
29
|
+
kubeconfig: zod.ZodString;
|
|
30
|
+
}, zod.core.$strip>;
|
|
31
|
+
export declare const provider: AccessProvider;
|
|
32
|
+
/**
|
|
33
|
+
* Pure tier assessment from the can-i matrix (unit-tested directly):
|
|
34
|
+
* read = can-i get pods, write = can-i create deployments. ro verifies when
|
|
35
|
+
* reading works and writing is denied; rw verifies when both work.
|
|
36
|
+
*/
|
|
37
|
+
export type { K8sProbeFacets } from './types.js';
|
|
38
|
+
import type { K8sProbeFacets } from './types.js';
|
|
39
|
+
export declare function assessK8sTier(read: boolean, write: boolean, tier: 'ro' | 'rw', facets?: K8sProbeFacets): {
|
|
40
|
+
status: 'verified' | 'mismatch';
|
|
41
|
+
detail?: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function apply(ctx: Context, config: {
|
|
44
|
+
probeTimeoutMs: number;
|
|
45
|
+
probeNamespace: string;
|
|
46
|
+
}): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ops access provider for Kubernetes.
|
|
3
|
+
*
|
|
4
|
+
* Validates `k8s` registry entries (`{ kubeconfig }`) and expands the
|
|
5
|
+
* kubeconfig path, exposing it as `kubeconfigPath`. Registers into the
|
|
6
|
+
* ops-access capability seam (ctx.opsAccess).
|
|
7
|
+
*
|
|
8
|
+
* @module @elinpf/dsh-ops-access-k8s
|
|
9
|
+
*/
|
|
10
|
+
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
import { z as zod } from 'zod';
|
|
12
|
+
import { parse as parseYaml } from 'yaml';
|
|
13
|
+
import { execFile } from 'node:child_process';
|
|
14
|
+
import { expandHome, registerAccessProvider } from '@elinpf/dsh-ops-access';
|
|
15
|
+
// ── Plugin identity ───────────────────────────────────────────────────────────
|
|
16
|
+
export const name = 'ops-access-k8s';
|
|
17
|
+
export const inject = [];
|
|
18
|
+
export const Config = z.object({
|
|
19
|
+
/** Save-time probe: timeout per `kubectl auth can-i` call (ms). Slow clusters may need more. */
|
|
20
|
+
probeTimeoutMs: z.number().default(10000),
|
|
21
|
+
/** Namespace the can-i probe checks permissions in. */
|
|
22
|
+
probeNamespace: z.string().default('default'),
|
|
23
|
+
});
|
|
24
|
+
// ── Provider ─────────────────────────────────────────────────────────────────
|
|
25
|
+
/** Zod schema for one k8s registry entry (excluding name and envelope fields). */
|
|
26
|
+
export const entrySchema = zod.object({
|
|
27
|
+
kubeconfig: zod.string(),
|
|
28
|
+
});
|
|
29
|
+
export const provider = {
|
|
30
|
+
kind: 'k8s',
|
|
31
|
+
schema: entrySchema,
|
|
32
|
+
fieldsDoc: 'kubeconfig: path to the kubeconfig file (~ is expanded)',
|
|
33
|
+
fileFields: ['kubeconfig'],
|
|
34
|
+
derivationDoc: "from the rw kubeconfig: create a ServiceAccount named <id>-ro (naming convention), bind it to the built-in view ClusterRole (read-most, no Secret contents), mint a long-lived token via a Secret of type kubernetes.io/service-account-token for the SA (kubectl create token output expires); extract the cluster server and CA with kubectl config view --raw (without --raw the CA shows as DATA+OMITTED); build the ro kubeconfig reusing the rw cluster entry, naming both context and user <id>-ro and setting current-context to <id>-ro (kubectl cannot select a context without it); register it via register_access; then verify BOTH directions — kubectl get pods must succeed and a write attempt (e.g. kubectl create configmap ro-write-check --dry-run=server) must be forbidden. If every rw call fails with 'context was not found', the rw kubeconfig's current-context is broken — report it to the operator instead of working around it with --context",
|
|
35
|
+
process(entry) {
|
|
36
|
+
const { kubeconfig } = entry;
|
|
37
|
+
return { kubeconfigPath: expandHome(kubeconfig) };
|
|
38
|
+
},
|
|
39
|
+
// Save-time guard against corrupt pastes: a kubeconfig must be a YAML
|
|
40
|
+
// mapping with clusters, contexts, and users. Structural only — no
|
|
41
|
+
// connectivity checks.
|
|
42
|
+
// Ticket 10: verify the credential's real permissions at save time.
|
|
43
|
+
probe: probeK8s,
|
|
44
|
+
validateContent(field, content) {
|
|
45
|
+
if (field !== 'kubeconfig')
|
|
46
|
+
return null;
|
|
47
|
+
let doc;
|
|
48
|
+
try {
|
|
49
|
+
doc = parseYaml(content);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const first = String(err?.message ?? err).split('\n')[0];
|
|
53
|
+
return `not valid YAML: ${first}`;
|
|
54
|
+
}
|
|
55
|
+
if (typeof doc !== 'object' || doc === null || Array.isArray(doc)) {
|
|
56
|
+
return 'not a YAML mapping — paste the full kubeconfig file';
|
|
57
|
+
}
|
|
58
|
+
const d = doc;
|
|
59
|
+
if (!Array.isArray(d.clusters) || d.clusters.length === 0)
|
|
60
|
+
return 'no clusters defined — paste the full kubeconfig file';
|
|
61
|
+
if (!Array.isArray(d.contexts) || d.contexts.length === 0)
|
|
62
|
+
return 'no contexts defined — paste the full kubeconfig file';
|
|
63
|
+
if (!Array.isArray(d.users) || d.users.length === 0)
|
|
64
|
+
return 'no users (credentials) defined — paste the full kubeconfig file';
|
|
65
|
+
// current-context must name a defined context: the ops kubectl tool
|
|
66
|
+
// never passes --context, so a missing or stale current-context breaks
|
|
67
|
+
// EVERY call at runtime ("no context currently selected" / "context was
|
|
68
|
+
// not found"). Catch it at save time instead.
|
|
69
|
+
const contextNames = d.contexts
|
|
70
|
+
.map((c) => (typeof c === 'object' && c !== null ? c.name : undefined))
|
|
71
|
+
.filter((n) => typeof n === 'string');
|
|
72
|
+
const current = d['current-context'];
|
|
73
|
+
if (typeof current !== 'string' || current === '') {
|
|
74
|
+
return `no current-context — kubectl cannot select a context without one (the ops tools do not pass --context). Defined contexts: ${contextNames.join(', ') || '(none)'}`;
|
|
75
|
+
}
|
|
76
|
+
if (!contextNames.includes(current)) {
|
|
77
|
+
return `current-context "${current}" does not match any defined context (defined: ${contextNames.join(', ') || '(none)'}) — fix it before saving`;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
function fmtVerdict(v) {
|
|
83
|
+
return v === null ? 'unknown' : v ? 'yes' : 'no';
|
|
84
|
+
}
|
|
85
|
+
export function assessK8sTier(read, write, tier, facets) {
|
|
86
|
+
// Facets ANNOTATE, never gate: subresource can-i verdicts can be wrong
|
|
87
|
+
// (the ticket-14 pods/portforward quirk), so they ride along as facts.
|
|
88
|
+
const facetNote = facets === undefined ? undefined : 'facets: services/proxy=' + fmtVerdict(facets.servicesProxy) + ', pods/exec=' + fmtVerdict(facets.podsExec);
|
|
89
|
+
const join = (base) => [base, facetNote].filter(Boolean).join(' · ') || undefined;
|
|
90
|
+
if (tier === 'ro') {
|
|
91
|
+
if (read && !write)
|
|
92
|
+
return { status: 'verified', ...(facetNote === undefined ? {} : { detail: facetNote }) };
|
|
93
|
+
if (write)
|
|
94
|
+
return { status: 'mismatch', detail: join('claims ro but can-i create deployments = yes — an over-privileged credential sits in the ro slot') };
|
|
95
|
+
return { status: 'mismatch', detail: join('claims ro but can-i get pods = no — the credential cannot even read') };
|
|
96
|
+
}
|
|
97
|
+
if (read && write)
|
|
98
|
+
return { status: 'verified', ...(facetNote === undefined ? {} : { detail: facetNote }) };
|
|
99
|
+
return { status: 'mismatch', detail: join('claims rw but can-i says: get pods=' + (read ? 'yes' : 'no') + ', create deployments=' + (write ? 'yes' : 'no')) };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* One can-i verdict: true/false from kubectl's answer, null when the check
|
|
103
|
+
* itself could not run (unreachable cluster, missing binary). stderr is
|
|
104
|
+
* never surfaced — kubectl echoes the kubeconfig path in its errors.
|
|
105
|
+
*/
|
|
106
|
+
function canI(kubeconfig, verb, resource, timeoutMs, namespace) {
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
execFile('kubectl', ['--kubeconfig', kubeconfig, 'auth', 'can-i', verb, resource, '-n', namespace], { timeout: timeoutMs }, (err, stdout) => {
|
|
109
|
+
if (err && err.code === 'ENOENT') {
|
|
110
|
+
resolve(null);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const answer = (stdout ?? '').trim().toLowerCase();
|
|
114
|
+
if (answer.startsWith('yes')) {
|
|
115
|
+
resolve(true);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (answer.startsWith('no')) {
|
|
119
|
+
resolve(false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// kubectl exits 1 with the verdict on stdout for 'no', but a
|
|
123
|
+
// connection failure produces no verdict at all. Any answer that
|
|
124
|
+
// is not a clean yes/no verdict means the check itself failed —
|
|
125
|
+
// unverifiable, never a silent 'no' (review fix).
|
|
126
|
+
resolve(null);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function probeK8s(fields, tier, timeoutMs = 10000, namespace = 'default') {
|
|
131
|
+
const kubeconfig = String(fields.kubeconfigPath ?? '');
|
|
132
|
+
const [read, write, servicesProxy, podsExec] = await Promise.all([
|
|
133
|
+
canI(kubeconfig, 'get', 'pods', timeoutMs, namespace),
|
|
134
|
+
canI(kubeconfig, 'create', 'deployments', timeoutMs, namespace),
|
|
135
|
+
canI(kubeconfig, 'get', 'services/proxy', timeoutMs, namespace),
|
|
136
|
+
canI(kubeconfig, 'create', 'pods/exec', timeoutMs, namespace),
|
|
137
|
+
]);
|
|
138
|
+
if (read === null || write === null) {
|
|
139
|
+
return { status: 'unverifiable', detail: 'kubectl auth can-i could not run (cluster unreachable or kubectl missing)' };
|
|
140
|
+
}
|
|
141
|
+
return assessK8sTier(read, write, tier, { servicesProxy, podsExec });
|
|
142
|
+
}
|
|
143
|
+
// ── Plugin apply ─────────────────────────────────────────────────────────────
|
|
144
|
+
export function apply(ctx, config) {
|
|
145
|
+
registerAccessProvider(ctx, {
|
|
146
|
+
...provider,
|
|
147
|
+
probe: (fields, tier) => probeK8s(fields, tier, config.probeTimeoutMs, config.probeNamespace),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invariant companion for @elinpf/dsh-ops-access-k8s.
|
|
3
|
+
*
|
|
4
|
+
* @module @elinpf/dsh-ops-access-k8s/invariant
|
|
5
|
+
*/
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
declare const name = "ops-access-k8s-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Context carrying the invariant service.
|
|
13
|
+
* @returns a promise resolving after registration.
|
|
14
|
+
*/
|
|
15
|
+
declare const apply: (ctx: any) => Promise<void>;
|
|
16
|
+
export { apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invariant companion for @elinpf/dsh-ops-access-k8s.
|
|
3
|
+
*
|
|
4
|
+
* @module @elinpf/dsh-ops-access-k8s/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = '@elinpf/dsh-ops-access-k8s';
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = 'ops-access-k8s-invariant';
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ['invariants'];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this provider owns no session event types and no
|
|
13
|
+
* durable shape of its own. It contributes one credential-kind validator
|
|
14
|
+
* (schema + field processing + save-time probe) to the ops-access registry;
|
|
15
|
+
* the registry file on disk is owned — and re-validated on every resolve —
|
|
16
|
+
* by @elinpf/dsh-ops-access.
|
|
17
|
+
*/
|
|
18
|
+
const install = () => { };
|
|
19
|
+
/**
|
|
20
|
+
* Register this package's invariant companion.
|
|
21
|
+
* @param ctx - Context carrying the invariant service.
|
|
22
|
+
* @returns a promise resolving after registration.
|
|
23
|
+
*/
|
|
24
|
+
const apply = async (ctx) => {
|
|
25
|
+
ctx.invariants.register(PACKAGE_NAME, install);
|
|
26
|
+
};
|
|
27
|
+
export { apply, inject, name };
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the ops-access-k8s plugin.
|
|
3
|
+
*
|
|
4
|
+
* Types only — no runtime values live here.
|
|
5
|
+
*
|
|
6
|
+
* @module @elinpf/dsh-ops-access-k8s/types
|
|
7
|
+
*/
|
|
8
|
+
/** Live verdicts for the operationally interesting faces (ticket 10); null = the check could not run. */
|
|
9
|
+
export interface K8sProbeFacets {
|
|
10
|
+
servicesProxy: boolean | null;
|
|
11
|
+
podsExec: boolean | null;
|
|
12
|
+
}
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elinpf/dsh-ops-access-k8s",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ops access provider for Kubernetes — validates k8s registry entries and expands kubeconfig paths.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./invariant": {
|
|
14
|
+
"types": "./lib/invariant.d.ts",
|
|
15
|
+
"default": "./lib/invariant.js"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./lib/types.d.ts",
|
|
19
|
+
"default": "./lib/types.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib/index.js",
|
|
25
|
+
"lib/invariant.js",
|
|
26
|
+
"lib/types.js",
|
|
27
|
+
"lib/**/*.d.ts",
|
|
28
|
+
"cordis.patch.yml"
|
|
29
|
+
],
|
|
30
|
+
"dsh": {
|
|
31
|
+
"bundle": {
|
|
32
|
+
"patch": "./cordis.patch.yml"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
37
|
+
"yaml": "^2.7.0",
|
|
38
|
+
"zod": "^4.4.3"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
43
|
+
"@elinpf/dsh-ops-access": "^0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
47
|
+
"typescript": "^5.4.0",
|
|
48
|
+
"vitest": "^4.1.11",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@elinpf/dsh-ops-access": "0.1.0"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "vitest run"
|
|
60
|
+
}
|
|
61
|
+
}
|