[Kubernetes Backend Plugin] Added new CatalogRelationServiceLocator (#23509)
* Added new CatalogRelationServiceLocator As described in #23491, in some environments it makes sense to have the kubernetes plugin show only referenced clusters rather than one or all Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Added Changeset Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Updated api-report Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Update the documentation to match the label Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Dropped underscore in variable name Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Moved filter to helper function Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Added more detail to the changeset Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> * Update .changeset/eleven-adults-add.md Co-authored-by: Philipp Hugenroth <tudi2d@users.noreply.github.com> Signed-off-by: swnia <119884634+swnia@users.noreply.github.com> --------- Signed-off-by: Severin Wischmann <severinwischmann@nianticlabs.com> Signed-off-by: swnia <119884634+swnia@users.noreply.github.com> Co-authored-by: Philipp Hugenroth <tudi2d@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Added a new service locator `CatalogRelationServiceLocator` that only returns clusters an entity lists in `relations.dependsOn`.
|
||||
@@ -59,6 +59,8 @@ Valid values are:
|
||||
|
||||
- `singleTenant` - This configuration assumes that current component run on one cluster in provided clusters.
|
||||
|
||||
- `catalogRelation` - This configuration assumes that the current component runs only on all clusters it is dependant on.
|
||||
|
||||
### `clusterLocatorMethods`
|
||||
|
||||
This is an array used to determine where to retrieve cluster configuration from.
|
||||
|
||||
@@ -163,6 +163,10 @@ export class KubernetesBuilder {
|
||||
[key: string]: AuthenticationStrategy_2;
|
||||
};
|
||||
// (undocumented)
|
||||
protected buildCatalogRelationServiceLocator(
|
||||
clusterSupplier: KubernetesClustersSupplier_2,
|
||||
): KubernetesServiceLocator_2;
|
||||
// (undocumented)
|
||||
protected buildClusterSupplier(
|
||||
refreshInterval: Duration,
|
||||
): KubernetesClustersSupplier_2;
|
||||
@@ -397,7 +401,11 @@ export class ServiceAccountStrategy implements AuthenticationStrategy_2 {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http';
|
||||
export type ServiceLocatorMethod =
|
||||
| 'multiTenant'
|
||||
| 'singleTenant'
|
||||
| 'catalogRelation'
|
||||
| 'http';
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type ServiceLocatorRequestContext =
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ServiceLocatorRequestContext } from '@backstage/plugin-kubernetes-node';
|
||||
import { CatalogRelationServiceLocator } from './CatalogRelationServiceLocator';
|
||||
|
||||
describe('CatalogRelationServiceLocator', () => {
|
||||
let serviceLocator: CatalogRelationServiceLocator;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the KubernetesClustersSupplier dependency
|
||||
const clusterSupplier = {
|
||||
getClusters: jest.fn().mockResolvedValue([
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
{
|
||||
name: 'cluster2',
|
||||
url: 'http://localhost:8081',
|
||||
authMetadata: {},
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
serviceLocator = new CatalogRelationServiceLocator(clusterSupplier);
|
||||
});
|
||||
|
||||
it('empty entity returns empty cluster details', async () => {
|
||||
const result = await serviceLocator.getClustersByEntity(
|
||||
{} as Entity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({ clusters: [] });
|
||||
});
|
||||
|
||||
it('empty clusters returns empty cluster details', async () => {
|
||||
const crsl = new CatalogRelationServiceLocator({
|
||||
getClusters: async () => [],
|
||||
});
|
||||
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
relations: [
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/cluster1' },
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/cluster2' },
|
||||
{ type: 'ownedBy', targetRef: 'group:default/group1' },
|
||||
],
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await crsl.getClustersByEntity(
|
||||
testEntity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({ clusters: [] });
|
||||
});
|
||||
|
||||
it('should return all referenced clusters when entity depends on multiple clusters', async () => {
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
relations: [
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/cluster1' },
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/cluster2' },
|
||||
{ type: 'ownedBy', targetRef: 'group:default/group1' },
|
||||
],
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await serviceLocator.getClustersByEntity(
|
||||
testEntity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
clusters: [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
{
|
||||
name: 'cluster2',
|
||||
url: 'http://localhost:8081',
|
||||
authMetadata: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return one cluster when entity has one dependant cluster', async () => {
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
relations: [
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/cluster1' },
|
||||
{ type: 'ownedBy', targetRef: 'group:default/group1' },
|
||||
],
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await serviceLocator.getClustersByEntity(
|
||||
testEntity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
clusters: [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty array when entity does not have dependsOn relation with resource target', async () => {
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
relations: [
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/database1' },
|
||||
{ type: 'dependsOn', targetRef: 'resource:default/database2' },
|
||||
{ type: 'ownedBy', targetRef: 'group:default/group1' },
|
||||
],
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await serviceLocator.getClustersByEntity(
|
||||
testEntity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
clusters: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
ClusterDetails,
|
||||
KubernetesClustersSupplier,
|
||||
KubernetesServiceLocator,
|
||||
ServiceLocatorRequestContext,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
|
||||
// This locator assumes that service is located on the clusters it depends on
|
||||
// Therefore it will return the clusters based on the relations it has or none otherwise
|
||||
export class CatalogRelationServiceLocator implements KubernetesServiceLocator {
|
||||
private readonly clusterSupplier: KubernetesClustersSupplier;
|
||||
|
||||
constructor(clusterSupplier: KubernetesClustersSupplier) {
|
||||
this.clusterSupplier = clusterSupplier;
|
||||
}
|
||||
|
||||
// As this implementation always returns all clusters serviceId is ignored here
|
||||
getClustersByEntity(
|
||||
entity: Entity,
|
||||
_requestContext: ServiceLocatorRequestContext,
|
||||
): Promise<{ clusters: ClusterDetails[] }> {
|
||||
if (
|
||||
entity.relations &&
|
||||
entity.relations.some(
|
||||
r => r.type === 'dependsOn' && r.targetRef.includes('resource:'),
|
||||
)
|
||||
) {
|
||||
return this.clusterSupplier.getClusters().then(clusters => {
|
||||
return {
|
||||
clusters: clusters.filter(c =>
|
||||
this.doesEntityDependOnCluster(entity, c),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ clusters: [] });
|
||||
}
|
||||
|
||||
protected doesEntityDependOnCluster(
|
||||
entity: Entity,
|
||||
cluster: ClusterDetails,
|
||||
): boolean {
|
||||
return entity.relations!.some(
|
||||
rel =>
|
||||
rel.type === 'dependsOn' &&
|
||||
rel.targetRef ===
|
||||
`resource:${entity.metadata.namespace ?? 'default'}/${cluster.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,20 +27,38 @@ import Router from 'express-promise-router';
|
||||
import { Duration } from 'luxon';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
import { getCombinedClusterSupplier } from '../cluster-locator';
|
||||
import {
|
||||
AnonymousStrategy,
|
||||
DispatchStrategy,
|
||||
GoogleStrategy,
|
||||
ServiceAccountStrategy,
|
||||
AwsIamStrategy,
|
||||
GoogleServiceAccountStrategy,
|
||||
AzureIdentityStrategy,
|
||||
OidcStrategy,
|
||||
AksStrategy,
|
||||
AnonymousStrategy,
|
||||
AwsIamStrategy,
|
||||
AzureIdentityStrategy,
|
||||
DispatchStrategy,
|
||||
GoogleServiceAccountStrategy,
|
||||
GoogleStrategy,
|
||||
OidcStrategy,
|
||||
ServiceAccountStrategy,
|
||||
} from '../auth';
|
||||
import { getCombinedClusterSupplier } from '../cluster-locator';
|
||||
|
||||
import { createLegacyAuthAdapters } from '@backstage/backend-common';
|
||||
import {
|
||||
AuthService,
|
||||
BackstageCredentials,
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
AuthMetadata,
|
||||
AuthenticationStrategy,
|
||||
CustomResource,
|
||||
KubernetesClustersSupplier,
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectTypes,
|
||||
KubernetesObjectsProvider,
|
||||
KubernetesServiceLocator,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import { addResourceRoutesToRouter } from '../routes/resourcesRoutes';
|
||||
import { CatalogRelationServiceLocator } from '../service-locator/CatalogRelationServiceLocator';
|
||||
import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';
|
||||
import { SingleTenantServiceLocator } from '../service-locator/SingleTenantServiceLocator';
|
||||
import {
|
||||
@@ -48,29 +66,12 @@ import {
|
||||
ObjectsByEntityRequest,
|
||||
ServiceLocatorMethod,
|
||||
} from '../types/types';
|
||||
import {
|
||||
AuthenticationStrategy,
|
||||
AuthMetadata,
|
||||
CustomResource,
|
||||
KubernetesClustersSupplier,
|
||||
KubernetesFetcher,
|
||||
KubernetesObjectsProvider,
|
||||
KubernetesObjectTypes,
|
||||
KubernetesServiceLocator,
|
||||
} from '@backstage/plugin-kubernetes-node';
|
||||
import {
|
||||
DEFAULT_OBJECTS,
|
||||
KubernetesFanOutHandler,
|
||||
} from './KubernetesFanOutHandler';
|
||||
import { KubernetesClientBasedFetcher } from './KubernetesFetcher';
|
||||
import { KubernetesProxy } from './KubernetesProxy';
|
||||
import { createLegacyAuthAdapters } from '@backstage/backend-common';
|
||||
import {
|
||||
AuthService,
|
||||
BackstageCredentials,
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -306,6 +307,10 @@ export class KubernetesBuilder {
|
||||
this.serviceLocator =
|
||||
this.buildSingleTenantServiceLocator(clusterSupplier);
|
||||
break;
|
||||
case 'catalogRelation':
|
||||
this.serviceLocator =
|
||||
this.buildCatalogRelationServiceLocator(clusterSupplier);
|
||||
break;
|
||||
case 'http':
|
||||
this.serviceLocator = this.buildHttpServiceLocator(clusterSupplier);
|
||||
break;
|
||||
@@ -330,6 +335,12 @@ export class KubernetesBuilder {
|
||||
return new SingleTenantServiceLocator(clusterSupplier);
|
||||
}
|
||||
|
||||
protected buildCatalogRelationServiceLocator(
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator {
|
||||
return new CatalogRelationServiceLocator(clusterSupplier);
|
||||
}
|
||||
|
||||
protected buildHttpServiceLocator(
|
||||
_clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator {
|
||||
|
||||
@@ -14,16 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
|
||||
import * as k8sTypes from '@backstage/plugin-kubernetes-node';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http'; // TODO implement http
|
||||
export type ServiceLocatorMethod =
|
||||
| 'multiTenant'
|
||||
| 'singleTenant'
|
||||
| 'catalogRelation'
|
||||
| 'http'; // TODO implement http
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user