@backstage/plugin-kubernetes-backend 0.18.7-next.0 → 0.18.7
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 +40 -0
- package/alpha/package.json +1 -1
- package/dist/alpha.cjs.js +3 -150
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/alpha.d.ts +3 -6
- package/dist/auth/AksStrategy.cjs.js +17 -0
- package/dist/auth/AksStrategy.cjs.js.map +1 -0
- package/dist/auth/AnonymousStrategy.cjs.js +16 -0
- package/dist/auth/AnonymousStrategy.cjs.js.map +1 -0
- package/dist/auth/AwsIamStrategy.cjs.js +80 -0
- package/dist/auth/AwsIamStrategy.cjs.js.map +1 -0
- package/dist/auth/AzureIdentityStrategy.cjs.js +58 -0
- package/dist/auth/AzureIdentityStrategy.cjs.js.map +1 -0
- package/dist/auth/DispatchStrategy.cjs.js +37 -0
- package/dist/auth/DispatchStrategy.cjs.js.map +1 -0
- package/dist/auth/GoogleServiceAccountStrategy.cjs.js +45 -0
- package/dist/auth/GoogleServiceAccountStrategy.cjs.js.map +1 -0
- package/dist/auth/GoogleStrategy.cjs.js +22 -0
- package/dist/auth/GoogleStrategy.cjs.js.map +1 -0
- package/dist/auth/OidcStrategy.cjs.js +34 -0
- package/dist/auth/OidcStrategy.cjs.js.map +1 -0
- package/dist/auth/ServiceAccountStrategy.cjs.js +33 -0
- package/dist/auth/ServiceAccountStrategy.cjs.js.map +1 -0
- package/dist/cluster-locator/CatalogClusterLocator.cjs.js +73 -0
- package/dist/cluster-locator/CatalogClusterLocator.cjs.js.map +1 -0
- package/dist/cluster-locator/ConfigClusterLocator.cjs.js +100 -0
- package/dist/cluster-locator/ConfigClusterLocator.cjs.js.map +1 -0
- package/dist/cluster-locator/GkeClusterLocator.cjs.js +126 -0
- package/dist/cluster-locator/GkeClusterLocator.cjs.js.map +1 -0
- package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js +35 -0
- package/dist/cluster-locator/LocalKubectlProxyLocator.cjs.js.map +1 -0
- package/dist/cluster-locator/index.cjs.js +67 -0
- package/dist/cluster-locator/index.cjs.js.map +1 -0
- package/dist/index.cjs.js +35 -1904
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/package.json.cjs.js +156 -0
- package/dist/package.json.cjs.js.map +1 -0
- package/dist/plugin.cjs.js +162 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/routes/resourcesRoutes.cjs.js +65 -0
- package/dist/routes/resourcesRoutes.cjs.js.map +1 -0
- package/dist/service/KubernetesBuilder.cjs.js +367 -0
- package/dist/service/KubernetesBuilder.cjs.js.map +1 -0
- package/dist/service/KubernetesFanOutHandler.cjs.js +254 -0
- package/dist/service/KubernetesFanOutHandler.cjs.js.map +1 -0
- package/dist/service/KubernetesFetcher.cjs.js +231 -0
- package/dist/service/KubernetesFetcher.cjs.js.map +1 -0
- package/dist/service/KubernetesProxy.cjs.js +195 -0
- package/dist/service/KubernetesProxy.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +11 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/service/runPeriodically.cjs.js +29 -0
- package/dist/service/runPeriodically.cjs.js.map +1 -0
- package/dist/service-locator/CatalogRelationServiceLocator.cjs.js +31 -0
- package/dist/service-locator/CatalogRelationServiceLocator.cjs.js.map +1 -0
- package/dist/service-locator/MultiTenantServiceLocator.cjs.js +15 -0
- package/dist/service-locator/MultiTenantServiceLocator.cjs.js.map +1 -0
- package/dist/service-locator/SingleTenantServiceLocator.cjs.js +24 -0
- package/dist/service-locator/SingleTenantServiceLocator.cjs.js.map +1 -0
- package/package.json +14 -13
package/dist/index.cjs.js
CHANGED
|
@@ -1,1907 +1,38 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
var
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
get: function () { return e[k]; }
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
n.default = e;
|
|
44
|
-
return Object.freeze(n);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
var container__namespace = /*#__PURE__*/_interopNamespaceCompat(container);
|
|
48
|
-
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
49
|
-
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
50
|
-
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
51
|
-
var dns__default = /*#__PURE__*/_interopDefaultCompat(dns);
|
|
52
|
-
var lodash__default = /*#__PURE__*/_interopDefaultCompat(lodash);
|
|
53
|
-
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
54
|
-
var https__namespace = /*#__PURE__*/_interopNamespaceCompat(https);
|
|
55
|
-
|
|
56
|
-
class AksStrategy {
|
|
57
|
-
async getCredential(_, requestAuth) {
|
|
58
|
-
const token = requestAuth.aks;
|
|
59
|
-
return token ? { type: "bearer token", token } : { type: "anonymous" };
|
|
60
|
-
}
|
|
61
|
-
validateCluster() {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
presentAuthMetadata(_authMetadata) {
|
|
65
|
-
return {};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
class AnonymousStrategy {
|
|
70
|
-
async getCredential() {
|
|
71
|
-
return { type: "anonymous" };
|
|
72
|
-
}
|
|
73
|
-
validateCluster() {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
presentAuthMetadata(_authMetadata) {
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const defaultRegion = "us-east-1";
|
|
82
|
-
class AwsIamStrategy {
|
|
83
|
-
credsManager;
|
|
84
|
-
constructor(opts) {
|
|
85
|
-
this.credsManager = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(opts.config);
|
|
86
|
-
}
|
|
87
|
-
async getCredential(clusterDetails) {
|
|
88
|
-
return {
|
|
89
|
-
type: "bearer token",
|
|
90
|
-
token: await this.getBearerToken(
|
|
91
|
-
clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_CLUSTER_ID] ?? clusterDetails.name,
|
|
92
|
-
clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE],
|
|
93
|
-
clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]
|
|
94
|
-
)
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
validateCluster() {
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
async getBearerToken(clusterId, assumeRole, externalId) {
|
|
101
|
-
const region = process.env.AWS_REGION ?? defaultRegion;
|
|
102
|
-
let credentials = (await this.credsManager.getCredentialProvider()).sdkCredentialProvider;
|
|
103
|
-
if (assumeRole) {
|
|
104
|
-
credentials = credentialProviders.fromTemporaryCredentials({
|
|
105
|
-
masterCredentials: credentials,
|
|
106
|
-
clientConfig: {
|
|
107
|
-
region
|
|
108
|
-
},
|
|
109
|
-
params: {
|
|
110
|
-
RoleArn: assumeRole,
|
|
111
|
-
ExternalId: externalId
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
const signer = new signatureV4.SignatureV4({
|
|
116
|
-
credentials,
|
|
117
|
-
region,
|
|
118
|
-
service: "sts",
|
|
119
|
-
sha256: sha256Js.Sha256
|
|
120
|
-
});
|
|
121
|
-
const request = await signer.presign(
|
|
122
|
-
{
|
|
123
|
-
headers: {
|
|
124
|
-
host: `sts.${region}.amazonaws.com`,
|
|
125
|
-
"x-k8s-aws-id": clusterId
|
|
126
|
-
},
|
|
127
|
-
hostname: `sts.${region}.amazonaws.com`,
|
|
128
|
-
method: "GET",
|
|
129
|
-
path: "/",
|
|
130
|
-
protocol: "https:",
|
|
131
|
-
query: {
|
|
132
|
-
Action: "GetCallerIdentity",
|
|
133
|
-
Version: "2011-06-15"
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
{ expiresIn: 0 }
|
|
137
|
-
);
|
|
138
|
-
const query = Object.keys(request?.query ?? {}).map(
|
|
139
|
-
(q) => `${encodeURIComponent(q)}=${encodeURIComponent(
|
|
140
|
-
request.query?.[q]
|
|
141
|
-
)}`
|
|
142
|
-
).join("&");
|
|
143
|
-
const url = `https://${request.hostname}${request.path}?${query}`;
|
|
144
|
-
return `k8s-aws-v1.${Buffer.from(url).toString("base64url")}`;
|
|
145
|
-
}
|
|
146
|
-
presentAuthMetadata(_authMetadata) {
|
|
147
|
-
return {};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const aksScope = "6dae42f8-4368-4678-94ff-3960e28e3630/.default";
|
|
152
|
-
class AzureIdentityStrategy {
|
|
153
|
-
constructor(logger, tokenCredential = new identity.DefaultAzureCredential()) {
|
|
154
|
-
this.logger = logger;
|
|
155
|
-
this.tokenCredential = tokenCredential;
|
|
156
|
-
}
|
|
157
|
-
accessToken = { token: "", expiresOnTimestamp: 0 };
|
|
158
|
-
newTokenPromise;
|
|
159
|
-
async getCredential() {
|
|
160
|
-
if (!this.tokenRequiresRefresh()) {
|
|
161
|
-
return { type: "bearer token", token: this.accessToken.token };
|
|
162
|
-
}
|
|
163
|
-
if (!this.newTokenPromise) {
|
|
164
|
-
this.newTokenPromise = this.fetchNewToken();
|
|
165
|
-
}
|
|
166
|
-
return this.newTokenPromise ? { type: "bearer token", token: await this.newTokenPromise } : { type: "anonymous" };
|
|
167
|
-
}
|
|
168
|
-
validateCluster() {
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
async fetchNewToken() {
|
|
172
|
-
try {
|
|
173
|
-
this.logger.info("Fetching new Azure token for AKS");
|
|
174
|
-
const newAccessToken = await this.tokenCredential.getToken(aksScope, {
|
|
175
|
-
requestOptions: { timeout: 1e4 }
|
|
176
|
-
// 10 seconds
|
|
177
|
-
});
|
|
178
|
-
if (!newAccessToken) {
|
|
179
|
-
throw new Error("AccessToken is null");
|
|
180
|
-
}
|
|
181
|
-
this.accessToken = newAccessToken;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
this.logger.error("Unable to fetch Azure token", err);
|
|
184
|
-
if (this.tokenExpired()) {
|
|
185
|
-
throw err;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
this.newTokenPromise = void 0;
|
|
189
|
-
return this.accessToken.token;
|
|
190
|
-
}
|
|
191
|
-
tokenRequiresRefresh() {
|
|
192
|
-
const expiresOn = this.accessToken.expiresOnTimestamp - 15 * 60 * 1e3;
|
|
193
|
-
return Date.now() >= expiresOn;
|
|
194
|
-
}
|
|
195
|
-
tokenExpired() {
|
|
196
|
-
return Date.now() >= this.accessToken.expiresOnTimestamp;
|
|
197
|
-
}
|
|
198
|
-
presentAuthMetadata(_authMetadata) {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class GoogleStrategy {
|
|
204
|
-
async getCredential(_, requestAuth) {
|
|
205
|
-
const token = requestAuth.google;
|
|
206
|
-
if (!token) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
"Google token not found under auth.google in request body"
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
return { type: "bearer token", token };
|
|
212
|
-
}
|
|
213
|
-
validateCluster() {
|
|
214
|
-
return [];
|
|
215
|
-
}
|
|
216
|
-
presentAuthMetadata(_authMetadata) {
|
|
217
|
-
return {};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
class GoogleServiceAccountStrategy {
|
|
222
|
-
async getCredential() {
|
|
223
|
-
const client = new container__namespace.v1.ClusterManagerClient();
|
|
224
|
-
const token = await client.auth.getAccessToken();
|
|
225
|
-
if (!token) {
|
|
226
|
-
throw new Error(
|
|
227
|
-
"Unable to obtain access token for the current Google Application Default Credentials"
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
return { type: "bearer token", token };
|
|
231
|
-
}
|
|
232
|
-
validateCluster() {
|
|
233
|
-
return [];
|
|
234
|
-
}
|
|
235
|
-
presentAuthMetadata(_authMetadata) {
|
|
236
|
-
return {};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
class DispatchStrategy {
|
|
241
|
-
strategyMap;
|
|
242
|
-
constructor(options) {
|
|
243
|
-
this.strategyMap = options.authStrategyMap;
|
|
244
|
-
}
|
|
245
|
-
getCredential(clusterDetails, auth) {
|
|
246
|
-
const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
247
|
-
if (this.strategyMap[authProvider]) {
|
|
248
|
-
return this.strategyMap[authProvider].getCredential(clusterDetails, auth);
|
|
249
|
-
}
|
|
250
|
-
throw new Error(
|
|
251
|
-
`authProvider "${authProvider}" has no AuthenticationStrategy associated with it`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
validateCluster(authMetadata) {
|
|
255
|
-
const authProvider = authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
256
|
-
const strategy = this.strategyMap[authProvider];
|
|
257
|
-
if (!strategy) {
|
|
258
|
-
return [
|
|
259
|
-
new Error(
|
|
260
|
-
`authProvider "${authProvider}" has no config associated with it`
|
|
261
|
-
)
|
|
262
|
-
];
|
|
263
|
-
}
|
|
264
|
-
return strategy.validateCluster(authMetadata);
|
|
265
|
-
}
|
|
266
|
-
presentAuthMetadata(_authMetadata) {
|
|
267
|
-
return {};
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
class ServiceAccountStrategy {
|
|
272
|
-
async getCredential(clusterDetails) {
|
|
273
|
-
const token = clusterDetails.authMetadata.serviceAccountToken;
|
|
274
|
-
if (token) {
|
|
275
|
-
return { type: "bearer token", token };
|
|
276
|
-
}
|
|
277
|
-
const kc = new clientNode.KubeConfig();
|
|
278
|
-
kc.loadFromCluster();
|
|
279
|
-
const user = kc.getCurrentUser();
|
|
280
|
-
return {
|
|
281
|
-
type: "bearer token",
|
|
282
|
-
token: fs__default.default.readFileSync(user.authProvider.config.tokenFile).toString()
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
validateCluster() {
|
|
286
|
-
return [];
|
|
287
|
-
}
|
|
288
|
-
presentAuthMetadata(_authMetadata) {
|
|
289
|
-
return {};
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
class OidcStrategy {
|
|
294
|
-
async getCredential(clusterDetails, authConfig) {
|
|
295
|
-
const oidcTokenProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
|
|
296
|
-
if (!oidcTokenProvider || oidcTokenProvider === "") {
|
|
297
|
-
throw new Error(
|
|
298
|
-
`oidc authProvider requires a configured oidcTokenProvider`
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
const token = authConfig.oidc?.[oidcTokenProvider];
|
|
302
|
-
if (!token) {
|
|
303
|
-
throw new Error(
|
|
304
|
-
`Auth token not found under oidc.${oidcTokenProvider} in request body`
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
return { type: "bearer token", token };
|
|
308
|
-
}
|
|
309
|
-
validateCluster(authMetadata) {
|
|
310
|
-
const oidcTokenProvider = authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
|
|
311
|
-
if (!oidcTokenProvider || oidcTokenProvider === "") {
|
|
312
|
-
return [new Error(`Must specify a token provider for 'oidc' strategy`)];
|
|
313
|
-
}
|
|
314
|
-
return [];
|
|
315
|
-
}
|
|
316
|
-
presentAuthMetadata(_authMetadata) {
|
|
317
|
-
return {};
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
class ConfigClusterLocator {
|
|
322
|
-
clusterDetails;
|
|
323
|
-
constructor(clusterDetails) {
|
|
324
|
-
this.clusterDetails = clusterDetails;
|
|
325
|
-
}
|
|
326
|
-
static fromConfig(config, authStrategy) {
|
|
327
|
-
const clusterNames = /* @__PURE__ */ new Set();
|
|
328
|
-
return new ConfigClusterLocator(
|
|
329
|
-
config.getConfigArray("clusters").map((c) => {
|
|
330
|
-
const authMetadataBlock = c.getOptional("authMetadata");
|
|
331
|
-
const name = c.getString("name");
|
|
332
|
-
if (clusterNames.has(name)) {
|
|
333
|
-
throw new Error(`Duplicate cluster name '${name}'`);
|
|
334
|
-
}
|
|
335
|
-
clusterNames.add(name);
|
|
336
|
-
const authProvider = authMetadataBlock?.[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER] ?? c.getOptionalString("authProvider");
|
|
337
|
-
if (!authProvider) {
|
|
338
|
-
throw new Error(
|
|
339
|
-
`cluster '${name}' has no auth provider configured; this must be specified via the 'authProvider' or 'authMetadata.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
const title = c.getOptionalString("title");
|
|
343
|
-
const clusterDetails = {
|
|
344
|
-
name,
|
|
345
|
-
...title && { title },
|
|
346
|
-
url: c.getString("url"),
|
|
347
|
-
skipTLSVerify: c.getOptionalBoolean("skipTLSVerify") ?? false,
|
|
348
|
-
skipMetricsLookup: c.getOptionalBoolean("skipMetricsLookup") ?? false,
|
|
349
|
-
caData: c.getOptionalString("caData"),
|
|
350
|
-
caFile: c.getOptionalString("caFile"),
|
|
351
|
-
authMetadata: {
|
|
352
|
-
[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider,
|
|
353
|
-
...ConfigClusterLocator.parseAuthMetadata(c),
|
|
354
|
-
...authMetadataBlock
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
const customResources = c.getOptionalConfigArray("customResources");
|
|
358
|
-
if (customResources) {
|
|
359
|
-
clusterDetails.customResources = customResources.map((cr) => {
|
|
360
|
-
return {
|
|
361
|
-
group: cr.getString("group"),
|
|
362
|
-
apiVersion: cr.getString("apiVersion"),
|
|
363
|
-
plural: cr.getString("plural")
|
|
364
|
-
};
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
const dashboardUrl = c.getOptionalString("dashboardUrl");
|
|
368
|
-
if (dashboardUrl) {
|
|
369
|
-
clusterDetails.dashboardUrl = dashboardUrl;
|
|
370
|
-
}
|
|
371
|
-
const dashboardApp = c.getOptionalString("dashboardApp");
|
|
372
|
-
if (dashboardApp) {
|
|
373
|
-
clusterDetails.dashboardApp = dashboardApp;
|
|
374
|
-
}
|
|
375
|
-
if (c.has("dashboardParameters")) {
|
|
376
|
-
clusterDetails.dashboardParameters = c.get("dashboardParameters");
|
|
377
|
-
}
|
|
378
|
-
const validationErrors = authStrategy.validateCluster(
|
|
379
|
-
clusterDetails.authMetadata
|
|
380
|
-
);
|
|
381
|
-
if (validationErrors.length !== 0) {
|
|
382
|
-
throw new Error(
|
|
383
|
-
`Invalid cluster '${clusterDetails.name}': ${validationErrors.map((e) => e.message).join(", ")}`
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
return clusterDetails;
|
|
387
|
-
})
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
static parseAuthMetadata(clusterConfig) {
|
|
391
|
-
const serviceAccountToken = clusterConfig.getOptionalString(
|
|
392
|
-
"serviceAccountToken"
|
|
393
|
-
);
|
|
394
|
-
const assumeRole = clusterConfig.getOptionalString("assumeRole");
|
|
395
|
-
const externalId = clusterConfig.getOptionalString("externalId");
|
|
396
|
-
const oidcTokenProvider = clusterConfig.getOptionalString("oidcTokenProvider");
|
|
397
|
-
return serviceAccountToken || assumeRole || externalId || oidcTokenProvider ? {
|
|
398
|
-
...serviceAccountToken && { serviceAccountToken },
|
|
399
|
-
...assumeRole && {
|
|
400
|
-
[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: assumeRole
|
|
401
|
-
},
|
|
402
|
-
...externalId && {
|
|
403
|
-
[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: externalId
|
|
404
|
-
},
|
|
405
|
-
...oidcTokenProvider && {
|
|
406
|
-
[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: oidcTokenProvider
|
|
407
|
-
}
|
|
408
|
-
} : void 0;
|
|
409
|
-
}
|
|
410
|
-
async getClusters() {
|
|
411
|
-
return this.clusterDetails;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function runPeriodically(fn, delayMs) {
|
|
416
|
-
let cancel;
|
|
417
|
-
let cancelled = false;
|
|
418
|
-
const cancellationPromise = new Promise((resolve) => {
|
|
419
|
-
cancel = () => {
|
|
420
|
-
resolve();
|
|
421
|
-
cancelled = true;
|
|
422
|
-
};
|
|
423
|
-
});
|
|
424
|
-
const startRefresh = async () => {
|
|
425
|
-
while (!cancelled) {
|
|
426
|
-
try {
|
|
427
|
-
await fn();
|
|
428
|
-
} catch {
|
|
429
|
-
}
|
|
430
|
-
await Promise.race([
|
|
431
|
-
new Promise((resolve) => setTimeout(resolve, delayMs)),
|
|
432
|
-
cancellationPromise
|
|
433
|
-
]);
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
startRefresh();
|
|
437
|
-
return cancel;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
var name = "@backstage/plugin-kubernetes-backend";
|
|
441
|
-
var version = "0.18.7-next.0";
|
|
442
|
-
var description = "A Backstage backend plugin that integrates towards Kubernetes";
|
|
443
|
-
var backstage = {
|
|
444
|
-
role: "backend-plugin",
|
|
445
|
-
pluginId: "kubernetes",
|
|
446
|
-
pluginPackages: [
|
|
447
|
-
"@backstage/plugin-kubernetes",
|
|
448
|
-
"@backstage/plugin-kubernetes-backend",
|
|
449
|
-
"@backstage/plugin-kubernetes-common",
|
|
450
|
-
"@backstage/plugin-kubernetes-node",
|
|
451
|
-
"@backstage/plugin-kubernetes-react"
|
|
452
|
-
]
|
|
453
|
-
};
|
|
454
|
-
var publishConfig = {
|
|
455
|
-
access: "public"
|
|
456
|
-
};
|
|
457
|
-
var keywords = [
|
|
458
|
-
"backstage",
|
|
459
|
-
"kubernetes"
|
|
460
|
-
];
|
|
461
|
-
var homepage = "https://backstage.io";
|
|
462
|
-
var repository = {
|
|
463
|
-
type: "git",
|
|
464
|
-
url: "https://github.com/backstage/backstage",
|
|
465
|
-
directory: "plugins/kubernetes-backend"
|
|
466
|
-
};
|
|
467
|
-
var license = "Apache-2.0";
|
|
468
|
-
var exports$1 = {
|
|
469
|
-
".": "./src/index.ts",
|
|
470
|
-
"./alpha": "./src/alpha.ts",
|
|
471
|
-
"./package.json": "./package.json"
|
|
472
|
-
};
|
|
473
|
-
var main = "src/index.ts";
|
|
474
|
-
var types = "src/index.ts";
|
|
475
|
-
var typesVersions = {
|
|
476
|
-
"*": {
|
|
477
|
-
alpha: [
|
|
478
|
-
"src/alpha.ts"
|
|
479
|
-
],
|
|
480
|
-
"package.json": [
|
|
481
|
-
"package.json"
|
|
482
|
-
]
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
var files = [
|
|
486
|
-
"dist",
|
|
487
|
-
"config.d.ts"
|
|
488
|
-
];
|
|
489
|
-
var scripts = {
|
|
490
|
-
build: "backstage-cli package build",
|
|
491
|
-
clean: "backstage-cli package clean",
|
|
492
|
-
lint: "backstage-cli package lint",
|
|
493
|
-
prepack: "backstage-cli package prepack",
|
|
494
|
-
postpack: "backstage-cli package postpack",
|
|
495
|
-
start: "backstage-cli package start",
|
|
496
|
-
test: "backstage-cli package test"
|
|
497
|
-
};
|
|
498
|
-
var dependencies = {
|
|
499
|
-
"@aws-crypto/sha256-js": "^5.0.0",
|
|
500
|
-
"@aws-sdk/credential-providers": "^3.350.0",
|
|
501
|
-
"@aws-sdk/signature-v4": "^3.347.0",
|
|
502
|
-
"@azure/identity": "^4.0.0",
|
|
503
|
-
"@backstage/backend-common": "^0.25.0",
|
|
504
|
-
"@backstage/backend-plugin-api": "workspace:^",
|
|
505
|
-
"@backstage/catalog-client": "workspace:^",
|
|
506
|
-
"@backstage/catalog-model": "workspace:^",
|
|
507
|
-
"@backstage/config": "workspace:^",
|
|
508
|
-
"@backstage/errors": "workspace:^",
|
|
509
|
-
"@backstage/integration-aws-node": "workspace:^",
|
|
510
|
-
"@backstage/plugin-auth-node": "workspace:^",
|
|
511
|
-
"@backstage/plugin-catalog-node": "workspace:^",
|
|
512
|
-
"@backstage/plugin-kubernetes-common": "workspace:^",
|
|
513
|
-
"@backstage/plugin-kubernetes-node": "workspace:^",
|
|
514
|
-
"@backstage/plugin-permission-common": "workspace:^",
|
|
515
|
-
"@backstage/plugin-permission-node": "workspace:^",
|
|
516
|
-
"@backstage/types": "workspace:^",
|
|
517
|
-
"@google-cloud/container": "^5.0.0",
|
|
518
|
-
"@jest-mock/express": "^2.0.1",
|
|
519
|
-
"@kubernetes/client-node": "0.20.0",
|
|
520
|
-
"@types/express": "^4.17.6",
|
|
521
|
-
"@types/http-proxy-middleware": "^1.0.0",
|
|
522
|
-
"@types/luxon": "^3.0.0",
|
|
523
|
-
compression: "^1.7.4",
|
|
524
|
-
cors: "^2.8.5",
|
|
525
|
-
express: "^4.17.1",
|
|
526
|
-
"express-promise-router": "^4.1.0",
|
|
527
|
-
"fs-extra": "^11.2.0",
|
|
528
|
-
helmet: "^6.0.0",
|
|
529
|
-
"http-proxy-middleware": "^2.0.6",
|
|
530
|
-
lodash: "^4.17.21",
|
|
531
|
-
luxon: "^3.0.0",
|
|
532
|
-
morgan: "^1.10.0",
|
|
533
|
-
"node-fetch": "^2.7.0",
|
|
534
|
-
"stream-buffers": "^3.0.2",
|
|
535
|
-
winston: "^3.2.1",
|
|
536
|
-
yn: "^4.0.0"
|
|
537
|
-
};
|
|
538
|
-
var devDependencies = {
|
|
539
|
-
"@backstage/backend-app-api": "workspace:^",
|
|
540
|
-
"@backstage/backend-defaults": "workspace:^",
|
|
541
|
-
"@backstage/backend-test-utils": "workspace:^",
|
|
542
|
-
"@backstage/cli": "workspace:^",
|
|
543
|
-
"@backstage/plugin-permission-backend": "workspace:^",
|
|
544
|
-
"@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^",
|
|
545
|
-
"@types/aws4": "^1.5.1",
|
|
546
|
-
msw: "^1.0.0",
|
|
547
|
-
supertest: "^7.0.0",
|
|
548
|
-
ws: "^8.18.0"
|
|
549
|
-
};
|
|
550
|
-
var configSchema = "config.d.ts";
|
|
551
|
-
var packageinfo = {
|
|
552
|
-
name: name,
|
|
553
|
-
version: version,
|
|
554
|
-
description: description,
|
|
555
|
-
backstage: backstage,
|
|
556
|
-
publishConfig: publishConfig,
|
|
557
|
-
keywords: keywords,
|
|
558
|
-
homepage: homepage,
|
|
559
|
-
repository: repository,
|
|
560
|
-
license: license,
|
|
561
|
-
exports: exports$1,
|
|
562
|
-
main: main,
|
|
563
|
-
types: types,
|
|
564
|
-
typesVersions: typesVersions,
|
|
565
|
-
files: files,
|
|
566
|
-
scripts: scripts,
|
|
567
|
-
dependencies: dependencies,
|
|
568
|
-
devDependencies: devDependencies,
|
|
569
|
-
configSchema: configSchema
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
class GkeClusterLocator {
|
|
573
|
-
constructor(options, client, clusterDetails = void 0, hasClusterDetails = false) {
|
|
574
|
-
this.options = options;
|
|
575
|
-
this.client = client;
|
|
576
|
-
this.clusterDetails = clusterDetails;
|
|
577
|
-
this.hasClusterDetails = hasClusterDetails;
|
|
578
|
-
}
|
|
579
|
-
static fromConfigWithClient(config, client, refreshInterval) {
|
|
580
|
-
const matchingResourceLabels = config.getOptionalConfigArray("matchingResourceLabels")?.map((mrl) => {
|
|
581
|
-
return { key: mrl.getString("key"), value: mrl.getString("value") };
|
|
582
|
-
}) ?? [];
|
|
583
|
-
const storeAuthProviderString = config.getOptionalString("authProvider") === "googleServiceAccount" ? "googleServiceAccount" : "google";
|
|
584
|
-
const options = {
|
|
585
|
-
projectId: config.getString("projectId"),
|
|
586
|
-
authProvider: storeAuthProviderString,
|
|
587
|
-
region: config.getOptionalString("region") ?? "-",
|
|
588
|
-
skipTLSVerify: config.getOptionalBoolean("skipTLSVerify") ?? false,
|
|
589
|
-
skipMetricsLookup: config.getOptionalBoolean("skipMetricsLookup") ?? false,
|
|
590
|
-
exposeDashboard: config.getOptionalBoolean("exposeDashboard") ?? false,
|
|
591
|
-
matchingResourceLabels
|
|
592
|
-
};
|
|
593
|
-
const gkeClusterLocator = new GkeClusterLocator(options, client);
|
|
594
|
-
if (refreshInterval) {
|
|
595
|
-
runPeriodically(
|
|
596
|
-
() => gkeClusterLocator.refreshClusters(),
|
|
597
|
-
refreshInterval.toMillis()
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
return gkeClusterLocator;
|
|
601
|
-
}
|
|
602
|
-
// Added an `x-goog-api-client` header to API requests made by the GKE cluster locator to clearly identify API requests from this plugin.
|
|
603
|
-
static fromConfig(config, refreshInterval = void 0) {
|
|
604
|
-
return GkeClusterLocator.fromConfigWithClient(
|
|
605
|
-
config,
|
|
606
|
-
new container__namespace.v1.ClusterManagerClient({
|
|
607
|
-
libName: `backstage/kubernetes-backend.GkeClusterLocator`,
|
|
608
|
-
libVersion: packageinfo.version
|
|
609
|
-
}),
|
|
610
|
-
refreshInterval
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
async getClusters() {
|
|
614
|
-
if (!this.hasClusterDetails) {
|
|
615
|
-
await this.refreshClusters();
|
|
616
|
-
}
|
|
617
|
-
return this.clusterDetails ?? [];
|
|
618
|
-
}
|
|
619
|
-
// TODO pass caData into the object
|
|
620
|
-
async refreshClusters() {
|
|
621
|
-
const {
|
|
622
|
-
projectId,
|
|
623
|
-
region,
|
|
624
|
-
authProvider,
|
|
625
|
-
skipTLSVerify,
|
|
626
|
-
skipMetricsLookup,
|
|
627
|
-
exposeDashboard,
|
|
628
|
-
matchingResourceLabels
|
|
629
|
-
} = this.options;
|
|
630
|
-
const request = {
|
|
631
|
-
parent: `projects/${projectId}/locations/${region}`
|
|
632
|
-
};
|
|
633
|
-
try {
|
|
634
|
-
const [response] = await this.client.listClusters(request);
|
|
635
|
-
this.clusterDetails = (response.clusters ?? []).filter((r) => {
|
|
636
|
-
return matchingResourceLabels?.every((mrl) => {
|
|
637
|
-
if (!r.resourceLabels) {
|
|
638
|
-
return false;
|
|
639
|
-
}
|
|
640
|
-
return r.resourceLabels[mrl.key] === mrl.value;
|
|
641
|
-
});
|
|
642
|
-
}).map((r) => ({
|
|
643
|
-
// TODO filter out clusters which don't have name or endpoint
|
|
644
|
-
name: r.name ?? "unknown",
|
|
645
|
-
url: `https://${r.endpoint ?? ""}`,
|
|
646
|
-
authMetadata: { [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider },
|
|
647
|
-
skipTLSVerify,
|
|
648
|
-
skipMetricsLookup,
|
|
649
|
-
...exposeDashboard ? {
|
|
650
|
-
dashboardApp: "gke",
|
|
651
|
-
dashboardParameters: {
|
|
652
|
-
projectId,
|
|
653
|
-
region,
|
|
654
|
-
clusterName: r.name
|
|
655
|
-
}
|
|
656
|
-
} : {}
|
|
657
|
-
}));
|
|
658
|
-
this.hasClusterDetails = true;
|
|
659
|
-
} catch (e) {
|
|
660
|
-
throw new errors.ForwardedError(
|
|
661
|
-
`There was an error retrieving clusters from GKE for projectId=${projectId} region=${region}`,
|
|
662
|
-
e
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function isObject(obj) {
|
|
669
|
-
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
670
|
-
}
|
|
671
|
-
class CatalogClusterLocator {
|
|
672
|
-
catalogClient;
|
|
673
|
-
auth;
|
|
674
|
-
constructor(catalogClient, auth) {
|
|
675
|
-
this.catalogClient = catalogClient;
|
|
676
|
-
this.auth = auth;
|
|
677
|
-
}
|
|
678
|
-
static fromConfig(catalogApi, auth) {
|
|
679
|
-
return new CatalogClusterLocator(catalogApi, auth);
|
|
680
|
-
}
|
|
681
|
-
async getClusters(options) {
|
|
682
|
-
const apiServerKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER}`;
|
|
683
|
-
const apiServerCaKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA}`;
|
|
684
|
-
const authProviderKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;
|
|
685
|
-
const filter = {
|
|
686
|
-
kind: "Resource",
|
|
687
|
-
"spec.type": "kubernetes-cluster",
|
|
688
|
-
[apiServerKey]: catalogClient.CATALOG_FILTER_EXISTS,
|
|
689
|
-
[apiServerCaKey]: catalogClient.CATALOG_FILTER_EXISTS,
|
|
690
|
-
[authProviderKey]: catalogClient.CATALOG_FILTER_EXISTS
|
|
691
|
-
};
|
|
692
|
-
const clusters = await this.catalogClient.getEntities(
|
|
693
|
-
{
|
|
694
|
-
filter: [filter]
|
|
695
|
-
},
|
|
696
|
-
options?.credentials ? {
|
|
697
|
-
token: (await this.auth.getPluginRequestToken({
|
|
698
|
-
onBehalfOf: options.credentials,
|
|
699
|
-
targetPluginId: "catalog"
|
|
700
|
-
})).token
|
|
701
|
-
} : void 0
|
|
702
|
-
);
|
|
703
|
-
return clusters.items.map((entity) => {
|
|
704
|
-
const annotations = entity.metadata.annotations;
|
|
705
|
-
const clusterDetails = {
|
|
706
|
-
name: entity.metadata.name,
|
|
707
|
-
title: entity.metadata.title,
|
|
708
|
-
url: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER],
|
|
709
|
-
authMetadata: annotations,
|
|
710
|
-
caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
|
|
711
|
-
skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
|
|
712
|
-
skipTLSVerify: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true",
|
|
713
|
-
dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
|
|
714
|
-
dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
|
|
715
|
-
dashboardParameters: this.getDashboardParameters(annotations)
|
|
716
|
-
};
|
|
717
|
-
return clusterDetails;
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
getDashboardParameters(annotations) {
|
|
721
|
-
const dashboardParamsString = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];
|
|
722
|
-
if (dashboardParamsString) {
|
|
723
|
-
try {
|
|
724
|
-
const dashboardParams = JSON.parse(dashboardParamsString);
|
|
725
|
-
return isObject(dashboardParams) ? dashboardParams : void 0;
|
|
726
|
-
} catch {
|
|
727
|
-
return void 0;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
return void 0;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
class LocalKubectlProxyClusterLocator {
|
|
735
|
-
clusterDetails;
|
|
736
|
-
// verbatim: when false, IPv4 addresses are placed before IPv6 addresses, ignoring the order from the DNS resolver
|
|
737
|
-
// By default kubectl proxy listens on 127.0.0.1 instead of [::1]
|
|
738
|
-
lookupPromise = dns__default.default.promises.lookup("localhost", { verbatim: false });
|
|
739
|
-
constructor() {
|
|
740
|
-
this.clusterDetails = [
|
|
741
|
-
{
|
|
742
|
-
name: "local",
|
|
743
|
-
url: "http://localhost:8001",
|
|
744
|
-
authMetadata: {
|
|
745
|
-
[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: "localKubectlProxy"
|
|
746
|
-
},
|
|
747
|
-
skipMetricsLookup: true
|
|
748
|
-
}
|
|
749
|
-
];
|
|
750
|
-
}
|
|
751
|
-
async getClusters() {
|
|
752
|
-
const lookupResolution = await this.lookupPromise;
|
|
753
|
-
this.clusterDetails[0].url = `http://${lookupResolution.address}:8001`;
|
|
754
|
-
return this.clusterDetails;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
class CombinedClustersSupplier {
|
|
759
|
-
constructor(clusterSuppliers, logger) {
|
|
760
|
-
this.clusterSuppliers = clusterSuppliers;
|
|
761
|
-
this.logger = logger;
|
|
762
|
-
}
|
|
763
|
-
async getClusters(options) {
|
|
764
|
-
const clusters = await Promise.all(
|
|
765
|
-
this.clusterSuppliers.map((supplier) => supplier.getClusters(options))
|
|
766
|
-
).then((res) => {
|
|
767
|
-
return res.flat();
|
|
768
|
-
}).catch((e) => {
|
|
769
|
-
throw e;
|
|
770
|
-
});
|
|
771
|
-
return this.warnDuplicates(clusters);
|
|
772
|
-
}
|
|
773
|
-
warnDuplicates(clusters) {
|
|
774
|
-
const clusterNames = /* @__PURE__ */ new Set();
|
|
775
|
-
const duplicatedNames = /* @__PURE__ */ new Set();
|
|
776
|
-
for (const clusterName of clusters.map((c) => c.name)) {
|
|
777
|
-
if (clusterNames.has(clusterName)) {
|
|
778
|
-
duplicatedNames.add(clusterName);
|
|
779
|
-
} else {
|
|
780
|
-
clusterNames.add(clusterName);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
for (const clusterName of duplicatedNames) {
|
|
784
|
-
this.logger.warn(`Duplicate cluster name '${clusterName}'`);
|
|
785
|
-
}
|
|
786
|
-
return clusters;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
const getCombinedClusterSupplier = (rootConfig, catalogClient, authStrategy, logger, refreshInterval = void 0, auth) => {
|
|
790
|
-
const clusterSuppliers = rootConfig.getConfigArray("kubernetes.clusterLocatorMethods").map((clusterLocatorMethod) => {
|
|
791
|
-
const type = clusterLocatorMethod.getString("type");
|
|
792
|
-
switch (type) {
|
|
793
|
-
case "catalog":
|
|
794
|
-
return CatalogClusterLocator.fromConfig(catalogClient, auth);
|
|
795
|
-
case "localKubectlProxy":
|
|
796
|
-
return new LocalKubectlProxyClusterLocator();
|
|
797
|
-
case "config":
|
|
798
|
-
return ConfigClusterLocator.fromConfig(
|
|
799
|
-
clusterLocatorMethod,
|
|
800
|
-
authStrategy
|
|
801
|
-
);
|
|
802
|
-
case "gke":
|
|
803
|
-
return GkeClusterLocator.fromConfig(
|
|
804
|
-
clusterLocatorMethod,
|
|
805
|
-
refreshInterval
|
|
806
|
-
);
|
|
807
|
-
default:
|
|
808
|
-
throw new Error(
|
|
809
|
-
`Unsupported kubernetes.clusterLocatorMethods: "${type}"`
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
return new CombinedClustersSupplier(clusterSuppliers, logger);
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
const addResourceRoutesToRouter = (router, catalogApi, objectsProvider, auth, httpAuth) => {
|
|
817
|
-
const getEntityByReq = async (req) => {
|
|
818
|
-
const rawEntityRef = req.body.entityRef;
|
|
819
|
-
if (rawEntityRef && typeof rawEntityRef !== "string") {
|
|
820
|
-
throw new errors.InputError(`entity query must be a string`);
|
|
821
|
-
} else if (!rawEntityRef) {
|
|
822
|
-
throw new errors.InputError("entity is a required field");
|
|
823
|
-
}
|
|
824
|
-
let entityRef = void 0;
|
|
825
|
-
try {
|
|
826
|
-
entityRef = catalogModel.parseEntityRef(rawEntityRef);
|
|
827
|
-
} catch (error) {
|
|
828
|
-
throw new errors.InputError(`Invalid entity ref, ${error}`);
|
|
829
|
-
}
|
|
830
|
-
const { token } = await auth.getPluginRequestToken({
|
|
831
|
-
onBehalfOf: await httpAuth.credentials(req),
|
|
832
|
-
targetPluginId: "catalog"
|
|
833
|
-
});
|
|
834
|
-
const entity = await catalogApi.getEntityByRef(entityRef, { token });
|
|
835
|
-
if (!entity) {
|
|
836
|
-
throw new errors.InputError(
|
|
837
|
-
`Entity ref missing, ${catalogModel.stringifyEntityRef(entityRef)}`
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
return entity;
|
|
841
|
-
};
|
|
842
|
-
router.post("/resources/workloads/query", async (req, res) => {
|
|
843
|
-
const entity = await getEntityByReq(req);
|
|
844
|
-
const response = await objectsProvider.getKubernetesObjectsByEntity(
|
|
845
|
-
{
|
|
846
|
-
entity,
|
|
847
|
-
auth: req.body.auth
|
|
848
|
-
},
|
|
849
|
-
{ credentials: await httpAuth.credentials(req) }
|
|
850
|
-
);
|
|
851
|
-
res.json(response);
|
|
852
|
-
});
|
|
853
|
-
router.post("/resources/custom/query", async (req, res) => {
|
|
854
|
-
const entity = await getEntityByReq(req);
|
|
855
|
-
if (!req.body.customResources) {
|
|
856
|
-
throw new errors.InputError("customResources is a required field");
|
|
857
|
-
} else if (!Array.isArray(req.body.customResources)) {
|
|
858
|
-
throw new errors.InputError("customResources must be an array");
|
|
859
|
-
} else if (req.body.customResources.length === 0) {
|
|
860
|
-
throw new errors.InputError("at least 1 customResource is required");
|
|
861
|
-
}
|
|
862
|
-
const response = await objectsProvider.getCustomResourcesByEntity(
|
|
863
|
-
{
|
|
864
|
-
entity,
|
|
865
|
-
customResources: req.body.customResources,
|
|
866
|
-
auth: req.body.auth
|
|
867
|
-
},
|
|
868
|
-
{ credentials: await httpAuth.credentials(req) }
|
|
869
|
-
);
|
|
870
|
-
res.json(response);
|
|
871
|
-
});
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
class CatalogRelationServiceLocator {
|
|
875
|
-
clusterSupplier;
|
|
876
|
-
constructor(clusterSupplier) {
|
|
877
|
-
this.clusterSupplier = clusterSupplier;
|
|
878
|
-
}
|
|
879
|
-
// As this implementation always returns all clusters serviceId is ignored here
|
|
880
|
-
getClustersByEntity(entity, requestContext) {
|
|
881
|
-
if (entity.relations && entity.relations.some(
|
|
882
|
-
(r) => r.type === "dependsOn" && r.targetRef.includes("resource:")
|
|
883
|
-
)) {
|
|
884
|
-
return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => {
|
|
885
|
-
return {
|
|
886
|
-
clusters: clusters.filter(
|
|
887
|
-
(c) => this.doesEntityDependOnCluster(entity, c)
|
|
888
|
-
)
|
|
889
|
-
};
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
return Promise.resolve({ clusters: [] });
|
|
893
|
-
}
|
|
894
|
-
doesEntityDependOnCluster(entity, cluster) {
|
|
895
|
-
return entity.relations.some(
|
|
896
|
-
(rel) => rel.type === "dependsOn" && rel.targetRef === `resource:${entity.metadata.namespace ?? "default"}/${cluster.name}`
|
|
897
|
-
);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
class MultiTenantServiceLocator {
|
|
902
|
-
clusterSupplier;
|
|
903
|
-
constructor(clusterSupplier) {
|
|
904
|
-
this.clusterSupplier = clusterSupplier;
|
|
905
|
-
}
|
|
906
|
-
// As this implementation always returns all clusters serviceId is ignored here
|
|
907
|
-
getClustersByEntity(_entity, requestContext) {
|
|
908
|
-
return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => ({ clusters }));
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
class SingleTenantServiceLocator {
|
|
913
|
-
clusterSupplier;
|
|
914
|
-
constructor(clusterSupplier) {
|
|
915
|
-
this.clusterSupplier = clusterSupplier;
|
|
916
|
-
}
|
|
917
|
-
// As this implementation always returns all clusters serviceId is ignored here
|
|
918
|
-
getClustersByEntity(_entity, requestContext) {
|
|
919
|
-
return this.clusterSupplier.getClusters({ credentials: requestContext.credentials }).then((clusters) => {
|
|
920
|
-
if (_entity.metadata?.annotations?.["backstage.io/kubernetes-cluster"]) {
|
|
921
|
-
return {
|
|
922
|
-
clusters: clusters.filter(
|
|
923
|
-
(c) => c.name === _entity.metadata?.annotations?.["backstage.io/kubernetes-cluster"]
|
|
924
|
-
)
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
return { clusters };
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
const DEFAULT_OBJECTS = [
|
|
933
|
-
{
|
|
934
|
-
group: "",
|
|
935
|
-
apiVersion: "v1",
|
|
936
|
-
plural: "pods",
|
|
937
|
-
objectType: "pods"
|
|
938
|
-
},
|
|
939
|
-
{
|
|
940
|
-
group: "",
|
|
941
|
-
apiVersion: "v1",
|
|
942
|
-
plural: "services",
|
|
943
|
-
objectType: "services"
|
|
944
|
-
},
|
|
945
|
-
{
|
|
946
|
-
group: "",
|
|
947
|
-
apiVersion: "v1",
|
|
948
|
-
plural: "configmaps",
|
|
949
|
-
objectType: "configmaps"
|
|
950
|
-
},
|
|
951
|
-
{
|
|
952
|
-
group: "",
|
|
953
|
-
apiVersion: "v1",
|
|
954
|
-
plural: "limitranges",
|
|
955
|
-
objectType: "limitranges"
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
group: "",
|
|
959
|
-
apiVersion: "v1",
|
|
960
|
-
plural: "resourcequotas",
|
|
961
|
-
objectType: "resourcequotas"
|
|
962
|
-
},
|
|
963
|
-
{
|
|
964
|
-
group: "apps",
|
|
965
|
-
apiVersion: "v1",
|
|
966
|
-
plural: "deployments",
|
|
967
|
-
objectType: "deployments"
|
|
968
|
-
},
|
|
969
|
-
{
|
|
970
|
-
group: "apps",
|
|
971
|
-
apiVersion: "v1",
|
|
972
|
-
plural: "replicasets",
|
|
973
|
-
objectType: "replicasets"
|
|
974
|
-
},
|
|
975
|
-
{
|
|
976
|
-
group: "autoscaling",
|
|
977
|
-
apiVersion: "v2",
|
|
978
|
-
plural: "horizontalpodautoscalers",
|
|
979
|
-
objectType: "horizontalpodautoscalers"
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
group: "batch",
|
|
983
|
-
apiVersion: "v1",
|
|
984
|
-
plural: "jobs",
|
|
985
|
-
objectType: "jobs"
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
group: "batch",
|
|
989
|
-
apiVersion: "v1",
|
|
990
|
-
plural: "cronjobs",
|
|
991
|
-
objectType: "cronjobs"
|
|
992
|
-
},
|
|
993
|
-
{
|
|
994
|
-
group: "networking.k8s.io",
|
|
995
|
-
apiVersion: "v1",
|
|
996
|
-
plural: "ingresses",
|
|
997
|
-
objectType: "ingresses"
|
|
998
|
-
},
|
|
999
|
-
{
|
|
1000
|
-
group: "apps",
|
|
1001
|
-
apiVersion: "v1",
|
|
1002
|
-
plural: "statefulsets",
|
|
1003
|
-
objectType: "statefulsets"
|
|
1004
|
-
},
|
|
1005
|
-
{
|
|
1006
|
-
group: "apps",
|
|
1007
|
-
apiVersion: "v1",
|
|
1008
|
-
plural: "daemonsets",
|
|
1009
|
-
objectType: "daemonsets"
|
|
1010
|
-
}
|
|
1011
|
-
];
|
|
1012
|
-
const isPodFetchResponse = (fr) => fr.type === "pods";
|
|
1013
|
-
const isString = (str) => str !== void 0;
|
|
1014
|
-
const numberOrBigIntToNumberOrString = (value) => {
|
|
1015
|
-
return typeof value === "bigint" ? value.toString() : value;
|
|
1016
|
-
};
|
|
1017
|
-
const toClientSafeResource = (current) => {
|
|
1018
|
-
return {
|
|
1019
|
-
currentUsage: numberOrBigIntToNumberOrString(current.CurrentUsage),
|
|
1020
|
-
requestTotal: numberOrBigIntToNumberOrString(current.RequestTotal),
|
|
1021
|
-
limitTotal: numberOrBigIntToNumberOrString(current.LimitTotal)
|
|
1022
|
-
};
|
|
1023
|
-
};
|
|
1024
|
-
const toClientSafeContainer = (container) => {
|
|
1025
|
-
return {
|
|
1026
|
-
container: container.Container,
|
|
1027
|
-
cpuUsage: toClientSafeResource(container.CPUUsage),
|
|
1028
|
-
memoryUsage: toClientSafeResource(container.MemoryUsage)
|
|
1029
|
-
};
|
|
1030
|
-
};
|
|
1031
|
-
const toClientSafePodMetrics = (podMetrics) => {
|
|
1032
|
-
return podMetrics.map((r) => r.resources).flat().map((pd) => {
|
|
1033
|
-
return {
|
|
1034
|
-
pod: pd.Pod,
|
|
1035
|
-
memory: toClientSafeResource(pd.Memory),
|
|
1036
|
-
cpu: toClientSafeResource(pd.CPU),
|
|
1037
|
-
containers: pd.Containers.map(toClientSafeContainer)
|
|
1038
|
-
};
|
|
1039
|
-
});
|
|
1040
|
-
};
|
|
1041
|
-
class KubernetesFanOutHandler {
|
|
1042
|
-
logger;
|
|
1043
|
-
fetcher;
|
|
1044
|
-
serviceLocator;
|
|
1045
|
-
customResources;
|
|
1046
|
-
objectTypesToFetch;
|
|
1047
|
-
authStrategy;
|
|
1048
|
-
constructor({
|
|
1049
|
-
logger,
|
|
1050
|
-
fetcher,
|
|
1051
|
-
serviceLocator,
|
|
1052
|
-
customResources,
|
|
1053
|
-
objectTypesToFetch = DEFAULT_OBJECTS,
|
|
1054
|
-
authStrategy
|
|
1055
|
-
}) {
|
|
1056
|
-
this.logger = logger;
|
|
1057
|
-
this.fetcher = fetcher;
|
|
1058
|
-
this.serviceLocator = serviceLocator;
|
|
1059
|
-
this.customResources = customResources;
|
|
1060
|
-
this.objectTypesToFetch = new Set(objectTypesToFetch);
|
|
1061
|
-
this.authStrategy = authStrategy;
|
|
1062
|
-
}
|
|
1063
|
-
async getCustomResourcesByEntity({ entity, auth, customResources }, options) {
|
|
1064
|
-
return this.fanOutRequests(
|
|
1065
|
-
entity,
|
|
1066
|
-
auth,
|
|
1067
|
-
{ credentials: options.credentials },
|
|
1068
|
-
/* @__PURE__ */ new Set(),
|
|
1069
|
-
customResources
|
|
1070
|
-
);
|
|
1071
|
-
}
|
|
1072
|
-
async getKubernetesObjectsByEntity({ entity, auth }, options) {
|
|
1073
|
-
return this.fanOutRequests(
|
|
1074
|
-
entity,
|
|
1075
|
-
auth,
|
|
1076
|
-
{
|
|
1077
|
-
credentials: options.credentials
|
|
1078
|
-
},
|
|
1079
|
-
this.objectTypesToFetch
|
|
1080
|
-
);
|
|
1081
|
-
}
|
|
1082
|
-
async fanOutRequests(entity, auth, options, objectTypesToFetch, customResources) {
|
|
1083
|
-
const entityName = entity.metadata?.annotations?.["backstage.io/kubernetes-id"] || entity.metadata?.name;
|
|
1084
|
-
const { clusters } = await this.serviceLocator.getClustersByEntity(entity, {
|
|
1085
|
-
objectTypesToFetch,
|
|
1086
|
-
customResources: customResources ?? [],
|
|
1087
|
-
credentials: options.credentials
|
|
1088
|
-
});
|
|
1089
|
-
this.logger.info(
|
|
1090
|
-
`entity.metadata.name=${entityName} clusterDetails=[${clusters.map((c) => c.name).join(", ")}]`
|
|
1091
|
-
);
|
|
1092
|
-
const labelSelector = entity.metadata?.annotations?.["backstage.io/kubernetes-label-selector"] || `backstage.io/kubernetes-id=${entityName}`;
|
|
1093
|
-
const namespace = entity.metadata?.annotations?.["backstage.io/kubernetes-namespace"];
|
|
1094
|
-
return Promise.all(
|
|
1095
|
-
clusters.map(async (clusterDetails) => {
|
|
1096
|
-
const credential = await this.authStrategy.getCredential(
|
|
1097
|
-
clusterDetails,
|
|
1098
|
-
auth
|
|
1099
|
-
);
|
|
1100
|
-
return this.fetcher.fetchObjectsForService({
|
|
1101
|
-
serviceId: entityName,
|
|
1102
|
-
clusterDetails,
|
|
1103
|
-
credential,
|
|
1104
|
-
objectTypesToFetch,
|
|
1105
|
-
labelSelector,
|
|
1106
|
-
customResources: (customResources || clusterDetails.customResources || this.customResources).map((c) => ({
|
|
1107
|
-
...c,
|
|
1108
|
-
objectType: "customresources"
|
|
1109
|
-
})),
|
|
1110
|
-
namespace
|
|
1111
|
-
}).then(
|
|
1112
|
-
(result) => this.getMetricsForPods(
|
|
1113
|
-
clusterDetails,
|
|
1114
|
-
credential,
|
|
1115
|
-
labelSelector,
|
|
1116
|
-
result
|
|
1117
|
-
)
|
|
1118
|
-
).catch(
|
|
1119
|
-
(e) => e.name === "FetchError" ? Promise.resolve([
|
|
1120
|
-
{
|
|
1121
|
-
errors: [
|
|
1122
|
-
{ errorType: "FETCH_ERROR", message: e.message }
|
|
1123
|
-
],
|
|
1124
|
-
responses: []
|
|
1125
|
-
},
|
|
1126
|
-
[]
|
|
1127
|
-
]) : Promise.reject(e)
|
|
1128
|
-
).then((r) => this.toClusterObjects(clusterDetails, r));
|
|
1129
|
-
})
|
|
1130
|
-
).then(this.toObjectsByEntityResponse);
|
|
1131
|
-
}
|
|
1132
|
-
toObjectsByEntityResponse(clusterObjects) {
|
|
1133
|
-
return {
|
|
1134
|
-
items: clusterObjects.filter(
|
|
1135
|
-
(item) => item.errors !== void 0 && item.errors.length >= 1 || item.resources !== void 0 && item.resources.length >= 1 && item.resources.some((fr) => fr.resources?.length >= 1)
|
|
1136
|
-
)
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
toClusterObjects(clusterDetails, [result, metrics]) {
|
|
1140
|
-
const objects = {
|
|
1141
|
-
cluster: {
|
|
1142
|
-
name: clusterDetails.name,
|
|
1143
|
-
...clusterDetails.title && { title: clusterDetails.title }
|
|
1144
|
-
},
|
|
1145
|
-
podMetrics: toClientSafePodMetrics(metrics),
|
|
1146
|
-
resources: result.responses,
|
|
1147
|
-
errors: result.errors
|
|
1148
|
-
};
|
|
1149
|
-
if (clusterDetails.dashboardUrl) {
|
|
1150
|
-
objects.cluster.dashboardUrl = clusterDetails.dashboardUrl;
|
|
1151
|
-
}
|
|
1152
|
-
if (clusterDetails.dashboardApp) {
|
|
1153
|
-
objects.cluster.dashboardApp = clusterDetails.dashboardApp;
|
|
1154
|
-
}
|
|
1155
|
-
if (clusterDetails.dashboardParameters) {
|
|
1156
|
-
objects.cluster.dashboardParameters = clusterDetails.dashboardParameters;
|
|
1157
|
-
}
|
|
1158
|
-
return objects;
|
|
1159
|
-
}
|
|
1160
|
-
async getMetricsForPods(clusterDetails, credential, labelSelector, result) {
|
|
1161
|
-
if (clusterDetails.skipMetricsLookup) {
|
|
1162
|
-
return [result, []];
|
|
1163
|
-
}
|
|
1164
|
-
const namespaces = new Set(
|
|
1165
|
-
result.responses.filter(isPodFetchResponse).flatMap((r) => r.resources).map((p) => p.metadata?.namespace).filter(isString)
|
|
1166
|
-
);
|
|
1167
|
-
if (namespaces.size === 0) {
|
|
1168
|
-
return [result, []];
|
|
1169
|
-
}
|
|
1170
|
-
const podMetrics = await this.fetcher.fetchPodMetricsByNamespaces(
|
|
1171
|
-
clusterDetails,
|
|
1172
|
-
credential,
|
|
1173
|
-
namespaces,
|
|
1174
|
-
labelSelector
|
|
1175
|
-
);
|
|
1176
|
-
result.errors.push(...podMetrics.errors);
|
|
1177
|
-
return [result, podMetrics.responses];
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
const isError = (fr) => fr.hasOwnProperty("errorType");
|
|
1182
|
-
function fetchResultsToResponseWrapper(results) {
|
|
1183
|
-
const groupBy = lodash__default.default.groupBy(results, (value) => {
|
|
1184
|
-
return isError(value) ? "errors" : "responses";
|
|
1185
|
-
});
|
|
1186
|
-
return {
|
|
1187
|
-
errors: groupBy.errors ?? [],
|
|
1188
|
-
responses: groupBy.responses ?? []
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
const statusCodeToErrorType = (statusCode) => {
|
|
1192
|
-
switch (statusCode) {
|
|
1193
|
-
case 400:
|
|
1194
|
-
return "BAD_REQUEST";
|
|
1195
|
-
case 401:
|
|
1196
|
-
return "UNAUTHORIZED_ERROR";
|
|
1197
|
-
case 404:
|
|
1198
|
-
return "NOT_FOUND";
|
|
1199
|
-
case 500:
|
|
1200
|
-
return "SYSTEM_ERROR";
|
|
1201
|
-
default:
|
|
1202
|
-
return "UNKNOWN_ERROR";
|
|
1203
|
-
}
|
|
1204
|
-
};
|
|
1205
|
-
class KubernetesClientBasedFetcher {
|
|
1206
|
-
logger;
|
|
1207
|
-
constructor({ logger }) {
|
|
1208
|
-
this.logger = logger;
|
|
1209
|
-
}
|
|
1210
|
-
fetchObjectsForService(params) {
|
|
1211
|
-
const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map(
|
|
1212
|
-
({ objectType, group, apiVersion, plural }) => this.fetchResource(
|
|
1213
|
-
params.clusterDetails,
|
|
1214
|
-
params.credential,
|
|
1215
|
-
group,
|
|
1216
|
-
apiVersion,
|
|
1217
|
-
plural,
|
|
1218
|
-
params.namespace,
|
|
1219
|
-
params.labelSelector
|
|
1220
|
-
).then(
|
|
1221
|
-
(r) => r.ok ? r.json().then(
|
|
1222
|
-
({ kind, items }) => ({
|
|
1223
|
-
type: objectType,
|
|
1224
|
-
resources: objectType === "customresources" ? items.map((item) => ({
|
|
1225
|
-
...item,
|
|
1226
|
-
kind: kind.replace(/(List)$/, "")
|
|
1227
|
-
})) : items
|
|
1228
|
-
})
|
|
1229
|
-
) : this.handleUnsuccessfulResponse(params.clusterDetails.name, r)
|
|
1230
|
-
)
|
|
1231
|
-
);
|
|
1232
|
-
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
|
|
1233
|
-
}
|
|
1234
|
-
fetchPodMetricsByNamespaces(clusterDetails, credential, namespaces, labelSelector) {
|
|
1235
|
-
const fetchResults = Array.from(namespaces).map(async (ns) => {
|
|
1236
|
-
const [podMetrics, podList] = await Promise.all([
|
|
1237
|
-
this.fetchResource(
|
|
1238
|
-
clusterDetails,
|
|
1239
|
-
credential,
|
|
1240
|
-
"metrics.k8s.io",
|
|
1241
|
-
"v1beta1",
|
|
1242
|
-
"pods",
|
|
1243
|
-
ns,
|
|
1244
|
-
labelSelector
|
|
1245
|
-
),
|
|
1246
|
-
this.fetchResource(
|
|
1247
|
-
clusterDetails,
|
|
1248
|
-
credential,
|
|
1249
|
-
"",
|
|
1250
|
-
"v1",
|
|
1251
|
-
"pods",
|
|
1252
|
-
ns,
|
|
1253
|
-
labelSelector
|
|
1254
|
-
)
|
|
1255
|
-
]);
|
|
1256
|
-
if (podMetrics.ok && podList.ok) {
|
|
1257
|
-
return clientNode.topPods(
|
|
1258
|
-
{
|
|
1259
|
-
listPodForAllNamespaces: () => podList.json().then((b) => ({ body: b }))
|
|
1260
|
-
},
|
|
1261
|
-
{
|
|
1262
|
-
getPodMetrics: () => podMetrics.json()
|
|
1263
|
-
}
|
|
1264
|
-
).then(
|
|
1265
|
-
(resources) => ({
|
|
1266
|
-
type: "podstatus",
|
|
1267
|
-
resources
|
|
1268
|
-
})
|
|
1269
|
-
);
|
|
1270
|
-
} else if (podMetrics.ok) {
|
|
1271
|
-
return this.handleUnsuccessfulResponse(clusterDetails.name, podList);
|
|
1272
|
-
}
|
|
1273
|
-
return this.handleUnsuccessfulResponse(clusterDetails.name, podMetrics);
|
|
1274
|
-
});
|
|
1275
|
-
return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
|
|
1276
|
-
}
|
|
1277
|
-
async handleUnsuccessfulResponse(clusterName, res) {
|
|
1278
|
-
const resourcePath = new URL(res.url).pathname;
|
|
1279
|
-
this.logger.warn(
|
|
1280
|
-
`Received ${res.status} status when fetching "${resourcePath}" from cluster "${clusterName}"; body=[${await res.text()}]`
|
|
1281
|
-
);
|
|
1282
|
-
return {
|
|
1283
|
-
errorType: statusCodeToErrorType(res.status),
|
|
1284
|
-
statusCode: res.status,
|
|
1285
|
-
resourcePath
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
fetchResource(clusterDetails, credential, group, apiVersion, plural, namespace, labelSelector) {
|
|
1289
|
-
const encode = (s) => encodeURIComponent(s);
|
|
1290
|
-
let resourcePath = group ? `/apis/${encode(group)}/${encode(apiVersion)}` : `/api/${encode(apiVersion)}`;
|
|
1291
|
-
if (namespace) {
|
|
1292
|
-
resourcePath += `/namespaces/${encode(namespace)}`;
|
|
1293
|
-
}
|
|
1294
|
-
resourcePath += `/${encode(plural)}`;
|
|
1295
|
-
let url;
|
|
1296
|
-
let requestInit;
|
|
1297
|
-
const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
1298
|
-
if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
|
|
1299
|
-
[url, requestInit] = this.fetchArgsInCluster(credential);
|
|
1300
|
-
} else if (!this.isCredentialMissing(authProvider, credential)) {
|
|
1301
|
-
[url, requestInit] = this.fetchArgs(clusterDetails, credential);
|
|
1302
|
-
} else {
|
|
1303
|
-
return Promise.reject(
|
|
1304
|
-
new Error(
|
|
1305
|
-
`no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`
|
|
1306
|
-
)
|
|
1307
|
-
);
|
|
1308
|
-
}
|
|
1309
|
-
if (url.pathname === "/") {
|
|
1310
|
-
url.pathname = resourcePath;
|
|
1311
|
-
} else {
|
|
1312
|
-
url.pathname += resourcePath;
|
|
1313
|
-
}
|
|
1314
|
-
if (labelSelector) {
|
|
1315
|
-
url.search = `labelSelector=${encode(labelSelector)}`;
|
|
1316
|
-
}
|
|
1317
|
-
return fetch__default.default(url, requestInit);
|
|
1318
|
-
}
|
|
1319
|
-
isServiceAccountAuthentication(authProvider, clusterDetails) {
|
|
1320
|
-
return authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default.default.pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH);
|
|
1321
|
-
}
|
|
1322
|
-
isCredentialMissing(authProvider, credential) {
|
|
1323
|
-
return authProvider !== "localKubectlProxy" && credential.type === "anonymous";
|
|
1324
|
-
}
|
|
1325
|
-
fetchArgs(clusterDetails, credential) {
|
|
1326
|
-
const requestInit = {
|
|
1327
|
-
method: "GET",
|
|
1328
|
-
headers: {
|
|
1329
|
-
Accept: "application/json",
|
|
1330
|
-
"Content-Type": "application/json",
|
|
1331
|
-
...credential.type === "bearer token" && {
|
|
1332
|
-
Authorization: `Bearer ${credential.token}`
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
};
|
|
1336
|
-
const url = new URL(clusterDetails.url);
|
|
1337
|
-
if (url.protocol === "https:") {
|
|
1338
|
-
requestInit.agent = new https__namespace.Agent({
|
|
1339
|
-
ca: clientNode.bufferFromFileOrString(
|
|
1340
|
-
clusterDetails.caFile,
|
|
1341
|
-
clusterDetails.caData
|
|
1342
|
-
) ?? void 0,
|
|
1343
|
-
rejectUnauthorized: !clusterDetails.skipTLSVerify,
|
|
1344
|
-
...credential.type === "x509 client certificate" && {
|
|
1345
|
-
cert: credential.cert,
|
|
1346
|
-
key: credential.key
|
|
1347
|
-
}
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
return [url, requestInit];
|
|
1351
|
-
}
|
|
1352
|
-
fetchArgsInCluster(credential) {
|
|
1353
|
-
const requestInit = {
|
|
1354
|
-
method: "GET",
|
|
1355
|
-
headers: {
|
|
1356
|
-
Accept: "application/json",
|
|
1357
|
-
"Content-Type": "application/json",
|
|
1358
|
-
...credential.type === "bearer token" && {
|
|
1359
|
-
Authorization: `Bearer ${credential.token}`
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
};
|
|
1363
|
-
const kc = new clientNode.KubeConfig();
|
|
1364
|
-
kc.loadFromCluster();
|
|
1365
|
-
const cluster = kc.getCurrentCluster();
|
|
1366
|
-
const url = new URL(cluster.server);
|
|
1367
|
-
if (url.protocol === "https:") {
|
|
1368
|
-
requestInit.agent = new https__namespace.Agent({
|
|
1369
|
-
ca: fs__default.default.readFileSync(cluster.caFile)
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
return [url, requestInit];
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
const HEADER_KUBERNETES_CLUSTER = "Backstage-Kubernetes-Cluster";
|
|
1377
|
-
const HEADER_KUBERNETES_AUTH = "Backstage-Kubernetes-Authorization";
|
|
1378
|
-
class KubernetesProxy {
|
|
1379
|
-
middlewareForClusterName = /* @__PURE__ */ new Map();
|
|
1380
|
-
logger;
|
|
1381
|
-
clusterSupplier;
|
|
1382
|
-
authStrategy;
|
|
1383
|
-
httpAuth;
|
|
1384
|
-
constructor(options) {
|
|
1385
|
-
this.logger = options.logger;
|
|
1386
|
-
this.clusterSupplier = options.clusterSupplier;
|
|
1387
|
-
this.authStrategy = options.authStrategy;
|
|
1388
|
-
const legacy = backendCommon.createLegacyAuthAdapters({
|
|
1389
|
-
discovery: options.discovery,
|
|
1390
|
-
httpAuth: options.httpAuth
|
|
1391
|
-
});
|
|
1392
|
-
this.httpAuth = legacy.httpAuth;
|
|
1393
|
-
}
|
|
1394
|
-
createRequestHandler(options) {
|
|
1395
|
-
const { permissionApi } = options;
|
|
1396
|
-
return async (req, res, next) => {
|
|
1397
|
-
const authorizeResponse = await permissionApi.authorize(
|
|
1398
|
-
[{ permission: pluginKubernetesCommon.kubernetesProxyPermission }],
|
|
1399
|
-
{
|
|
1400
|
-
credentials: await this.httpAuth.credentials(req)
|
|
1401
|
-
}
|
|
1402
|
-
);
|
|
1403
|
-
const auth = authorizeResponse[0];
|
|
1404
|
-
if (auth.result === pluginPermissionCommon.AuthorizeResult.DENY) {
|
|
1405
|
-
res.status(403).json({ error: new errors.NotAllowedError("Unauthorized") });
|
|
1406
|
-
return;
|
|
1407
|
-
}
|
|
1408
|
-
const middleware = await this.getMiddleware(req);
|
|
1409
|
-
if (req.header("connection")?.toLowerCase() === "upgrade" && req.header("upgrade")?.toLowerCase() === "websocket") {
|
|
1410
|
-
middleware.upgrade(req, req.socket, void 0);
|
|
1411
|
-
} else {
|
|
1412
|
-
middleware(req, res, next);
|
|
1413
|
-
}
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
// We create one middleware per remote cluster and hold on to them, because
|
|
1417
|
-
// the secure property isn't possible to decide on a per-request basis with a
|
|
1418
|
-
// single middleware instance - and we don't expect it to change over time.
|
|
1419
|
-
async getMiddleware(originalReq) {
|
|
1420
|
-
const originalCluster = await this.getClusterForRequest(originalReq);
|
|
1421
|
-
let middleware = this.middlewareForClusterName.get(originalCluster.name);
|
|
1422
|
-
if (!middleware) {
|
|
1423
|
-
const logger = this.logger.child({ cluster: originalCluster.name });
|
|
1424
|
-
middleware = httpProxyMiddleware.createProxyMiddleware({
|
|
1425
|
-
// TODO: Add 'log' to LoggerService
|
|
1426
|
-
logProvider: () => backendCommon.loggerToWinstonLogger(logger),
|
|
1427
|
-
ws: true,
|
|
1428
|
-
secure: !originalCluster.skipTLSVerify,
|
|
1429
|
-
changeOrigin: true,
|
|
1430
|
-
pathRewrite: async (path, req) => {
|
|
1431
|
-
const cluster = await this.getClusterForRequest(req);
|
|
1432
|
-
const url = new URL(cluster.url);
|
|
1433
|
-
return path.replace(
|
|
1434
|
-
new RegExp(`^${originalReq.baseUrl}`),
|
|
1435
|
-
url.pathname || ""
|
|
1436
|
-
);
|
|
1437
|
-
},
|
|
1438
|
-
router: async (req) => {
|
|
1439
|
-
const cluster = await this.getClusterForRequest(req);
|
|
1440
|
-
const url = new URL(cluster.url);
|
|
1441
|
-
const target = {
|
|
1442
|
-
protocol: url.protocol,
|
|
1443
|
-
host: url.hostname,
|
|
1444
|
-
port: url.port,
|
|
1445
|
-
ca: clientNode.bufferFromFileOrString(
|
|
1446
|
-
cluster.caFile,
|
|
1447
|
-
cluster.caData
|
|
1448
|
-
)?.toString()
|
|
1449
|
-
};
|
|
1450
|
-
const authHeader = req.headers[HEADER_KUBERNETES_AUTH.toLocaleLowerCase("en-US")];
|
|
1451
|
-
if (typeof authHeader === "string") {
|
|
1452
|
-
req.headers.authorization = authHeader;
|
|
1453
|
-
} else {
|
|
1454
|
-
const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
|
|
1455
|
-
req.headers
|
|
1456
|
-
);
|
|
1457
|
-
const credential = await this.getClusterForRequest(req).then((cd) => {
|
|
1458
|
-
return this.authStrategy.getCredential(cd, authObj);
|
|
1459
|
-
});
|
|
1460
|
-
if (credential.type === "bearer token") {
|
|
1461
|
-
req.headers.authorization = `Bearer ${credential.token}`;
|
|
1462
|
-
} else if (credential.type === "x509 client certificate") {
|
|
1463
|
-
target.key = credential.key;
|
|
1464
|
-
target.cert = credential.cert;
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
return target;
|
|
1468
|
-
},
|
|
1469
|
-
onError: (error, req, res) => {
|
|
1470
|
-
const wrappedError = new errors.ForwardedError(
|
|
1471
|
-
`Cluster '${originalCluster.name}' request error`,
|
|
1472
|
-
error
|
|
1473
|
-
);
|
|
1474
|
-
logger.error("Kubernetes proxy error", wrappedError);
|
|
1475
|
-
const body = {
|
|
1476
|
-
error: errors.serializeError(wrappedError, {
|
|
1477
|
-
includeStack: process.env.NODE_ENV === "development"
|
|
1478
|
-
}),
|
|
1479
|
-
request: { method: req.method, url: req.originalUrl },
|
|
1480
|
-
response: { statusCode: 500 }
|
|
1481
|
-
};
|
|
1482
|
-
res.status(500).json(body);
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1485
|
-
this.middlewareForClusterName.set(originalCluster.name, middleware);
|
|
1486
|
-
}
|
|
1487
|
-
return middleware;
|
|
1488
|
-
}
|
|
1489
|
-
async getClusterForRequest(req) {
|
|
1490
|
-
const clusterName = req.headers[HEADER_KUBERNETES_CLUSTER.toLowerCase()];
|
|
1491
|
-
const clusters = await this.clusterSupplier.getClusters({
|
|
1492
|
-
credentials: await this.httpAuth.credentials(req)
|
|
1493
|
-
});
|
|
1494
|
-
if (!clusters || clusters.length <= 0) {
|
|
1495
|
-
throw new errors.NotFoundError(`No Clusters configured`);
|
|
1496
|
-
}
|
|
1497
|
-
const hasClusterNameHeader = typeof clusterName === "string" && clusterName.length > 0;
|
|
1498
|
-
let cluster;
|
|
1499
|
-
if (hasClusterNameHeader) {
|
|
1500
|
-
cluster = clusters.find((c) => c.name === clusterName);
|
|
1501
|
-
} else if (clusters.length === 1) {
|
|
1502
|
-
cluster = clusters.at(0);
|
|
1503
|
-
}
|
|
1504
|
-
if (!cluster) {
|
|
1505
|
-
throw new errors.NotFoundError(`Cluster '${clusterName}' not found`);
|
|
1506
|
-
}
|
|
1507
|
-
const authProvider = cluster.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
1508
|
-
if (authProvider === "serviceAccount" && fs__default.default.pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH) && !cluster.authMetadata.serviceAccountToken) {
|
|
1509
|
-
const kc = new clientNode.KubeConfig();
|
|
1510
|
-
kc.loadFromCluster();
|
|
1511
|
-
const clusterFromKubeConfig = kc.getCurrentCluster();
|
|
1512
|
-
const url = new URL(clusterFromKubeConfig.server);
|
|
1513
|
-
cluster.url = clusterFromKubeConfig.server;
|
|
1514
|
-
if (url.protocol === "https:") {
|
|
1515
|
-
cluster.caFile = clusterFromKubeConfig.caFile;
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
return cluster;
|
|
1519
|
-
}
|
|
1520
|
-
static authHeadersToKubernetesRequestAuth(originalHeaders) {
|
|
1521
|
-
return Object.keys(originalHeaders).filter((header) => header.startsWith("backstage-kubernetes-authorization")).map(
|
|
1522
|
-
(header) => KubernetesProxy.headerToDictionary(header, originalHeaders)
|
|
1523
|
-
).filter((headerAsDic) => Object.keys(headerAsDic).length !== 0).reduce(KubernetesProxy.combineHeaders, {});
|
|
1524
|
-
}
|
|
1525
|
-
static headerToDictionary(header, originalHeaders) {
|
|
1526
|
-
const obj = {};
|
|
1527
|
-
const headerSplitted = header.split("-");
|
|
1528
|
-
if (headerSplitted.length >= 4) {
|
|
1529
|
-
const framework = headerSplitted[3].toLowerCase();
|
|
1530
|
-
if (headerSplitted.length >= 5) {
|
|
1531
|
-
const provider = headerSplitted.slice(4).join("-").toLowerCase();
|
|
1532
|
-
obj[framework] = { [provider]: originalHeaders[header] };
|
|
1533
|
-
} else {
|
|
1534
|
-
obj[framework] = originalHeaders[header];
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return obj;
|
|
1538
|
-
}
|
|
1539
|
-
static combineHeaders(authObj, header) {
|
|
1540
|
-
const framework = Object.keys(header)[0];
|
|
1541
|
-
if (authObj[framework]) {
|
|
1542
|
-
authObj[framework] = {
|
|
1543
|
-
...authObj[framework],
|
|
1544
|
-
...header[framework]
|
|
1545
|
-
};
|
|
1546
|
-
} else {
|
|
1547
|
-
authObj[framework] = header[framework];
|
|
1548
|
-
}
|
|
1549
|
-
return authObj;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
class KubernetesBuilder {
|
|
1554
|
-
constructor(env) {
|
|
1555
|
-
this.env = env;
|
|
1556
|
-
}
|
|
1557
|
-
clusterSupplier;
|
|
1558
|
-
defaultClusterRefreshInterval = luxon.Duration.fromObject({
|
|
1559
|
-
minutes: 60
|
|
1560
|
-
});
|
|
1561
|
-
objectsProvider;
|
|
1562
|
-
fetcher;
|
|
1563
|
-
serviceLocator;
|
|
1564
|
-
proxy;
|
|
1565
|
-
authStrategyMap;
|
|
1566
|
-
static createBuilder(env) {
|
|
1567
|
-
return new KubernetesBuilder(env);
|
|
1568
|
-
}
|
|
1569
|
-
async build() {
|
|
1570
|
-
const logger = this.env.logger;
|
|
1571
|
-
const config = this.env.config;
|
|
1572
|
-
const permissions = this.env.permissions;
|
|
1573
|
-
logger.info("Initializing Kubernetes backend");
|
|
1574
|
-
if (!config.has("kubernetes")) {
|
|
1575
|
-
if (process.env.NODE_ENV !== "development") {
|
|
1576
|
-
throw new Error("Kubernetes configuration is missing");
|
|
1577
|
-
}
|
|
1578
|
-
logger.warn(
|
|
1579
|
-
"Failed to initialize kubernetes backend: kubernetes config is missing"
|
|
1580
|
-
);
|
|
1581
|
-
return {
|
|
1582
|
-
router: Router__default.default()
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
const { auth, httpAuth } = backendCommon.createLegacyAuthAdapters({
|
|
1586
|
-
auth: this.env.auth,
|
|
1587
|
-
httpAuth: this.env.httpAuth,
|
|
1588
|
-
discovery: this.env.discovery
|
|
1589
|
-
});
|
|
1590
|
-
const customResources = this.buildCustomResources();
|
|
1591
|
-
const fetcher = this.getFetcher();
|
|
1592
|
-
const clusterSupplier = this.getClusterSupplier();
|
|
1593
|
-
const authStrategyMap = this.getAuthStrategyMap();
|
|
1594
|
-
const proxy = this.getProxy(
|
|
1595
|
-
logger,
|
|
1596
|
-
clusterSupplier,
|
|
1597
|
-
this.env.discovery,
|
|
1598
|
-
httpAuth
|
|
1599
|
-
);
|
|
1600
|
-
const serviceLocator = this.getServiceLocator();
|
|
1601
|
-
const objectsProvider = this.getObjectsProvider({
|
|
1602
|
-
logger,
|
|
1603
|
-
fetcher,
|
|
1604
|
-
config,
|
|
1605
|
-
serviceLocator,
|
|
1606
|
-
customResources,
|
|
1607
|
-
objectTypesToFetch: this.getObjectTypesToFetch()
|
|
1608
|
-
});
|
|
1609
|
-
const router = this.buildRouter(
|
|
1610
|
-
objectsProvider,
|
|
1611
|
-
clusterSupplier,
|
|
1612
|
-
this.env.catalogApi,
|
|
1613
|
-
proxy,
|
|
1614
|
-
permissions,
|
|
1615
|
-
auth,
|
|
1616
|
-
httpAuth
|
|
1617
|
-
);
|
|
1618
|
-
return {
|
|
1619
|
-
clusterSupplier,
|
|
1620
|
-
customResources,
|
|
1621
|
-
fetcher,
|
|
1622
|
-
proxy,
|
|
1623
|
-
objectsProvider,
|
|
1624
|
-
router,
|
|
1625
|
-
serviceLocator,
|
|
1626
|
-
authStrategyMap
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
setClusterSupplier(clusterSupplier) {
|
|
1630
|
-
this.clusterSupplier = clusterSupplier;
|
|
1631
|
-
return this;
|
|
1632
|
-
}
|
|
1633
|
-
setDefaultClusterRefreshInterval(refreshInterval) {
|
|
1634
|
-
this.defaultClusterRefreshInterval = refreshInterval;
|
|
1635
|
-
return this;
|
|
1636
|
-
}
|
|
1637
|
-
setObjectsProvider(objectsProvider) {
|
|
1638
|
-
this.objectsProvider = objectsProvider;
|
|
1639
|
-
return this;
|
|
1640
|
-
}
|
|
1641
|
-
setFetcher(fetcher) {
|
|
1642
|
-
this.fetcher = fetcher;
|
|
1643
|
-
return this;
|
|
1644
|
-
}
|
|
1645
|
-
setServiceLocator(serviceLocator) {
|
|
1646
|
-
this.serviceLocator = serviceLocator;
|
|
1647
|
-
return this;
|
|
1648
|
-
}
|
|
1649
|
-
setProxy(proxy) {
|
|
1650
|
-
this.proxy = proxy;
|
|
1651
|
-
return this;
|
|
1652
|
-
}
|
|
1653
|
-
setAuthStrategyMap(authStrategyMap) {
|
|
1654
|
-
this.authStrategyMap = authStrategyMap;
|
|
1655
|
-
}
|
|
1656
|
-
addAuthStrategy(key, strategy) {
|
|
1657
|
-
if (key.includes("-")) {
|
|
1658
|
-
throw new Error("Strategy name can not include dashes");
|
|
1659
|
-
}
|
|
1660
|
-
this.getAuthStrategyMap()[key] = strategy;
|
|
1661
|
-
return this;
|
|
1662
|
-
}
|
|
1663
|
-
buildCustomResources() {
|
|
1664
|
-
const customResources = (this.env.config.getOptionalConfigArray("kubernetes.customResources") ?? []).map(
|
|
1665
|
-
(c) => ({
|
|
1666
|
-
group: c.getString("group"),
|
|
1667
|
-
apiVersion: c.getString("apiVersion"),
|
|
1668
|
-
plural: c.getString("plural"),
|
|
1669
|
-
objectType: "customresources"
|
|
1670
|
-
})
|
|
1671
|
-
);
|
|
1672
|
-
this.env.logger.info(
|
|
1673
|
-
`action=LoadingCustomResources numOfCustomResources=${customResources.length}`
|
|
1674
|
-
);
|
|
1675
|
-
return customResources;
|
|
1676
|
-
}
|
|
1677
|
-
buildClusterSupplier(refreshInterval) {
|
|
1678
|
-
const config = this.env.config;
|
|
1679
|
-
const { auth } = backendCommon.createLegacyAuthAdapters(this.env);
|
|
1680
|
-
this.clusterSupplier = getCombinedClusterSupplier(
|
|
1681
|
-
config,
|
|
1682
|
-
this.env.catalogApi,
|
|
1683
|
-
new DispatchStrategy({ authStrategyMap: this.getAuthStrategyMap() }),
|
|
1684
|
-
this.env.logger,
|
|
1685
|
-
refreshInterval,
|
|
1686
|
-
auth
|
|
1687
|
-
);
|
|
1688
|
-
return this.clusterSupplier;
|
|
1689
|
-
}
|
|
1690
|
-
buildObjectsProvider(options) {
|
|
1691
|
-
const authStrategyMap = this.getAuthStrategyMap();
|
|
1692
|
-
this.objectsProvider = new KubernetesFanOutHandler({
|
|
1693
|
-
...options,
|
|
1694
|
-
authStrategy: new DispatchStrategy({
|
|
1695
|
-
authStrategyMap
|
|
1696
|
-
})
|
|
1697
|
-
});
|
|
1698
|
-
return this.objectsProvider;
|
|
1699
|
-
}
|
|
1700
|
-
buildFetcher() {
|
|
1701
|
-
this.fetcher = new KubernetesClientBasedFetcher({
|
|
1702
|
-
logger: this.env.logger
|
|
1703
|
-
});
|
|
1704
|
-
return this.fetcher;
|
|
1705
|
-
}
|
|
1706
|
-
buildServiceLocator(method, clusterSupplier) {
|
|
1707
|
-
switch (method) {
|
|
1708
|
-
case "multiTenant":
|
|
1709
|
-
this.serviceLocator = this.buildMultiTenantServiceLocator(clusterSupplier);
|
|
1710
|
-
break;
|
|
1711
|
-
case "singleTenant":
|
|
1712
|
-
this.serviceLocator = this.buildSingleTenantServiceLocator(clusterSupplier);
|
|
1713
|
-
break;
|
|
1714
|
-
case "catalogRelation":
|
|
1715
|
-
this.serviceLocator = this.buildCatalogRelationServiceLocator(clusterSupplier);
|
|
1716
|
-
break;
|
|
1717
|
-
case "http":
|
|
1718
|
-
this.serviceLocator = this.buildHttpServiceLocator(clusterSupplier);
|
|
1719
|
-
break;
|
|
1720
|
-
default:
|
|
1721
|
-
throw new Error(
|
|
1722
|
-
`Unsupported kubernetes.serviceLocatorMethod "${method}"`
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
return this.serviceLocator;
|
|
1726
|
-
}
|
|
1727
|
-
buildMultiTenantServiceLocator(clusterSupplier) {
|
|
1728
|
-
return new MultiTenantServiceLocator(clusterSupplier);
|
|
1729
|
-
}
|
|
1730
|
-
buildSingleTenantServiceLocator(clusterSupplier) {
|
|
1731
|
-
return new SingleTenantServiceLocator(clusterSupplier);
|
|
1732
|
-
}
|
|
1733
|
-
buildCatalogRelationServiceLocator(clusterSupplier) {
|
|
1734
|
-
return new CatalogRelationServiceLocator(clusterSupplier);
|
|
1735
|
-
}
|
|
1736
|
-
buildHttpServiceLocator(_clusterSupplier) {
|
|
1737
|
-
throw new Error("not implemented");
|
|
1738
|
-
}
|
|
1739
|
-
buildProxy(logger, clusterSupplier, discovery, httpAuth) {
|
|
1740
|
-
const authStrategyMap = this.getAuthStrategyMap();
|
|
1741
|
-
const authStrategy = new DispatchStrategy({
|
|
1742
|
-
authStrategyMap
|
|
1743
|
-
});
|
|
1744
|
-
this.proxy = new KubernetesProxy({
|
|
1745
|
-
logger,
|
|
1746
|
-
clusterSupplier,
|
|
1747
|
-
authStrategy,
|
|
1748
|
-
discovery,
|
|
1749
|
-
httpAuth
|
|
1750
|
-
});
|
|
1751
|
-
return this.proxy;
|
|
1752
|
-
}
|
|
1753
|
-
buildRouter(objectsProvider, clusterSupplier, catalogApi, proxy, permissionApi, authService, httpAuth) {
|
|
1754
|
-
const logger = this.env.logger;
|
|
1755
|
-
const router = Router__default.default();
|
|
1756
|
-
router.use("/proxy", proxy.createRequestHandler({ permissionApi }));
|
|
1757
|
-
router.use(express__default.default.json());
|
|
1758
|
-
router.use(
|
|
1759
|
-
pluginPermissionNode.createPermissionIntegrationRouter({
|
|
1760
|
-
permissions: pluginKubernetesCommon.kubernetesPermissions
|
|
1761
|
-
})
|
|
1762
|
-
);
|
|
1763
|
-
router.post("/services/:serviceId", async (req, res) => {
|
|
1764
|
-
const serviceId = req.params.serviceId;
|
|
1765
|
-
const requestBody = req.body;
|
|
1766
|
-
try {
|
|
1767
|
-
const response = await objectsProvider.getKubernetesObjectsByEntity(
|
|
1768
|
-
{
|
|
1769
|
-
entity: requestBody.entity,
|
|
1770
|
-
auth: requestBody.auth || {}
|
|
1771
|
-
},
|
|
1772
|
-
{ credentials: await httpAuth.credentials(req) }
|
|
1773
|
-
);
|
|
1774
|
-
res.json(response);
|
|
1775
|
-
} catch (e) {
|
|
1776
|
-
logger.error(
|
|
1777
|
-
`action=retrieveObjectsByServiceId service=${serviceId}, error=${e}`
|
|
1778
|
-
);
|
|
1779
|
-
res.status(500).json({ error: e.message });
|
|
1780
|
-
}
|
|
1781
|
-
});
|
|
1782
|
-
router.get("/clusters", async (req, res) => {
|
|
1783
|
-
const credentials = await httpAuth.credentials(req);
|
|
1784
|
-
const clusterDetails = await this.fetchClusterDetails(clusterSupplier, {
|
|
1785
|
-
credentials
|
|
1786
|
-
});
|
|
1787
|
-
res.json({
|
|
1788
|
-
items: clusterDetails.map((cd) => {
|
|
1789
|
-
const oidcTokenProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
|
|
1790
|
-
const authProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
|
|
1791
|
-
const strategy = this.getAuthStrategyMap()[authProvider];
|
|
1792
|
-
let auth = {};
|
|
1793
|
-
if (strategy) {
|
|
1794
|
-
auth = strategy.presentAuthMetadata(cd.authMetadata);
|
|
1795
|
-
}
|
|
1796
|
-
return {
|
|
1797
|
-
name: cd.name,
|
|
1798
|
-
title: cd.title,
|
|
1799
|
-
dashboardUrl: cd.dashboardUrl,
|
|
1800
|
-
authProvider,
|
|
1801
|
-
...oidcTokenProvider && { oidcTokenProvider },
|
|
1802
|
-
...auth && Object.keys(auth).length !== 0 && { auth }
|
|
1803
|
-
};
|
|
1804
|
-
})
|
|
1805
|
-
});
|
|
1806
|
-
});
|
|
1807
|
-
addResourceRoutesToRouter(
|
|
1808
|
-
router,
|
|
1809
|
-
catalogApi,
|
|
1810
|
-
objectsProvider,
|
|
1811
|
-
authService,
|
|
1812
|
-
httpAuth
|
|
1813
|
-
);
|
|
1814
|
-
return router;
|
|
1815
|
-
}
|
|
1816
|
-
buildAuthStrategyMap() {
|
|
1817
|
-
this.authStrategyMap = {
|
|
1818
|
-
aks: new AksStrategy(),
|
|
1819
|
-
aws: new AwsIamStrategy({ config: this.env.config }),
|
|
1820
|
-
azure: new AzureIdentityStrategy(this.env.logger),
|
|
1821
|
-
google: new GoogleStrategy(),
|
|
1822
|
-
googleServiceAccount: new GoogleServiceAccountStrategy(),
|
|
1823
|
-
localKubectlProxy: new AnonymousStrategy(),
|
|
1824
|
-
oidc: new OidcStrategy(),
|
|
1825
|
-
serviceAccount: new ServiceAccountStrategy()
|
|
1826
|
-
};
|
|
1827
|
-
return this.authStrategyMap;
|
|
1828
|
-
}
|
|
1829
|
-
async fetchClusterDetails(clusterSupplier, options) {
|
|
1830
|
-
const clusterDetails = await clusterSupplier.getClusters(options);
|
|
1831
|
-
this.env.logger.info(
|
|
1832
|
-
`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`
|
|
1833
|
-
);
|
|
1834
|
-
return clusterDetails;
|
|
1835
|
-
}
|
|
1836
|
-
getServiceLocatorMethod() {
|
|
1837
|
-
return this.env.config.getString(
|
|
1838
|
-
"kubernetes.serviceLocatorMethod.type"
|
|
1839
|
-
);
|
|
1840
|
-
}
|
|
1841
|
-
getFetcher() {
|
|
1842
|
-
return this.fetcher ?? this.buildFetcher();
|
|
1843
|
-
}
|
|
1844
|
-
getClusterSupplier() {
|
|
1845
|
-
return this.clusterSupplier ?? this.buildClusterSupplier(this.defaultClusterRefreshInterval);
|
|
1846
|
-
}
|
|
1847
|
-
getServiceLocator() {
|
|
1848
|
-
return this.serviceLocator ?? this.buildServiceLocator(
|
|
1849
|
-
this.getServiceLocatorMethod(),
|
|
1850
|
-
this.getClusterSupplier()
|
|
1851
|
-
);
|
|
1852
|
-
}
|
|
1853
|
-
getObjectsProvider(options) {
|
|
1854
|
-
return this.objectsProvider ?? this.buildObjectsProvider(options);
|
|
1855
|
-
}
|
|
1856
|
-
getObjectTypesToFetch() {
|
|
1857
|
-
const objectTypesToFetchStrings = this.env.config.getOptionalStringArray(
|
|
1858
|
-
"kubernetes.objectTypes"
|
|
1859
|
-
);
|
|
1860
|
-
const apiVersionOverrides = this.env.config.getOptionalConfig(
|
|
1861
|
-
"kubernetes.apiVersionOverrides"
|
|
1862
|
-
);
|
|
1863
|
-
let objectTypesToFetch;
|
|
1864
|
-
if (objectTypesToFetchStrings) {
|
|
1865
|
-
objectTypesToFetch = DEFAULT_OBJECTS.filter(
|
|
1866
|
-
(obj) => objectTypesToFetchStrings.includes(obj.objectType)
|
|
1867
|
-
);
|
|
1868
|
-
}
|
|
1869
|
-
if (apiVersionOverrides) {
|
|
1870
|
-
objectTypesToFetch = objectTypesToFetch ?? DEFAULT_OBJECTS;
|
|
1871
|
-
for (const obj of objectTypesToFetch) {
|
|
1872
|
-
if (apiVersionOverrides.has(obj.objectType)) {
|
|
1873
|
-
obj.apiVersion = apiVersionOverrides.getString(obj.objectType);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return objectTypesToFetch;
|
|
1878
|
-
}
|
|
1879
|
-
getProxy(logger, clusterSupplier, discovery, httpAuth) {
|
|
1880
|
-
return this.proxy ?? this.buildProxy(logger, clusterSupplier, discovery, httpAuth);
|
|
1881
|
-
}
|
|
1882
|
-
getAuthStrategyMap() {
|
|
1883
|
-
return this.authStrategyMap ?? this.buildAuthStrategyMap();
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
async function createRouter(options) {
|
|
1888
|
-
const { router } = await KubernetesBuilder.createBuilder(options).setClusterSupplier(options.clusterSupplier).build();
|
|
1889
|
-
return router;
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
exports.AksStrategy = AksStrategy;
|
|
1893
|
-
exports.AnonymousStrategy = AnonymousStrategy;
|
|
1894
|
-
exports.AwsIamStrategy = AwsIamStrategy;
|
|
1895
|
-
exports.AzureIdentityStrategy = AzureIdentityStrategy;
|
|
1896
|
-
exports.DEFAULT_OBJECTS = DEFAULT_OBJECTS;
|
|
1897
|
-
exports.DispatchStrategy = DispatchStrategy;
|
|
1898
|
-
exports.GoogleServiceAccountStrategy = GoogleServiceAccountStrategy;
|
|
1899
|
-
exports.GoogleStrategy = GoogleStrategy;
|
|
1900
|
-
exports.HEADER_KUBERNETES_AUTH = HEADER_KUBERNETES_AUTH;
|
|
1901
|
-
exports.HEADER_KUBERNETES_CLUSTER = HEADER_KUBERNETES_CLUSTER;
|
|
1902
|
-
exports.KubernetesBuilder = KubernetesBuilder;
|
|
1903
|
-
exports.KubernetesProxy = KubernetesProxy;
|
|
1904
|
-
exports.OidcStrategy = OidcStrategy;
|
|
1905
|
-
exports.ServiceAccountStrategy = ServiceAccountStrategy;
|
|
1906
|
-
exports.createRouter = createRouter;
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var plugin = require('./plugin.cjs.js');
|
|
6
|
+
var AksStrategy = require('./auth/AksStrategy.cjs.js');
|
|
7
|
+
var AnonymousStrategy = require('./auth/AnonymousStrategy.cjs.js');
|
|
8
|
+
var AwsIamStrategy = require('./auth/AwsIamStrategy.cjs.js');
|
|
9
|
+
var AzureIdentityStrategy = require('./auth/AzureIdentityStrategy.cjs.js');
|
|
10
|
+
var GoogleStrategy = require('./auth/GoogleStrategy.cjs.js');
|
|
11
|
+
var GoogleServiceAccountStrategy = require('./auth/GoogleServiceAccountStrategy.cjs.js');
|
|
12
|
+
var DispatchStrategy = require('./auth/DispatchStrategy.cjs.js');
|
|
13
|
+
var ServiceAccountStrategy = require('./auth/ServiceAccountStrategy.cjs.js');
|
|
14
|
+
var OidcStrategy = require('./auth/OidcStrategy.cjs.js');
|
|
15
|
+
var KubernetesBuilder = require('./service/KubernetesBuilder.cjs.js');
|
|
16
|
+
var KubernetesFanOutHandler = require('./service/KubernetesFanOutHandler.cjs.js');
|
|
17
|
+
var KubernetesProxy = require('./service/KubernetesProxy.cjs.js');
|
|
18
|
+
var router = require('./service/router.cjs.js');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
exports.default = plugin.kubernetesPlugin;
|
|
23
|
+
exports.AksStrategy = AksStrategy.AksStrategy;
|
|
24
|
+
exports.AnonymousStrategy = AnonymousStrategy.AnonymousStrategy;
|
|
25
|
+
exports.AwsIamStrategy = AwsIamStrategy.AwsIamStrategy;
|
|
26
|
+
exports.AzureIdentityStrategy = AzureIdentityStrategy.AzureIdentityStrategy;
|
|
27
|
+
exports.GoogleStrategy = GoogleStrategy.GoogleStrategy;
|
|
28
|
+
exports.GoogleServiceAccountStrategy = GoogleServiceAccountStrategy.GoogleServiceAccountStrategy;
|
|
29
|
+
exports.DispatchStrategy = DispatchStrategy.DispatchStrategy;
|
|
30
|
+
exports.ServiceAccountStrategy = ServiceAccountStrategy.ServiceAccountStrategy;
|
|
31
|
+
exports.OidcStrategy = OidcStrategy.OidcStrategy;
|
|
32
|
+
exports.KubernetesBuilder = KubernetesBuilder.KubernetesBuilder;
|
|
33
|
+
exports.DEFAULT_OBJECTS = KubernetesFanOutHandler.DEFAULT_OBJECTS;
|
|
34
|
+
exports.HEADER_KUBERNETES_AUTH = KubernetesProxy.HEADER_KUBERNETES_AUTH;
|
|
35
|
+
exports.HEADER_KUBERNETES_CLUSTER = KubernetesProxy.HEADER_KUBERNETES_CLUSTER;
|
|
36
|
+
exports.KubernetesProxy = KubernetesProxy.KubernetesProxy;
|
|
37
|
+
exports.createRouter = router.createRouter;
|
|
1907
38
|
//# sourceMappingURL=index.cjs.js.map
|