diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts new file mode 100644 index 0000000000..d74462693f --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2022 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 { + CatalogProcessor, + CatalogProcessorEmit, + LocationSpec, +} from '@backstage/plugin-catalog-backend'; +import { Credentials, EKS } from 'aws-sdk'; +import { AWSCredentialFactory } from '../types'; + +const ACCOUNTID_ANNOTATION: string = 'amazonaws.com/account-id'; +const ARN_ANNOTATION: string = 'amazonaws.com/arn'; +const KUBERNETES_API_SERVER_ANNOTATION = 'kubernetes.io/api-server'; +const KUBERNETES_API_SERVER_CA_ANNOTATION = + 'kubernetes.io/api-server-certificate-authority'; +const KUBERNETES_AUTH_METHOD_ANNOTATION = 'kubernetes.io/auth-provider'; + +export class AwsEKSClusterProcessor implements CatalogProcessor { + private credentialsFactory?: AWSCredentialFactory; + + constructor(credentialsFactory?: AWSCredentialFactory) { + this.credentialsFactory = credentialsFactory; + } + + getProcessorName(): string { + return 'aws-eks'; + } + + normalizeName(name: string): string { + return name + .trim() + .toLocaleLowerCase() + .replace(/[^a-zA-Z0-9\-]/g, '-'); + } + + async readLocation( + location: LocationSpec, + _optional: boolean, + emit: CatalogProcessorEmit, + ): Promise { + if (location.type !== 'aws-eks') { + return false; + } + + // location target is of format "account-id/region" + const [accountId, region] = location.target.split('/'); + + let credentials: Credentials | undefined; + + if (this.credentialsFactory) { + credentials = await this.credentialsFactory(accountId); + } + + const eksClient = new EKS({ credentials, region }); + const clusters = await eksClient.listClusters({}).promise(); + if (clusters.clusters === undefined) { + return true; + } + + const results = clusters.clusters + .map(cluster => eksClient.describeCluster({ name: cluster }).promise()) + .map(async describedClusterPromise => { + const describedCluster = await describedClusterPromise; + if (describedCluster.cluster) { + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + annotations: { + [ACCOUNTID_ANNOTATION]: accountId, + [ARN_ANNOTATION]: describedCluster.cluster.arn || '', + [KUBERNETES_API_SERVER_ANNOTATION]: + describedCluster.cluster.endpoint || '', + [KUBERNETES_API_SERVER_CA_ANNOTATION]: + describedCluster.cluster.certificateAuthority?.data || '', + [KUBERNETES_AUTH_METHOD_ANNOTATION]: 'aws', + }, + name: this.normalizeName(describedCluster.cluster.name as string), + namespace: 'default', + }, + spec: { + type: 'kubernetes-cluster', + owner: 'unknown', + }, + }; + emit({ + type: 'entity', + entity, + location, + }); + } + }); + await Promise.all(results); + return true; + } +} diff --git a/plugins/catalog-backend-module-aws/src/types.ts b/plugins/catalog-backend-module-aws/src/types.ts new file mode 100644 index 0000000000..88fdc067eb --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2022 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 { Credentials } from 'aws-sdk'; + +export type AWSCredentialFactory = ( + awsAccountId: string, +) => Promise; diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index db732b8634..1926448635 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -37,8 +37,10 @@ "dependencies": { "@azure/identity": "^2.0.4", "@backstage/backend-common": "^0.13.6-next.1", + "@backstage/catalog-client": "^1.0.3-next.0", "@backstage/catalog-model": "^1.0.3-next.0", "@backstage/config": "^1.0.1", + "@backstage/core-plugin-api": "^1.0.3-next.0", "@backstage/errors": "^1.0.0", "@backstage/plugin-kubernetes-common": "^0.3.0-next.1", "@google-cloud/container": "^3.0.0", diff --git a/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts new file mode 100644 index 0000000000..1e474f637a --- /dev/null +++ b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2020 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 { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import { + CATALOG_FILTER_EXISTS, + CatalogClient, +} from '@backstage/catalog-client'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; + +export class CatalogClusterLocator implements KubernetesClustersSupplier { + private catalogClient: CatalogClient; + + constructor(catalogClient: CatalogClient) { + this.catalogClient = catalogClient; + } + + static fromConfig(discoveryApi: DiscoveryApi): CatalogClusterLocator { + return new CatalogClusterLocator(new CatalogClient({ discoveryApi })); + } + + async getClusters(): Promise { + const clusters = await this.catalogClient.getEntities({ + filter: [ + { 'spec.type': 'kubernetes-cluster' }, + { + 'metadata.annotations.kubernetes.io/api-server': + CATALOG_FILTER_EXISTS, + }, + { + 'metadata.annotations.kubernetes.io/api-server-certificate-authority': + CATALOG_FILTER_EXISTS, + }, + { + 'metadata.annotations.kubernetes.io/auth-provider': + CATALOG_FILTER_EXISTS, + }, + ], + }); + return clusters.items.map(entity => { + const clusterDetails: ClusterDetails = { + name: entity.metadata.name, + // @ts-ignore filtered out by catalog-client query. + url: entity.metadata.annotations['kubernetes.io/api-server'], + // @ts-ignore filtered out by catalog-client query. + caData: + entity.metadata.annotations[ + 'kubernetes.io/api-server-certificate-authority' + ], + // @ts-ignore filtered out by catalog-client query. + authProvider: + entity.metadata.annotations['kubernetes.io/auth-provider'], + }; + + return clusterDetails; + }); + } +}