@forgeportal/plugin-kubernetes 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/dist/KubernetesTab.d.ts +8 -0
- package/dist/KubernetesTab.d.ts.map +1 -0
- package/dist/KubernetesTab.js +91 -0
- package/dist/KubernetesTab.js.map +1 -0
- package/dist/LogsDrawer.d.ts +11 -0
- package/dist/LogsDrawer.d.ts.map +1 -0
- package/dist/LogsDrawer.js +21 -0
- package/dist/LogsDrawer.js.map +1 -0
- package/dist/PodStatusBadge.d.ts +7 -0
- package/dist/PodStatusBadge.d.ts.map +1 -0
- package/dist/PodStatusBadge.js +21 -0
- package/dist/PodStatusBadge.js.map +1 -0
- package/dist/__tests__/api-client.test.d.ts +2 -0
- package/dist/__tests__/api-client.test.d.ts.map +1 -0
- package/dist/__tests__/api-client.test.js +260 -0
- package/dist/__tests__/api-client.test.js.map +1 -0
- package/dist/actions.d.ts +27 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +124 -0
- package/dist/actions.js.map +1 -0
- package/dist/api-client.d.ts +27 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +193 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/routes.d.ts +14 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +115 -0
- package/dist/routes.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +11 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +18 -0
- package/dist/ui.js.map +1 -0
- package/forgeportal-plugin.json +32 -0
- package/package.json +51 -0
- package/src/KubernetesTab.tsx +391 -0
- package/src/LogsDrawer.tsx +83 -0
- package/src/PodStatusBadge.tsx +36 -0
- package/src/__tests__/api-client.test.ts +324 -0
- package/src/actions.ts +146 -0
- package/src/api-client.ts +248 -0
- package/src/index.ts +46 -0
- package/src/routes.ts +154 -0
- package/src/types.ts +103 -0
- package/src/ui.ts +19 -0
- package/tsconfig.json +11 -0
package/dist/actions.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { KubernetesApiClient, resolveCluster } from './api-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* kubernetes.restartDeployment@v1
|
|
4
|
+
*
|
|
5
|
+
* Triggers a rolling restart of a named Kubernetes Deployment by patching
|
|
6
|
+
* the pod template annotation `kubectl.kubernetes.io/restartedAt`.
|
|
7
|
+
*
|
|
8
|
+
* Input:
|
|
9
|
+
* - deploymentName: string (required)
|
|
10
|
+
* - namespace: string (optional, defaults to "default")
|
|
11
|
+
* - cluster: string (optional, defaults to first configured cluster)
|
|
12
|
+
*/
|
|
13
|
+
export function createRestartDeploymentAction(clusters, defaultNamespace) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'kubernetes.restartDeployment',
|
|
16
|
+
version: 'v1',
|
|
17
|
+
schema: {
|
|
18
|
+
input: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
required: ['deploymentName'],
|
|
21
|
+
properties: {
|
|
22
|
+
deploymentName: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
title: 'Deployment Name',
|
|
25
|
+
description: 'Name of the Kubernetes Deployment to restart.',
|
|
26
|
+
},
|
|
27
|
+
namespace: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
title: 'Namespace',
|
|
30
|
+
description: 'Kubernetes namespace (default: plugin defaultNamespace or "default").',
|
|
31
|
+
},
|
|
32
|
+
cluster: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
title: 'Cluster',
|
|
35
|
+
description: 'Cluster name from plugin config (default: first configured cluster).',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
output: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
restartedAt: { type: 'string', description: 'ISO timestamp of the restart patch.' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
async handler(ctx, input) {
|
|
47
|
+
const deploymentName = input['deploymentName'];
|
|
48
|
+
const namespace = input['namespace'] ?? defaultNamespace;
|
|
49
|
+
const clusterName = input['cluster'];
|
|
50
|
+
ctx.logger.info(`Restarting deployment "${deploymentName}" in namespace "${namespace}"`);
|
|
51
|
+
const clusterCfg = resolveCluster(clusters, clusterName);
|
|
52
|
+
const client = new KubernetesApiClient(clusterCfg);
|
|
53
|
+
await client.restartDeployment(namespace, deploymentName);
|
|
54
|
+
const restartedAt = new Date().toISOString();
|
|
55
|
+
ctx.logger.info(`Deployment "${deploymentName}" restart patch applied at ${restartedAt}`);
|
|
56
|
+
return {
|
|
57
|
+
status: 'success',
|
|
58
|
+
outputs: { restartedAt },
|
|
59
|
+
links: [
|
|
60
|
+
{
|
|
61
|
+
title: `View in cluster ${clusterCfg.name}`,
|
|
62
|
+
url: `${clusterCfg.url}/apis/apps/v1/namespaces/${namespace}/deployments/${deploymentName}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* kubernetes.scaleDeployment@v1
|
|
71
|
+
*
|
|
72
|
+
* Scales a Kubernetes Deployment to the desired replica count.
|
|
73
|
+
*
|
|
74
|
+
* Input:
|
|
75
|
+
* - deploymentName: string (required)
|
|
76
|
+
* - replicas: number (required)
|
|
77
|
+
* - namespace: string (optional)
|
|
78
|
+
* - cluster: string (optional)
|
|
79
|
+
*/
|
|
80
|
+
export function createScaleDeploymentAction(clusters, defaultNamespace) {
|
|
81
|
+
return {
|
|
82
|
+
id: 'kubernetes.scaleDeployment',
|
|
83
|
+
version: 'v1',
|
|
84
|
+
schema: {
|
|
85
|
+
input: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
required: ['deploymentName', 'replicas'],
|
|
88
|
+
properties: {
|
|
89
|
+
deploymentName: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
title: 'Deployment Name',
|
|
92
|
+
description: 'Name of the Kubernetes Deployment to scale.',
|
|
93
|
+
},
|
|
94
|
+
replicas: {
|
|
95
|
+
type: 'number',
|
|
96
|
+
title: 'Replicas',
|
|
97
|
+
description: 'Desired number of replicas (0–100).',
|
|
98
|
+
},
|
|
99
|
+
namespace: { type: 'string', title: 'Namespace' },
|
|
100
|
+
cluster: { type: 'string', title: 'Cluster' },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
output: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
replicas: { type: 'number', description: 'Replica count applied.' },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
async handler(ctx, input) {
|
|
111
|
+
const deploymentName = input['deploymentName'];
|
|
112
|
+
const replicas = input['replicas'];
|
|
113
|
+
const namespace = input['namespace'] ?? defaultNamespace;
|
|
114
|
+
const clusterName = input['cluster'];
|
|
115
|
+
ctx.logger.info(`Scaling deployment "${deploymentName}" to ${replicas} replica(s) in namespace "${namespace}"`);
|
|
116
|
+
const clusterCfg = resolveCluster(clusters, clusterName);
|
|
117
|
+
const client = new KubernetesApiClient(clusterCfg);
|
|
118
|
+
await client.scaleDeployment(namespace, deploymentName, replicas);
|
|
119
|
+
ctx.logger.info(`Deployment "${deploymentName}" scaled to ${replicas}.`);
|
|
120
|
+
return { status: 'success', outputs: { replicas } };
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ClusterConfig, WorkloadsResponse } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse the `clusters` JSON string from plugin config and resolve per-cluster
|
|
4
|
+
* tokens from environment variables.
|
|
5
|
+
*
|
|
6
|
+
* Token env var convention: FORGEPORTAL_PLUGIN_KUBERNETES_<CLUSTER_NAME_UPPER>_TOKEN
|
|
7
|
+
* e.g. cluster "production" → FORGEPORTAL_PLUGIN_KUBERNETES_PRODUCTION_TOKEN
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseClusters(rawClustersJson: string, getToken: (envKey: string) => string | undefined): ClusterConfig[];
|
|
10
|
+
export declare class KubernetesApiClient {
|
|
11
|
+
private readonly cluster;
|
|
12
|
+
private readonly dispatcher;
|
|
13
|
+
constructor(cluster: ClusterConfig);
|
|
14
|
+
private request;
|
|
15
|
+
/**
|
|
16
|
+
* Aggregate Deployments, Pods, Services, and Ingresses for the given label selector.
|
|
17
|
+
*/
|
|
18
|
+
getWorkloads(namespace: string, labelSelector: string): Promise<WorkloadsResponse>;
|
|
19
|
+
/** Returns the last N log lines for a pod container as a plain string. */
|
|
20
|
+
getPodLogs(namespace: string, podName: string, tailLines?: number): Promise<string>;
|
|
21
|
+
/** Trigger a rolling restart via a strategic merge patch on the pod template annotation. */
|
|
22
|
+
restartDeployment(namespace: string, deploymentName: string): Promise<void>;
|
|
23
|
+
/** Scale a deployment to the desired replica count. */
|
|
24
|
+
scaleDeployment(namespace: string, deploymentName: string, replicas: number): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveCluster(clusters: ClusterConfig[], clusterName?: string): ClusterConfig;
|
|
27
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EASlB,MAAM,YAAY,CAAC;AAIpB;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC/C,aAAa,EAAE,CAmBjB;AA2ED,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAEvB,OAAO,EAAE,aAAa;YAMrC,OAAO;IAoBrB;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6BxF,0EAA0E;IACpE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYtF,4FAA4F;IACtF,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBjF,uDAAuD;IACjD,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlG;AAID,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,EACzB,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CAaf"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Agent } from 'undici';
|
|
2
|
+
// ─── Cluster config parser ────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Parse the `clusters` JSON string from plugin config and resolve per-cluster
|
|
5
|
+
* tokens from environment variables.
|
|
6
|
+
*
|
|
7
|
+
* Token env var convention: FORGEPORTAL_PLUGIN_KUBERNETES_<CLUSTER_NAME_UPPER>_TOKEN
|
|
8
|
+
* e.g. cluster "production" → FORGEPORTAL_PLUGIN_KUBERNETES_PRODUCTION_TOKEN
|
|
9
|
+
*/
|
|
10
|
+
export function parseClusters(rawClustersJson, getToken) {
|
|
11
|
+
let raw;
|
|
12
|
+
try {
|
|
13
|
+
raw = JSON.parse(rawClustersJson);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error('kubernetes plugin: config.clusters must be a valid JSON string representing an array of {name, url} objects');
|
|
17
|
+
}
|
|
18
|
+
return raw.map((c) => {
|
|
19
|
+
const envKey = c.name.toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
20
|
+
return {
|
|
21
|
+
name: c.name,
|
|
22
|
+
url: c.url.replace(/\/$/, ''),
|
|
23
|
+
token: getToken(envKey) ?? '',
|
|
24
|
+
skipTLSVerify: c.skipTLSVerify ?? false,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// ─── Mappers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
function mapDeployment(d) {
|
|
30
|
+
const desired = d.spec.replicas ?? 1;
|
|
31
|
+
const ready = d.status?.readyReplicas ?? 0;
|
|
32
|
+
const available = d.status?.availableReplicas ?? 0;
|
|
33
|
+
const image = d.spec.template.spec.containers[0]?.image ?? '';
|
|
34
|
+
const rolloutCondition = d.status?.conditions?.find((c) => c.type === 'Progressing');
|
|
35
|
+
const lastRollout = rolloutCondition?.lastUpdateTime ?? d.metadata.creationTimestamp ?? null;
|
|
36
|
+
return {
|
|
37
|
+
name: d.metadata.name,
|
|
38
|
+
namespace: d.metadata.namespace,
|
|
39
|
+
replicas: { desired, ready, available },
|
|
40
|
+
image,
|
|
41
|
+
lastRollout,
|
|
42
|
+
healthy: ready >= desired && desired > 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function resolvePodStatus(pod) {
|
|
46
|
+
// Check for CrashLoopBackOff in any container's waiting state
|
|
47
|
+
for (const cs of pod.status?.containerStatuses ?? []) {
|
|
48
|
+
if (cs.state?.waiting?.reason === 'CrashLoopBackOff')
|
|
49
|
+
return 'CrashLoopBackOff';
|
|
50
|
+
}
|
|
51
|
+
return pod.status?.phase ?? 'Unknown';
|
|
52
|
+
}
|
|
53
|
+
function mapPod(p) {
|
|
54
|
+
const containerStatuses = p.status?.containerStatuses ?? [];
|
|
55
|
+
const ready = containerStatuses.length > 0 && containerStatuses.every((cs) => cs.ready);
|
|
56
|
+
return {
|
|
57
|
+
name: p.metadata.name,
|
|
58
|
+
namespace: p.metadata.namespace,
|
|
59
|
+
status: resolvePodStatus(p),
|
|
60
|
+
ready,
|
|
61
|
+
containers: p.spec?.containers?.length ?? containerStatuses.length,
|
|
62
|
+
nodeName: p.spec?.nodeName ?? null,
|
|
63
|
+
startTime: p.status?.startTime ?? null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function mapService(s) {
|
|
67
|
+
return {
|
|
68
|
+
name: s.metadata.name,
|
|
69
|
+
namespace: s.metadata.namespace,
|
|
70
|
+
type: s.spec?.type ?? 'ClusterIP',
|
|
71
|
+
clusterIp: s.spec?.clusterIP ?? '',
|
|
72
|
+
ports: (s.spec?.ports ?? []).map((p) => ({
|
|
73
|
+
port: p.port,
|
|
74
|
+
protocol: p.protocol ?? 'TCP',
|
|
75
|
+
targetPort: p.targetPort ?? p.port,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function mapIngress(i) {
|
|
80
|
+
const hosts = (i.spec?.rules ?? [])
|
|
81
|
+
.map((r) => r.host ?? '')
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
return {
|
|
84
|
+
name: i.metadata.name,
|
|
85
|
+
namespace: i.metadata.namespace,
|
|
86
|
+
hosts,
|
|
87
|
+
tls: (i.spec?.tls?.length ?? 0) > 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ─── Client ───────────────────────────────────────────────────────────────────
|
|
91
|
+
export class KubernetesApiClient {
|
|
92
|
+
cluster;
|
|
93
|
+
dispatcher;
|
|
94
|
+
constructor(cluster) {
|
|
95
|
+
this.cluster = cluster;
|
|
96
|
+
if (cluster.skipTLSVerify) {
|
|
97
|
+
this.dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async request(path, init) {
|
|
101
|
+
const url = `${this.cluster.url}${path}`;
|
|
102
|
+
const options = {
|
|
103
|
+
...(init ?? {}),
|
|
104
|
+
headers: {
|
|
105
|
+
Authorization: `Bearer ${this.cluster.token}`,
|
|
106
|
+
Accept: 'application/json',
|
|
107
|
+
...(init?.headers ?? {}),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
if (this.dispatcher)
|
|
111
|
+
options['dispatcher'] = this.dispatcher;
|
|
112
|
+
const res = await fetch(url, options);
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const body = await res.text().catch(() => res.statusText);
|
|
115
|
+
throw new Error(`Kubernetes API [${this.cluster.name}] ${path} → ${res.status}: ${body}`);
|
|
116
|
+
}
|
|
117
|
+
return res.json();
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Aggregate Deployments, Pods, Services, and Ingresses for the given label selector.
|
|
121
|
+
*/
|
|
122
|
+
async getWorkloads(namespace, labelSelector) {
|
|
123
|
+
const qs = `labelSelector=${encodeURIComponent(labelSelector)}&limit=100`;
|
|
124
|
+
const [deployments, pods, services, ingresses] = await Promise.all([
|
|
125
|
+
this.request(`/apis/apps/v1/namespaces/${namespace}/deployments?${qs}`),
|
|
126
|
+
this.request(`/api/v1/namespaces/${namespace}/pods?${qs}`),
|
|
127
|
+
this.request(`/api/v1/namespaces/${namespace}/services?${qs}`),
|
|
128
|
+
this.request(`/apis/networking.k8s.io/v1/namespaces/${namespace}/ingresses?${qs}`).catch(() => ({ items: [] })),
|
|
129
|
+
]);
|
|
130
|
+
return {
|
|
131
|
+
cluster: this.cluster.name,
|
|
132
|
+
namespace,
|
|
133
|
+
labelSelector,
|
|
134
|
+
deployments: deployments.items.map(mapDeployment),
|
|
135
|
+
pods: pods.items.map(mapPod),
|
|
136
|
+
services: services.items.map(mapService),
|
|
137
|
+
ingresses: ingresses.items.map(mapIngress),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Returns the last N log lines for a pod container as a plain string. */
|
|
141
|
+
async getPodLogs(namespace, podName, tailLines = 100) {
|
|
142
|
+
const path = `/api/v1/namespaces/${namespace}/pods/${podName}/log?tailLines=${tailLines}×tamps=true`;
|
|
143
|
+
const options = {
|
|
144
|
+
headers: { Authorization: `Bearer ${this.cluster.token}` },
|
|
145
|
+
};
|
|
146
|
+
if (this.dispatcher)
|
|
147
|
+
options['dispatcher'] = this.dispatcher;
|
|
148
|
+
const res = await fetch(`${this.cluster.url}${path}`, options);
|
|
149
|
+
if (!res.ok)
|
|
150
|
+
throw new Error(`Pod logs [${podName}]: ${res.status}`);
|
|
151
|
+
return res.text();
|
|
152
|
+
}
|
|
153
|
+
/** Trigger a rolling restart via a strategic merge patch on the pod template annotation. */
|
|
154
|
+
async restartDeployment(namespace, deploymentName) {
|
|
155
|
+
const patch = {
|
|
156
|
+
spec: {
|
|
157
|
+
template: {
|
|
158
|
+
metadata: {
|
|
159
|
+
annotations: { 'kubectl.kubernetes.io/restartedAt': new Date().toISOString() },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
await this.request(`/apis/apps/v1/namespaces/${namespace}/deployments/${deploymentName}`, {
|
|
165
|
+
method: 'PATCH',
|
|
166
|
+
headers: { 'Content-Type': 'application/strategic-merge-patch+json' },
|
|
167
|
+
body: JSON.stringify(patch),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** Scale a deployment to the desired replica count. */
|
|
171
|
+
async scaleDeployment(namespace, deploymentName, replicas) {
|
|
172
|
+
await this.request(`/apis/apps/v1/namespaces/${namespace}/deployments/${deploymentName}/scale`, {
|
|
173
|
+
method: 'PUT',
|
|
174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
175
|
+
body: JSON.stringify({ spec: { replicas } }),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ─── Cluster resolver ─────────────────────────────────────────────────────────
|
|
180
|
+
export function resolveCluster(clusters, clusterName) {
|
|
181
|
+
if (!clusterName) {
|
|
182
|
+
const first = clusters[0];
|
|
183
|
+
if (!first)
|
|
184
|
+
throw new Error('kubernetes plugin: no clusters configured');
|
|
185
|
+
return first;
|
|
186
|
+
}
|
|
187
|
+
const found = clusters.find((c) => c.name === clusterName);
|
|
188
|
+
if (!found) {
|
|
189
|
+
throw new Error(`kubernetes plugin: cluster "${clusterName}" not found. Available: ${clusters.map((c) => c.name).join(', ')}`);
|
|
190
|
+
}
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ForgeBackendPluginSDK } from '@forgeportal/plugin-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Backend entry point for the Kubernetes plugin.
|
|
4
|
+
* Called by the ForgePortal plugin loader at startup.
|
|
5
|
+
*
|
|
6
|
+
* Configuration (forgeportal.yaml → plugins.kubernetes.config):
|
|
7
|
+
* clusters: JSON string — array of {name, url, skipTLSVerify?}
|
|
8
|
+
* defaultNamespace: string (default: "default")
|
|
9
|
+
*
|
|
10
|
+
* Per-cluster tokens come from env:
|
|
11
|
+
* FORGEPORTAL_PLUGIN_KUBERNETES_<CLUSTER_NAME_UPPER>_TOKEN
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerBackendPlugin(sdk: ForgeBackendPluginSDK): void;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKrE;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,qBAAqB,GAAG,IAAI,CA6BtE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { parseClusters } from './api-client.js';
|
|
2
|
+
import { createRoutes } from './routes.js';
|
|
3
|
+
import { createRestartDeploymentAction, createScaleDeploymentAction } from './actions.js';
|
|
4
|
+
/**
|
|
5
|
+
* Backend entry point for the Kubernetes plugin.
|
|
6
|
+
* Called by the ForgePortal plugin loader at startup.
|
|
7
|
+
*
|
|
8
|
+
* Configuration (forgeportal.yaml → plugins.kubernetes.config):
|
|
9
|
+
* clusters: JSON string — array of {name, url, skipTLSVerify?}
|
|
10
|
+
* defaultNamespace: string (default: "default")
|
|
11
|
+
*
|
|
12
|
+
* Per-cluster tokens come from env:
|
|
13
|
+
* FORGEPORTAL_PLUGIN_KUBERNETES_<CLUSTER_NAME_UPPER>_TOKEN
|
|
14
|
+
*/
|
|
15
|
+
export function registerBackendPlugin(sdk) {
|
|
16
|
+
const rawClusters = sdk.config.get('clusters') ?? '[]';
|
|
17
|
+
const defaultNs = sdk.config.get('defaultNamespace') ?? 'default';
|
|
18
|
+
const clusters = parseClusters(rawClusters, (envKey) => process.env[`FORGEPORTAL_PLUGIN_KUBERNETES_${envKey}_TOKEN`]);
|
|
19
|
+
if (clusters.length === 0) {
|
|
20
|
+
sdk.logger.warn('kubernetes plugin: no clusters configured. ' +
|
|
21
|
+
'Set plugins.kubernetes.config.clusters in forgeportal.yaml.');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
sdk.logger.info(`kubernetes plugin: ${clusters.length} cluster(s) configured — ${clusters.map((c) => c.name).join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
// Mount backend routes under /api/v1/plugins/kubernetes/
|
|
27
|
+
sdk.registerBackendRoute({
|
|
28
|
+
path: '',
|
|
29
|
+
handler: createRoutes(clusters, defaultNs),
|
|
30
|
+
});
|
|
31
|
+
// Register template-usable action providers
|
|
32
|
+
sdk.registerActionProvider(createRestartDeploymentAction(clusters, defaultNs));
|
|
33
|
+
sdk.registerActionProvider(createScaleDeploymentAction(clusters, defaultNs));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,6BAA6B,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAA0B;IAC9D,MAAM,WAAW,GAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAS,UAAU,CAAC,IAAI,IAAI,CAAC;IACnE,MAAM,SAAS,GAAS,GAAG,CAAC,MAAM,CAAC,GAAG,CAAS,kBAAkB,CAAC,IAAI,SAAS,CAAC;IAEhF,MAAM,QAAQ,GAAG,aAAa,CAC5B,WAAW,EACX,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,iCAAiC,MAAM,QAAQ,CAAC,CACzE,CAAC;IAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,6CAA6C;YAC7C,6DAA6D,CAC9D,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,sBAAsB,QAAQ,CAAC,MAAM,4BAA4B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC1G,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,GAAG,CAAC,oBAAoB,CAAC;QACvB,IAAI,EAAK,EAAE;QACX,OAAO,EAAE,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC3C,CAAC,CAAC;IAEH,4CAA4C;IAC5C,GAAG,CAAC,sBAAsB,CAAC,6BAA6B,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/E,GAAG,CAAC,sBAAsB,CAAC,2BAA2B,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAC/E,CAAC"}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { ClusterConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates Fastify route handlers for the Kubernetes plugin.
|
|
5
|
+
* All routes are mounted under /api/v1/plugins/kubernetes/ by the plugin loader.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET clusters
|
|
9
|
+
* GET entities/:entityId/workloads
|
|
10
|
+
* GET entities/:entityId/pods/:podName/logs
|
|
11
|
+
* POST entities/:entityId/deployments/:deploymentName/restart
|
|
12
|
+
*/
|
|
13
|
+
export declare function createRoutes(clusters: ClusterConfig[], defaultNamespace: string): (fastify: FastifyInstance) => Promise<void>;
|
|
14
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAYhD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAU,aAAa,EAAE,EACjC,gBAAgB,EAAE,MAAM,IAEM,SAAS,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC,CA8HvE"}
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { KubernetesApiClient, resolveCluster } from './api-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates Fastify route handlers for the Kubernetes plugin.
|
|
4
|
+
* All routes are mounted under /api/v1/plugins/kubernetes/ by the plugin loader.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET clusters
|
|
8
|
+
* GET entities/:entityId/workloads
|
|
9
|
+
* GET entities/:entityId/pods/:podName/logs
|
|
10
|
+
* POST entities/:entityId/deployments/:deploymentName/restart
|
|
11
|
+
*/
|
|
12
|
+
export function createRoutes(clusters, defaultNamespace) {
|
|
13
|
+
return async function handler(fastify) {
|
|
14
|
+
/**
|
|
15
|
+
* GET /clusters
|
|
16
|
+
*
|
|
17
|
+
* Returns the list of configured cluster names and URLs (no tokens).
|
|
18
|
+
*/
|
|
19
|
+
fastify.get('clusters', async (_request, reply) => {
|
|
20
|
+
return reply.send({
|
|
21
|
+
data: clusters.map((c) => ({
|
|
22
|
+
name: c.name,
|
|
23
|
+
url: c.url,
|
|
24
|
+
skipTLSVerify: c.skipTLSVerify,
|
|
25
|
+
})),
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* GET /entities/:entityId/workloads
|
|
30
|
+
*
|
|
31
|
+
* Query params:
|
|
32
|
+
* labelSelector (required) — K8s label selector, e.g. "app=payment-api"
|
|
33
|
+
* namespace (optional) — K8s namespace, defaults to plugin defaultNamespace
|
|
34
|
+
* cluster (optional) — cluster name, defaults to first configured cluster
|
|
35
|
+
*/
|
|
36
|
+
fastify.get('entities/:entityId/workloads', async (request, reply) => {
|
|
37
|
+
const { labelSelector, namespace: ns, cluster: clusterName } = request.query;
|
|
38
|
+
if (!labelSelector) {
|
|
39
|
+
return reply.status(400).send({
|
|
40
|
+
error: 'Bad Request',
|
|
41
|
+
message: 'Query parameter "labelSelector" is required.',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const namespace = ns ?? defaultNamespace;
|
|
45
|
+
try {
|
|
46
|
+
const clusterCfg = resolveCluster(clusters, clusterName);
|
|
47
|
+
const client = new KubernetesApiClient(clusterCfg);
|
|
48
|
+
const workloads = await client.getWorkloads(namespace, labelSelector);
|
|
49
|
+
return reply.send({ data: workloads });
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
request.log.error({ err }, 'kubernetes plugin: getWorkloads failed');
|
|
54
|
+
if (message.includes('not found')) {
|
|
55
|
+
return reply.status(404).send({ error: 'Not Found', message });
|
|
56
|
+
}
|
|
57
|
+
return reply.status(502).send({ error: 'Bad Gateway', message });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
/**
|
|
61
|
+
* GET /entities/:entityId/pods/:podName/logs
|
|
62
|
+
*
|
|
63
|
+
* Query params:
|
|
64
|
+
* namespace (optional)
|
|
65
|
+
* cluster (optional)
|
|
66
|
+
* tail (optional, default 100) — number of log lines to return
|
|
67
|
+
*/
|
|
68
|
+
fastify.get('entities/:entityId/pods/:podName/logs', async (request, reply) => {
|
|
69
|
+
const { podName } = request.params;
|
|
70
|
+
const { namespace: ns, cluster: clusterName, tail } = request.query;
|
|
71
|
+
const namespace = ns ?? defaultNamespace;
|
|
72
|
+
const tailLines = tail ? parseInt(tail, 10) : 100;
|
|
73
|
+
try {
|
|
74
|
+
const clusterCfg = resolveCluster(clusters, clusterName);
|
|
75
|
+
const client = new KubernetesApiClient(clusterCfg);
|
|
76
|
+
const logs = await client.getPodLogs(namespace, podName, tailLines);
|
|
77
|
+
return reply.send({ data: { logs } });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
request.log.error({ err }, 'kubernetes plugin: getPodLogs failed');
|
|
82
|
+
return reply.status(502).send({ error: 'Bad Gateway', message });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* POST /entities/:entityId/deployments/:deploymentName/restart
|
|
87
|
+
*
|
|
88
|
+
* Body: { namespace?: string; cluster?: string }
|
|
89
|
+
*/
|
|
90
|
+
fastify.post('entities/:entityId/deployments/:deploymentName/restart', async (request, reply) => {
|
|
91
|
+
const { deploymentName } = request.params;
|
|
92
|
+
const { namespace: ns, cluster: clusterName } = request.body ?? {};
|
|
93
|
+
const namespace = ns ?? defaultNamespace;
|
|
94
|
+
try {
|
|
95
|
+
const clusterCfg = resolveCluster(clusters, clusterName);
|
|
96
|
+
const client = new KubernetesApiClient(clusterCfg);
|
|
97
|
+
await client.restartDeployment(namespace, deploymentName);
|
|
98
|
+
return reply.status(202).send({
|
|
99
|
+
data: {
|
|
100
|
+
deploymentName,
|
|
101
|
+
namespace,
|
|
102
|
+
cluster: clusterCfg.name,
|
|
103
|
+
restartedAt: new Date().toISOString(),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
109
|
+
request.log.error({ err }, 'kubernetes plugin: restartDeployment failed');
|
|
110
|
+
return reply.status(502).send({ error: 'Bad Gateway', message });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|