@backstage-community/plugin-ocm-backend 5.2.1
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 +639 -0
- package/README.md +5 -0
- package/config.d.ts +71 -0
- package/dist/bundle.cjs.js +19 -0
- package/dist/bundle.cjs.js.map +1 -0
- package/dist/constants.cjs.js +10 -0
- package/dist/constants.cjs.js.map +1 -0
- package/dist/helpers/config.cjs.js +80 -0
- package/dist/helpers/config.cjs.js.map +1 -0
- package/dist/helpers/kubernetes.cjs.js +106 -0
- package/dist/helpers/kubernetes.cjs.js.map +1 -0
- package/dist/helpers/parser.cjs.js +81 -0
- package/dist/helpers/parser.cjs.js.map +1 -0
- package/dist/index.cjs.js +16 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/providers/ManagedClusterProvider.cjs.js +148 -0
- package/dist/providers/ManagedClusterProvider.cjs.js.map +1 -0
- package/dist/providers/module.cjs.js +38 -0
- package/dist/providers/module.cjs.js.map +1 -0
- package/dist/schema/openapi.generated.cjs.js +301 -0
- package/dist/schema/openapi.generated.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +125 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/package.json +94 -0
package/config.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 The Backstage Authors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
17
|
+
|
|
18
|
+
export interface Config {
|
|
19
|
+
catalog?: {
|
|
20
|
+
providers?: {
|
|
21
|
+
ocm?: {
|
|
22
|
+
/**
|
|
23
|
+
* Key is reflected as provider ID. Defines and claims plugin instance ownership of entities
|
|
24
|
+
*/
|
|
25
|
+
[key: string]: (
|
|
26
|
+
| {
|
|
27
|
+
/**
|
|
28
|
+
* KubernetesPluginRef
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Match the cluster name in kubernetes plugin config
|
|
32
|
+
*/
|
|
33
|
+
kubernetesPluginRef: string;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
/**
|
|
37
|
+
* HubClusterConfig
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Url of the hub cluster API endpoint
|
|
41
|
+
*/
|
|
42
|
+
url: string;
|
|
43
|
+
/**
|
|
44
|
+
* Name that the hub cluster will assume in Backstage Catalog (in OCM this is always local-cluster which can be confusing)
|
|
45
|
+
*/
|
|
46
|
+
name: string;
|
|
47
|
+
/**
|
|
48
|
+
* Service Account Token which is used for querying data from the hub
|
|
49
|
+
* @visibility secret
|
|
50
|
+
*/
|
|
51
|
+
serviceAccountToken?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Skip TLS certificate verification presented by the API server, defaults to false
|
|
54
|
+
*/
|
|
55
|
+
skipTLSVerify?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Base64-encoded CA bundle in PEM format used for verifying the TLS cert presented by the API server
|
|
58
|
+
*/
|
|
59
|
+
caData?: string;
|
|
60
|
+
}
|
|
61
|
+
) & {
|
|
62
|
+
/**
|
|
63
|
+
* Owner reference to created cluster entities in the catalog
|
|
64
|
+
*/
|
|
65
|
+
owner?: string;
|
|
66
|
+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
require('@backstage/catalog-model');
|
|
5
|
+
require('@backstage/errors');
|
|
6
|
+
require('@backstage-community/plugin-ocm-common');
|
|
7
|
+
require('@kubernetes/client-node');
|
|
8
|
+
require('semver');
|
|
9
|
+
var module$1 = require('./providers/module.cjs.js');
|
|
10
|
+
var router = require('./service/router.cjs.js');
|
|
11
|
+
|
|
12
|
+
const bundle = backendPluginApi.createBackendFeatureLoader({
|
|
13
|
+
async loader() {
|
|
14
|
+
return [module$1.catalogModuleOCMEntityProvider, router.ocmPlugin];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
exports.bundle = bundle;
|
|
19
|
+
//# sourceMappingURL=bundle.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.cjs.js","sources":["../src/bundle.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { createBackendFeatureLoader } from '@backstage/backend-plugin-api';\n\nimport { catalogModuleOCMEntityProvider } from './providers';\nimport { ocmPlugin } from './service/router';\n\nexport const bundle = createBackendFeatureLoader({\n async loader() {\n return [catalogModuleOCMEntityProvider, ocmPlugin];\n },\n});\n"],"names":["createBackendFeatureLoader","catalogModuleOCMEntityProvider","ocmPlugin"],"mappings":";;;;;;;;;;;AAqBO,MAAM,SAASA,2CAA2B,CAAA;AAAA,EAC/C,MAAM,MAAS,GAAA;AACb,IAAO,OAAA,CAACC,yCAAgCC,gBAAS,CAAA,CAAA;AAAA,GACnD;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CONSOLE_CLAIM = "consoleurl.cluster.open-cluster-management.io";
|
|
4
|
+
const HUB_CLUSTER_NAME_IN_OCM = "local-cluster";
|
|
5
|
+
const ANNOTATION_KUBERNETES_API_SERVER = "kubernetes.io/api-server";
|
|
6
|
+
|
|
7
|
+
exports.ANNOTATION_KUBERNETES_API_SERVER = ANNOTATION_KUBERNETES_API_SERVER;
|
|
8
|
+
exports.CONSOLE_CLAIM = CONSOLE_CLAIM;
|
|
9
|
+
exports.HUB_CLUSTER_NAME_IN_OCM = HUB_CLUSTER_NAME_IN_OCM;
|
|
10
|
+
//# sourceMappingURL=constants.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.cjs.js","sources":["../src/constants.ts"],"sourcesContent":["/*\n * Copyright 2024 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 */\nexport const CONSOLE_CLAIM = 'consoleurl.cluster.open-cluster-management.io';\nexport const HUB_CLUSTER_NAME_IN_OCM = 'local-cluster';\nexport const ANNOTATION_KUBERNETES_API_SERVER = 'kubernetes.io/api-server';\n"],"names":[],"mappings":";;AAeO,MAAM,aAAgB,GAAA,gDAAA;AACtB,MAAM,uBAA0B,GAAA,gBAAA;AAChC,MAAM,gCAAmC,GAAA;;;;;;"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
|
|
5
|
+
const KUBERNETES_PLUGIN_CONFIG = "kubernetes.clusterLocatorMethods";
|
|
6
|
+
const OCM_PREFIX = "catalog.providers.ocm";
|
|
7
|
+
const KUBERNETES_PLUGIN_KEY = "kubernetesPluginRef";
|
|
8
|
+
const OWNER_KEY = "owner";
|
|
9
|
+
const isValidUrl = (url) => {
|
|
10
|
+
try {
|
|
11
|
+
new URL(url);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
17
|
+
const deferToKubernetesPlugin = (config) => {
|
|
18
|
+
if (config.has(KUBERNETES_PLUGIN_KEY)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const getHubClusterFromKubernetesConfig = (id, config, globalConfig) => {
|
|
24
|
+
const name = config.getOptionalString(KUBERNETES_PLUGIN_KEY);
|
|
25
|
+
const _logTemplate = `Hub cluster ${OCM_PREFIX}.${id}.${KUBERNETES_PLUGIN_KEY}=${name}`;
|
|
26
|
+
const hub = globalConfig.getConfigArray(KUBERNETES_PLUGIN_CONFIG).flatMap((method) => method.getOptionalConfigArray("clusters") || []).find((cluster) => cluster.getString("name") === name);
|
|
27
|
+
if (!hub) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`${_logTemplate} not defined in kubernetes in ${KUBERNETES_PLUGIN_CONFIG}.clusters`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (hub.getString("authProvider") !== "serviceAccount") {
|
|
33
|
+
throw new Error(`${_logTemplate} has to authenticate via 'serviceAccount'`);
|
|
34
|
+
}
|
|
35
|
+
return hub;
|
|
36
|
+
};
|
|
37
|
+
const getHubClusterFromOcmConfig = (id, config) => {
|
|
38
|
+
const requiredValues = ["name", "url"];
|
|
39
|
+
requiredValues.forEach((key) => {
|
|
40
|
+
if (!config.has(key)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Value must be specified in config at '${OCM_PREFIX}.${id}.${key}'`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return config;
|
|
47
|
+
};
|
|
48
|
+
const getHubClusterFromConfig = (id, config, globalConfig) => {
|
|
49
|
+
const hub = deferToKubernetesPlugin(config) ? getHubClusterFromKubernetesConfig(id, config, globalConfig) : getHubClusterFromOcmConfig(id, config);
|
|
50
|
+
const url = hub.getString("url");
|
|
51
|
+
if (!isValidUrl(url)) {
|
|
52
|
+
throw new Error(`"${url}" is not a valid url`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
url,
|
|
57
|
+
hubResourceName: hub.getString("name"),
|
|
58
|
+
serviceAccountToken: hub.getOptionalString("serviceAccountToken"),
|
|
59
|
+
skipTLSVerify: hub.getOptionalBoolean("skipTLSVerify") || false,
|
|
60
|
+
caData: hub.getOptionalString("caData"),
|
|
61
|
+
owner: config.getOptionalString(OWNER_KEY) ?? "unknown",
|
|
62
|
+
schedule: config.has("schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
|
63
|
+
config.getConfig("schedule")
|
|
64
|
+
) : void 0
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const readOcmConfigs = (config) => {
|
|
68
|
+
const ocmConfigs = config.getOptionalConfig(OCM_PREFIX);
|
|
69
|
+
if (!ocmConfigs) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return ocmConfigs.keys().map((id) => getHubClusterFromConfig(id, ocmConfigs.getConfig(id), config));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
exports.deferToKubernetesPlugin = deferToKubernetesPlugin;
|
|
76
|
+
exports.getHubClusterFromConfig = getHubClusterFromConfig;
|
|
77
|
+
exports.getHubClusterFromKubernetesConfig = getHubClusterFromKubernetesConfig;
|
|
78
|
+
exports.getHubClusterFromOcmConfig = getHubClusterFromOcmConfig;
|
|
79
|
+
exports.readOcmConfigs = readOcmConfigs;
|
|
80
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/helpers/config.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\n\nimport { OcmConfig } from '../types';\n\nconst KUBERNETES_PLUGIN_CONFIG = 'kubernetes.clusterLocatorMethods';\nconst OCM_PREFIX = 'catalog.providers.ocm';\nconst KUBERNETES_PLUGIN_KEY = 'kubernetesPluginRef';\nconst OWNER_KEY = 'owner';\n\nconst isValidUrl = (url: string): boolean => {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n } catch (error) {\n return false;\n }\n return true;\n};\n\nexport const deferToKubernetesPlugin = (config: Config): boolean => {\n if (config.has(KUBERNETES_PLUGIN_KEY)) {\n return true;\n }\n return false;\n};\n\nexport const getHubClusterFromKubernetesConfig = (\n id: string,\n config: Config,\n globalConfig: Config,\n): Config => {\n const name = config.getOptionalString(KUBERNETES_PLUGIN_KEY);\n const _logTemplate = `Hub cluster ${OCM_PREFIX}.${id}.${KUBERNETES_PLUGIN_KEY}=${name}`;\n\n const hub = globalConfig\n .getConfigArray(KUBERNETES_PLUGIN_CONFIG)\n .flatMap(method => method.getOptionalConfigArray('clusters') || [])\n .find(cluster => cluster.getString('name') === name);\n if (!hub) {\n throw new Error(\n `${_logTemplate} not defined in kubernetes in ${KUBERNETES_PLUGIN_CONFIG}.clusters`,\n );\n }\n\n if (hub.getString('authProvider') !== 'serviceAccount') {\n throw new Error(`${_logTemplate} has to authenticate via 'serviceAccount'`);\n }\n return hub;\n};\n\nexport const getHubClusterFromOcmConfig = (\n id: string,\n config: Config,\n): Config => {\n // Check if required values are valid\n const requiredValues = ['name', 'url'];\n requiredValues.forEach(key => {\n if (!config.has(key)) {\n throw new Error(\n `Value must be specified in config at '${OCM_PREFIX}.${id}.${key}'`,\n );\n }\n });\n return config;\n};\n\nexport const getHubClusterFromConfig = (\n id: string,\n config: Config,\n globalConfig: Config,\n): OcmConfig => {\n const hub = deferToKubernetesPlugin(config)\n ? getHubClusterFromKubernetesConfig(id, config, globalConfig)\n : getHubClusterFromOcmConfig(id, config);\n\n const url = hub.getString('url');\n if (!isValidUrl(url)) {\n throw new Error(`\"${url}\" is not a valid url`);\n }\n\n return {\n id,\n url,\n hubResourceName: hub.getString('name'),\n serviceAccountToken: hub.getOptionalString('serviceAccountToken'),\n skipTLSVerify: hub.getOptionalBoolean('skipTLSVerify') || false,\n caData: hub.getOptionalString('caData'),\n owner: config.getOptionalString(OWNER_KEY) ?? 'unknown',\n schedule: config.has('schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('schedule'),\n )\n : undefined,\n };\n};\n\nexport const readOcmConfigs = (config: Config): OcmConfig[] => {\n const ocmConfigs = config.getOptionalConfig(OCM_PREFIX);\n\n if (!ocmConfigs) {\n return [];\n }\n\n return ocmConfigs\n .keys()\n .map(id => getHubClusterFromConfig(id, ocmConfigs.getConfig(id), config));\n};\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clientNode = require('@kubernetes/client-node');
|
|
4
|
+
|
|
5
|
+
const hubApiClient = (clusterConfig, logger) => {
|
|
6
|
+
const kubeConfig = new clientNode.KubeConfig();
|
|
7
|
+
if (!clusterConfig.serviceAccountToken) {
|
|
8
|
+
logger.info("Using default kubernetes config");
|
|
9
|
+
kubeConfig.loadFromDefault();
|
|
10
|
+
return kubeConfig.makeApiClient(clientNode.CustomObjectsApi);
|
|
11
|
+
}
|
|
12
|
+
logger.info("Loading kubernetes config from config file");
|
|
13
|
+
const user = {
|
|
14
|
+
name: "backstage",
|
|
15
|
+
token: clusterConfig.serviceAccountToken
|
|
16
|
+
};
|
|
17
|
+
const context = {
|
|
18
|
+
name: clusterConfig.hubResourceName,
|
|
19
|
+
user: user.name,
|
|
20
|
+
cluster: clusterConfig.hubResourceName
|
|
21
|
+
};
|
|
22
|
+
kubeConfig.loadFromOptions({
|
|
23
|
+
clusters: [
|
|
24
|
+
{
|
|
25
|
+
server: clusterConfig.url,
|
|
26
|
+
name: clusterConfig.hubResourceName,
|
|
27
|
+
skipTLSVerify: clusterConfig.skipTLSVerify,
|
|
28
|
+
caData: clusterConfig.caData
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
users: [user],
|
|
32
|
+
contexts: [context],
|
|
33
|
+
currentContext: context.name
|
|
34
|
+
});
|
|
35
|
+
return kubeConfig.makeApiClient(clientNode.CustomObjectsApi);
|
|
36
|
+
};
|
|
37
|
+
const kubeApiResponseHandler = (call) => {
|
|
38
|
+
return call.then((r) => {
|
|
39
|
+
return r.body;
|
|
40
|
+
}).catch((r) => {
|
|
41
|
+
if (!r.body) {
|
|
42
|
+
throw Object.assign(new Error(r.message), {
|
|
43
|
+
// If there is no body, there is no status code, default to 500
|
|
44
|
+
statusCode: 500,
|
|
45
|
+
name: r.message
|
|
46
|
+
});
|
|
47
|
+
} else if (typeof r.body === "string") {
|
|
48
|
+
throw Object.assign(new Error(r.body), {
|
|
49
|
+
statusCode: r.body.code || r.statusCode,
|
|
50
|
+
name: r.body
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
throw Object.assign(new Error(r.body.reason), {
|
|
54
|
+
// Name and statusCode are required by the backstage error handler
|
|
55
|
+
statusCode: r.body.code || r.statusCode,
|
|
56
|
+
name: r.body.reason,
|
|
57
|
+
...r.body
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const getManagedCluster = (api, name) => {
|
|
62
|
+
return kubeApiResponseHandler(
|
|
63
|
+
api.getClusterCustomObject(
|
|
64
|
+
"cluster.open-cluster-management.io",
|
|
65
|
+
"v1",
|
|
66
|
+
"managedclusters",
|
|
67
|
+
name
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
const listManagedClusters = (api) => {
|
|
72
|
+
return kubeApiResponseHandler(
|
|
73
|
+
api.listClusterCustomObject(
|
|
74
|
+
"cluster.open-cluster-management.io",
|
|
75
|
+
"v1",
|
|
76
|
+
"managedclusters"
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
const getManagedClusterInfo = (api, name) => {
|
|
81
|
+
return kubeApiResponseHandler(
|
|
82
|
+
api.getNamespacedCustomObject(
|
|
83
|
+
"internal.open-cluster-management.io",
|
|
84
|
+
"v1beta1",
|
|
85
|
+
name,
|
|
86
|
+
"managedclusterinfos",
|
|
87
|
+
name
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
const listManagedClusterInfos = (api) => {
|
|
92
|
+
return kubeApiResponseHandler(
|
|
93
|
+
api.listClusterCustomObject(
|
|
94
|
+
"internal.open-cluster-management.io",
|
|
95
|
+
"v1beta1",
|
|
96
|
+
"managedclusterinfos"
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
exports.getManagedCluster = getManagedCluster;
|
|
102
|
+
exports.getManagedClusterInfo = getManagedClusterInfo;
|
|
103
|
+
exports.hubApiClient = hubApiClient;
|
|
104
|
+
exports.listManagedClusterInfos = listManagedClusterInfos;
|
|
105
|
+
exports.listManagedClusters = listManagedClusters;
|
|
106
|
+
//# sourceMappingURL=kubernetes.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kubernetes.cjs.js","sources":["../../src/helpers/kubernetes.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { LoggerService } from '@backstage/backend-plugin-api';\n\nimport {\n CustomObjectsApi,\n KubeConfig,\n KubernetesListObject,\n} from '@kubernetes/client-node';\n\nimport http from 'http';\n\nimport { ManagedCluster, ManagedClusterInfo, OcmConfig } from '../types';\n\nexport const hubApiClient = (\n clusterConfig: OcmConfig,\n logger: LoggerService,\n): CustomObjectsApi => {\n const kubeConfig = new KubeConfig();\n\n if (!clusterConfig.serviceAccountToken) {\n logger.info('Using default kubernetes config');\n kubeConfig.loadFromDefault();\n return kubeConfig.makeApiClient(CustomObjectsApi);\n }\n\n logger.info('Loading kubernetes config from config file');\n\n const user = {\n name: 'backstage',\n token: clusterConfig.serviceAccountToken,\n };\n\n const context = {\n name: clusterConfig.hubResourceName,\n user: user.name,\n cluster: clusterConfig.hubResourceName,\n };\n\n kubeConfig.loadFromOptions({\n clusters: [\n {\n server: clusterConfig.url,\n name: clusterConfig.hubResourceName,\n skipTLSVerify: clusterConfig.skipTLSVerify,\n caData: clusterConfig.caData,\n },\n ],\n users: [user],\n contexts: [context],\n currentContext: context.name,\n });\n return kubeConfig.makeApiClient(CustomObjectsApi);\n};\n\nconst kubeApiResponseHandler = <T extends Object>(\n call: Promise<{\n response: http.IncomingMessage;\n body: object;\n }>,\n) => {\n return call\n .then(r => {\n return r.body as T;\n })\n .catch(r => {\n if (!r.body) {\n throw Object.assign(new Error(r.message), {\n // If there is no body, there is no status code, default to 500\n statusCode: 500,\n name: r.message,\n });\n } else if (typeof r.body === 'string') {\n throw Object.assign(new Error(r.body), {\n statusCode: r.body.code || r.statusCode,\n name: r.body,\n });\n }\n throw Object.assign(new Error(r.body.reason), {\n // Name and statusCode are required by the backstage error handler\n statusCode: r.body.code || r.statusCode,\n name: r.body.reason,\n ...r.body,\n });\n });\n};\n\nexport const getManagedCluster = (api: CustomObjectsApi, name: string) => {\n return kubeApiResponseHandler<ManagedCluster>(\n api.getClusterCustomObject(\n 'cluster.open-cluster-management.io',\n 'v1',\n 'managedclusters',\n name,\n ),\n );\n};\n\nexport const listManagedClusters = (api: CustomObjectsApi) => {\n return kubeApiResponseHandler<KubernetesListObject<ManagedCluster>>(\n api.listClusterCustomObject(\n 'cluster.open-cluster-management.io',\n 'v1',\n 'managedclusters',\n ),\n );\n};\n\nexport const getManagedClusterInfo = (api: CustomObjectsApi, name: string) => {\n return kubeApiResponseHandler<ManagedClusterInfo>(\n api.getNamespacedCustomObject(\n 'internal.open-cluster-management.io',\n 'v1beta1',\n name,\n 'managedclusterinfos',\n name,\n ),\n );\n};\n\nexport const listManagedClusterInfos = (api: CustomObjectsApi) => {\n return kubeApiResponseHandler<KubernetesListObject<ManagedClusterInfo>>(\n api.listClusterCustomObject(\n 'internal.open-cluster-management.io',\n 'v1beta1',\n 'managedclusterinfos',\n ),\n );\n};\n"],"names":["KubeConfig","CustomObjectsApi"],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var semver = require('semver');
|
|
4
|
+
var constants = require('../constants.cjs.js');
|
|
5
|
+
|
|
6
|
+
const convertCpus = (cpus) => {
|
|
7
|
+
if (!cpus) {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
if (cpus.endsWith("m")) {
|
|
11
|
+
return parseInt(cpus.slice(0, cpus.length - 1), 10) / 1e3;
|
|
12
|
+
}
|
|
13
|
+
return parseInt(cpus, 10);
|
|
14
|
+
};
|
|
15
|
+
const parseResources = (resources) => ({
|
|
16
|
+
cpuCores: convertCpus(resources?.cpu),
|
|
17
|
+
memorySize: resources?.memory,
|
|
18
|
+
numberOfPods: parseInt(resources?.pods, 10) || void 0
|
|
19
|
+
});
|
|
20
|
+
const getClaim = (cluster, claimName) => cluster.status?.clusterClaims?.find((value) => value.name === claimName)?.value ?? "";
|
|
21
|
+
const parseClusterStatus = (mc) => {
|
|
22
|
+
const available = mc.status?.conditions.find(
|
|
23
|
+
(value) => value.type === "ManagedClusterConditionAvailable"
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
available: available?.status.toLowerCase() === "true",
|
|
27
|
+
reason: available?.message
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const parseManagedCluster = (mc) => ({
|
|
31
|
+
status: parseClusterStatus(mc),
|
|
32
|
+
consoleUrl: getClaim(mc, constants.CONSOLE_CLAIM),
|
|
33
|
+
kubernetesVersion: getClaim(mc, "kubeversion.open-cluster-management.io"),
|
|
34
|
+
oauthUrl: getClaim(mc, "oauthredirecturis.openshift.io"),
|
|
35
|
+
openshiftId: mc.metadata.labels?.clusterID ?? getClaim(mc, "id.openshift.io"),
|
|
36
|
+
openshiftVersion: mc.metadata.labels?.openshiftVersion ?? getClaim(mc, "version.openshift.io"),
|
|
37
|
+
platform: getClaim(mc, "platform.open-cluster-management.io"),
|
|
38
|
+
region: getClaim(mc, "region.open-cluster-management.io"),
|
|
39
|
+
allocatableResources: parseResources(mc.status?.allocatable || {}),
|
|
40
|
+
availableResources: parseResources(mc.status?.capacity || {})
|
|
41
|
+
});
|
|
42
|
+
const parseUpdateInfo = (clusterInfo) => {
|
|
43
|
+
const { availableUpdates, versionAvailableUpdates } = clusterInfo.status?.distributionInfo.ocp || {};
|
|
44
|
+
if (!availableUpdates || availableUpdates?.length === 0 || !versionAvailableUpdates || versionAvailableUpdates?.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
update: {
|
|
47
|
+
available: false
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const version = semver.maxSatisfying(availableUpdates, "*");
|
|
52
|
+
return {
|
|
53
|
+
update: {
|
|
54
|
+
available: true,
|
|
55
|
+
version,
|
|
56
|
+
url: versionAvailableUpdates[availableUpdates.indexOf(version)]?.url
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const parseNodeStatus = (clusterInfo) => clusterInfo.status?.nodeList?.map((node) => {
|
|
61
|
+
if (node.conditions.length !== 1) {
|
|
62
|
+
throw new Error("Found more node conditions then one");
|
|
63
|
+
}
|
|
64
|
+
const condition = node.conditions[0];
|
|
65
|
+
return {
|
|
66
|
+
status: condition.status,
|
|
67
|
+
type: condition.type
|
|
68
|
+
};
|
|
69
|
+
}) || [];
|
|
70
|
+
const translateResourceToOCM = (clusterName, hubResourceName) => clusterName === hubResourceName ? constants.HUB_CLUSTER_NAME_IN_OCM : clusterName;
|
|
71
|
+
const translateOCMToResource = (clusterName, hubResourceName) => clusterName === constants.HUB_CLUSTER_NAME_IN_OCM ? hubResourceName : clusterName;
|
|
72
|
+
|
|
73
|
+
exports.getClaim = getClaim;
|
|
74
|
+
exports.parseClusterStatus = parseClusterStatus;
|
|
75
|
+
exports.parseManagedCluster = parseManagedCluster;
|
|
76
|
+
exports.parseNodeStatus = parseNodeStatus;
|
|
77
|
+
exports.parseResources = parseResources;
|
|
78
|
+
exports.parseUpdateInfo = parseUpdateInfo;
|
|
79
|
+
exports.translateOCMToResource = translateOCMToResource;
|
|
80
|
+
exports.translateResourceToOCM = translateResourceToOCM;
|
|
81
|
+
//# sourceMappingURL=parser.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.cjs.js","sources":["../../src/helpers/parser.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { maxSatisfying } from 'semver';\n\nimport type {\n ClusterDetails,\n ClusterNodesStatus,\n ClusterStatus,\n} from '@backstage-community/plugin-ocm-common';\n\nimport { CONSOLE_CLAIM, HUB_CLUSTER_NAME_IN_OCM } from '../constants';\nimport { ClusterClaim, ManagedCluster, ManagedClusterInfo } from '../types';\n\nconst convertCpus = (cpus: string | undefined): number | undefined => {\n if (!cpus) {\n return undefined;\n }\n if (cpus.endsWith('m')) {\n return parseInt(cpus.slice(0, cpus.length - 1), 10) / 1000;\n }\n return parseInt(cpus, 10);\n};\n\nexport const parseResources = (\n resources: Record<string, string>,\n): Record<string, string | number | undefined> => ({\n cpuCores: convertCpus(resources?.cpu),\n memorySize: resources?.memory,\n numberOfPods: parseInt(resources?.pods, 10) || undefined,\n});\n\nexport const getClaim = (\n cluster: { status?: { clusterClaims: ClusterClaim[] } },\n claimName: string,\n): string =>\n cluster.status?.clusterClaims?.find(value => value.name === claimName)\n ?.value ?? '';\n\nexport const parseClusterStatus = (mc: ManagedCluster): ClusterStatus => {\n const available = mc.status?.conditions.find(\n (value: any) => value.type === 'ManagedClusterConditionAvailable',\n );\n\n return {\n available: available?.status.toLowerCase() === 'true',\n reason: available?.message,\n };\n};\n\nexport const parseManagedCluster = (mc: ManagedCluster): ClusterDetails => ({\n status: parseClusterStatus(mc),\n consoleUrl: getClaim(mc, CONSOLE_CLAIM),\n kubernetesVersion: getClaim(mc, 'kubeversion.open-cluster-management.io'),\n oauthUrl: getClaim(mc, 'oauthredirecturis.openshift.io'),\n openshiftId:\n mc.metadata!.labels?.clusterID ?? getClaim(mc, 'id.openshift.io'),\n openshiftVersion:\n mc.metadata!.labels?.openshiftVersion ??\n getClaim(mc, 'version.openshift.io'),\n platform: getClaim(mc, 'platform.open-cluster-management.io'),\n region: getClaim(mc, 'region.open-cluster-management.io'),\n allocatableResources: parseResources(mc.status?.allocatable || {}),\n availableResources: parseResources(mc.status?.capacity || {}),\n});\n\nexport const parseUpdateInfo = (clusterInfo: ManagedClusterInfo) => {\n const { availableUpdates, versionAvailableUpdates } =\n clusterInfo.status?.distributionInfo.ocp || {};\n\n if (\n !availableUpdates ||\n availableUpdates?.length === 0 ||\n !versionAvailableUpdates ||\n versionAvailableUpdates?.length === 0\n ) {\n return {\n update: {\n available: false,\n },\n };\n }\n\n const version = maxSatisfying(availableUpdates, '*');\n\n return {\n update: {\n available: true,\n version,\n url: versionAvailableUpdates[availableUpdates.indexOf(version as string)]\n ?.url,\n },\n };\n};\n\nexport const parseNodeStatus = (clusterInfo: ManagedClusterInfo) =>\n clusterInfo.status?.nodeList?.map(node => {\n if (node.conditions.length !== 1) {\n throw new Error('Found more node conditions then one');\n }\n const condition = node.conditions[0];\n return {\n status: condition.status,\n type: condition.type,\n } as ClusterNodesStatus;\n }) || [];\n\nexport const translateResourceToOCM = (\n clusterName: string,\n hubResourceName: string,\n) => (clusterName === hubResourceName ? HUB_CLUSTER_NAME_IN_OCM : clusterName);\n\nexport const translateOCMToResource = (\n clusterName: string,\n hubResourceName: string,\n) => (clusterName === HUB_CLUSTER_NAME_IN_OCM ? hubResourceName : clusterName);\n"],"names":["CONSOLE_CLAIM","maxSatisfying","HUB_CLUSTER_NAME_IN_OCM"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var bundle = require('./bundle.cjs.js');
|
|
6
|
+
var ManagedClusterProvider = require('./providers/ManagedClusterProvider.cjs.js');
|
|
7
|
+
var module$1 = require('./providers/module.cjs.js');
|
|
8
|
+
var router = require('./service/router.cjs.js');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
exports.default = bundle.bundle;
|
|
13
|
+
exports.ManagedClusterProvider = ManagedClusterProvider.ManagedClusterProvider;
|
|
14
|
+
exports.catalogModuleOCMEntityProvider = module$1.catalogModuleOCMEntityProvider;
|
|
15
|
+
exports.ocmPlugin = router.ocmPlugin;
|
|
16
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { LoggerService, SchedulerServiceTaskRunner, SchedulerService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { Config } from '@backstage/config';
|
|
4
|
+
import { EntityProvider, EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
|
5
|
+
import { CustomObjectsApi } from '@kubernetes/client-node';
|
|
6
|
+
|
|
7
|
+
declare const bundle: _backstage_backend_plugin_api.BackendFeature;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provides OpenShift cluster resource entities from Open Cluster Management.
|
|
11
|
+
*/
|
|
12
|
+
declare class ManagedClusterProvider implements EntityProvider {
|
|
13
|
+
protected readonly client: CustomObjectsApi;
|
|
14
|
+
protected readonly hubResourceName: string;
|
|
15
|
+
protected readonly id: string;
|
|
16
|
+
protected readonly owner: string;
|
|
17
|
+
protected readonly logger: LoggerService;
|
|
18
|
+
private readonly scheduleFn;
|
|
19
|
+
protected connection?: EntityProviderConnection;
|
|
20
|
+
protected constructor(client: CustomObjectsApi, hubResourceName: string, id: string, deps: {
|
|
21
|
+
logger: LoggerService;
|
|
22
|
+
}, owner: string, taskRunner: SchedulerServiceTaskRunner);
|
|
23
|
+
static fromConfig(deps: {
|
|
24
|
+
config: Config;
|
|
25
|
+
logger: LoggerService;
|
|
26
|
+
}, options: {
|
|
27
|
+
schedule: SchedulerServiceTaskRunner;
|
|
28
|
+
} | {
|
|
29
|
+
scheduler: SchedulerService;
|
|
30
|
+
}): ManagedClusterProvider[];
|
|
31
|
+
connect(connection: EntityProviderConnection): Promise<void>;
|
|
32
|
+
private createScheduleFn;
|
|
33
|
+
getProviderName(): string;
|
|
34
|
+
run(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare const catalogModuleOCMEntityProvider: _backstage_backend_plugin_api.BackendFeature;
|
|
38
|
+
|
|
39
|
+
declare const ocmPlugin: _backstage_backend_plugin_api.BackendFeature;
|
|
40
|
+
|
|
41
|
+
export { ManagedClusterProvider, catalogModuleOCMEntityProvider, bundle as default, ocmPlugin };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
var pluginOcmCommon = require('@backstage-community/plugin-ocm-common');
|
|
6
|
+
var constants = require('../constants.cjs.js');
|
|
7
|
+
var config = require('../helpers/config.cjs.js');
|
|
8
|
+
var kubernetes = require('../helpers/kubernetes.cjs.js');
|
|
9
|
+
var parser = require('../helpers/parser.cjs.js');
|
|
10
|
+
|
|
11
|
+
class ManagedClusterProvider {
|
|
12
|
+
client;
|
|
13
|
+
hubResourceName;
|
|
14
|
+
id;
|
|
15
|
+
owner;
|
|
16
|
+
logger;
|
|
17
|
+
scheduleFn;
|
|
18
|
+
connection;
|
|
19
|
+
constructor(client, hubResourceName, id, deps, owner, taskRunner) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.hubResourceName = hubResourceName;
|
|
22
|
+
this.id = id;
|
|
23
|
+
this.logger = deps.logger;
|
|
24
|
+
this.owner = owner;
|
|
25
|
+
this.scheduleFn = this.createScheduleFn(taskRunner);
|
|
26
|
+
}
|
|
27
|
+
static fromConfig(deps, options) {
|
|
28
|
+
const { config: config$1, logger } = deps;
|
|
29
|
+
return config.readOcmConfigs(config$1).map((providerConfig) => {
|
|
30
|
+
const client = kubernetes.hubApiClient(providerConfig, logger);
|
|
31
|
+
let taskRunner;
|
|
32
|
+
if ("scheduler" in options && providerConfig.schedule) {
|
|
33
|
+
taskRunner = options.scheduler.createScheduledTaskRunner(
|
|
34
|
+
providerConfig.schedule
|
|
35
|
+
);
|
|
36
|
+
} else if ("schedule" in options) {
|
|
37
|
+
taskRunner = options.schedule;
|
|
38
|
+
} else {
|
|
39
|
+
throw new errors.InputError(
|
|
40
|
+
`No schedule provided via config for OCMProvider:${providerConfig.id}.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return new ManagedClusterProvider(
|
|
44
|
+
client,
|
|
45
|
+
providerConfig.hubResourceName,
|
|
46
|
+
providerConfig.id,
|
|
47
|
+
deps,
|
|
48
|
+
providerConfig.owner,
|
|
49
|
+
taskRunner
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async connect(connection) {
|
|
54
|
+
this.connection = connection;
|
|
55
|
+
await this.scheduleFn();
|
|
56
|
+
}
|
|
57
|
+
createScheduleFn(taskRunner) {
|
|
58
|
+
return async () => {
|
|
59
|
+
return taskRunner.run({
|
|
60
|
+
id: `run_ocm_refresh_${this.getProviderName()}`,
|
|
61
|
+
fn: async () => {
|
|
62
|
+
try {
|
|
63
|
+
await this.run();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logger.error(
|
|
66
|
+
"Error while syncing cluster resources from Open Cluster Management",
|
|
67
|
+
{
|
|
68
|
+
// Default Error properties:
|
|
69
|
+
name: error.name,
|
|
70
|
+
message: error.message,
|
|
71
|
+
stack: error.stack,
|
|
72
|
+
// Additional status code if available:
|
|
73
|
+
status: error.response?.status
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
getProviderName() {
|
|
82
|
+
return `ocm-managed-cluster:${this.id}`;
|
|
83
|
+
}
|
|
84
|
+
async run() {
|
|
85
|
+
if (!this.connection) {
|
|
86
|
+
throw new Error("Not initialized");
|
|
87
|
+
}
|
|
88
|
+
this.logger.info(
|
|
89
|
+
`Providing OpenShift cluster resources from Open Cluster Management`
|
|
90
|
+
);
|
|
91
|
+
const hubConsole = parser.getClaim(
|
|
92
|
+
await kubernetes.getManagedCluster(this.client, constants.HUB_CLUSTER_NAME_IN_OCM),
|
|
93
|
+
constants.CONSOLE_CLAIM
|
|
94
|
+
);
|
|
95
|
+
const resources = (await kubernetes.listManagedClusters(this.client)).items.map((i) => {
|
|
96
|
+
const normalizedName = parser.translateOCMToResource(
|
|
97
|
+
i.metadata.name,
|
|
98
|
+
this.hubResourceName
|
|
99
|
+
);
|
|
100
|
+
return {
|
|
101
|
+
kind: "Resource",
|
|
102
|
+
apiVersion: "backstage.io/v1beta1",
|
|
103
|
+
metadata: {
|
|
104
|
+
name: normalizedName,
|
|
105
|
+
annotations: {
|
|
106
|
+
/**
|
|
107
|
+
* Can also be pulled from ManagedClusterInfo on .spec.masterEndpoint (details in discussion: https://github.com/janus-idp/backstage-plugins/pull/94#discussion_r1093228858)
|
|
108
|
+
*/
|
|
109
|
+
[constants.ANNOTATION_KUBERNETES_API_SERVER]: i.spec?.managedClusterClientConfigs?.[0]?.url,
|
|
110
|
+
[pluginOcmCommon.ANNOTATION_CLUSTER_ID]: i.metadata?.labels?.clusterID,
|
|
111
|
+
[catalogModel.ANNOTATION_LOCATION]: this.getProviderName(),
|
|
112
|
+
[catalogModel.ANNOTATION_ORIGIN_LOCATION]: this.getProviderName(),
|
|
113
|
+
[pluginOcmCommon.ANNOTATION_PROVIDER_ID]: this.id
|
|
114
|
+
},
|
|
115
|
+
links: [
|
|
116
|
+
{
|
|
117
|
+
url: parser.getClaim(i, constants.CONSOLE_CLAIM),
|
|
118
|
+
title: "OpenShift Console",
|
|
119
|
+
icon: "dashboard"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
url: `${hubConsole}/multicloud/infrastructure/clusters/details/${i.metadata.name}/`,
|
|
123
|
+
title: "OCM Console"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
url: `https://console.redhat.com/openshift/details/s/${i.metadata.labels.clusterID}`,
|
|
127
|
+
title: "OpenShift Cluster Manager"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
spec: {
|
|
132
|
+
owner: this.owner,
|
|
133
|
+
type: "kubernetes-cluster"
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
await this.connection.applyMutation({
|
|
138
|
+
type: "full",
|
|
139
|
+
entities: resources.map((entity) => ({
|
|
140
|
+
entity,
|
|
141
|
+
locationKey: this.getProviderName()
|
|
142
|
+
}))
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
exports.ManagedClusterProvider = ManagedClusterProvider;
|
|
148
|
+
//# sourceMappingURL=ManagedClusterProvider.cjs.js.map
|