fix: sent token to get entities request

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2024-03-05 10:39:24 +01:00
parent 6e0a8e5227
commit 69d0217cba
10 changed files with 115 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-node': patch
---
Accept auth credentials to get kubernetes clusters
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Pass user credentials when calling catalog get entities api.
+4
View File
@@ -6,6 +6,7 @@
import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node';
import { AuthMetadata as AuthMetadata_2 } from '@backstage/plugin-kubernetes-node';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node';
import { Config } from '@backstage/config';
@@ -212,6 +213,9 @@ export class KubernetesBuilder {
// (undocumented)
protected fetchClusterDetails(
clusterSupplier: KubernetesClustersSupplier_2,
options: {
credentials: BackstageCredentials;
},
): Promise<ClusterDetails_2[]>;
// (undocumented)
protected getAuthStrategyMap(): {
@@ -23,6 +23,7 @@ import {
} from '@backstage/plugin-kubernetes-common';
import { CatalogClusterLocator } from './CatalogClusterLocator';
import { CatalogApi } from '@backstage/catalog-client';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
const mockCatalogApi = {
getEntityByRef: jest.fn(),
@@ -77,29 +78,46 @@ describe('CatalogClusterLocator', () => {
items: [],
}),
} as Partial<CatalogApi> as CatalogApi;
const auth = mockServices.auth();
const clusterSupplier =
CatalogClusterLocator.fromConfig(emptyMockCatalogApi);
const clusterSupplier = CatalogClusterLocator.fromConfig(
emptyMockCatalogApi,
auth,
);
const result = await clusterSupplier.getClusters();
const credentials = mockCredentials.user();
const result = await clusterSupplier.getClusters({ credentials });
expect(result).toHaveLength(0);
expect(result).toStrictEqual([]);
});
it('returns the cluster details provided by annotations', async () => {
const clusterSupplier = CatalogClusterLocator.fromConfig(mockCatalogApi);
const auth = mockServices.auth();
const clusterSupplier = CatalogClusterLocator.fromConfig(
mockCatalogApi,
auth,
);
const result = await clusterSupplier.getClusters();
const credentials = mockCredentials.user();
const result = await clusterSupplier.getClusters({ credentials });
expect(result).toHaveLength(2);
expect(result[0]).toMatchSnapshot();
});
it('returns the aws cluster details provided by annotations', async () => {
const clusterSupplier = CatalogClusterLocator.fromConfig(mockCatalogApi);
const auth = mockServices.auth();
const clusterSupplier = CatalogClusterLocator.fromConfig(
mockCatalogApi,
auth,
);
const result = await clusterSupplier.getClusters();
const credentials = mockCredentials.user();
const result = await clusterSupplier.getClusters({ credentials });
expect(result).toHaveLength(2);
expect(result[1]).toMatchSnapshot();
@@ -14,6 +14,10 @@
* limitations under the License.
*/
import {
AuthService,
BackstageCredentials,
} from '@backstage/backend-plugin-api';
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client';
import {
@@ -34,16 +38,23 @@ function isObject(obj: unknown): obj is JsonObject {
export class CatalogClusterLocator implements KubernetesClustersSupplier {
private catalogClient: CatalogApi;
private auth: AuthService;
constructor(catalogClient: CatalogApi) {
constructor(catalogClient: CatalogApi, auth: AuthService) {
this.catalogClient = catalogClient;
this.auth = auth;
}
static fromConfig(catalogApi: CatalogApi): CatalogClusterLocator {
return new CatalogClusterLocator(catalogApi);
static fromConfig(
catalogApi: CatalogApi,
auth: AuthService,
): CatalogClusterLocator {
return new CatalogClusterLocator(catalogApi, auth);
}
async getClusters(): Promise<ClusterDetails[]> {
async getClusters(options: {
credentials: BackstageCredentials;
}): Promise<ClusterDetails[]> {
const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;
const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;
const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;
@@ -56,9 +67,21 @@ export class CatalogClusterLocator implements KubernetesClustersSupplier {
[authProviderKey]: CATALOG_FILTER_EXISTS,
};
const clusters = await this.catalogClient.getEntities({
filter: [filter],
});
const clusters = await this.catalogClient.getEntities(
{
filter: [filter],
},
options.credentials
? {
token: (
await this.auth.getPluginRequestToken({
onBehalfOf: options.credentials,
targetPluginId: 'catalog',
})
).token,
}
: undefined,
);
return clusters.items.map(entity => {
const annotations = entity.metadata.annotations!;
const clusterDetails: ClusterDetails = {
@@ -21,6 +21,7 @@ import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernete
import { getCombinedClusterSupplier } from './index';
import { ClusterDetails } from '../types/types';
import { AuthenticationStrategy, DispatchStrategy } from '../auth';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
describe('getCombinedClusterSupplier', () => {
let catalogApi: CatalogApi;
@@ -57,13 +58,18 @@ describe('getCombinedClusterSupplier', () => {
presentAuthMetadata: jest.fn(),
};
const auth = mockServices.auth();
const credentials = mockCredentials.user();
const clusterSupplier = getCombinedClusterSupplier(
config,
catalogApi,
mockStrategy,
getVoidLogger(),
undefined,
auth,
);
const result = await clusterSupplier.getClusters();
const result = await clusterSupplier.getClusters({ credentials });
expect(result).toStrictEqual<ClusterDetails[]>([
{
@@ -96,12 +102,16 @@ describe('getCombinedClusterSupplier', () => {
'ctx',
);
const auth = mockServices.auth();
expect(() =>
getCombinedClusterSupplier(
config,
catalogApi,
new DispatchStrategy({ authStrategyMap: {} }),
getVoidLogger(),
undefined,
auth,
),
).toThrow(
new Error('Unsupported kubernetes.clusterLocatorMethods: "magic"'),
@@ -151,13 +161,19 @@ describe('getCombinedClusterSupplier', () => {
validateEntity: jest.fn(),
};
const auth = mockServices.auth();
const credentials = mockCredentials.user();
const clusterSupplier = getCombinedClusterSupplier(
config,
catalogApi,
mockStrategy,
logger,
undefined,
auth,
);
await clusterSupplier.getClusters();
await clusterSupplier.getClusters({ credentials });
expect(warn).toHaveBeenCalledWith(`Duplicate cluster name 'cluster'`);
});
@@ -24,6 +24,10 @@ import { ConfigClusterLocator } from './ConfigClusterLocator';
import { GkeClusterLocator } from './GkeClusterLocator';
import { CatalogClusterLocator } from './CatalogClusterLocator';
import { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';
import {
AuthService,
BackstageCredentials,
} from '@backstage/backend-plugin-api';
class CombinedClustersSupplier implements KubernetesClustersSupplier {
constructor(
@@ -31,9 +35,11 @@ class CombinedClustersSupplier implements KubernetesClustersSupplier {
readonly logger: Logger,
) {}
async getClusters(): Promise<ClusterDetails[]> {
async getClusters(options: {
credentials: BackstageCredentials;
}): Promise<ClusterDetails[]> {
const clusters = await Promise.all(
this.clusterSuppliers.map(supplier => supplier.getClusters()),
this.clusterSuppliers.map(supplier => supplier.getClusters(options)),
)
.then(res => {
return res.flat();
@@ -67,6 +73,7 @@ export const getCombinedClusterSupplier = (
authStrategy: AuthenticationStrategy,
logger: Logger,
refreshInterval: Duration | undefined = undefined,
auth: AuthService,
): KubernetesClustersSupplier => {
const clusterSuppliers = rootConfig
.getConfigArray('kubernetes.clusterLocatorMethods')
@@ -74,7 +81,7 @@ export const getCombinedClusterSupplier = (
const type = clusterLocatorMethod.getString('type');
switch (type) {
case 'catalog':
return CatalogClusterLocator.fromConfig(catalogClient);
return CatalogClusterLocator.fromConfig(catalogClient, auth);
case 'localKubectlProxy':
return new LocalKubectlProxyClusterLocator();
case 'config':
@@ -67,6 +67,7 @@ import { KubernetesProxy } from './KubernetesProxy';
import { createLegacyAuthAdapters } from '@backstage/backend-common';
import {
AuthService,
BackstageCredentials,
DiscoveryService,
HttpAuthService,
} from '@backstage/backend-plugin-api';
@@ -257,12 +258,14 @@ export class KubernetesBuilder {
refreshInterval: Duration,
): KubernetesClustersSupplier {
const config = this.env.config;
const { auth } = createLegacyAuthAdapters(this.env);
this.clusterSupplier = getCombinedClusterSupplier(
config,
this.env.catalogApi,
new DispatchStrategy({ authStrategyMap: this.getAuthStrategyMap() }),
this.env.logger,
refreshInterval,
auth,
);
return this.clusterSupplier;
@@ -385,8 +388,11 @@ export class KubernetesBuilder {
}
});
router.get('/clusters', async (_, res) => {
const clusterDetails = await this.fetchClusterDetails(clusterSupplier);
router.get('/clusters', async (req, res) => {
const credentials = await httpAuth.credentials(req);
const clusterDetails = await this.fetchClusterDetails(clusterSupplier, {
credentials,
});
res.json({
items: clusterDetails.map(cd => {
const oidcTokenProvider =
@@ -438,8 +444,9 @@ export class KubernetesBuilder {
protected async fetchClusterDetails(
clusterSupplier: KubernetesClustersSupplier,
options: { credentials: BackstageCredentials },
) {
const clusterDetails = await clusterSupplier.getClusters();
const clusterDetails = await clusterSupplier.getClusters(options);
this.env.logger.info(
`action=loadClusterDetails numOfClustersLoaded=${clusterDetails.length}`,
+4 -1
View File
@@ -4,6 +4,7 @@
```ts
import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node';
import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common';
import { Entity } from '@backstage/catalog-model';
@@ -87,7 +88,9 @@ export const kubernetesAuthStrategyExtensionPoint: ExtensionPoint<KubernetesAuth
// @public
export interface KubernetesClustersSupplier {
getClusters(): Promise<ClusterDetails[]>;
getClusters(options?: {
credentials: BackstageCredentials;
}): Promise<ClusterDetails[]>;
}
// @public
+4 -1
View File
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { Entity } from '@backstage/catalog-model';
import {
CustomResourceMatcher,
@@ -134,7 +135,9 @@ export interface KubernetesClustersSupplier {
* Implementations _should_ cache the clusters and refresh them periodically,
* as getClusters is called whenever the list of clusters is needed.
*/
getClusters(): Promise<ClusterDetails[]>;
getClusters(options?: {
credentials: BackstageCredentials;
}): Promise<ClusterDetails[]>;
}
/**