@platform-mesh/portal-server-lib 0.5.44 → 0.5.46

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.
@@ -1,18 +1,33 @@
1
+ import { K8sRequestContext, K8sResourceDescriptor } from '../models/k8s.js';
1
2
  import { getOrganization } from '../utils/domain.js';
2
- import { CustomObjectsApi, KubeConfig } from '@kubernetes/client-node';
3
- import { Injectable } from '@nestjs/common';
3
+ import {
4
+ CoreV1Api,
5
+ CustomObjectsApi,
6
+ KubeConfig,
7
+ } from '@kubernetes/client-node';
8
+ import { PromiseMiddlewareWrapper } from '@kubernetes/client-node/dist/gen/middleware.js';
9
+ import { Injectable, Logger } from '@nestjs/common';
4
10
  import type { Request } from 'express';
5
11
 
6
12
  @Injectable()
7
13
  export class KcpKubernetesService {
8
- private readonly k8sApi: CustomObjectsApi;
9
- private readonly baseUrl: URL;
14
+ private logger: Logger = new Logger(KcpKubernetesService.name);
15
+
16
+ private readonly kubeConfigKcp = process.env.KUBECONFIG_KCP;
17
+ private k8sCustomObjectsApiOIDCUser: CustomObjectsApi;
18
+ private k8sCustomObjectsApi: CustomObjectsApi;
19
+ private k8sCoreV1Api: CoreV1Api;
20
+ private baseUrl: URL;
10
21
 
11
22
  constructor() {
12
- const kubeConfigKcp = process.env.KUBECONFIG_KCP;
23
+ this.createK8sCustomObjectsApiForOIDCUser();
24
+ this.createK8sCustomObjectsApi();
25
+ this.createKcpK8sCoreV1Api();
26
+ }
27
+
28
+ private createK8sCustomObjectsApiForOIDCUser() {
13
29
  const kc = new KubeConfig();
14
- kc.loadFromFile(kubeConfigKcp);
15
- // Temporary change to test.
30
+ kc.loadFromFile(this.kubeConfigKcp);
16
31
  kc.addUser({
17
32
  name: 'oidc',
18
33
  });
@@ -23,15 +38,38 @@ export class KcpKubernetesService {
23
38
  });
24
39
  kc.setCurrentContext('oidc');
25
40
  this.baseUrl = new URL(kc.getCurrentCluster()?.server || '');
26
- this.k8sApi = kc.makeApiClient(CustomObjectsApi);
41
+ this.k8sCustomObjectsApiOIDCUser = kc.makeApiClient(CustomObjectsApi);
42
+ }
43
+
44
+ private createK8sCustomObjectsApi() {
45
+ const kc = new KubeConfig();
46
+ kc.loadFromFile(this.kubeConfigKcp);
47
+ this.k8sCustomObjectsApi = kc.makeApiClient(CustomObjectsApi);
48
+ }
49
+
50
+ private createKcpK8sCoreV1Api() {
51
+ const kc = new KubeConfig();
52
+ kc.loadFromFile(this.kubeConfigKcp);
53
+ this.k8sCoreV1Api = kc.makeApiClient(CoreV1Api);
54
+ }
55
+
56
+ getKcpK8sCustomObjectsApiOIDCUser() {
57
+ return this.k8sCustomObjectsApiOIDCUser;
27
58
  }
28
59
 
29
- getKcpK8sApiClient() {
30
- return this.k8sApi;
60
+ getKcpK8sCustomObjectsApi() {
61
+ return this.k8sCustomObjectsApi;
31
62
  }
32
63
 
33
- private buildWorkspacePath(organization: string, account?: string) {
34
- let path = `root:orgs:${organization}`;
64
+ getKcpK8sCoreV1Api() {
65
+ return this.k8sCoreV1Api;
66
+ }
67
+
68
+ private buildWorkspacePath(organization?: string, account?: string) {
69
+ let path = `root:orgs`;
70
+ if (organization) {
71
+ path += `:${organization}`;
72
+ }
35
73
  if (account) {
36
74
  path += `:${account}`; // FIXME: how are nested accounts and paths handled in the portal?
37
75
  }
@@ -46,7 +84,7 @@ export class KcpKubernetesService {
46
84
  );
47
85
  }
48
86
 
49
- getKcpWorkspaceUrl(organization: string, account: string) {
87
+ getKcpWorkspaceUrl(organization?: string, account?: string) {
50
88
  const path = this.buildWorkspacePath(organization, account);
51
89
  return new URL(`${this.baseUrl.origin}/clusters/${path}`);
52
90
  }
@@ -75,4 +113,100 @@ export class KcpKubernetesService {
75
113
  portFromRequest === '80' || portFromRequest === '443' || !portFromRequest;
76
114
  return isStandardOrEmptyPort ? '' : `:${portFromRequest}`;
77
115
  }
116
+
117
+ public async listClusterCustomObject(
118
+ gvr: K8sResourceDescriptor,
119
+ requestContext: K8sRequestContext,
120
+ ) {
121
+ return await this.k8sCustomObjectsApi.listClusterCustomObject(gvr, {
122
+ middleware: [
123
+ new PromiseMiddlewareWrapper({
124
+ pre: async (context) => {
125
+ const accountPath =
126
+ requestContext?.accountPath ??
127
+ requestContext?.['core_platform-mesh_io_account'];
128
+
129
+ const kcpUrl = this.getKcpWorkspaceUrl(
130
+ requestContext.organization,
131
+ accountPath,
132
+ );
133
+ const path = `${kcpUrl}/apis/${gvr.group}/${gvr.version}/${gvr.plural}/${gvr.name}`;
134
+ this.logger.log(`kcp url: ${path}`);
135
+ context.setUrl(path);
136
+ return context;
137
+ },
138
+ post: async (context) => context,
139
+ }),
140
+ ],
141
+ });
142
+ }
143
+
144
+ public async listClusterCustomObjectInKcpVirtualWorkspace(
145
+ gvr: K8sResourceDescriptor,
146
+ requestContext: K8sRequestContext,
147
+ token: string,
148
+ ) {
149
+ return await this.k8sCustomObjectsApiOIDCUser.listClusterCustomObject(gvr, {
150
+ middleware: [
151
+ new PromiseMiddlewareWrapper({
152
+ pre: async (context) => {
153
+ const accountPath =
154
+ requestContext?.accountPath ??
155
+ requestContext?.['core_platform-mesh_io_account'];
156
+
157
+ const kcpUrl = this.getKcpVirtualWorkspaceUrl(
158
+ requestContext.organization,
159
+ accountPath,
160
+ );
161
+ const path = `${kcpUrl}/apis/${gvr.group}/${gvr.version}/${gvr.plural}`;
162
+ this.logger.log(`kcp url: ${path}`);
163
+
164
+ context.setUrl(path);
165
+ context.setHeaderParam('Authorization', `Bearer ${token}`);
166
+ return context;
167
+ },
168
+ post: async (context) => context,
169
+ }),
170
+ ],
171
+ });
172
+ }
173
+
174
+ public async getClientSecret(orgName: string) {
175
+ const secretName = `portal-client-secret-${orgName}-${orgName}`;
176
+ const namespace = 'default';
177
+
178
+ try {
179
+ const res = await this.k8sCoreV1Api.readNamespacedSecret(
180
+ {
181
+ namespace,
182
+ name: secretName,
183
+ },
184
+ {
185
+ middleware: [
186
+ new PromiseMiddlewareWrapper({
187
+ pre: async (context) => {
188
+ const kcpUrl = this.getKcpWorkspaceUrl();
189
+ const path = `${kcpUrl}/api/v1/namespaces/${namespace}/secrets/${secretName}`;
190
+ this.logger.log(`kcp url: ${path}`);
191
+ context.setUrl(path);
192
+ return context;
193
+ },
194
+ post: async (context) => context,
195
+ }),
196
+ ],
197
+ },
198
+ );
199
+ const secretData = res.data;
200
+ return Buffer.from(secretData['client_secret'], 'base64').toString(
201
+ 'utf-8',
202
+ );
203
+ } catch (err) {
204
+ this.logger.error(
205
+ `Failed to fetch secret %s:`,
206
+ secretName,
207
+ err.response?.body || err,
208
+ );
209
+ throw err;
210
+ }
211
+ }
78
212
  }