SingleTenant ServiceLocatorMethod for Kubernetes cluster by entity annotation (#20954)
* Filter Kubernetes cluster by entity annotation Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> * Documentation updated Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> * SingleTenantLocator implemented Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> * API reports updated Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> * spelling corrected Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> * document updated and fixed review comments Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com> --------- Signed-off-by: Deepankumar Loganathan <deepan0433@gmail.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': minor
|
||||
---
|
||||
|
||||
You can now select `single` kubernetes cluster that the entity is part-of from all your defined kubernetes clusters, by passing `backstage.io/kubernetes-cluster` annotation with the defined cluster name.
|
||||
|
||||
If you do not specify the annotation by `default it fetches all` defined kubernetes cluster.
|
||||
|
||||
To apply
|
||||
|
||||
catalog-info.yaml
|
||||
|
||||
```diff
|
||||
annotations:
|
||||
'backstage.io/kubernetes-id': dice-roller
|
||||
'backstage.io/kubernetes-namespace': dice-space
|
||||
+ 'backstage.io/kubernetes-cluster': dice-cluster
|
||||
'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end'
|
||||
```
|
||||
@@ -51,11 +51,13 @@ kubernetes:
|
||||
|
||||
This configures how to determine which clusters a component is running in.
|
||||
|
||||
Currently, the only valid value is:
|
||||
Valid values are:
|
||||
|
||||
- `multiTenant` - This configuration assumes that all components run on all the
|
||||
provided clusters.
|
||||
|
||||
- `singleTenant` - This configuration assumes that current component run on one cluster in provided clusters.
|
||||
|
||||
### `clusterLocatorMethods`
|
||||
|
||||
This is an array used to determine where to retrieve cluster configuration from.
|
||||
@@ -577,6 +579,22 @@ for more info.
|
||||
'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end'
|
||||
```
|
||||
|
||||
### Cluster Selection annotation
|
||||
|
||||
This is applicable only for `singleTenant` serviceLocatorMethod.
|
||||
|
||||
You can now select `single` kubernetes cluster that the entity is part-of from all your defined kubernetes clusters. To apply this use the following annotation.
|
||||
|
||||
SingleTenant Cluster:
|
||||
|
||||
```yaml
|
||||
'backstage.io/kubernetes-cluster': dice-cluster
|
||||
```
|
||||
|
||||
In the example above, we configured the "backstage.io/kubernetes-cluster" annotation on the entity `catalog-info.yaml` file to specify that the current component is running in a single cluster called "dice-cluster", so this cluster must have been specified in the `app-config.yaml`, under the Kubernetes clusters configuration (for more details, see [`Configuring Kubernetes clusters`](#configuring-kubernetes-clusters)).
|
||||
|
||||
If you do not specify the annotation by `default Backstage fetches all` defined Kubernetes cluster.
|
||||
|
||||
[1]: https://cloud.google.com/kubernetes-engine
|
||||
[2]: https://cloud.google.com/docs/authentication/production#linux-or-macos
|
||||
[3]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
|
||||
|
||||
@@ -210,6 +210,10 @@ export class KubernetesBuilder {
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator;
|
||||
// (undocumented)
|
||||
protected buildSingleTenantServiceLocator(
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator;
|
||||
// (undocumented)
|
||||
static createBuilder(env: KubernetesEnvironment): KubernetesBuilder;
|
||||
// (undocumented)
|
||||
protected readonly env: KubernetesEnvironment;
|
||||
@@ -452,7 +456,7 @@ export class ServiceAccountStrategy implements AuthenticationStrategy {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'http';
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ServiceLocatorRequestContext {
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2023 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 '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ServiceLocatorRequestContext } from '../types/types';
|
||||
import { SingleTenantServiceLocator } from './SingleTenantServiceLocator';
|
||||
|
||||
describe('SingleTenantConfigClusterLocator', () => {
|
||||
it('empty clusters returns empty cluster details', async () => {
|
||||
const sut = new SingleTenantServiceLocator({
|
||||
getClusters: async () => [],
|
||||
});
|
||||
|
||||
const result = await sut.getClustersByEntity(
|
||||
{} as Entity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({ clusters: [] });
|
||||
});
|
||||
|
||||
it('one cluster return from two clusters', async () => {
|
||||
const sut = new SingleTenantServiceLocator({
|
||||
getClusters: async () => {
|
||||
return [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
{
|
||||
name: 'cluster2',
|
||||
url: 'http://localhost:8081',
|
||||
authMetadata: {},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
annotations: {
|
||||
'backstage.io/kubernetes-cluster': 'cluster1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await sut.getClustersByEntity(
|
||||
testEntity as Entity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
clusters: [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('no annotation return all cluster', async () => {
|
||||
const definedClusters = [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
{
|
||||
name: 'cluster2',
|
||||
url: 'http://localhost:8081',
|
||||
authMetadata: {},
|
||||
},
|
||||
];
|
||||
|
||||
const sut = new SingleTenantServiceLocator({
|
||||
getClusters: async () => {
|
||||
return definedClusters;
|
||||
},
|
||||
});
|
||||
|
||||
const result = await sut.getClustersByEntity(
|
||||
{} as Entity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
clusters: definedClusters,
|
||||
});
|
||||
});
|
||||
|
||||
it('wrong annotation returns empty cluster', async () => {
|
||||
const sut = new SingleTenantServiceLocator({
|
||||
getClusters: async () => {
|
||||
return [
|
||||
{
|
||||
name: 'cluster1',
|
||||
url: 'http://localhost:8080',
|
||||
authMetadata: {},
|
||||
},
|
||||
{
|
||||
name: 'cluster2',
|
||||
url: 'http://localhost:8081',
|
||||
authMetadata: {},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const testEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
name: 'testEntity',
|
||||
annotations: {
|
||||
'backstage.io/kubernetes-cluster': 'cluster3',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = await sut.getClustersByEntity(
|
||||
testEntity as Entity,
|
||||
{} as ServiceLocatorRequestContext,
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
clusters: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2023 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 '../types/types';
|
||||
|
||||
// This locator assumes that service is located on one cluster
|
||||
// Therefore it will always return specified cluster provided in backstage.io/kubernetes-cluster annotation
|
||||
// If backstage.io/kubernetes-cluster annotation not provided will always return all cluster provided
|
||||
export class SingleTenantServiceLocator 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[] }> {
|
||||
return this.clusterSupplier.getClusters().then(clusters => {
|
||||
if (_entity.metadata?.annotations?.['backstage.io/kubernetes-cluster']) {
|
||||
return {
|
||||
clusters: clusters.filter(
|
||||
c =>
|
||||
c.name ===
|
||||
_entity.metadata?.annotations?.[
|
||||
'backstage.io/kubernetes-cluster'
|
||||
],
|
||||
),
|
||||
};
|
||||
}
|
||||
return { clusters };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
|
||||
import { addResourceRoutesToRouter } from '../routes/resourcesRoutes';
|
||||
import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';
|
||||
import { SingleTenantServiceLocator } from '../service-locator/SingleTenantServiceLocator';
|
||||
import {
|
||||
CustomResource,
|
||||
KubernetesClustersSupplier,
|
||||
@@ -276,6 +277,10 @@ export class KubernetesBuilder {
|
||||
this.serviceLocator =
|
||||
this.buildMultiTenantServiceLocator(clusterSupplier);
|
||||
break;
|
||||
case 'singleTenant':
|
||||
this.serviceLocator =
|
||||
this.buildSingleTenantServiceLocator(clusterSupplier);
|
||||
break;
|
||||
case 'http':
|
||||
this.serviceLocator = this.buildHttpServiceLocator(clusterSupplier);
|
||||
break;
|
||||
@@ -294,6 +299,12 @@ export class KubernetesBuilder {
|
||||
return new MultiTenantServiceLocator(clusterSupplier);
|
||||
}
|
||||
|
||||
protected buildSingleTenantServiceLocator(
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator {
|
||||
return new SingleTenantServiceLocator(clusterSupplier);
|
||||
}
|
||||
|
||||
protected buildHttpServiceLocator(
|
||||
_clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesServiceLocator {
|
||||
|
||||
@@ -144,7 +144,7 @@ export interface KubernetesServiceLocator {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'http'; // TODO implement http
|
||||
export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http'; // TODO implement http
|
||||
|
||||
/**
|
||||
* Provider-specific authentication configuration
|
||||
|
||||
Reference in New Issue
Block a user