@backstage/plugin-kubernetes-backend 0.21.11-next.1 → 0.21.11
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/CHANGELOG.md +41 -0
- package/config.schema.json +13 -0
- package/dist/actions/createGetKubernetesClustersAction.cjs.js +88 -0
- package/dist/actions/createGetKubernetesClustersAction.cjs.js.map +1 -0
- package/dist/actions/createGetKubernetesResourcesForEntityAction.cjs.js +137 -0
- package/dist/actions/createGetKubernetesResourcesForEntityAction.cjs.js.map +1 -0
- package/dist/actions/index.cjs.js +12 -0
- package/dist/actions/index.cjs.js.map +1 -0
- package/dist/cluster-locator/CatalogClusterLocator.cjs.js +63 -24
- package/dist/cluster-locator/CatalogClusterLocator.cjs.js.map +1 -1
- package/dist/cluster-locator/CatalogClusterLocatorOptions.cjs.js +15 -0
- package/dist/cluster-locator/CatalogClusterLocatorOptions.cjs.js.map +1 -0
- package/dist/cluster-locator/catalogClusterAuthMetadata.cjs.js +19 -0
- package/dist/cluster-locator/catalogClusterAuthMetadata.cjs.js.map +1 -0
- package/dist/cluster-locator/index.cjs.js +6 -1
- package/dist/cluster-locator/index.cjs.js.map +1 -1
- package/dist/cluster-locator/validateClusterApiServerUrl.cjs.js +89 -0
- package/dist/cluster-locator/validateClusterApiServerUrl.cjs.js.map +1 -0
- package/dist/package.json.cjs.js +1 -1
- package/dist/plugin.cjs.js +16 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/service/KubernetesConnection.cjs.js +8 -3
- package/dist/service/KubernetesConnection.cjs.js.map +1 -1
- package/dist/service/KubernetesProxy.cjs.js +1 -1
- package/dist/service/KubernetesProxy.cjs.js.map +1 -1
- package/dist/service/KubernetesRouter.cjs.js +0 -6
- package/dist/service/KubernetesRouter.cjs.js.map +1 -1
- package/package.json +21 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @backstage/plugin-kubernetes-backend
|
|
2
2
|
|
|
3
|
+
## 0.21.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 736d84e: Use locale-insensitive Unicode casing for consistent string handling across environments.
|
|
8
|
+
- 1d7b704: Reject unsupported service account authentication in catalog-provided cluster definitions.
|
|
9
|
+
- b11c9b4: Improved entity resolution on the deprecated services endpoint.
|
|
10
|
+
- 5268d63: Migrated tests from MSW v1 to MSW v2.
|
|
11
|
+
- ad8f2a8: Added actions to get kubernetes clusters and entity resources
|
|
12
|
+
- 28bdf75: Improved handling of Kubernetes resource response data.
|
|
13
|
+
- 97a7e8e: The catalog cluster locator now validates Kubernetes API server URLs to block SSRF targets (non-public addresses, cloud metadata endpoints, and non-HTTPS URLs by default). Operators may list trusted hostnames in `dangerouslyAllowClusterUrls` on the catalog locator method to permit HTTP or non-public addresses for those hosts only (for example local minikube). Catalog entities cannot use the `serviceAccount` auth provider, cannot enable TLS verification skipping unless `dangerouslyAllowSkipTLSVerify` is set on the locator method, and only permitted annotations are passed through as auth metadata. Kubernetes API fetches no longer follow HTTP redirects automatically.
|
|
14
|
+
- d9a57de: Add `KubernetesWatcher` interface for streaming Kubernetes resource changes via an async iterator. The watcher is separated from `KubernetesFetcher` because watching is a long-lived streaming connection that only works with server-side auth providers. Watch supports all event types (ADDED, MODIFIED, DELETED, BOOKMARK, ERROR) with errors yielded as data rather than thrown.
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @backstage/catalog-client@1.16.2
|
|
17
|
+
- @backstage/catalog-model@1.10.1
|
|
18
|
+
- @backstage/config@1.3.9
|
|
19
|
+
- @backstage/plugin-kubernetes-node@0.4.8
|
|
20
|
+
- @backstage/plugin-kubernetes-common@0.9.13
|
|
21
|
+
- @backstage/plugin-permission-node@0.11.4
|
|
22
|
+
- @backstage/backend-plugin-api@1.10.1
|
|
23
|
+
- @backstage/integration-aws-node@0.2.2
|
|
24
|
+
- @backstage/plugin-catalog-node@2.2.5
|
|
25
|
+
- @backstage/plugin-permission-common@0.9.11
|
|
26
|
+
|
|
27
|
+
## 0.21.11-next.2
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 5268d63: Migrated tests from MSW v1 to MSW v2.
|
|
32
|
+
- 97a7e8e: The catalog cluster locator now validates Kubernetes API server URLs to block SSRF targets (non-public addresses, cloud metadata endpoints, and non-HTTPS URLs by default). Operators may list trusted hostnames in `dangerouslyAllowClusterUrls` on the catalog locator method to permit HTTP or non-public addresses for those hosts only (for example local minikube). Catalog entities cannot use the `serviceAccount` auth provider, cannot enable TLS verification skipping unless `dangerouslyAllowSkipTLSVerify` is set on the locator method, and only permitted annotations are passed through as auth metadata. Kubernetes API fetches no longer follow HTTP redirects automatically.
|
|
33
|
+
- Updated dependencies
|
|
34
|
+
- @backstage/config@1.3.9-next.0
|
|
35
|
+
- @backstage/plugin-kubernetes-node@0.4.8-next.2
|
|
36
|
+
- @backstage/catalog-client@1.16.2-next.1
|
|
37
|
+
- @backstage/plugin-catalog-node@2.2.5-next.1
|
|
38
|
+
- @backstage/backend-plugin-api@1.10.1-next.1
|
|
39
|
+
- @backstage/integration-aws-node@0.2.2-next.0
|
|
40
|
+
- @backstage/plugin-permission-common@0.9.11-next.0
|
|
41
|
+
- @backstage/plugin-permission-node@0.11.4-next.1
|
|
42
|
+
- @backstage/plugin-kubernetes-common@0.9.13-next.1
|
|
43
|
+
|
|
3
44
|
## 0.21.11-next.1
|
|
4
45
|
|
|
5
46
|
### Patch Changes
|
package/config.schema.json
CHANGED
|
@@ -150,6 +150,19 @@
|
|
|
150
150
|
"type": "string",
|
|
151
151
|
"const": "catalog",
|
|
152
152
|
"visibility": "frontend"
|
|
153
|
+
},
|
|
154
|
+
"dangerouslyAllowClusterUrls": {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"items": {
|
|
157
|
+
"type": "string"
|
|
158
|
+
},
|
|
159
|
+
"description": "Hostname patterns (for example `127.0.0.1` or `*.example.com`) for which catalog cluster API server URLs may use HTTP or non-public addresses. Omitted hostnames must use HTTPS and pass SSRF checks.",
|
|
160
|
+
"visibility": "frontend"
|
|
161
|
+
},
|
|
162
|
+
"dangerouslyAllowSkipTLSVerify": {
|
|
163
|
+
"type": "boolean",
|
|
164
|
+
"description": "When true, catalog cluster entities may set skip TLS verify via annotation. Defaults to false.",
|
|
165
|
+
"visibility": "frontend"
|
|
153
166
|
}
|
|
154
167
|
},
|
|
155
168
|
"required": [
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
|
|
4
|
+
|
|
5
|
+
const createGetKubernetesClustersAction = ({
|
|
6
|
+
clusterSupplier,
|
|
7
|
+
actionsRegistry
|
|
8
|
+
}) => {
|
|
9
|
+
actionsRegistry.register({
|
|
10
|
+
name: "get-kubernetes-clusters",
|
|
11
|
+
title: "Get Kubernetes Clusters",
|
|
12
|
+
description: `
|
|
13
|
+
Lists all Kubernetes clusters registered with this Backstage instance.
|
|
14
|
+
|
|
15
|
+
Each cluster entry includes its internal \`name\` (unique identifier used in other actions and config),
|
|
16
|
+
an optional human-readable \`title\`, an optional \`dashboardUrl\` to its Kubernetes dashboard,
|
|
17
|
+
and an optional \`dashboardApp\` indicating the dashboard type (e.g. "standard", "gke", "eks", "aks",
|
|
18
|
+
"rancher", "openshift").
|
|
19
|
+
|
|
20
|
+
## When to use
|
|
21
|
+
|
|
22
|
+
Use this action to discover which clusters are available before looking up resources on a specific
|
|
23
|
+
cluster. The \`name\` returned here is the identifier to use when describing which cluster a resource
|
|
24
|
+
belongs to.
|
|
25
|
+
`,
|
|
26
|
+
attributes: {
|
|
27
|
+
destructive: false,
|
|
28
|
+
readOnly: true,
|
|
29
|
+
idempotent: true
|
|
30
|
+
},
|
|
31
|
+
visibilityPermission: pluginKubernetesCommon.kubernetesClustersReadPermission,
|
|
32
|
+
schema: {
|
|
33
|
+
input: (z) => z.object({}),
|
|
34
|
+
output: (z) => z.object({
|
|
35
|
+
clusters: z.array(
|
|
36
|
+
z.object({
|
|
37
|
+
name: z.string().describe(
|
|
38
|
+
"Unique internal name of the cluster, used as an identifier in other actions."
|
|
39
|
+
),
|
|
40
|
+
title: z.string().optional().describe(
|
|
41
|
+
"Human-readable display name for the cluster, if configured."
|
|
42
|
+
),
|
|
43
|
+
dashboardUrl: z.string().optional().describe(
|
|
44
|
+
"URL to the Kubernetes dashboard for this cluster."
|
|
45
|
+
),
|
|
46
|
+
dashboardApp: z.string().optional().describe(
|
|
47
|
+
'Dashboard type, e.g. "standard", "gke", "eks", "aks", "rancher", "openshift".'
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
).describe("List of all registered Kubernetes clusters.")
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
examples: [
|
|
54
|
+
{
|
|
55
|
+
title: "List all clusters",
|
|
56
|
+
input: {},
|
|
57
|
+
output: {
|
|
58
|
+
clusters: [
|
|
59
|
+
{ name: "production", title: "Production Cluster" },
|
|
60
|
+
{
|
|
61
|
+
name: "staging",
|
|
62
|
+
title: "Staging Cluster",
|
|
63
|
+
dashboardApp: "standard",
|
|
64
|
+
dashboardUrl: "https://dashboard.staging.example.com"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
action: async ({ credentials, logger }) => {
|
|
71
|
+
logger.info("Fetching Kubernetes clusters");
|
|
72
|
+
const clusterDetails = await clusterSupplier.getClusters({ credentials });
|
|
73
|
+
return {
|
|
74
|
+
output: {
|
|
75
|
+
clusters: clusterDetails.map((cd) => ({
|
|
76
|
+
name: cd.name,
|
|
77
|
+
...cd.title && { title: cd.title },
|
|
78
|
+
...cd.dashboardUrl && { dashboardUrl: cd.dashboardUrl },
|
|
79
|
+
...cd.dashboardApp && { dashboardApp: cd.dashboardApp }
|
|
80
|
+
}))
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.createGetKubernetesClustersAction = createGetKubernetesClustersAction;
|
|
88
|
+
//# sourceMappingURL=createGetKubernetesClustersAction.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGetKubernetesClustersAction.cjs.js","sources":["../../src/actions/createGetKubernetesClustersAction.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport { kubernetesClustersReadPermission } from '@backstage/plugin-kubernetes-common';\nimport { KubernetesClustersSupplier } from '@backstage/plugin-kubernetes-node';\n\nexport const createGetKubernetesClustersAction = ({\n clusterSupplier,\n actionsRegistry,\n}: {\n clusterSupplier: KubernetesClustersSupplier;\n actionsRegistry: ActionsRegistryService;\n}) => {\n actionsRegistry.register({\n name: 'get-kubernetes-clusters',\n title: 'Get Kubernetes Clusters',\n description: `\nLists all Kubernetes clusters registered with this Backstage instance.\n\nEach cluster entry includes its internal \\`name\\` (unique identifier used in other actions and config),\nan optional human-readable \\`title\\`, an optional \\`dashboardUrl\\` to its Kubernetes dashboard,\nand an optional \\`dashboardApp\\` indicating the dashboard type (e.g. \"standard\", \"gke\", \"eks\", \"aks\",\n\"rancher\", \"openshift\").\n\n## When to use\n\nUse this action to discover which clusters are available before looking up resources on a specific\ncluster. The \\`name\\` returned here is the identifier to use when describing which cluster a resource\nbelongs to.\n `,\n attributes: {\n destructive: false,\n readOnly: true,\n idempotent: true,\n },\n visibilityPermission: kubernetesClustersReadPermission,\n schema: {\n input: z => z.object({}),\n output: z =>\n z.object({\n clusters: z\n .array(\n z.object({\n name: z\n .string()\n .describe(\n 'Unique internal name of the cluster, used as an identifier in other actions.',\n ),\n title: z\n .string()\n .optional()\n .describe(\n 'Human-readable display name for the cluster, if configured.',\n ),\n dashboardUrl: z\n .string()\n .optional()\n .describe(\n 'URL to the Kubernetes dashboard for this cluster.',\n ),\n dashboardApp: z\n .string()\n .optional()\n .describe(\n 'Dashboard type, e.g. \"standard\", \"gke\", \"eks\", \"aks\", \"rancher\", \"openshift\".',\n ),\n }),\n )\n .describe('List of all registered Kubernetes clusters.'),\n }),\n },\n examples: [\n {\n title: 'List all clusters',\n input: {},\n output: {\n clusters: [\n { name: 'production', title: 'Production Cluster' },\n {\n name: 'staging',\n title: 'Staging Cluster',\n dashboardApp: 'standard',\n dashboardUrl: 'https://dashboard.staging.example.com',\n },\n ],\n },\n },\n ],\n action: async ({ credentials, logger }) => {\n logger.info('Fetching Kubernetes clusters');\n\n const clusterDetails = await clusterSupplier.getClusters({ credentials });\n\n return {\n output: {\n clusters: clusterDetails.map(cd => ({\n name: cd.name,\n ...(cd.title && { title: cd.title }),\n ...(cd.dashboardUrl && { dashboardUrl: cd.dashboardUrl }),\n ...(cd.dashboardApp && { dashboardApp: cd.dashboardApp }),\n })),\n },\n };\n },\n });\n};\n"],"names":["kubernetesClustersReadPermission"],"mappings":";;;;AAmBO,MAAM,oCAAoC,CAAC;AAAA,EAChD,eAAA;AAAA,EACA;AACF,CAAA,KAGM;AACJ,EAAA,eAAA,CAAgB,QAAA,CAAS;AAAA,IACvB,IAAA,EAAM,yBAAA;AAAA,IACN,KAAA,EAAO,yBAAA;AAAA,IACP,WAAA,EAAa;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAAA,IAcb,UAAA,EAAY;AAAA,MACV,WAAA,EAAa,KAAA;AAAA,MACb,QAAA,EAAU,IAAA;AAAA,MACV,UAAA,EAAY;AAAA,KACd;AAAA,IACA,oBAAA,EAAsBA,uDAAA;AAAA,IACtB,MAAA,EAAQ;AAAA,MACN,KAAA,EAAO,CAAA,CAAA,KAAK,CAAA,CAAE,MAAA,CAAO,EAAE,CAAA;AAAA,MACvB,MAAA,EAAQ,CAAA,CAAA,KACN,CAAA,CAAE,MAAA,CAAO;AAAA,QACP,UAAU,CAAA,CACP,KAAA;AAAA,UACC,EAAE,MAAA,CAAO;AAAA,YACP,IAAA,EAAM,CAAA,CACH,MAAA,EAAO,CACP,QAAA;AAAA,cACC;AAAA,aACF;AAAA,YACF,KAAA,EAAO,CAAA,CACJ,MAAA,EAAO,CACP,UAAS,CACT,QAAA;AAAA,cACC;AAAA,aACF;AAAA,YACF,YAAA,EAAc,CAAA,CACX,MAAA,EAAO,CACP,UAAS,CACT,QAAA;AAAA,cACC;AAAA,aACF;AAAA,YACF,YAAA,EAAc,CAAA,CACX,MAAA,EAAO,CACP,UAAS,CACT,QAAA;AAAA,cACC;AAAA;AACF,WACH;AAAA,SACH,CACC,SAAS,6CAA6C;AAAA,OAC1D;AAAA,KACL;AAAA,IACA,QAAA,EAAU;AAAA,MACR;AAAA,QACE,KAAA,EAAO,mBAAA;AAAA,QACP,OAAO,EAAC;AAAA,QACR,MAAA,EAAQ;AAAA,UACN,QAAA,EAAU;AAAA,YACR,EAAE,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,oBAAA,EAAqB;AAAA,YAClD;AAAA,cACE,IAAA,EAAM,SAAA;AAAA,cACN,KAAA,EAAO,iBAAA;AAAA,cACP,YAAA,EAAc,UAAA;AAAA,cACd,YAAA,EAAc;AAAA;AAChB;AACF;AACF;AACF,KACF;AAAA,IACA,MAAA,EAAQ,OAAO,EAAE,WAAA,EAAa,QAAO,KAAM;AACzC,MAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA;AAE1C,MAAA,MAAM,iBAAiB,MAAM,eAAA,CAAgB,WAAA,CAAY,EAAE,aAAa,CAAA;AAExE,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ;AAAA,UACN,QAAA,EAAU,cAAA,CAAe,GAAA,CAAI,CAAA,EAAA,MAAO;AAAA,YAClC,MAAM,EAAA,CAAG,IAAA;AAAA,YACT,GAAI,EAAA,CAAG,KAAA,IAAS,EAAE,KAAA,EAAO,GAAG,KAAA,EAAM;AAAA,YAClC,GAAI,EAAA,CAAG,YAAA,IAAgB,EAAE,YAAA,EAAc,GAAG,YAAA,EAAa;AAAA,YACvD,GAAI,EAAA,CAAG,YAAA,IAAgB,EAAE,YAAA,EAAc,GAAG,YAAA;AAAa,WACzD,CAAE;AAAA;AACJ,OACF;AAAA,IACF;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
|
|
5
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
6
|
+
|
|
7
|
+
const createGetKubernetesResourcesForEntityAction = ({
|
|
8
|
+
objectsProvider,
|
|
9
|
+
catalog,
|
|
10
|
+
actionsRegistry
|
|
11
|
+
}) => {
|
|
12
|
+
actionsRegistry.register({
|
|
13
|
+
name: "get-kubernetes-resources-for-entity",
|
|
14
|
+
title: "Get Kubernetes Resources for Entity",
|
|
15
|
+
description: `
|
|
16
|
+
Fetches all Kubernetes resources associated with a Backstage catalog entity across all registered clusters.
|
|
17
|
+
|
|
18
|
+
The response groups results by cluster. Each cluster entry includes:
|
|
19
|
+
- \`cluster.name\`: the cluster identifier
|
|
20
|
+
- \`resources\`: list of Kubernetes resource groups, each with a \`type\` (e.g. "pods", "deployments",
|
|
21
|
+
"services", "configmaps", "replicasets", "statefulsets", "daemonsets", "jobs", "cronjobs",
|
|
22
|
+
"horizontalpodautoscalers", "ingresses") and a \`resources\` array containing the raw Kubernetes objects.
|
|
23
|
+
- \`podMetrics\`: CPU and memory usage per pod and container, if metrics-server is installed.
|
|
24
|
+
- \`errors\`: any fetch errors encountered for that cluster.
|
|
25
|
+
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
Use this action to inspect the live Kubernetes state of a service or component \u2014 for example to check
|
|
29
|
+
deployment rollout status, pod health, replica counts, or resource quotas.
|
|
30
|
+
`,
|
|
31
|
+
attributes: {
|
|
32
|
+
destructive: false,
|
|
33
|
+
readOnly: true,
|
|
34
|
+
idempotent: true
|
|
35
|
+
},
|
|
36
|
+
visibilityPermission: pluginKubernetesCommon.kubernetesResourcesReadPermission,
|
|
37
|
+
schema: {
|
|
38
|
+
input: (z) => z.object({
|
|
39
|
+
name: z.string().describe("The name of the catalog entity."),
|
|
40
|
+
kind: z.string().optional().describe(
|
|
41
|
+
'The kind of the catalog entity. Defaults to "Component" if omitted.'
|
|
42
|
+
),
|
|
43
|
+
namespace: z.string().optional().describe(
|
|
44
|
+
'The namespace of the catalog entity. Defaults to "default" if omitted.'
|
|
45
|
+
)
|
|
46
|
+
}),
|
|
47
|
+
output: (z) => z.object({
|
|
48
|
+
items: z.array(z.unknown()).describe(
|
|
49
|
+
"List of ClusterObjects, one per cluster. Each entry has cluster info, resources grouped by type, pod metrics, and any errors."
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
examples: [
|
|
54
|
+
{
|
|
55
|
+
title: "Get Kubernetes resources for a component",
|
|
56
|
+
input: { name: "my-service" },
|
|
57
|
+
output: {
|
|
58
|
+
items: [
|
|
59
|
+
{
|
|
60
|
+
cluster: { name: "production" },
|
|
61
|
+
resources: [
|
|
62
|
+
{
|
|
63
|
+
type: "deployments",
|
|
64
|
+
resources: [
|
|
65
|
+
{
|
|
66
|
+
metadata: { name: "my-service", namespace: "default" },
|
|
67
|
+
spec: { replicas: 3 },
|
|
68
|
+
status: { readyReplicas: 3 }
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "pods",
|
|
74
|
+
resources: [
|
|
75
|
+
{
|
|
76
|
+
metadata: {
|
|
77
|
+
name: "my-service-abc12",
|
|
78
|
+
namespace: "default"
|
|
79
|
+
},
|
|
80
|
+
status: { phase: "Running" }
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
podMetrics: [],
|
|
86
|
+
errors: []
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: "Get Kubernetes resources for a specific kind and namespace",
|
|
93
|
+
input: { name: "my-worker", kind: "Service", namespace: "production" }
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
action: async ({ input, credentials, logger }) => {
|
|
97
|
+
const entityRef = catalogModel.stringifyEntityRef({
|
|
98
|
+
kind: input.kind ?? "Component",
|
|
99
|
+
namespace: input.namespace ?? "default",
|
|
100
|
+
name: input.name
|
|
101
|
+
});
|
|
102
|
+
logger.info(`Fetching Kubernetes resources for entity "${entityRef}"`);
|
|
103
|
+
const entity = await catalog.getEntityByRef(
|
|
104
|
+
{
|
|
105
|
+
kind: input.kind ?? "Component",
|
|
106
|
+
namespace: input.namespace ?? "default",
|
|
107
|
+
name: input.name
|
|
108
|
+
},
|
|
109
|
+
{ credentials }
|
|
110
|
+
);
|
|
111
|
+
if (!entity) {
|
|
112
|
+
throw new errors.NotFoundError(
|
|
113
|
+
`Entity "${entityRef}" not found in the catalog`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const response = await objectsProvider.getKubernetesObjectsByEntity(
|
|
117
|
+
{
|
|
118
|
+
entity,
|
|
119
|
+
// auth carries provider-specific tokens (e.g. OIDC) sent by browser
|
|
120
|
+
// clients. Server-side actions don't have these; passing an empty
|
|
121
|
+
// object tells the AuthenticationStrategy to use the server-side
|
|
122
|
+
// configured credentials (service account, etc.).
|
|
123
|
+
auth: {}
|
|
124
|
+
},
|
|
125
|
+
{ credentials }
|
|
126
|
+
);
|
|
127
|
+
return {
|
|
128
|
+
output: {
|
|
129
|
+
items: response.items
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
exports.createGetKubernetesResourcesForEntityAction = createGetKubernetesResourcesForEntityAction;
|
|
137
|
+
//# sourceMappingURL=createGetKubernetesResourcesForEntityAction.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGetKubernetesResourcesForEntityAction.cjs.js","sources":["../../src/actions/createGetKubernetesResourcesForEntityAction.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport { NotFoundError } from '@backstage/errors';\nimport { kubernetesResourcesReadPermission } from '@backstage/plugin-kubernetes-common';\nimport { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\n\nexport const createGetKubernetesResourcesForEntityAction = ({\n objectsProvider,\n catalog,\n actionsRegistry,\n}: {\n objectsProvider: KubernetesObjectsProvider;\n catalog: CatalogService;\n actionsRegistry: ActionsRegistryService;\n}) => {\n actionsRegistry.register({\n name: 'get-kubernetes-resources-for-entity',\n title: 'Get Kubernetes Resources for Entity',\n description: `\nFetches all Kubernetes resources associated with a Backstage catalog entity across all registered clusters.\n\nThe response groups results by cluster. Each cluster entry includes:\n- \\`cluster.name\\`: the cluster identifier\n- \\`resources\\`: list of Kubernetes resource groups, each with a \\`type\\` (e.g. \"pods\", \"deployments\",\n \"services\", \"configmaps\", \"replicasets\", \"statefulsets\", \"daemonsets\", \"jobs\", \"cronjobs\",\n \"horizontalpodautoscalers\", \"ingresses\") and a \\`resources\\` array containing the raw Kubernetes objects.\n- \\`podMetrics\\`: CPU and memory usage per pod and container, if metrics-server is installed.\n- \\`errors\\`: any fetch errors encountered for that cluster.\n\n## When to use\n\nUse this action to inspect the live Kubernetes state of a service or component — for example to check\ndeployment rollout status, pod health, replica counts, or resource quotas.\n `,\n attributes: {\n destructive: false,\n readOnly: true,\n idempotent: true,\n },\n visibilityPermission: kubernetesResourcesReadPermission,\n schema: {\n input: z =>\n z.object({\n name: z.string().describe('The name of the catalog entity.'),\n kind: z\n .string()\n .optional()\n .describe(\n 'The kind of the catalog entity. Defaults to \"Component\" if omitted.',\n ),\n namespace: z\n .string()\n .optional()\n .describe(\n 'The namespace of the catalog entity. Defaults to \"default\" if omitted.',\n ),\n }),\n output: z =>\n z.object({\n items: z\n .array(z.unknown())\n .describe(\n 'List of ClusterObjects, one per cluster. Each entry has cluster info, resources grouped by type, pod metrics, and any errors.',\n ),\n }),\n },\n examples: [\n {\n title: 'Get Kubernetes resources for a component',\n input: { name: 'my-service' },\n output: {\n items: [\n {\n cluster: { name: 'production' },\n resources: [\n {\n type: 'deployments',\n resources: [\n {\n metadata: { name: 'my-service', namespace: 'default' },\n spec: { replicas: 3 },\n status: { readyReplicas: 3 },\n },\n ],\n },\n {\n type: 'pods',\n resources: [\n {\n metadata: {\n name: 'my-service-abc12',\n namespace: 'default',\n },\n status: { phase: 'Running' },\n },\n ],\n },\n ],\n podMetrics: [],\n errors: [],\n },\n ],\n },\n },\n {\n title: 'Get Kubernetes resources for a specific kind and namespace',\n input: { name: 'my-worker', kind: 'Service', namespace: 'production' },\n },\n ],\n action: async ({ input, credentials, logger }) => {\n const entityRef = stringifyEntityRef({\n kind: input.kind ?? 'Component',\n namespace: input.namespace ?? 'default',\n name: input.name,\n });\n\n logger.info(`Fetching Kubernetes resources for entity \"${entityRef}\"`);\n\n const entity = await catalog.getEntityByRef(\n {\n kind: input.kind ?? 'Component',\n namespace: input.namespace ?? 'default',\n name: input.name,\n },\n { credentials },\n );\n if (!entity) {\n throw new NotFoundError(\n `Entity \"${entityRef}\" not found in the catalog`,\n );\n }\n\n const response = await objectsProvider.getKubernetesObjectsByEntity(\n {\n entity,\n // auth carries provider-specific tokens (e.g. OIDC) sent by browser\n // clients. Server-side actions don't have these; passing an empty\n // object tells the AuthenticationStrategy to use the server-side\n // configured credentials (service account, etc.).\n auth: {},\n },\n { credentials },\n );\n\n return {\n output: {\n items: response.items,\n },\n };\n },\n });\n};\n"],"names":["kubernetesResourcesReadPermission","stringifyEntityRef","NotFoundError"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var createGetKubernetesClustersAction = require('./createGetKubernetesClustersAction.cjs.js');
|
|
4
|
+
var createGetKubernetesResourcesForEntityAction = require('./createGetKubernetesResourcesForEntityAction.cjs.js');
|
|
5
|
+
|
|
6
|
+
const createKubernetesActions = (options) => {
|
|
7
|
+
createGetKubernetesClustersAction.createGetKubernetesClustersAction(options);
|
|
8
|
+
createGetKubernetesResourcesForEntityAction.createGetKubernetesResourcesForEntityAction(options);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
exports.createKubernetesActions = createKubernetesActions;
|
|
12
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../src/actions/index.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';\nimport {\n KubernetesClustersSupplier,\n KubernetesObjectsProvider,\n} from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { createGetKubernetesClustersAction } from './createGetKubernetesClustersAction';\nimport { createGetKubernetesResourcesForEntityAction } from './createGetKubernetesResourcesForEntityAction';\n\nexport const createKubernetesActions = (options: {\n actionsRegistry: ActionsRegistryService;\n clusterSupplier: KubernetesClustersSupplier;\n objectsProvider: KubernetesObjectsProvider;\n catalog: CatalogService;\n}) => {\n createGetKubernetesClustersAction(options);\n createGetKubernetesResourcesForEntityAction(options);\n};\n"],"names":["createGetKubernetesClustersAction","createGetKubernetesResourcesForEntityAction"],"mappings":";;;;;AAwBO,MAAM,uBAAA,GAA0B,CAAC,OAAA,KAKlC;AACJ,EAAAA,mEAAA,CAAkC,OAAO,CAAA;AACzC,EAAAC,uFAAA,CAA4C,OAAO,CAAA;AACrD;;;;"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var catalogClient = require('@backstage/catalog-client');
|
|
4
4
|
var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
|
|
5
|
+
var catalogClusterAuthMetadata = require('./catalogClusterAuthMetadata.cjs.js');
|
|
6
|
+
var CatalogClusterLocatorOptions = require('./CatalogClusterLocatorOptions.cjs.js');
|
|
7
|
+
var validateClusterApiServerUrl = require('./validateClusterApiServerUrl.cjs.js');
|
|
5
8
|
|
|
6
9
|
function isObject(obj) {
|
|
7
10
|
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
@@ -9,12 +12,21 @@ function isObject(obj) {
|
|
|
9
12
|
class CatalogClusterLocator {
|
|
10
13
|
catalogService;
|
|
11
14
|
auth;
|
|
12
|
-
|
|
15
|
+
options;
|
|
16
|
+
logger;
|
|
17
|
+
constructor(catalogService, auth, options, logger) {
|
|
13
18
|
this.catalogService = catalogService;
|
|
14
19
|
this.auth = auth;
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.logger = logger;
|
|
15
22
|
}
|
|
16
|
-
static fromConfig(catalogApi, auth) {
|
|
17
|
-
return new CatalogClusterLocator(
|
|
23
|
+
static fromConfig(catalogApi, auth, clusterLocatorConfig, logger) {
|
|
24
|
+
return new CatalogClusterLocator(
|
|
25
|
+
catalogApi,
|
|
26
|
+
auth,
|
|
27
|
+
CatalogClusterLocatorOptions.readCatalogClusterLocatorOptions(clusterLocatorConfig),
|
|
28
|
+
logger
|
|
29
|
+
);
|
|
18
30
|
}
|
|
19
31
|
async getClusters(options) {
|
|
20
32
|
const apiServerKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER}`;
|
|
@@ -35,28 +47,55 @@ class CatalogClusterLocator {
|
|
|
35
47
|
credentials: options?.credentials ?? await this.auth.getNoneCredentials()
|
|
36
48
|
}
|
|
37
49
|
);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
`Invalid cluster '${entity.metadata.name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`
|
|
44
|
-
);
|
|
50
|
+
const clusterDetails = [];
|
|
51
|
+
for (const entity of clusters.items) {
|
|
52
|
+
const details = await this.toClusterDetails(entity);
|
|
53
|
+
if (details) {
|
|
54
|
+
clusterDetails.push(details);
|
|
45
55
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
}
|
|
57
|
+
return clusterDetails;
|
|
58
|
+
}
|
|
59
|
+
async toClusterDetails(entity) {
|
|
60
|
+
const name = entity.metadata.name;
|
|
61
|
+
const annotations = entity.metadata.annotations;
|
|
62
|
+
if (!annotations) {
|
|
63
|
+
this.logger.warn(
|
|
64
|
+
`Ignoring kubernetes-cluster Resource "${name}" without annotations`
|
|
65
|
+
);
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
const authProvider = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
69
|
+
if (authProvider === "serviceAccount") {
|
|
70
|
+
this.logger.warn(
|
|
71
|
+
`Ignoring kubernetes-cluster Resource "${name}": catalog cluster locator does not support the serviceAccount auth provider`
|
|
72
|
+
);
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
const apiServerUrl = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER];
|
|
76
|
+
try {
|
|
77
|
+
await validateClusterApiServerUrl.validateClusterApiServerUrl(apiServerUrl, this.options);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.logger.warn(
|
|
80
|
+
`Ignoring kubernetes-cluster Resource "${name}": ${error instanceof Error ? error.message : String(error)}`
|
|
81
|
+
);
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
const allowSkipTlsVerify = this.options.dangerouslyAllowSkipTLSVerify ?? false;
|
|
85
|
+
const skipTLSVerify = allowSkipTlsVerify && annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true";
|
|
86
|
+
const clusterDetails = {
|
|
87
|
+
name,
|
|
88
|
+
title: entity.metadata.title,
|
|
89
|
+
url: apiServerUrl,
|
|
90
|
+
authMetadata: catalogClusterAuthMetadata.filterCatalogClusterAuthMetadata(annotations),
|
|
91
|
+
caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
|
|
92
|
+
skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
|
|
93
|
+
skipTLSVerify,
|
|
94
|
+
dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
|
|
95
|
+
dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
|
|
96
|
+
dashboardParameters: this.getDashboardParameters(annotations)
|
|
97
|
+
};
|
|
98
|
+
return clusterDetails;
|
|
60
99
|
}
|
|
61
100
|
getDashboardParameters(annotations) {
|
|
62
101
|
const dashboardParamsString = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CatalogClusterLocator.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocator.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n} from '@backstage/backend-plugin-api';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';\nimport {\n ANNOTATION_KUBERNETES_API_SERVER,\n ANNOTATION_KUBERNETES_API_SERVER_CA,\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP,\n ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY,\n ANNOTATION_KUBERNETES_DASHBOARD_URL,\n ANNOTATION_KUBERNETES_DASHBOARD_APP,\n ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS,\n} from '@backstage/plugin-kubernetes-common';\nimport { JsonObject } from '@backstage/types';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nfunction isObject(obj: unknown): obj is JsonObject {\n return typeof obj === 'object' && obj !== null && !Array.isArray(obj);\n}\n\nexport class CatalogClusterLocator implements KubernetesClustersSupplier {\n private catalogService: CatalogService;\n private auth: AuthService;\n\n constructor(catalogService: CatalogService, auth: AuthService) {\n this.catalogService = catalogService;\n this.auth = auth;\n }\n\n static fromConfig(\n catalogApi: CatalogService,\n auth: AuthService,\n ): CatalogClusterLocator {\n return new CatalogClusterLocator(catalogApi, auth);\n }\n\n async getClusters(options?: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;\n const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;\n const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;\n\n const filter: Record<string, symbol | string> = {\n kind: 'Resource',\n 'spec.type': 'kubernetes-cluster',\n [apiServerKey]: CATALOG_FILTER_EXISTS,\n [apiServerCaKey]: CATALOG_FILTER_EXISTS,\n [authProviderKey]: CATALOG_FILTER_EXISTS,\n };\n\n const clusters = await this.catalogService.getEntities(\n {\n filter: [filter],\n },\n {\n credentials:\n options?.credentials ?? (await this.auth.getNoneCredentials()),\n },\n );\n return clusters.items.map(entity => {\n const annotations = entity.metadata.annotations!;\n const authProvider = annotations[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n if (authProvider === 'serviceAccount') {\n throw new Error(\n `Invalid cluster '${entity.metadata.name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`,\n );\n }\n\n const clusterDetails: ClusterDetails = {\n name: entity.metadata.name,\n title: entity.metadata.title,\n url: annotations[ANNOTATION_KUBERNETES_API_SERVER],\n authMetadata: annotations,\n caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],\n skipMetricsLookup:\n annotations[ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === 'true',\n skipTLSVerify:\n annotations[ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === 'true',\n dashboardUrl: annotations[ANNOTATION_KUBERNETES_DASHBOARD_URL],\n dashboardApp: annotations[ANNOTATION_KUBERNETES_DASHBOARD_APP],\n dashboardParameters: this.getDashboardParameters(annotations),\n };\n\n return clusterDetails;\n });\n }\n\n private getDashboardParameters(\n annotations: Record<string, string>,\n ): JsonObject | undefined {\n const dashboardParamsString =\n annotations[ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];\n if (dashboardParamsString) {\n try {\n const dashboardParams = JSON.parse(dashboardParamsString);\n return isObject(dashboardParams) ? dashboardParams : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_API_SERVER","ANNOTATION_KUBERNETES_API_SERVER_CA","ANNOTATION_KUBERNETES_AUTH_PROVIDER","CATALOG_FILTER_EXISTS","ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP","ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY","ANNOTATION_KUBERNETES_DASHBOARD_URL","ANNOTATION_KUBERNETES_DASHBOARD_APP","ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CatalogClusterLocator.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocator.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';\nimport {\n ANNOTATION_KUBERNETES_API_SERVER,\n ANNOTATION_KUBERNETES_API_SERVER_CA,\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP,\n ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY,\n ANNOTATION_KUBERNETES_DASHBOARD_URL,\n ANNOTATION_KUBERNETES_DASHBOARD_APP,\n ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS,\n} from '@backstage/plugin-kubernetes-common';\nimport { JsonObject } from '@backstage/types';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { filterCatalogClusterAuthMetadata } from './catalogClusterAuthMetadata';\nimport {\n CatalogClusterLocatorOptions,\n readCatalogClusterLocatorOptions,\n} from './CatalogClusterLocatorOptions';\nimport { validateClusterApiServerUrl } from './validateClusterApiServerUrl';\n\nfunction isObject(obj: unknown): obj is JsonObject {\n return typeof obj === 'object' && obj !== null && !Array.isArray(obj);\n}\n\nexport class CatalogClusterLocator implements KubernetesClustersSupplier {\n private catalogService: CatalogService;\n private auth: AuthService;\n private readonly options: CatalogClusterLocatorOptions;\n private readonly logger: LoggerService;\n\n constructor(\n catalogService: CatalogService,\n auth: AuthService,\n options: CatalogClusterLocatorOptions,\n logger: LoggerService,\n ) {\n this.catalogService = catalogService;\n this.auth = auth;\n this.options = options;\n this.logger = logger;\n }\n\n static fromConfig(\n catalogApi: CatalogService,\n auth: AuthService,\n clusterLocatorConfig: Config,\n logger: LoggerService,\n ): CatalogClusterLocator {\n return new CatalogClusterLocator(\n catalogApi,\n auth,\n readCatalogClusterLocatorOptions(clusterLocatorConfig),\n logger,\n );\n }\n\n async getClusters(options?: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;\n const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;\n const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;\n\n const filter: Record<string, symbol | string> = {\n kind: 'Resource',\n 'spec.type': 'kubernetes-cluster',\n [apiServerKey]: CATALOG_FILTER_EXISTS,\n [apiServerCaKey]: CATALOG_FILTER_EXISTS,\n [authProviderKey]: CATALOG_FILTER_EXISTS,\n };\n\n const clusters = await this.catalogService.getEntities(\n {\n filter: [filter],\n },\n {\n credentials:\n options?.credentials ?? (await this.auth.getNoneCredentials()),\n },\n );\n\n const clusterDetails: ClusterDetails[] = [];\n for (const entity of clusters.items) {\n const details = await this.toClusterDetails(entity);\n if (details) {\n clusterDetails.push(details);\n }\n }\n return clusterDetails;\n }\n\n private async toClusterDetails(\n entity: Awaited<ReturnType<CatalogService['getEntities']>>['items'][number],\n ): Promise<ClusterDetails | undefined> {\n const name = entity.metadata.name;\n const annotations = entity.metadata.annotations;\n if (!annotations) {\n this.logger.warn(\n `Ignoring kubernetes-cluster Resource \"${name}\" without annotations`,\n );\n return undefined;\n }\n\n const authProvider = annotations[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n if (authProvider === 'serviceAccount') {\n this.logger.warn(\n `Ignoring kubernetes-cluster Resource \"${name}\": catalog cluster locator does not support the serviceAccount auth provider`,\n );\n return undefined;\n }\n\n const apiServerUrl = annotations[ANNOTATION_KUBERNETES_API_SERVER];\n try {\n await validateClusterApiServerUrl(apiServerUrl, this.options);\n } catch (error) {\n this.logger.warn(\n `Ignoring kubernetes-cluster Resource \"${name}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return undefined;\n }\n\n const allowSkipTlsVerify =\n this.options.dangerouslyAllowSkipTLSVerify ?? false;\n const skipTLSVerify =\n allowSkipTlsVerify &&\n annotations[ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === 'true';\n\n const clusterDetails: ClusterDetails = {\n name,\n title: entity.metadata.title,\n url: apiServerUrl,\n authMetadata: filterCatalogClusterAuthMetadata(annotations),\n caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],\n skipMetricsLookup:\n annotations[ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === 'true',\n skipTLSVerify,\n dashboardUrl: annotations[ANNOTATION_KUBERNETES_DASHBOARD_URL],\n dashboardApp: annotations[ANNOTATION_KUBERNETES_DASHBOARD_APP],\n dashboardParameters: this.getDashboardParameters(annotations),\n };\n\n return clusterDetails;\n }\n\n private getDashboardParameters(\n annotations: Record<string, string>,\n ): JsonObject | undefined {\n const dashboardParamsString =\n annotations[ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];\n if (dashboardParamsString) {\n try {\n const dashboardParams = JSON.parse(dashboardParamsString);\n return isObject(dashboardParams) ? dashboardParams : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n}\n"],"names":["readCatalogClusterLocatorOptions","ANNOTATION_KUBERNETES_API_SERVER","ANNOTATION_KUBERNETES_API_SERVER_CA","ANNOTATION_KUBERNETES_AUTH_PROVIDER","CATALOG_FILTER_EXISTS","validateClusterApiServerUrl","ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY","filterCatalogClusterAuthMetadata","ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP","ANNOTATION_KUBERNETES_DASHBOARD_URL","ANNOTATION_KUBERNETES_DASHBOARD_APP","ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function readCatalogClusterLocatorOptions(config) {
|
|
4
|
+
return {
|
|
5
|
+
dangerouslyAllowClusterUrls: config.getOptionalStringArray(
|
|
6
|
+
"dangerouslyAllowClusterUrls"
|
|
7
|
+
),
|
|
8
|
+
dangerouslyAllowSkipTLSVerify: config.getOptionalBoolean(
|
|
9
|
+
"dangerouslyAllowSkipTLSVerify"
|
|
10
|
+
)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.readCatalogClusterLocatorOptions = readCatalogClusterLocatorOptions;
|
|
15
|
+
//# sourceMappingURL=CatalogClusterLocatorOptions.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CatalogClusterLocatorOptions.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocatorOptions.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ValidateClusterApiServerUrlOptions } from './validateClusterApiServerUrl';\n\nexport type CatalogClusterLocatorOptions =\n ValidateClusterApiServerUrlOptions & {\n dangerouslyAllowSkipTLSVerify?: boolean;\n };\n\nexport function readCatalogClusterLocatorOptions(\n config: Config,\n): CatalogClusterLocatorOptions {\n return {\n dangerouslyAllowClusterUrls: config.getOptionalStringArray(\n 'dangerouslyAllowClusterUrls',\n ),\n dangerouslyAllowSkipTLSVerify: config.getOptionalBoolean(\n 'dangerouslyAllowSkipTLSVerify',\n ),\n };\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iCACd,MAAA,EAC8B;AAC9B,EAAA,OAAO;AAAA,IACL,6BAA6B,MAAA,CAAO,sBAAA;AAAA,MAClC;AAAA,KACF;AAAA,IACA,+BAA+B,MAAA,CAAO,kBAAA;AAAA,MACpC;AAAA;AACF,GACF;AACF;;;;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const KUBERNETES_IO_PREFIX = "kubernetes.io/";
|
|
4
|
+
const BLOCKED_AUTH_METADATA_KEYS = /* @__PURE__ */ new Set(["serviceAccountToken"]);
|
|
5
|
+
function filterCatalogClusterAuthMetadata(annotations) {
|
|
6
|
+
const filtered = {};
|
|
7
|
+
for (const [key, value] of Object.entries(annotations)) {
|
|
8
|
+
if (BLOCKED_AUTH_METADATA_KEYS.has(key)) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (key.startsWith(KUBERNETES_IO_PREFIX)) {
|
|
12
|
+
filtered[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return filtered;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.filterCatalogClusterAuthMetadata = filterCatalogClusterAuthMetadata;
|
|
19
|
+
//# sourceMappingURL=catalogClusterAuthMetadata.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalogClusterAuthMetadata.cjs.js","sources":["../../src/cluster-locator/catalogClusterAuthMetadata.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst KUBERNETES_IO_PREFIX = 'kubernetes.io/';\n\nconst BLOCKED_AUTH_METADATA_KEYS = new Set(['serviceAccountToken']);\n\nexport function filterCatalogClusterAuthMetadata(\n annotations: Record<string, string>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(annotations)) {\n if (BLOCKED_AUTH_METADATA_KEYS.has(key)) {\n continue;\n }\n if (key.startsWith(KUBERNETES_IO_PREFIX)) {\n filtered[key] = value;\n }\n }\n\n return filtered;\n}\n"],"names":[],"mappings":";;AAgBA,MAAM,oBAAA,GAAuB,gBAAA;AAE7B,MAAM,0BAAA,mBAA6B,IAAI,GAAA,CAAI,CAAC,qBAAqB,CAAC,CAAA;AAE3D,SAAS,iCACd,WAAA,EACwB;AACxB,EAAA,MAAM,WAAmC,EAAC;AAE1C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtD,IAAA,IAAI,0BAAA,CAA2B,GAAA,CAAI,GAAG,CAAA,EAAG;AACvC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,oBAAoB,CAAA,EAAG;AACxC,MAAA,QAAA,CAAS,GAAG,CAAA,GAAI,KAAA;AAAA,IAClB;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;;;;"}
|
|
@@ -60,7 +60,12 @@ const getCombinedClusterSupplier = (rootConfig, catalogService, authStrategy, lo
|
|
|
60
60
|
const type = clusterLocatorMethod.getString("type");
|
|
61
61
|
switch (type) {
|
|
62
62
|
case "catalog":
|
|
63
|
-
return CatalogClusterLocator.CatalogClusterLocator.fromConfig(
|
|
63
|
+
return CatalogClusterLocator.CatalogClusterLocator.fromConfig(
|
|
64
|
+
catalogService,
|
|
65
|
+
auth,
|
|
66
|
+
clusterLocatorMethod,
|
|
67
|
+
logger
|
|
68
|
+
);
|
|
64
69
|
case "localKubectlProxy":
|
|
65
70
|
return new LocalKubectlProxyLocator.LocalKubectlProxyClusterLocator();
|
|
66
71
|
case "config":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../../src/cluster-locator/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport { Duration } from 'luxon';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\nimport { CatalogClusterLocator } from './CatalogClusterLocator';\nimport { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nclass CombinedClustersSupplier implements KubernetesClustersSupplier {\n readonly clusterSuppliers: KubernetesClustersSupplier[];\n readonly logger: LoggerService;\n readonly continueOnError: boolean;\n\n constructor(\n clusterSuppliers: KubernetesClustersSupplier[],\n logger: LoggerService,\n continueOnError: boolean = false,\n ) {\n this.clusterSuppliers = clusterSuppliers;\n this.logger = logger;\n this.continueOnError = continueOnError;\n }\n\n async getClusters(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const clusters = this.continueOnError\n ? await this.getClustersSettled(options)\n : await Promise.all(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n ).then(res => res.flat());\n return this.warnDuplicates(clusters);\n }\n\n private async getClustersSettled(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const results = await Promise.allSettled(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n );\n const clusters: ClusterDetails[] = [];\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n if (result.status === 'fulfilled') {\n clusters.push(...result.value);\n } else {\n this.logger.error(\n `Failed to retrieve clusters from cluster locator method #${i + 1}`,\n toError(result.reason),\n );\n }\n }\n return clusters;\n }\n\n private warnDuplicates(clusters: ClusterDetails[]): ClusterDetails[] {\n const clusterNames = new Set<string>();\n const duplicatedNames = new Set<string>();\n for (const clusterName of clusters.map(c => c.name)) {\n if (clusterNames.has(clusterName)) {\n duplicatedNames.add(clusterName);\n } else {\n clusterNames.add(clusterName);\n }\n }\n for (const clusterName of duplicatedNames) {\n this.logger.warn(`Duplicate cluster name '${clusterName}'`);\n }\n return clusters;\n }\n}\n\nexport const getCombinedClusterSupplier = (\n rootConfig: Config,\n catalogService: CatalogService,\n authStrategy: AuthenticationStrategy,\n logger: LoggerService,\n refreshInterval: Duration | undefined = undefined,\n auth: AuthService,\n): KubernetesClustersSupplier => {\n const clusterSuppliers = rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'catalog':\n return CatalogClusterLocator.fromConfig(catalogService
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../../src/cluster-locator/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport { Duration } from 'luxon';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\nimport { CatalogClusterLocator } from './CatalogClusterLocator';\nimport { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nclass CombinedClustersSupplier implements KubernetesClustersSupplier {\n readonly clusterSuppliers: KubernetesClustersSupplier[];\n readonly logger: LoggerService;\n readonly continueOnError: boolean;\n\n constructor(\n clusterSuppliers: KubernetesClustersSupplier[],\n logger: LoggerService,\n continueOnError: boolean = false,\n ) {\n this.clusterSuppliers = clusterSuppliers;\n this.logger = logger;\n this.continueOnError = continueOnError;\n }\n\n async getClusters(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const clusters = this.continueOnError\n ? await this.getClustersSettled(options)\n : await Promise.all(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n ).then(res => res.flat());\n return this.warnDuplicates(clusters);\n }\n\n private async getClustersSettled(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const results = await Promise.allSettled(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n );\n const clusters: ClusterDetails[] = [];\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n if (result.status === 'fulfilled') {\n clusters.push(...result.value);\n } else {\n this.logger.error(\n `Failed to retrieve clusters from cluster locator method #${i + 1}`,\n toError(result.reason),\n );\n }\n }\n return clusters;\n }\n\n private warnDuplicates(clusters: ClusterDetails[]): ClusterDetails[] {\n const clusterNames = new Set<string>();\n const duplicatedNames = new Set<string>();\n for (const clusterName of clusters.map(c => c.name)) {\n if (clusterNames.has(clusterName)) {\n duplicatedNames.add(clusterName);\n } else {\n clusterNames.add(clusterName);\n }\n }\n for (const clusterName of duplicatedNames) {\n this.logger.warn(`Duplicate cluster name '${clusterName}'`);\n }\n return clusters;\n }\n}\n\nexport const getCombinedClusterSupplier = (\n rootConfig: Config,\n catalogService: CatalogService,\n authStrategy: AuthenticationStrategy,\n logger: LoggerService,\n refreshInterval: Duration | undefined = undefined,\n auth: AuthService,\n): KubernetesClustersSupplier => {\n const clusterSuppliers = rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'catalog':\n return CatalogClusterLocator.fromConfig(\n catalogService,\n auth,\n clusterLocatorMethod,\n logger,\n );\n case 'localKubectlProxy':\n return new LocalKubectlProxyClusterLocator();\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n authStrategy,\n );\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n logger,\n refreshInterval,\n );\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n });\n\n const continueOnError =\n rootConfig.getOptionalBoolean('kubernetes.clusterLocatorContinueOnError') ??\n false;\n\n return new CombinedClustersSupplier(\n clusterSuppliers,\n logger,\n continueOnError,\n );\n};\n"],"names":["toError","CatalogClusterLocator","LocalKubectlProxyClusterLocator","ConfigClusterLocator","GkeClusterLocator"],"mappings":"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"}
|