fix: sent token to get entities request
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-node': patch
|
||||
---
|
||||
|
||||
Accept auth credentials to get kubernetes clusters
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Pass user credentials when calling catalog get entities api.
|
||||
@@ -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,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
|
||||
|
||||
@@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user