diff --git a/backstage-plugins/plugins/aws-apps/.eslintrc.js b/backstage-plugins/plugins/aws-apps/.eslintrc.js new file mode 100644 index 00000000..e2a53a6a --- /dev/null +++ b/backstage-plugins/plugins/aws-apps/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/backstage-plugins/plugins/aws-apps/CHANGELOG.md b/backstage-plugins/plugins/aws-apps/CHANGELOG.md new file mode 100644 index 00000000..c88c5ad2 --- /dev/null +++ b/backstage-plugins/plugins/aws-apps/CHANGELOG.md @@ -0,0 +1,20 @@ +# @aws/plugin-aws-apps-for-backstage + +## 0.4.0 + +### Minor Changes + +- b76307e: Implemented `@backstage/integration-aws-node` enabling reuse of credentials shared with other AWS integrated plugins (e.g. [Roadie](https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-aws), [AWS CodeStar](https://github.com/awslabs/backstage-plugins-for-aws)), and running Harmonix outside of an AWS account (Credits: @fjudith). Bump framework version 1.30.4, optimized code-style, and comply CI with [community-plugins](https://github.com/backstage/community-plugins) (Credits: @fleveillee). + +### Patch Changes + +- Updated dependencies [b76307e] + - @aws/plugin-aws-apps-common-for-backstage@0.4.0 + +## 0.3.6 + +### Patch Changes + +- 95c1ddb: Bump framework version 1.30.4 and aligned with `backstage/community-plugins` best practices +- Updated dependencies [95c1ddb] + - @aws/plugin-aws-apps-common-for-backstage@0.3.5 diff --git a/backstage-plugins/plugins/aws-apps/README.md b/backstage-plugins/plugins/aws-apps/README.md index 3ffcac33..054f0dbd 100644 --- a/backstage-plugins/plugins/aws-apps/README.md +++ b/backstage-plugins/plugins/aws-apps/README.md @@ -1,10 +1,11 @@ - + # OPA on AWS Frontend -This is the frontend UI of the OPA on AWS plugin. An AWS Catalog Page and several entity cards are contributed to the UI from this plugin. +This is the frontend UI of the OPA on AWS plugin. An AWS Catalog Page and several entity cards are contributed to the UI from this plugin. - [Installation](#installation) - [Configuration](#configuration) @@ -23,13 +24,14 @@ yarn add --cwd packages/app @aws/plugin-aws-apps-for-backstage@0.2.0 ## Configuration -Each of the UI components contributed in the OPA on AWS frontend plugin can be configured independently and added to your Backstage platform as desired. Details for adding each type of UI component are found in the sections below. +Each of the UI components contributed in the OPA on AWS frontend plugin can be configured independently and added to your Backstage platform as desired. Details for adding each type of UI component are found in the sections below. ### EntityPage customization for AWS apps To build an AWS app-specific entity presentation, we will rely on identification of a component as being of type "aws-app" (as specified under the `spec.type` configuration in the entity's `catalog-info.yaml` file). Add the code shown below to `EntityPage.tsx` + ```ts // packages/app/src/components/catalog/EntityPage.tsx @@ -117,9 +119,10 @@ const resourceEntityPage = ( ); ``` + When running the Backstage app, you are now setup for customized views of AWS applications in the platform. -### AWS Software Catalog Page +### AWS Software Catalog Page The AWS Software Catalog page provides a customized view into the Backstage catalog with a focus on applications deployed to AWS through Backstage. ![AWS Software Catalog Page](images/ui_aws_software_catalog.png 'AWS Software Catalog Page') @@ -159,7 +162,7 @@ const routes = ( ``` Next, add the AWS Software Catalog to the sidebar navigation in the `Root.tsx` file. -Determine your preferred placement in the sidebar using the example below as guidance. Exact contents and children of the may differ in your installation. +Determine your preferred placement in the sidebar using the example below as guidance. Exact contents and children of the may differ in your installation. ```diff // packages/app/src/components/Root/Root.tsx diff --git a/backstage-plugins/plugins/aws-apps/api-report.md b/backstage-plugins/plugins/aws-apps/api-report.md new file mode 100644 index 00000000..930aa3c1 --- /dev/null +++ b/backstage-plugins/plugins/aws-apps/api-report.md @@ -0,0 +1,115 @@ +## API Report File for "@aws/plugin-aws-apps-for-backstage" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DefaultCatalogPageProps } from '@backstage/plugin-catalog'; +import { JSX as JSX_2 } from 'react'; +import { default as React_2 } from 'react'; +import { ReactNode } from 'react'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-forgotten-export) The symbol "AppCatalogPage_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const AppCatalogPage: AppCatalogPage_2; + +// Warning: (ae-forgotten-export) The symbol "AwsAppPage_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const AwsAppPage: AwsAppPage_2; + +// Warning: (ae-forgotten-export) The symbol "AwsComponentPage_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const AwsComponentPage: AwsComponentPage_2; + +// Warning: (ae-forgotten-export) The symbol "AwsEnvironmentPage_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const AwsEnvironmentPage: AwsEnvironmentPage_2; + +// Warning: (ae-forgotten-export) The symbol "AwsEnvironmentProviderPage_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const AwsEnvironmentProviderPage: AwsEnvironmentProviderPage_2; + +// @public (undocumented) +export const EntityAnnotationTypeTable: ({ + type, +}: { + type: string; +}) => JSX_2.Element; + +// @public (undocumented) +export const EntityAppConfigCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityAppPromoCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityAppStateCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityAppStateCardCloudFormation: () => JSX_2.Element; + +// @public (undocumented) +export const EntityAuditTable: () => JSX_2.Element; + +// @public (undocumented) +export const EntityAwsEnvironmentProviderSelectorCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityCloudwatchLogsTable: () => JSX_2.Element; + +// @public (undocumented) +export const EntityDeleteAppCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityDeleteEnvironmentCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityDeleteProviderCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityEnvironmentInfoCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityEnvironmentSelector: () => JSX_2.Element; + +// @public (undocumented) +export const EntityGeneralInfoCard: ({ + appPending, +}: { + appPending: boolean; +}) => JSX_2.Element; + +// @public (undocumented) +export const EntityInfrastructureInfoCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityK8sAppStateCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityLabelTable: () => JSX_2.Element; + +// @public (undocumented) +export const EntityProviderInfoCard: () => JSX_2.Element; + +// @public (undocumented) +export const EntityResourceBindingCard: () => JSX_2.Element; + +// @public (undocumented) +export const opaPlugin: BackstagePlugin< + { + root: RouteRef; + }, + {}, + {} +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/backstage-plugins/plugins/aws-apps/package.json b/backstage-plugins/plugins/aws-apps/package.json index ca05c0fe..914063cc 100644 --- a/backstage-plugins/plugins/aws-apps/package.json +++ b/backstage-plugins/plugins/aws-apps/package.json @@ -1,13 +1,13 @@ { "name": "@aws/plugin-aws-apps-for-backstage", "description": "App Development for Backstage.io on AWS Frontend plugin", - "version": "0.3.5", + "version": "0.4.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "author": { "name": "Amazon Web Services", - "url": "http://aws.amazon.com" + "url": "https://aws.amazon.com" }, "publishConfig": { "access": "public", @@ -25,8 +25,11 @@ "backstage": { "role": "frontend-plugin", "pluginId": "aws-apps", - "pluginPackages": ["@backstage/plugin-catalog"] + "pluginPackages": [ + "@aws/plugin-aws-apps-for-backstage" + ] }, + "sideEffects": false, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -37,33 +40,39 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "@aws/plugin-aws-apps-common-for-backstage": "workspace:^", "@aws-sdk/client-cloudformation": "^3.623.0", "@aws-sdk/client-cloudwatch-logs": "^3.623.0", "@aws-sdk/client-dynamodb": "^3.623.0", "@aws-sdk/client-ecs": "^3.623.0", "@aws-sdk/client-eks": "^3.623.0", - "@aws-sdk/client-s3": "^3.623.0", "@aws-sdk/client-lambda": "^3.623.0", + "@aws-sdk/client-s3": "^3.623.0", "@aws-sdk/client-secrets-manager": "^3.623.0", "@aws-sdk/client-ssm": "^3.623.0", "@aws-sdk/util-arn-parser": "^3.568.0", - "@aws/plugin-aws-apps-common-for-backstage": "^0.3.4", - "@aws/backstage-plugin-catalog-backend-module-aws-apps-entities-processor": "^0.3.4", - "@immobiliarelabs/backstage-plugin-gitlab": "^6.6.0", + "@backstage-community/plugin-github-actions": "^0.6.16", "@backstage/catalog-model": "^1.5.0", "@backstage/core-components": "^0.14.9", "@backstage/core-plugin-api": "^1.9.3", "@backstage/errors": "^1.2.4", "@backstage/plugin-catalog": "^1.21.1", + "@backstage/plugin-catalog-graph": "^0.4.7", "@backstage/plugin-catalog-react": "^1.12.2", "@backstage/plugin-permission-react": "^0.4.24", + "@backstage/plugin-techdocs": "^1.10.7", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.12", + "@backstage/plugin-techdocs-react": "^1.2.6", "@backstage/theme": "^0.5.6", + "@backstage/types": "^1.1.1", "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", + "@immobiliarelabs/backstage-plugin-gitlab": "^6.6.0", "@kubernetes/client-node": "^0.21.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^5.0.0-alpha.4", + "@material-ui/lab": "^4.0.0-alpha.61", + "@mui/base": "^5.0.0-beta.40", "@mui/icons-material": "^5.16.6", "@mui/material": "^5.16.6", "@mui/system": "^5.16.6", @@ -71,18 +80,23 @@ "react-use": "^17.5.1" }, "peerDependencies": { - "react": "^18.0.2" + "react": "^18.0.2", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router": "6.0.0-beta.0 || ^6.3.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" }, "devDependencies": { "@backstage/cli": "^0.26.11", - "@backstage/core-app-api": "^1.14.1", - "@backstage/dev-utils": "^1.0.36", - "@backstage/test-utils": "^1.5.9", + "@backstage/core-app-api": "^1.14.0", + "@backstage/dev-utils": "^1.0.35", + "@backstage/test-utils": "^1.5.8", + "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/file-saver": "^2.0.5", "@types/node": "*", + "@types/react": "*", "cross-fetch": "^4.0.0", "msw": "^2.3.5" }, diff --git a/backstage-plugins/plugins/aws-apps/src/api/OPAApi.ts b/backstage-plugins/plugins/aws-apps/src/api/OPAApi.ts index 6eee06f6..1ef43a89 100644 --- a/backstage-plugins/plugins/aws-apps/src/api/OPAApi.ts +++ b/backstage-plugins/plugins/aws-apps/src/api/OPAApi.ts @@ -6,30 +6,40 @@ import { DeleteStackCommandOutput, DescribeStackEventsCommandOutput, Stack, - UpdateStackCommandOutput -} from "@aws-sdk/client-cloudformation"; + UpdateStackCommandOutput, +} from '@aws-sdk/client-cloudformation'; import { LogStream } from '@aws-sdk/client-cloudwatch-logs'; import { ScanCommandOutput } from '@aws-sdk/client-dynamodb'; import { Service, Task, TaskDefinition } from '@aws-sdk/client-ecs'; import { HeadObjectCommandOutput } from '@aws-sdk/client-s3'; -import { DeleteSecretCommandOutput, GetSecretValueCommandOutput } from '@aws-sdk/client-secrets-manager'; +import { + DeleteSecretCommandOutput, + GetSecretValueCommandOutput, +} from '@aws-sdk/client-secrets-manager'; import { GetParameterCommandOutput } from '@aws-sdk/client-ssm'; -import { AWSProviderParams, AWSServiceResources, BackendParams, BindResourceParams, AWSEnvironmentProviderRecord } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSProviderParams, + AWSServiceResources, + BackendParams, + BindResourceParams, + AWSEnvironmentProviderRecord, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { createApiRef } from '@backstage/core-plugin-api'; import { ContainerDetailsType } from '../types'; -import { InvokeCommandOutput } from "@aws-sdk/client-lambda"; -import { IRepositoryInfo } from "@aws/plugin-aws-apps-common-for-backstage"; +import { InvokeCommandOutput } from '@aws-sdk/client-lambda'; +import { IRepositoryInfo } from '@aws/plugin-aws-apps-common-for-backstage'; export const opaApiRef = createApiRef({ id: 'plugin.opa.app', }); export interface OPAApi { - setPlatformParams(appName: string, region: string): void; setBackendParams(backendParams: BackendParams): void; - getAuditDetails(backendParamsOverrides?: BackendParams): Promise; + getAuditDetails( + backendParamsOverrides?: BackendParams, + ): Promise; getTaskDetails({ service, @@ -38,7 +48,7 @@ export interface OPAApi { }: { service: string; cluster: string; - backendParamsOverrides?: BackendParams + backendParamsOverrides?: BackendParams; }): Promise; updateService({ @@ -130,7 +140,7 @@ export interface OPAApi { }): Promise; deletePlatformSecret({ - secretName + secretName, }: { secretName: string; }): Promise; @@ -291,7 +301,7 @@ export interface OPAApi { functionName, actionDescription, body, - backendParamsOverrides + backendParamsOverrides, }: { functionName: string; actionDescription: string; @@ -303,13 +313,13 @@ export interface OPAApi { envName, gitAdminSecret, repoInfo, - backendParamsOverrides + backendParamsOverrides, }: { envName: string; gitAdminSecret: string; repoInfo: IRepositoryInfo; backendParamsOverrides?: BackendParams; - }): Promise + }): Promise; updateEKSApp({ actionDescription, @@ -321,7 +331,7 @@ export interface OPAApi { lambdaRoleArn, gitAdminSecret, repoInfo, - backendParamsOverrides + backendParamsOverrides, }: { actionDescription: string; envName: string; diff --git a/backstage-plugins/plugins/aws-apps/src/api/OPAApiClient.ts b/backstage-plugins/plugins/aws-apps/src/api/OPAApiClient.ts index 001e5d0a..f44d1c55 100644 --- a/backstage-plugins/plugins/aws-apps/src/api/OPAApiClient.ts +++ b/backstage-plugins/plugins/aws-apps/src/api/OPAApiClient.ts @@ -6,22 +6,31 @@ import { DeleteStackCommandOutput, DescribeStackEventsCommandOutput, Stack, - UpdateStackCommandOutput -} from "@aws-sdk/client-cloudformation"; + UpdateStackCommandOutput, +} from '@aws-sdk/client-cloudformation'; import { LogStream } from '@aws-sdk/client-cloudwatch-logs'; import { ScanCommandOutput } from '@aws-sdk/client-dynamodb'; import { Service, Task, TaskDefinition } from '@aws-sdk/client-ecs'; import { HeadObjectCommandOutput } from '@aws-sdk/client-s3'; -import { DeleteSecretCommandOutput, GetSecretValueCommandOutput } from '@aws-sdk/client-secrets-manager'; +import { + DeleteSecretCommandOutput, + GetSecretValueCommandOutput, +} from '@aws-sdk/client-secrets-manager'; import { GetParameterCommandOutput } from '@aws-sdk/client-ssm'; -import { AWSEnvironmentProviderRecord, AWSProviderParams, AWSServiceResources, BackendParams, BindResourceParams, IRepositoryInfo } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSEnvironmentProviderRecord, + AWSProviderParams, + AWSServiceResources, + BackendParams, + BindResourceParams, + IRepositoryInfo, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { ConfigApi, FetchApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { OPAApi } from '.'; import { HTTP } from '../helpers/constants'; import { ContainerDetailsType } from '../types'; -import { InvokeCommandOutput } from "@aws-sdk/client-lambda"; - +import { InvokeCommandOutput } from '@aws-sdk/client-lambda'; export class OPAApiClient implements OPAApi { private readonly configApi: ConfigApi; @@ -30,10 +39,16 @@ export class OPAApiClient implements OPAApi { private platformAppName: string; private platformRegion: string; - public constructor(options: { configApi: ConfigApi; fetchApi: FetchApi; }) { + public constructor(options: { configApi: ConfigApi; fetchApi: FetchApi }) { this.configApi = options.configApi; this.fetchApi = options.fetchApi; - this.backendParams = { appName: '', awsAccount: '', awsRegion: '', prefix: '', providerName: '' }; + this.backendParams = { + appName: '', + awsAccount: '', + awsRegion: '', + prefix: '', + providerName: '', + }; this.platformAppName = ''; this.platformRegion = ''; } @@ -47,12 +62,13 @@ export class OPAApiClient implements OPAApi { this.backendParams = backendParams; } - private getAppliedBackendParams(backendParamsOverrides?: BackendParams): BackendParams { + private getAppliedBackendParams( + backendParamsOverrides?: BackendParams, + ): BackendParams { if (backendParamsOverrides) { return backendParamsOverrides!; - } else { - return this.backendParams; } + return this.backendParams; } async getTaskDetails({ @@ -62,28 +78,26 @@ export class OPAApiClient implements OPAApi { }: { cluster: string; service: string; - backendParamsOverrides?: BackendParams + backendParamsOverrides?: BackendParams; }): Promise { - const beParams = this.getAppliedBackendParams(backendParamsOverrides); const postBody = { ...beParams, serviceName: service, clusterName: cluster, - } - - const task = this.fetch('/ecs', HTTP.POST, postBody); + }; - return task; + return this.fetch('/ecs', HTTP.POST, postBody); } - async getAuditDetails(backendParamsOverrides?: BackendParams): Promise { + async getAuditDetails( + backendParamsOverrides?: BackendParams, + ): Promise { const body = this.getAppliedBackendParams(backendParamsOverrides); const path = `/audit-entries`; - const results = this.fetch(path, HTTP.POST, body); - return results; + return this.fetch(path, HTTP.POST, body); } async updateService({ @@ -99,9 +113,8 @@ export class OPAApiClient implements OPAApi { taskDefinition: string; desiredCount: number | undefined; restart: boolean; - backendParamsOverrides?: BackendParams + backendParamsOverrides?: BackendParams; }): Promise { - const beParams = this.getAppliedBackendParams(backendParamsOverrides); const postBody = { @@ -111,11 +124,9 @@ export class OPAApiClient implements OPAApi { taskDefinition: taskDefinition, restart: restart, desiredCount: desiredCount, - } - - const serviceDetails = this.fetch('/ecs/updateService', HTTP.POST, postBody); + }; - return serviceDetails; + return this.fetch('/ecs/updateService', HTTP.POST, postBody); } async getSecret({ @@ -130,10 +141,12 @@ export class OPAApiClient implements OPAApi { const postBody = { ...beParams, secretArn: secretName, - } - const secretDetails = this.fetch('/secrets', HTTP.POST, postBody); - - return secretDetails; + }; + return this.fetch( + '/secrets', + HTTP.POST, + postBody, + ); } async getLogStreamNames({ @@ -146,12 +159,10 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/logs/stream'; - const logStreams = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, logGroupName, }); - - return logStreams; } async getLogStreamData({ @@ -161,35 +172,34 @@ export class OPAApiClient implements OPAApi { }: { logGroupName: string; logStreamName: string; - backendParamsOverrides?: BackendParams + backendParamsOverrides?: BackendParams; }): Promise { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/logs/stream-events'; - const logStreamData = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, logGroupName, logStreamName, }); - - return logStreamData; } - //TODO: Move platform calls to separate backend endpoints - interface does not require provider information + // TODO: Move platform calls to separate backend endpoints - interface does not require provider information async getPlatformSecret({ secretName, }: { secretName: string; }): Promise { - const postBody = { ...this.backendParams, - secretArn: secretName - } - - const secretDetails = this.fetch('/platform/secrets', HTTP.POST, postBody); + secretArn: secretName, + }; - return secretDetails; + return this.fetch( + '/platform/secrets', + HTTP.POST, + postBody, + ); } async getPlatformSSMParam({ @@ -197,16 +207,16 @@ export class OPAApiClient implements OPAApi { }: { paramName: string; }): Promise { - - const postBody = { ...this.backendParams, - paramName - } - - const paramDetails = this.fetch('/platform/ssm', HTTP.POST, postBody); + paramName, + }; - return paramDetails; + return this.fetch( + '/platform/ssm', + HTTP.POST, + postBody, + ); } async bindResource({ @@ -217,10 +227,8 @@ export class OPAApiClient implements OPAApi { repoInfo: IRepositoryInfo; params: BindResourceParams; gitAdminSecret: string; - backendParamsOverrides?: BackendParams + backendParamsOverrides?: BackendParams; }): Promise { - - const postBody = { ...this.backendParams, providerName: params.providerName, @@ -229,12 +237,10 @@ export class OPAApiClient implements OPAApi { envName: params.envName, policies: params.policies, resourceName: params.resourceName, - resourceEntityRef: params.resourceEntityRef - } - - const bindResponse = this.fetch('/platform/bind-resource', HTTP.POST, postBody); + resourceEntityRef: params.resourceEntityRef, + }; - return bindResponse; + return this.fetch('/platform/bind-resource', HTTP.POST, postBody); } async unBindResource({ @@ -259,12 +265,10 @@ export class OPAApiClient implements OPAApi { envName: params.envName, policies: params.policies, resourceName: params.resourceName, - resourceEntityRef: params.resourceEntityRef - } - - const unBindResponse = this.fetch('/platform/unbind-resource', HTTP.POST, postBody); + resourceEntityRef: params.resourceEntityRef, + }; - return unBindResponse; + return this.fetch('/platform/unbind-resource', HTTP.POST, postBody); } async updateProviderToEnvironment({ @@ -276,7 +280,7 @@ export class OPAApiClient implements OPAApi { backendParamsOverrides, }: { repoInfo: IRepositoryInfo; - provider: AWSEnvironmentProviderRecord + provider: AWSEnvironmentProviderRecord; gitAdminSecret: string; envName: string; action: string; @@ -289,11 +293,10 @@ export class OPAApiClient implements OPAApi { repoInfo, gitAdminSecret, envName, - action - } + action, + }; - const addProviderResponse = this.fetch('/platform/update-provider', HTTP.POST, postBody); - return addProviderResponse + return this.fetch('/platform/update-provider', HTTP.POST, postBody); } deleteTFProvider({ @@ -303,7 +306,7 @@ export class OPAApiClient implements OPAApi { envName, }: { backendParamsOverrides: BackendParams; - repoInfo: IRepositoryInfo + repoInfo: IRepositoryInfo; gitAdminSecret: string; envName: string; }): Promise { @@ -312,16 +315,14 @@ export class OPAApiClient implements OPAApi { ...beParams, repoInfo, gitAdminSecret, - envName - } - - const deleteResponse = this.fetch('/platform/delete-tf-provider', HTTP.POST, postBody); + envName, + }; - return deleteResponse; + return this.fetch('/platform/delete-tf-provider', HTTP.POST, postBody); } async deletePlatformSecret({ - secretName + secretName, }: { secretName: string; }): Promise { @@ -331,14 +332,15 @@ export class OPAApiClient implements OPAApi { appName: this.platformAppName, prefix: '', providerName: '', - secretName - } - - const deleteResponse = this.fetch('/platform/delete-secret', HTTP.POST, postBody); - - return deleteResponse; + secretName, + }; - }; + return this.fetch( + '/platform/delete-secret', + HTTP.POST, + postBody, + ); + } async deleteRepository({ repoInfo, @@ -353,10 +355,9 @@ export class OPAApiClient implements OPAApi { const postBody = { ...beParams, repoInfo, - gitAdminSecret - } - const deleteResponse = this.fetch('/platform/delete-repository', HTTP.POST, postBody); - return deleteResponse; + gitAdminSecret, + }; + return this.fetch('/platform/delete-repository', HTTP.POST, postBody); } async getStackDetails({ @@ -369,26 +370,26 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/cloudformation/describeStack'; - const stack = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, stackName, }); - - return stack; } - async getStackEvents( - { stackName, backendParamsOverrides, }: { stackName: string; backendParamsOverrides?: BackendParams; }) - : Promise { + async getStackEvents({ + stackName, + backendParamsOverrides, + }: { + stackName: string; + backendParamsOverrides?: BackendParams; + }): Promise { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/cloudformation/describeStackEvents'; - const stack = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, stackName, }); - - return stack; } async updateStack({ @@ -413,7 +414,7 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/cloudformation/updateStack'; - const stack = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, componentName, stackName, @@ -423,8 +424,6 @@ export class OPAApiClient implements OPAApi { gitAdminSecret, environmentName, }); - - return stack; } async createStack({ @@ -449,7 +448,7 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/cloudformation/createStack'; - const stack = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, componentName, stackName, @@ -459,8 +458,6 @@ export class OPAApiClient implements OPAApi { gitAdminSecret, environmentName, }); - - return stack; } async deleteStack({ @@ -475,13 +472,11 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/cloudformation/deleteStack'; - const stack = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, componentName, stackName, }); - - return stack; } doesS3FileExist({ @@ -496,13 +491,11 @@ export class OPAApiClient implements OPAApi { const beParams = this.getAppliedBackendParams(backendParamsOverrides); const path = '/s3/doesFileExist'; - const fileExistsOutput = this.fetch(path, HTTP.POST, { + return this.fetch(path, HTTP.POST, { ...beParams, bucketName, fileName, }); - - return fileExistsOutput; } async getResourceGroupResources({ @@ -518,8 +511,11 @@ export class OPAApiClient implements OPAApi { resourceGroupName: rscGroupArn, }; - const rscGroupDetails = this.fetch('/resource-group', HTTP.POST, postBody); - return rscGroupDetails; + return this.fetch( + '/resource-group', + HTTP.POST, + postBody, + ); } async getSSMParameter({ @@ -535,8 +531,11 @@ export class OPAApiClient implements OPAApi { ssmParamName, }; - const ssmParamDetails = this.fetch('/ssm-parameter', HTTP.POST, postBody); - return ssmParamDetails; + return this.fetch( + '/ssm-parameter', + HTTP.POST, + postBody, + ); } async describeTaskDefinition({ @@ -550,10 +549,12 @@ export class OPAApiClient implements OPAApi { const postBody = { ...beParams, taskDefinition: taskDefinitionArn, - } - const taskD = this.fetch('/ecs/describeTaskDefinition', HTTP.POST, postBody); - - return taskD; + }; + return this.fetch( + '/ecs/describeTaskDefinition', + HTTP.POST, + postBody, + ); } async updateTaskDefinition({ @@ -569,11 +570,14 @@ export class OPAApiClient implements OPAApi { const postBody = { ...beParams, taskDefinition: taskDefinitionArn, - envVar: envVar - } + envVar: envVar, + }; - const taskD = this.fetch('/ecs/updateTaskDefinition', HTTP.POST, postBody); - return taskD; + return this.fetch( + '/ecs/updateTaskDefinition', + HTTP.POST, + postBody, + ); } async promoteApp({ @@ -597,17 +601,16 @@ export class OPAApiClient implements OPAApi { envRequiresManualApproval, repoInfo, gitAdminSecret, - providers: providersData - } - const results = this.fetch('/git/promote', HTTP.POST, postBody); - return results; + providers: providersData, + }; + return this.fetch('/git/promote', HTTP.POST, postBody); } async invokeLambda({ functionName, actionDescription, body, - backendParamsOverrides + backendParamsOverrides, }: { functionName: string; actionDescription: string; @@ -619,18 +622,21 @@ export class OPAApiClient implements OPAApi { ...beParams, functionName, actionDescription, - body + body, }; - // console.log(postBody) - const lambdaResult = await this.fetch('/lambda/invoke', HTTP.POST, postBody); - return lambdaResult; + + return await this.fetch( + '/lambda/invoke', + HTTP.POST, + postBody, + ); } async getEKSAppManifests({ envName, gitAdminSecret, repoInfo, - backendParamsOverrides + backendParamsOverrides, }: { envName: string; gitAdminSecret: string; @@ -643,14 +649,14 @@ export class OPAApiClient implements OPAApi { ...beParams, envName, gitAdminSecret, - repoInfo + repoInfo, }; - // console.log(postBody) - let configResult = await this.fetch('/platform/fetch-eks-config', HTTP.POST, postBody); - // console.log(configResult); - - return configResult; + return await this.fetch( + '/platform/fetch-eks-config', + HTTP.POST, + postBody, + ); } async updateEKSApp({ @@ -663,7 +669,7 @@ export class OPAApiClient implements OPAApi { lambdaRoleArn, gitAdminSecret, repoInfo, - backendParamsOverrides + backendParamsOverrides, }: { actionDescription: string; envName: string; @@ -676,11 +682,11 @@ export class OPAApiClient implements OPAApi { repoInfo: IRepositoryInfo; backendParamsOverrides?: BackendParams; }): Promise { - let configResult = await this.getEKSAppManifests({ + const configResult = await this.getEKSAppManifests({ envName, gitAdminSecret, repoInfo, - backendParamsOverrides + backendParamsOverrides, }); const updateKeyArray = updateKey.split('.'); @@ -689,54 +695,51 @@ export class OPAApiClient implements OPAApi { for (let i = 0; i < updateKeyArray.length; i++) { const currKey = updateKeyArray[i]; if (currObj.hasOwnProperty(currKey)) { - // console.log(currKey) - // console.log(currObj) if (i === updateKeyArray.length - 1) { - // console.log(`key ${currKey} found , current value ${currObj[currKey]}`) - currObj[currKey] = updateValue - // console.log(`Updating key ${currKey} found , current value ${currObj[currKey]}`) - + currObj[currKey] = updateValue; } else { - currObj = currObj[currKey] + currObj = currObj[currKey]; } - } - else { - break + } else { + break; } } - }) - // console.log(configResult) + }); - //make changes to config - const manifest = JSON.stringify(configResult) + // make changes to config + const manifest = JSON.stringify(configResult); const bodyParam = { - RequestType: "Update", - ResourceType: "Custom::AWSCDK-EKS-KubernetesResource", + RequestType: 'Update', + ResourceType: 'Custom::AWSCDK-EKS-KubernetesResource', ResourceProperties: { - TimeoutSeconds: "5", + TimeoutSeconds: '5', ClusterName: cluster, RoleArn: lambdaRoleArn, InvocationType: 'RequestResponse', Manifest: manifest, - } + }, }; - const configUpdateResult = await this.invokeLambda({ + return await this.invokeLambda({ functionName: kubectlLambda, actionDescription, - body: JSON.stringify(bodyParam) - }) - - return configUpdateResult; + body: JSON.stringify(bodyParam), + }); } - private async fetch(path: string, method = HTTP.GET, data?: any): Promise { - const baseUrl = `${await this.configApi.getString('backend.baseUrl')}/api/aws-apps-backend`; + private async fetch( + path: string, + method = HTTP.GET, + data?: any, + ): Promise { + const baseUrl = `${this.configApi.getString( + 'backend.baseUrl', + )}/api/aws-apps-backend`; const url = baseUrl + path; - let headers: { [k: string]: string } = {}; + const headers: { [k: string]: string } = {}; - let requestOptions: RequestInit = { + const requestOptions: RequestInit = { method, headers, }; @@ -751,12 +754,11 @@ export class OPAApiClient implements OPAApi { throw await ResponseError.fromResponse(response); } - let responseType = response.headers.get('Content-Type'); + const responseType = response.headers.get('Content-Type'); if (responseType && responseType.indexOf('application/json') >= 0) { - return response.json() as Promise; - } else { - return response.text() as unknown as Promise; + return (await response.json()) as Promise; } + return (await response.text()) as unknown as Promise; } } diff --git a/backstage-plugins/plugins/aws-apps/src/components/AnnotationTypeTable/AnnotationTypeTable.tsx b/backstage-plugins/plugins/aws-apps/src/components/AnnotationTypeTable/AnnotationTypeTable.tsx index ef07cb96..efc8020a 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AnnotationTypeTable/AnnotationTypeTable.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AnnotationTypeTable/AnnotationTypeTable.tsx @@ -9,14 +9,19 @@ import { useAnnotationsFromEntity } from '../../hooks/custom-hooks'; import { isAnnotationsAvailable } from '../../plugin'; import { Entity } from '@backstage/catalog-model'; -const AnnotationTypeTable = ({ entity, type }: { entity: Entity; type: string }) => { +const AnnotationTypeTable = ({ + entity, + type, +}: { + entity: Entity; + type: string; +}) => { const initAnnotations = { ...useAnnotationsFromEntity(entity) }; Object.keys(initAnnotations).forEach(key => { - // If annotation is of the correct keep it + // If the annotation is of the correct type, keep it if (key.includes(type)) { - console.log(key); - // Seperate out Annotation + // Separate out Annotation let newKey = key.replace(`${type}/`, '').replace(/-/g, ' '); // Capital case the annotation newKey = newKey @@ -28,13 +33,22 @@ const AnnotationTypeTable = ({ entity, type }: { entity: Entity; type: string }) delete initAnnotations[key]; }); - return ; + return ( + + ); }; export const AnnotationWidget = ({ type }: { type: string }) => { const { entity } = useEntity(); return !isAnnotationsAvailable(entity) ? ( - + ) : ( ); diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AdvancedEntityTypePicker.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AdvancedEntityTypePicker.tsx index 860026f1..0f5f76a9 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AdvancedEntityTypePicker.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AdvancedEntityTypePicker.tsx @@ -7,9 +7,15 @@ import { Box } from '@material-ui/core'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { Select } from '@backstage/core-components'; -import { EntityTypePickerProps, useEntityTypeFilter } from '@backstage/plugin-catalog-react'; +import { + EntityTypePickerProps, + useEntityTypeFilter, +} from '@backstage/plugin-catalog-react'; -function filterTypes(allTypes: string[], allowedTypes?: string[]): Record { +function filterTypes( + allTypes: string[], + allowedTypes?: string[], +): Record { // Before allTypes is loaded, or when a kind is entered manually in the URL, selectedKind may not // be present in allTypes. It should still be shown in the dropdown, but may not have the nice // enforced casing from the catalog-backend. This makes a key/value record for the Select options, @@ -18,21 +24,25 @@ function filterTypes(allTypes: string[], allowedTypes?: string[]): Record - allowedTypes.some(a => a.toLocaleLowerCase('en-US') === k.toLocaleLowerCase('en-US')), + allowedTypes.some( + a => a.toLocaleLowerCase('en-US') === k.toLocaleLowerCase('en-US'), + ), ); } availableTypes.sort((a, b) => { - if (a < b) { return -1; } - if (a > b) { return 1; } + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } return 0; }); - const typesMap = availableTypes.reduce((acc, kind) => { + return availableTypes.reduce((acc, kind) => { acc[kind.toLocaleLowerCase('en-US')] = kind; return acc; }, {} as Record); - - return typesMap; } /** @@ -45,10 +55,13 @@ export interface AdvancedEntityTypePickerProps extends EntityTypePickerProps { } /** @public */ -export const AdvancedEntityTypePicker = (props: AdvancedEntityTypePickerProps) => { +export const AdvancedEntityTypePicker = ( + props: AdvancedEntityTypePickerProps, +) => { const { allowedTypes, hidden, initialFilter } = props; const alertApi = useApi(alertApiRef); - const { error, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter(); + const { error, availableTypes, selectedTypes, setSelectedTypes } = + useEntityTypeFilter(); useEffect(() => { if (error) { @@ -80,7 +93,9 @@ export const AdvancedEntityTypePicker = (props: AdvancedEntityTypePickerProps) = label="Type" items={items} selected={(items.length > 1 ? selectedTypes[0] : undefined) ?? 'all'} - onChange={value => setSelectedTypes(value === 'all' ? [] : [String(value)])} + onChange={value => + setSelectedTypes(value === 'all' ? [] : [String(value)]) + } /> ); diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AppCatalogPage.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AppCatalogPage.tsx index cd5db3f0..af437e4f 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AppCatalogPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/AppCatalogPage.tsx @@ -7,9 +7,11 @@ import { CreateButton, PageWithHeader, SupportButton, - TableColumn, } from '@backstage/core-components'; -import { CatalogTable, CatalogTableRow, DefaultCatalogPageProps } from '@backstage/plugin-catalog'; +import { + CatalogTable, + DefaultCatalogPageProps, +} from '@backstage/plugin-catalog'; import { CatalogFilterLayout, EntityKindPicker, @@ -33,31 +35,40 @@ export interface AppCatalogPageProps extends DefaultCatalogPageProps { } export function AppCatalogPage(props: AppCatalogPageProps) { + const { actions, tableOptions = {}, emptyContent, kind } = props; + let { columns, - actions, initiallySelectedFilter = 'owned', initialKind = 'component', initialType = 'aws-app', - tableOptions = {}, - emptyContent, - kind, } = props; let allowedTypesComponent = ['aws-app']; let allowedTypesResource = ['aws-rds', 'aws-s3']; let allowedTypesEnvironment = ['environment-provider', 'environment']; - let allowedKinds = ['Component', 'Resource', 'AWSEnvironment', 'AWSEnvironmentProvider']; + let allowedKinds = [ + 'Component', + 'Resource', + 'AWSEnvironment', + 'AWSEnvironmentProvider', + ]; if (kind === 'all') { allowedTypesComponent = ['aws-app']; allowedTypesResource = ['aws-rds', 'aws-s3']; allowedTypesEnvironment = ['environment-provider', 'environment']; - allowedKinds = ['Component', 'Resource', 'AWSEnvironment', 'AWSEnvironmentProvider']; + allowedKinds = [ + 'Component', + 'Resource', + 'AWSEnvironment', + 'AWSEnvironmentProvider', + ]; initialKind = 'component'; initialType = 'aws-app'; } else if (kind === 'awsenvironment') { - const awsEnvironmentColumns: TableColumn[] = [ + // AWS Environment Columns + columns = [ columnFactories.createTitleColumn({ hidden: true }), columnFactories.createNameColumn({ defaultKind: initialKind }), columnFactories.createOwnerColumn(), @@ -72,7 +83,6 @@ export function AppCatalogPage(props: AppCatalogPageProps) { columnFactories.createEnvironmentLevelColumn(), columnFactories.createTagsColumn(), ]; - columns = awsEnvironmentColumns; allowedTypesResource = []; allowedTypesEnvironment = ['environment']; initialType = 'environment'; @@ -81,7 +91,8 @@ export function AppCatalogPage(props: AppCatalogPageProps) { allowedTypesComponent = []; initiallySelectedFilter = 'all'; } else if (kind === 'awsenvironmentprovider') { - const awsProviderColumns: TableColumn[] = [ + // AWS Provider Columns + columns = [ columnFactories.createTitleColumn({ hidden: true }), columnFactories.createNameColumn({ defaultKind: initialKind }), columnFactories.createOwnerColumn(), @@ -93,7 +104,6 @@ export function AppCatalogPage(props: AppCatalogPageProps) { columnFactories.createProviderRegionColumn(), columnFactories.createTagsColumn(), ]; - columns = awsProviderColumns; allowedKinds = ['AWSEnvironmentProvider']; allowedTypesResource = []; allowedTypesEnvironment = ['environment-provider']; @@ -102,7 +112,8 @@ export function AppCatalogPage(props: AppCatalogPageProps) { allowedTypesComponent = []; initiallySelectedFilter = 'all'; } else if (kind === 'component' && initialType === 'aws-app') { - const awsAppsColumns: TableColumn[] = [ + // AWS Apps Columns + columns = [ columnFactories.createTitleColumn({ hidden: true }), columnFactories.createNameColumn({ defaultKind: initialKind }), columnFactories.createMetadataDescriptionColumn(), @@ -112,12 +123,11 @@ export function AppCatalogPage(props: AppCatalogPageProps) { columnFactories.createMetadataDescriptionColumn(), columnFactories.createTagsColumn(), ]; - columns=awsAppsColumns allowedKinds = ['Component']; initiallySelectedFilter = 'all'; - } else if (kind === 'resource') { - const awsResourcesColumns: TableColumn[] = [ + // AWS Resources Columns + columns = [ columnFactories.createTitleColumn({ hidden: true }), columnFactories.createNameColumn({ defaultKind: initialKind }), columnFactories.createOwnerColumn(), @@ -127,7 +137,6 @@ export function AppCatalogPage(props: AppCatalogPageProps) { columnFactories.createIACColumn(), columnFactories.createTagsColumn(), ]; - columns = awsResourcesColumns; allowedKinds = ['Resource']; allowedTypesResource = ['aws-resource']; allowedTypesEnvironment = []; @@ -136,27 +145,34 @@ export function AppCatalogPage(props: AppCatalogPageProps) { allowedTypesComponent = []; initiallySelectedFilter = 'all'; } else { - console.error(`catalog not yet implemented for kind ${kind}`); + // console.error(`catalog not yet implemented for kind ${kind}`); columns = []; } return ( - + All your AWS software catalog - + diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/awsColumns.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/awsColumns.tsx index 4e3f8df8..81ee8e9e 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/awsColumns.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppCatalogPage/awsColumns.tsx @@ -40,7 +40,7 @@ export const columnFactories = Object.freeze({ return formatContent(entity1).localeCompare(formatContent(entity2)); }, cellStyle: { - minWidth:'175px' + minWidth: '175px', }, render: ({ entity }) => ( ), - }; }, createSystemColumn(): TableColumn { @@ -61,7 +60,7 @@ export const columnFactories = Object.freeze({ defaultKind="system" /> ), - width:'auto' + width: 'auto', }; }, createOwnerColumn(): TableColumn { @@ -69,7 +68,7 @@ export const columnFactories = Object.freeze({ title: 'Owner', field: 'resolved.ownedByRelationsTitle', cellStyle: { - minWidth:'130px' + minWidth: '130px', }, render: ({ resolved }) => ( { @@ -120,7 +119,7 @@ export const columnFactories = Object.freeze({ title: 'Description', field: 'entity.metadata.description', cellStyle: { - minWidth:'175px' + minWidth: '175px', }, render: ({ entity }) => ( ), - }; }, createProviderAccountColumn(): TableColumn { @@ -137,14 +135,10 @@ export const columnFactories = Object.freeze({ field: 'entity.metadata["awsAccount"]', cellStyle: { padding: '0px 16px 0px 20px', - minWidth:'150px' + minWidth: '150px', }, render: ({ entity }) => ( - <> - { - entity.metadata["awsAccount"]?.toString() || "" - } - + <>{entity.metadata.awsAccount?.toString() || ''} ), width: 'auto', }; @@ -157,11 +151,7 @@ export const columnFactories = Object.freeze({ padding: '0px 16px 0px 20px', }, render: ({ entity }) => ( - <> - { - entity.metadata["awsRegion"]?.toString() || "" - } - + <>{entity.metadata.awsRegion?.toString() || ''} ), width: 'auto', }; @@ -173,13 +163,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.metadata["prefix"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.metadata.prefix?.toString() || ''}, width: 'auto', }; }, @@ -189,17 +173,12 @@ export const columnFactories = Object.freeze({ field: 'entity.metadata["resourceType"]', cellStyle: { padding: '0px 16px 0px 20px', - minWidth:'30px' + minWidth: '30px', }, render: ({ entity }) => ( - <> - { - entity.metadata["resourceType"]?.toString() || "" - } - + <>{entity.metadata.resourceType?.toString() || ''} ), - //width: 'auto', - + // width: 'auto', }; }, createIACColumn(): TableColumn { @@ -209,13 +188,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.metadata["iacType"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.metadata.iacType?.toString() || ''}, width: 'auto', }; }, @@ -227,11 +200,7 @@ export const columnFactories = Object.freeze({ padding: '0px 16px 0px 20px', }, render: ({ entity }) => ( - <> - { - entity.metadata["environmentType"]?.toString() || "" - } - + <>{entity.metadata.environmentType?.toString() || ''} ), width: 'auto', }; @@ -243,13 +212,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.spec?.["system"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.spec?.system?.toString() || ''}, width: 'auto', }; }, @@ -260,13 +223,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.metadata["category"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.metadata.category?.toString() || ''}, width: 'auto', }; }, @@ -278,11 +235,7 @@ export const columnFactories = Object.freeze({ padding: '0px 16px 0px 20px', }, render: ({ entity }) => ( - <> - { - entity.metadata["classification"]?.toString() || "" - } - + <>{entity.metadata.classification?.toString() || ''} ), width: 'auto', }; @@ -294,13 +247,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.metadata["level"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.metadata.level?.toString() || ''}, width: 'auto', }; }, @@ -312,11 +259,7 @@ export const columnFactories = Object.freeze({ padding: '0px 16px 0px 20px', }, render: ({ entity }) => ( - <> - { - entity.metadata["envTypeAccount"]?.toString() || "" - } - + <>{entity.metadata.envTypeAccount?.toString() || ''} ), width: 'auto', }; @@ -329,11 +272,7 @@ export const columnFactories = Object.freeze({ padding: '0px 16px 0px 20px', }, render: ({ entity }) => ( - <> - { - entity.metadata["envTypeRegion"]?.toString() || "" - } - + <>{entity.metadata.envTypeRegion?.toString() || ''} ), width: 'auto', }; @@ -345,13 +284,7 @@ export const columnFactories = Object.freeze({ cellStyle: { padding: '0px 16px 0px 20px', }, - render: ({ entity }) => ( - <> - { - entity.metadata["envType"]?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.metadata.envType?.toString() || ''}, width: 'auto', }; }, @@ -361,15 +294,9 @@ export const columnFactories = Object.freeze({ field: 'entity.spec.subType', cellStyle: { padding: '0px 16px 0px 20px', - minWidth:'150px' + minWidth: '150px', }, - render: ({ entity }) => ( - <> - { - entity.spec?.subType?.toString() || "" - } - - ), + render: ({ entity }) => <>{entity.spec?.subType?.toString() || ''}, width: 'auto', }; }, diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppConfigCard/AppConfigCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppConfigCard/AppConfigCard.tsx index 7a5dc96e..51766443 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppConfigCard/AppConfigCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppConfigCard/AppConfigCard.tsx @@ -3,13 +3,20 @@ import { InfoCard, EmptyState } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { IconButton, LinearProgress, Tooltip } from '@material-ui/core'; -import { Button, CardContent, Grid, TextField, Typography } from '@mui/material'; +import Button from '@mui/material/Button'; +import Grid from '@mui/material/Grid'; +import CardContent from '@mui/material/CardContent'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; import DeleteIcon from '@mui/icons-material/Delete'; import React, { useEffect, useState } from 'react'; import { opaApiRef } from '../../api'; import { useAsyncAwsApp } from '../../hooks/useAwsApp'; import { ContainerDetailsType } from '../../types'; -import { AWSComponent, AWSECSAppDeploymentEnvironment } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSComponent, + AWSECSAppDeploymentEnvironment, +} from '@aws/plugin-aws-apps-common-for-backstage'; const AppConfigOverview = ({ input: { awsComponent }, @@ -21,53 +28,69 @@ const AppConfigOverview = ({ const api = useApi(opaApiRef); // States managed by React useState - const [savedEnvVariables, setSavedEnvVariables] = useState([]); + const [savedEnvVariables, setSavedEnvVariables] = useState< + ContainerDetailsType[] + >([]); const [envVariables, setEnvVariables] = useState([]); const [loading, setLoading] = useState(true); const [unsavedChanges, setUnsavedChanges] = useState(false); const [edit, setEdit] = useState(false); - const [error, setError] = useState<{ isError: boolean; errorMsg: string | null }>({ isError: false, errorMsg: null }); + const [error, setError] = useState<{ + isError: boolean; + errorMsg: string | null; + }>({ isError: false, errorMsg: null }); const env = awsComponent.currentEnvironment as AWSECSAppDeploymentEnvironment; // get latest task definition - const latestTaskDef = env.app.taskDefArn.substring(0, env.app.taskDefArn.lastIndexOf(":")) - - async function getData() { - const taskDefinition = await api.describeTaskDefinition({ - taskDefinitionArn: latestTaskDef, - }); + const latestTaskDef = env.app.taskDefArn.substring( + 0, + env.app.taskDefArn.lastIndexOf(':'), + ); - const containerDetails = taskDefinition.containerDefinitions?.map(containerDef => { - return { - containerName: containerDef?.name, - env: containerDef?.environment, - }; - }); + useEffect(() => { + async function getData() { + const taskDefinition = await api.describeTaskDefinition({ + taskDefinitionArn: latestTaskDef, + }); - setSavedEnvVariables(containerDetails!); - setEnvVariables(containerDetails!); - } + const containerDetails = taskDefinition.containerDefinitions?.map( + containerDef => { + return { + containerName: containerDef?.name, + env: containerDef?.environment, + }; + }, + ); - useEffect(() => { + setSavedEnvVariables(containerDetails!); + setEnvVariables(containerDetails!); + } getData() .then(() => { setLoading(false); setError({ isError: false, errorMsg: '' }); }) .catch(e => { - setError({ isError: true, errorMsg: `Unexpected error occurred while getting taskDefinition data: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while getting taskDefinition data: ${e}`, + }); setLoading(false); }); - }, []); + }, [api, latestTaskDef]); const onEdit = (containerName: string) => { - // don't allow switching out of edit mode if any environment variables are empty if (edit) { let emptyVar = false; - const containerDetails = envVariables.filter(details => details.containerName === containerName)[0]; + const containerDetails = envVariables.filter( + details => details.containerName === containerName, + )[0]; for (const i in containerDetails.env) { - if (containerDetails.env[Number(i)].name === '' || containerDetails.env[Number(i)].value === '') { + if ( + containerDetails.env[Number(i)].name === '' || + containerDetails.env[Number(i)].value === '' + ) { emptyVar = true; break; } @@ -84,10 +107,14 @@ const AppConfigOverview = ({ const onSave = () => { setLoading(true); let emptyVar = false; - const env = awsComponent.currentEnvironment as AWSECSAppDeploymentEnvironment; - envVariables.map(containerDef => { + const currentEnvironment = + awsComponent.currentEnvironment as AWSECSAppDeploymentEnvironment; + envVariables.forEach(containerDef => { for (const i in containerDef.env) { - if (containerDef.env[Number(i)].name === '' || containerDef.env[Number(i)].value === '') { + if ( + containerDef.env[Number(i)].name === '' || + containerDef.env[Number(i)].value === '' + ) { emptyVar = true; break; } @@ -101,7 +128,7 @@ const AppConfigOverview = ({ api .updateTaskDefinition({ taskDefinitionArn: latestTaskDef, - envVar: envVariables + envVar: envVariables, }) .then(td => { const containerDet = td.containerDefinitions?.map(condef => { @@ -118,9 +145,9 @@ const AppConfigOverview = ({ api .updateService({ - cluster: env.clusterName, - service: env.app.serviceArn, - taskDefinition: env.app.taskDefArn, + cluster: currentEnvironment.clusterName, + service: currentEnvironment.app.serviceArn, + taskDefinition: currentEnvironment.app.taskDefArn, restart: true, desiredCount: undefined, // prefix, @@ -133,16 +160,22 @@ const AppConfigOverview = ({ }) .catch(e => { setLoading(false); - setError({ isError: true, errorMsg: `Unexpected error occurred while udpating taskDefinition: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while udpating taskDefinition: ${e}`, + }); }); - }; // Returns a new object reference that is a shallow clone of envVariables, except for the - // specific containerName, which will be deep cloned. - const getEnvVarsPartialDeepClone = (containerName: string): ContainerDetailsType[] => { + // specific containerName, which will be deep-cloned. + const getEnvVarsPartialDeepClone = ( + containerName: string, + ): ContainerDetailsType[] => { const newState = [...envVariables]; - const containerIndex = newState.findIndex((search: ContainerDetailsType) => search.containerName === containerName); + const containerIndex = newState.findIndex( + (search: ContainerDetailsType) => search.containerName === containerName, + ); // the env array object reference needs to be changed or else we can't detect // unsaved changes @@ -152,8 +185,10 @@ const AppConfigOverview = ({ return newState; }; - const checkForUnsavedChanges = (containerName: string, newDetails: ContainerDetailsType[]) => { - + const checkForUnsavedChanges = ( + containerName: string, + newDetails: ContainerDetailsType[], + ) => { if (savedEnvVariables === newDetails) { setUnsavedChanges(false); return; @@ -164,8 +199,12 @@ const AppConfigOverview = ({ return; } - const savedDetails = savedEnvVariables.filter(details => details.containerName === containerName)[0]; - const details = newDetails.filter(details => details.containerName === containerName)[0]; + const savedDetails = savedEnvVariables.filter( + details => details.containerName === containerName, + )[0]; + const details = newDetails.filter( + detail => detail.containerName === containerName, + )[0]; if (savedDetails.env?.length !== details.env?.length) { setUnsavedChanges(true); @@ -177,24 +216,32 @@ const AppConfigOverview = ({ for (let index = 0; index < (savedDetails.env?.length || 0); index++) { const keyValPair = savedDetails.env![index]; - if (keyValPair?.name !== details.env?.[index]?.name || - keyValPair?.value !== details.env?.[index]?.value) { - + if ( + keyValPair?.name !== details.env?.[index]?.name || + keyValPair?.value !== details.env?.[index]?.value + ) { setUnsavedChanges(true); return; } } setUnsavedChanges(false); - } + }; - const onEnvVarChange = (containerName: string, type: string, value: string, envVarIndex: number) => { + const onEnvVarChange = ( + containerName: string, + type: string, + value: string, + envVarIndex: number, + ) => { const newState = getEnvVarsPartialDeepClone(containerName); - const containerDetails = newState.filter(details => details.containerName === containerName)[0]; + const containerDetails = newState.filter( + details => details.containerName === containerName, + )[0]; const originalKeyVal = containerDetails.env![envVarIndex]; - if (type === "key") { + if (type === 'key') { containerDetails.env![envVarIndex] = { ...originalKeyVal, name: value }; } else { containerDetails.env![envVarIndex] = { ...originalKeyVal, value }; @@ -206,16 +253,20 @@ const AppConfigOverview = ({ const onDeleteEnvVar = (containerName: string, envVarIndex: number) => { const newState = getEnvVarsPartialDeepClone(containerName); - const containerDetails = newState.filter(details => details.containerName === containerName)[0]; + const containerDetails = newState.filter( + details => details.containerName === containerName, + )[0]; containerDetails.env!.splice(envVarIndex, 1); // delete the env var out of the array checkForUnsavedChanges(containerName, newState); setEnvVariables(newState); - } + }; const onAddEnvVar = (containerName: string) => { const newState = getEnvVarsPartialDeepClone(containerName); - const containerDetails = newState.filter(details => details.containerName === containerName)[0]; + const containerDetails = newState.filter( + details => details.containerName === containerName, + )[0]; if (!containerDetails.env) { containerDetails.env = []; @@ -237,7 +288,9 @@ const AppConfigOverview = ({ } if (error.isError) { - return {error.errorMsg}; + return ( + {error.errorMsg} + ); } return ( @@ -247,15 +300,23 @@ const AppConfigOverview = ({ {envVariables.map((containerDetails, index) => { return (
- + - ENVIRONMENT VARIABLES: "{containerDetails.containerName}" + + ENVIRONMENT VARIABLES: "{containerDetails.containerName}" + @@ -265,7 +326,7 @@ const AppConfigOverview = ({ size="small" id={index.toString()} onClick={() => onEdit(containerDetails.containerName!)} - disabled={!containerDetails.env || !containerDetails.env.length} + disabled={containerDetails?.env?.length === 0} > Edit @@ -279,36 +340,73 @@ const AppConfigOverview = ({ > Save - {containerDetails.env?.length != 0 ? ( - + {containerDetails.env?.length !== 0 ? ( + - Name + + Name + - Value + + {' '} + Value + ) : ( {' '} - No environment variables defined for container {containerDetails.containerName} + No environment variables defined for container{' '} + {containerDetails.containerName} )} {containerDetails.env?.map((nameAndValue, envVarIndex) => ( - + - ) => onEnvVarChange(containerDetails.containerName!, "key", e.target.value, envVarIndex)} + onChange={( + e: React.ChangeEvent, + ) => + onEnvVarChange( + containerDetails.containerName!, + 'key', + e.target.value, + envVarIndex, + ) + } disabled={!edit} error={!nameAndValue.name} - helperText={nameAndValue.name ? '' : 'Cannot be Empty'} - > + helperText={ + nameAndValue.name ? '' : 'Cannot be Empty' + } + /> ) => onEnvVarChange(containerDetails.containerName!, "value", e.target.value, envVarIndex)} + onChange={( + e: React.ChangeEvent, + ) => + onEnvVarChange( + containerDetails.containerName!, + 'value', + e.target.value, + envVarIndex, + ) + } disabled={!edit} error={!nameAndValue.value} - helperText={nameAndValue.value ? '' : 'Cannot be Empty'} - > + helperText={ + nameAndValue.value ? '' : 'Cannot be Empty' + } + /> - {edit && + {edit && ( - - onDeleteEnvVar(containerDetails.containerName!, envVarIndex)}> - + + onDeleteEnvVar( + containerDetails.containerName!, + envVarIndex, + ) + } + > + - } + )} ))} @@ -352,11 +472,16 @@ export const AppConfigCard = () => { return ; } else if (awsAppLoadingStatus.component) { const input = { - awsComponent: awsAppLoadingStatus.component + awsComponent: awsAppLoadingStatus.component, }; return ; - } else { - return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/AppLinksCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/AppLinksCard.tsx index 4e1905f3..d1d15c97 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/AppLinksCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/AppLinksCard.tsx @@ -1,14 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { useEntity, } from '@backstage/plugin-catalog-react'; +import { useEntity } from '@backstage/plugin-catalog-react'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; import { ColumnBreakpoints } from '@backstage/plugin-catalog'; import { AppLinksEmptyState } from './AppLinksEmptyState'; import { AppLinksGridList } from './AppLinksGridList'; import { IconComponent, useApp } from '@backstage/core-plugin-api'; -import { EmptyState, InfoCard, InfoCardVariants } from '@backstage/core-components'; +import { + EmptyState, + InfoCard, + InfoCardVariants, +} from '@backstage/core-components'; import { LinearProgress } from '@material-ui/core'; import { useAsyncAwsApp } from '../../hooks/useAwsApp'; import { AWSComponent } from '@aws/plugin-aws-apps-common-for-backstage'; @@ -58,7 +62,12 @@ export const AppLinksCard = () => { return ; } else if (awsAppLoadingStatus.component) { return ; - } else { - return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/useDynamicColumns.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/useDynamicColumns.tsx index 4402ec50..29df453f 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/useDynamicColumns.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppLinksCard/useDynamicColumns.tsx @@ -23,7 +23,7 @@ export function useDynamicColumns( useMediaQuery((theme: Theme) => theme.breakpoints.up('xs')) ? 'xs' : null, ]; - let numOfCols = 1; + let numOfCols: number; if (typeof cols === 'number') { numOfCols = cols; diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AppPromoCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AppPromoCard.tsx index 86bf78e7..3aea62c7 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AppPromoCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AppPromoCard.tsx @@ -1,14 +1,40 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { AWSComponent, AWSProviderParams, AwsDeploymentEnvironments, getGitCredentailsSecret } from '@aws/plugin-aws-apps-common-for-backstage'; -import { CompoundEntityRef, Entity, EntityRelation, parseEntityRef } from '@backstage/catalog-model'; -import { EmptyState, InfoCard, } from '@backstage/core-components'; +import { + AWSComponent, + AwsDeploymentEnvironments, + AWSProviderParams, + getGitCredentailsSecret, +} from '@aws/plugin-aws-apps-common-for-backstage'; +import { + CompoundEntityRef, + Entity, + EntityRelation, + parseEntityRef, +} from '@backstage/catalog-model'; +import { EmptyState, InfoCard } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { CatalogApi, catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; -import { Button, CardContent, FormControl, FormHelperText, Grid, InputLabel, LinearProgress, MenuItem, Select } from '@material-ui/core'; +import { + CatalogApi, + catalogApiRef, + useEntity, +} from '@backstage/plugin-catalog-react'; +import { + Button, + CardContent, + FormControl, + FormHelperText, + Grid, + InputLabel, + LinearProgress, + MenuItem, + Select, +} from '@material-ui/core'; import InfoIcon from '@mui/icons-material/Info'; -import { Alert, AlertTitle, Typography } from '@mui/material'; +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Typography from '@mui/material/Typography'; import Backdrop from '@mui/material/Backdrop'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; @@ -22,64 +48,78 @@ import { AwsEksEnvPromoDialog } from './AwsEksEnvPromoDialog'; const AppPromoCard = ({ input: { awsComponent, catalogApi, appEntity }, }: { - input: { awsComponent: AWSComponent; catalogApi: CatalogApi, appEntity: Entity }; + input: { + awsComponent: AWSComponent; + catalogApi: CatalogApi; + appEntity: Entity; + }; }) => { const [envChoices, setEnvChoices] = useState([]); - const [selectedItem, setSelectedItem] = useState(""); + const [selectedItem, setSelectedItem] = useState(''); const [disabled, setDisabled] = useState(false); const [spinning, setSpinning] = useState(false); const [openEksDialog, setOpenEksDialog] = useState(false); const [isPromotionSuccessful, setIsPromotionSuccessful] = useState(false); - const [promotedEnvName, setPromotedEnvName] = useState(""); - const [promoteResultMessage, setPromoteResultMessage] = useState(""); - const [suggestedEksNamespace, setSuggestedEksNamespace] = useState(""); - const [suggestedIamRoleArn, setSuggestedIamRoleArn] = useState(""); + const [promotedEnvName, setPromotedEnvName] = useState(''); + const [promoteResultMessage, setPromoteResultMessage] = useState(''); + const [suggestedEksNamespace, setSuggestedEksNamespace] = useState(''); + const [suggestedIamRoleArn, setSuggestedIamRoleArn] = useState(''); const api = useApi(opaApiRef); - - function getHighestLevelEnvironment(currentEnvironments: AwsDeploymentEnvironments) { + + function getHighestLevelEnvironment( + currentEnvironments: AwsDeploymentEnvironments, + ) { let highestLevel = 1; Object.keys(currentEnvironments).forEach(env => { if (highestLevel <= currentEnvironments[env].environment.level) { highestLevel = currentEnvironments[env].environment.level; - } - }) + }); return highestLevel; } - function getApplicableEnvironments( - catalogEntities: Entity[], - envType: string, - currentEnvironments: AwsDeploymentEnvironments): Entity[] { - - // by now, we got applicable environments for the same runtime and that we have yet to deploy on. - const lowestEnvironmentLevel = getHighestLevelEnvironment(currentEnvironments); - - const currentEnvKeys = Object.keys(currentEnvironments); - - return catalogEntities - .filter(en => { - return ( - en.metadata["environmentType"] === envType && - !currentEnvKeys.includes(en.metadata.name) && - Number.parseInt(en.metadata["level"]?.toString()!) >= lowestEnvironmentLevel - ) - }) - .sort( - (a, b) => Number.parseInt(a.metadata['level']?.toString()!) - Number.parseInt(b.metadata['level']?.toString()!), - ); - }; + useEffect(() => { + const filterExpression = { + kind: 'awsenvironment', + 'metadata.environmentType': + awsComponent.currentEnvironment.environment.envType, + // 'spec.system': component.currentEnvironment.environment.system, TODO: when system is implemented filter on similar system. + }; - const filterExpression = { - 'kind': "awsenvironment", - 'metadata.environmentType': awsComponent.currentEnvironment.environment.envType - // 'spec.system': component.currentEnvironment.environment.system, TODO: when system is implemented filter on similar system. - }; + function getApplicableEnvironments( + catalogEntities: Entity[], + envType: string, + currentEnvironments: AwsDeploymentEnvironments, + ): Entity[] { + // by now, we got applicable environments for the same runtime and that we have yet to deploy on. + const lowestEnvironmentLevel = + getHighestLevelEnvironment(currentEnvironments); + + const currentEnvKeys = Object.keys(currentEnvironments); + + return catalogEntities + .filter(en => { + return ( + en.metadata.environmentType === envType && + !currentEnvKeys.includes(en.metadata.name) && + Number.parseInt(en.metadata.level?.toString()!, 10) >= + lowestEnvironmentLevel + ); + }) + .sort( + (a, b) => + Number.parseInt(a.metadata.level?.toString()!, 10) - + Number.parseInt(b.metadata.level?.toString()!, 10), + ); + } - useEffect(() => { catalogApi.getEntities({ filter: filterExpression }).then(entities => { - const data = getApplicableEnvironments(entities.items, awsComponent.currentEnvironment.environment.envType, awsComponent.environments); + const data = getApplicableEnvironments( + entities.items, + awsComponent.currentEnvironment.environment.envType, + awsComponent.environments, + ); setEnvChoices(data); if (data && data[0]) { setSelectedItem(data[0].metadata.name); @@ -87,160 +127,196 @@ const AppPromoCard = ({ setDisabled(true); } }); - }, []); - - const handleChange = (event: ChangeEvent<{ name?: string; value: unknown; }>) => { - setSelectedItem((event.target.value as string)) + }, [ + awsComponent.currentEnvironment.environment.envType, + awsComponent.environments, + catalogApi, + ]); + + const handleChange = ( + event: ChangeEvent<{ name?: string; value: unknown }>, + ) => { + setSelectedItem(event.target.value as string); }; - async function getParameters(envProviderEntity: Entity): Promise<{ [key: string]: string; }> { - //For the current provider - set the API to the appropriate provider target + async function getParameters( + envProviderEntity: Entity, + ): Promise<{ [key: string]: string }> { + // For the current provider - set the API to the appropriate provider target const backendParamsOverrides = { appName: awsComponent.componentName, - awsAccount: envProviderEntity.metadata['awsAccount']?.toString() || "", - awsRegion: envProviderEntity.metadata['awsRegion']?.toString() || "", - prefix: envProviderEntity.metadata['prefix']?.toString() || "", - providerName: envProviderEntity.metadata.name + awsAccount: envProviderEntity.metadata.awsAccount?.toString() || '', + awsRegion: envProviderEntity.metadata.awsRegion?.toString() || '', + prefix: envProviderEntity.metadata.prefix?.toString() || '', + providerName: envProviderEntity.metadata.name, }; - const envType = envProviderEntity.metadata['envType']?.toString().toLowerCase(); + const envType = envProviderEntity.metadata.envType + ?.toString() + .toLowerCase(); if (envType === ProviderType.ECS) { - - const metaVpc = "vpc"; - const metaRole = "provisioningRole"; - const metaCluster = "clusterName"; + const metaVpc = 'vpc'; + const metaRole = 'provisioningRole'; + const metaCluster = 'clusterName'; const metadataKeys = [metaVpc, metaCluster, metaRole]; - const ssmValues = await Promise.all(metadataKeys.map(async (metaKey) => { - const paramKey = envProviderEntity.metadata[metaKey]?.toString() || metaKey; - const value = (await api.getSSMParameter({ ssmParamName: paramKey, backendParamsOverrides })).Parameter?.Value || ""; - return value; - })); + const ssmValues = await Promise.all( + metadataKeys.map(async metaKey => { + const paramKey = + envProviderEntity.metadata[metaKey]?.toString() || metaKey; + return ( + ( + await api.getSSMParameter({ + ssmParamName: paramKey, + backendParamsOverrides, + }) + ).Parameter?.Value || '' + ); + }), + ); - let parametersMap = { + return { TARGET_VPCID: ssmValues[metadataKeys.indexOf(metaVpc)], TARGET_ECS_CLUSTER_ARN: ssmValues[metadataKeys.indexOf(metaCluster)], ENV_ROLE_ARN: ssmValues[metadataKeys.indexOf(metaRole)], // 'TARGET_ENV_AUDIT': auditTable }; - return parametersMap; - } else if (envType === ProviderType.EKS) { - - const metaVpc = "vpc"; - const metaRole = "provisioningRole"; - const metaCluster = "clusterName"; + const metaVpc = 'vpc'; + const metaRole = 'provisioningRole'; + const metaCluster = 'clusterName'; const metadataKeys = [metaVpc, metaCluster, metaRole]; - const ssmValues = await Promise.all(metadataKeys.map(async (metaKey) => { - const paramKey = envProviderEntity.metadata[metaKey]?.toString() || metaKey; - const value = (await api.getSSMParameter({ ssmParamName: paramKey, backendParamsOverrides })).Parameter?.Value || ""; - return value; - })); + const ssmValues = await Promise.all( + metadataKeys.map(async metaKey => { + const paramKey = + envProviderEntity.metadata[metaKey]?.toString() || metaKey; + return ( + ( + await api.getSSMParameter({ + ssmParamName: paramKey, + backendParamsOverrides, + }) + ).Parameter?.Value || '' + ); + }), + ); - let parametersMap = { + return { TARGET_VPCID: ssmValues[metadataKeys.indexOf(metaVpc)], TARGET_EKS_CLUSTER_ARN: ssmValues[metadataKeys.indexOf(metaCluster)], ENV_ROLE_ARN: ssmValues[metadataKeys.indexOf(metaRole)], - TARGET_KUBECTL_LAMBDA_ARN: envProviderEntity.metadata.kubectlLambdaArn as string, - TARGET_KUBECTL_LAMBDA_ROLE_ARN: envProviderEntity.metadata.clusterAdminRole as string, + TARGET_KUBECTL_LAMBDA_ARN: envProviderEntity.metadata + .kubectlLambdaArn as string, + TARGET_KUBECTL_LAMBDA_ROLE_ARN: envProviderEntity.metadata + .clusterAdminRole as string, }; - return parametersMap; - } else if (envType === ProviderType.SERVERLESS) { - - const metaVpc = "vpc"; - const metaRole = "provisioningRole"; - const vpcParam = envProviderEntity.metadata[metaVpc]?.toString() || ""; + const metaVpc = 'vpc'; + const metaRole = 'provisioningRole'; + const vpcParam = envProviderEntity.metadata[metaVpc]?.toString() || ''; const metaPubNet = `${vpcParam}/public-subnets`; const metaPrivNet = `${vpcParam}/private-subnets`; const metadataKeys = [metaVpc, metaRole, metaPubNet, metaPrivNet]; - const ssmValues = await Promise.all(metadataKeys.map(async (metaKey) => { - const paramKey = envProviderEntity.metadata[metaKey]?.toString() || metaKey; - const value = (await api.getSSMParameter({ ssmParamName: paramKey, backendParamsOverrides })).Parameter?.Value || ""; - return value; - })); + const ssmValues = await Promise.all( + metadataKeys.map(async metaKey => { + const paramKey = + envProviderEntity.metadata[metaKey]?.toString() || metaKey; + return ( + ( + await api.getSSMParameter({ + ssmParamName: paramKey, + backendParamsOverrides, + }) + ).Parameter?.Value || '' + ); + }), + ); - let parametersMap = { + return { TARGET_VPCID: ssmValues[metadataKeys.indexOf(metaVpc)], ENV_ROLE_ARN: ssmValues[metadataKeys.indexOf(metaRole)], TARGET_PRIVATE_SUBNETS: ssmValues[metadataKeys.indexOf(metaPrivNet)], TARGET_PUBLIC_SUBNETS: ssmValues[metadataKeys.indexOf(metaPubNet)], // TARGET_ENV_AUDIT: auditTable }; - return parametersMap; - } - else { - throw new Error(`UNKNOWN PROVIDER TYPE" ${envType}`); } + throw new Error(`UNKNOWN PROVIDER TYPE" ${envType}`); } type EnvironmentProviders = { providers: AWSProviderParams[]; - } + }; async function getEnvProviders(): Promise { - let envProviders: EnvironmentProviders = { providers: [] }; + const envProviders: EnvironmentProviders = { providers: [] }; - const selectedEnv = await catalogApi.getEntities({ filter: { 'kind': "awsenvironment", 'metadata.name': selectedItem } }); + const selectedEnv = await catalogApi.getEntities({ + filter: { kind: 'awsenvironment', 'metadata.name': selectedItem }, + }); const envEntity = selectedEnv.items[0]; - const envRequiresManualApproval = !!envEntity.metadata['deploymentRequiresApproval']; - - const envProviderRefs: EntityRelation[] | undefined = envEntity.relations?.filter( - relation => parseEntityRef(relation?.targetRef).kind === 'awsenvironmentprovider')!; - - const providerEntities = await Promise.all(envProviderRefs.map(async (entityRef: { targetRef: string | CompoundEntityRef; }) => { - const entity = await catalogApi.getEntityByRef(entityRef.targetRef); - return entity; - })); - - await Promise.all(providerEntities.map(async (et) => { - const providerResolvedData = await getParameters(et!); - envProviders.providers.push( - { + const envRequiresManualApproval = + !!envEntity.metadata.deploymentRequiresApproval; + + const envProviderRefs: EntityRelation[] | undefined = + envEntity.relations?.filter( + relation => + parseEntityRef(relation?.targetRef).kind === 'awsenvironmentprovider', + )!; + + const providerEntities = await Promise.all( + envProviderRefs.map( + async (entityRef: { targetRef: string | CompoundEntityRef }) => { + return await catalogApi.getEntityByRef(entityRef.targetRef); + }, + ), + ); + + await Promise.all( + providerEntities.map(async et => { + const providerResolvedData = await getParameters(et!); + envProviders.providers.push({ environmentName: envEntity.metadata.name, envRequiresManualApproval, providerName: et?.metadata.name || '', - awsAccount: et?.metadata['awsAccount']?.toString() || '', - awsRegion: et?.metadata['awsRegion']?.toString() || '', - prefix: et?.metadata['prefix']?.toString() || '', - assumedRoleArn: et?.metadata['provisioningRole']?.toString() || '', - parameters: providerResolvedData + awsAccount: et?.metadata.awsAccount?.toString() || '', + awsRegion: et?.metadata.awsRegion?.toString() || '', + prefix: et?.metadata.prefix?.toString() || '', + assumedRoleArn: et?.metadata.provisioningRole?.toString() || '', + parameters: providerResolvedData, }); - })) + }), + ); return envProviders; } const handleCloseAlert = () => { - setPromotedEnvName(""); + setPromotedEnvName(''); }; const closeEksDialog = () => setOpenEksDialog(false); - const submitNewEksEnvironmentHandler = (namespace: string, iamRoleArn: string, roleBehavior: string) => { - // console.log(`CREATE ENV - namespace=${namespace} roleBehavior=${roleBehavior} iamRoleArn=${iamRoleArn}`); - createNewEnvironment({["NAMESPACE"]: namespace, ["APP_ADMIN_ROLE_ARN"]: iamRoleArn, ["K8S_IAM_ROLE_BINDING_TYPE"]: roleBehavior}); - }; - - const createNewEnvironment = (extraParameters?: { [key: string]: string }) => { + const createNewEnvironment = (extraParameters?: { + [key: string]: string; + }) => { setSpinning(true); - setPromotedEnvName(""); + setPromotedEnvName(''); // Build a list of environment variables required to invoke a job to promote the app - let repoInfo = awsComponent.getRepoInfo(); + const repoInfo = awsComponent.getRepoInfo(); repoInfo.gitJobID = 'create-subsequent-environment-ci-config'; getEnvProviders().then(envProviders => { - const promoBody = { envName: selectedItem, - envRequiresManualApproval: envProviders.providers[0].envRequiresManualApproval, + envRequiresManualApproval: + envProviders.providers[0].envRequiresManualApproval, repoInfo, - gitAdminSecret:getGitCredentailsSecret(repoInfo), - providersData: envProviders.providers + gitAdminSecret: getGitCredentailsSecret(repoInfo), + providersData: envProviders.providers, }; if (extraParameters) { @@ -252,57 +328,81 @@ const AppPromoCard = ({ } // now call the API and submit the promo request - api.promoteApp(promoBody).then(results => { - setSpinning(false); - setPromotedEnvName(selectedItem); - if (results.message) { - setPromoteResultMessage(results.message); - } - - if (results.status === "SUCCESS") { - // Remove promoted environment from dropdown - const newEnvChoices = [...envChoices].filter(function (item) { - return item.metadata.name !== selectedItem - }); + api + .promoteApp(promoBody) + .then(results => { + setSpinning(false); + setPromotedEnvName(selectedItem); + if (results.message) { + setPromoteResultMessage(results.message); + } - if (newEnvChoices.length === 0) { - setDisabled(true); - setSelectedItem(""); + if (results.status === 'SUCCESS') { + // Remove promoted environment from dropdown + const newEnvChoices = [...envChoices].filter(item => { + return item.metadata.name !== selectedItem; + }); + + if (newEnvChoices.length === 0) { + setDisabled(true); + setSelectedItem(''); + } else { + setSelectedItem(newEnvChoices[0].metadata.name); + } + setEnvChoices(newEnvChoices); + setIsPromotionSuccessful(true); } else { - setSelectedItem(newEnvChoices[0].metadata.name); + setIsPromotionSuccessful(false); } - setEnvChoices(newEnvChoices); - setIsPromotionSuccessful(true); - - } else { + }) + .catch(() => { + setSpinning(false); + setPromotedEnvName(selectedItem); setIsPromotionSuccessful(false); - } - }).catch(err => { - console.log(err); - setSpinning(false); - setPromotedEnvName(selectedItem); - setIsPromotionSuccessful(false); - }) + }); + }); + }; + const submitNewEksEnvironmentHandler = ( + namespace: string, + iamRoleArn: string, + roleBehavior: string, + ) => { + // console.log(`CREATE ENV - namespace=${namespace} roleBehavior=${roleBehavior} iamRoleArn=${iamRoleArn}`); + createNewEnvironment({ + ['NAMESPACE']: namespace, + ['APP_ADMIN_ROLE_ARN']: iamRoleArn, + ['K8S_IAM_ROLE_BINDING_TYPE']: roleBehavior, }); }; const handleClick = () => { if (!selectedItem) { + // eslint-disable-next-line no-alert alert('Select an Environment'); return; } - const envType = awsComponent.currentEnvironment.environment.envType.toLowerCase(); + const envType = + awsComponent.currentEnvironment.environment.envType.toLowerCase(); // Show dialog asking user for additional EKS input if (envType === ProviderType.EKS) { - - if (appEntity.metadata.appData && Object.keys(appEntity.metadata.appData).length) { + if ( + appEntity.metadata.appData && + Object.keys(appEntity.metadata.appData).length + ) { const firstEnv = Object.values(appEntity.metadata.appData)[0]; - const firstEnvProvider = Object.values(firstEnv)[0] as { Namespace: string; AppAdminRoleArn: string; }; - setSuggestedEksNamespace(`suggestions: "${appEntity.metadata.name}", "${appEntity.metadata.name}-${selectedItem}", "${firstEnvProvider.Namespace}"`); - setSuggestedIamRoleArn(`suggestions: "${firstEnvProvider.AppAdminRoleArn}"`); + const firstEnvProvider = Object.values(firstEnv)[0] as { + Namespace: string; + AppAdminRoleArn: string; + }; + setSuggestedEksNamespace( + `suggestions: "${appEntity.metadata.name}", "${appEntity.metadata.name}-${selectedItem}", "${firstEnvProvider.Namespace}"`, + ); + setSuggestedIamRoleArn( + `suggestions: "${firstEnvProvider.AppAdminRoleArn}"`, + ); } setOpenEksDialog(true); @@ -310,7 +410,7 @@ const AppPromoCard = ({ } createNewEnvironment(); - } + }; return ( @@ -319,23 +419,47 @@ const AppPromoCard = ({ {isPromotionSuccessful && !!promotedEnvName && ( - + Success - {promotedEnvName} was successfully scheduled for deployment! - {!!promoteResultMessage && (<>

{promoteResultMessage})} + {promotedEnvName} was successfully scheduled + for deployment! + {!!promoteResultMessage && ( + <> +
+
+ {promoteResultMessage} + + )}
)} {!isPromotionSuccessful && !!promotedEnvName && ( - + Error - Failed to schedule {promotedEnvName} deployment. - {!!promoteResultMessage && (<>

{promoteResultMessage})} + Failed to schedule {promotedEnvName}{' '} + deployment. + {!!promoteResultMessage && ( + <> +
+
+ {promoteResultMessage} + + )}
)} Add Environment: - + @@ -344,7 +468,9 @@ const AppPromoCard = ({
- Environments + + Environments + - Select the environment you wish to start deploying to. + + Select the environment you wish to start deploying to. + - +
- + theme.zIndex.drawer + 1 }} + sx={{ color: '#fff', zIndex: theme => theme.zIndex.drawer + 1 }} open={spinning} > @@ -399,15 +535,20 @@ export const AppPromoWidget = () => { if (awsAppLoadingStatus.loading) { return ; } else if (awsAppLoadingStatus.component) { - const component = awsAppLoadingStatus.component + const component = awsAppLoadingStatus.component; const input = { awsComponent: component, catalogApi, - appEntity: entity + appEntity: entity, }; return ; - } else { - return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AwsEksEnvPromoDialog.tsx b/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AwsEksEnvPromoDialog.tsx index 23c4c041..6184e822 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AwsEksEnvPromoDialog.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AppPromoCard/AwsEksEnvPromoDialog.tsx @@ -6,8 +6,14 @@ import { Dialog, DialogActions, DialogContent, - DialogTitle, Grid, - IconButton, InputLabel, MenuItem, Select, TextField, makeStyles + DialogTitle, + Grid, + IconButton, + InputLabel, + MenuItem, + Select, + TextField, + makeStyles, } from '@material-ui/core'; import { Close } from '@mui/icons-material'; import React, { useState } from 'react'; @@ -40,7 +46,9 @@ const useStyles = makeStyles(theme => ({ * @param isOpen Boolean describing whether the dialog is displayed (open) or not (closed) * @param closeDialogHandler the handler callback when the dialog is dismissed/cancelled * @param submitHandler the handler callback when the dialog is submitted - * @param environmentName the name of the environment that will be added to the app's CICD pipeline + * @param environmentName the name of the environment that will be added to the app's CI/CD pipeline + * @param namespaceDefault + * @param iamRoleArnDefault * @namespaceDefault suggestions on what the user could enter for a namespace * @iamRoleArnDefault suggestions on what the user could enter for the IAM role ARN * @returns @@ -55,61 +63,91 @@ export const AwsEksEnvPromoDialog = ({ }: { isOpen: boolean; closeDialogHandler: () => void; - submitHandler: (namespace: string, iamRoleArn: string, roleBehavior: string) => void; + submitHandler: ( + namespace: string, + iamRoleArn: string, + roleBehavior: string, + ) => void; environmentName: string; namespaceDefault: string; iamRoleArnDefault: string; }) => { - const classes = useStyles(); - const [namespace, setNamespace] = useState(""); + const [namespace, setNamespace] = useState(''); const [namespaceIsInvalid, setNamespaceIsInvalid] = useState(false); - const [namespaceDescription, setNamespaceDescription] = useState(`The k8s namespace to assign to application resources for the ${environmentName} environment`); + const [namespaceDescription, setNamespaceDescription] = useState( + `The k8s namespace to assign to application resources for the ${environmentName} environment`, + ); - const [iamRoleArn, setIamRoleArn] = useState(""); + const [iamRoleArn, setIamRoleArn] = useState(''); const [iamRoleArnIsInvalid, setIamRoleArnIsInvalid] = useState(false); - const [iamRoleArnDescription, setIamRoleArnDescription] = useState("Existing IAM role to grant namespace privileges to"); + const [iamRoleArnDescription, setIamRoleArnDescription] = useState( + 'Existing IAM role to grant namespace privileges to', + ); - const [roleBehavior, setRoleBehavior] = useState("create_new_k8s_namespace_admin_iam_role"); + const [roleBehavior, setRoleBehavior] = useState( + 'create_new_k8s_namespace_admin_iam_role', + ); + + const checkIamRoleArn = () => { + if (!iamRoleArn) { + setIamRoleArnDescription('Cannot be Empty'); + setIamRoleArnIsInvalid(true); + } else { + setIamRoleArnDescription( + 'Existing IAM role to grant namespace privileges to', + ); + setIamRoleArnIsInvalid(false); + } + }; const submitNewEnvironmentHandler = () => { - if (roleBehavior === 'existing_new_k8s_namespace_admin_iam_role' && !iamRoleArn) { + if ( + roleBehavior === 'existing_new_k8s_namespace_admin_iam_role' && + !iamRoleArn + ) { checkIamRoleArn(); return; } - if (namespaceIsInvalid || (iamRoleArnIsInvalid && roleBehavior === 'existing_new_k8s_namespace_admin_iam_role')) { + if ( + namespaceIsInvalid || + (iamRoleArnIsInvalid && + roleBehavior === 'existing_new_k8s_namespace_admin_iam_role') + ) { return; } closeDialogHandler(); - submitHandler(namespace as string, iamRoleArn as string, roleBehavior as string); + submitHandler(namespace, iamRoleArn, roleBehavior); }; const checkNamespace = () => { if (!namespace) { - setNamespaceDescription("Cannot be Empty"); + setNamespaceDescription('Cannot be Empty'); setNamespaceIsInvalid(true); } else { - setNamespaceDescription(`The k8s namespace to assign to application resources for the ${environmentName} environment`); + setNamespaceDescription( + `The k8s namespace to assign to application resources for the ${environmentName} environment`, + ); setNamespaceIsInvalid(false); } }; - const checkIamRoleArn = () => { - if (!iamRoleArn) { - setIamRoleArnDescription("Cannot be Empty"); - setIamRoleArnIsInvalid(true); - } else { - setIamRoleArnDescription("Existing IAM role to grant namespace privileges to"); - setIamRoleArnIsInvalid(false); - } - }; - return ( - + Add Environment: {environmentName} - + @@ -118,57 +156,68 @@ export const AwsEksEnvPromoDialog = ({ K8s Namespace ) => setNamespace(e.target.value)} + onChange={(e: React.ChangeEvent) => + setNamespace(e.target.value) + } onBlur={() => checkNamespace()} error={namespaceIsInvalid} helperText={namespaceDescription} placeholder={namespaceDefault} required - autoFocus - > + />
- Namespace-bound Kubectl Admin Access + + Namespace-bound Kubectl Admin Access + - {roleBehavior === 'existing_new_k8s_namespace_admin_iam_role' && + {roleBehavior === 'existing_new_k8s_namespace_admin_iam_role' && ( IAM Role ) => setIamRoleArn(e.target.value)} + onChange={(e: React.ChangeEvent) => + setIamRoleArn(e.target.value) + } onBlur={() => checkIamRoleArn()} error={iamRoleArnIsInvalid} helperText={iamRoleArnDescription} placeholder={iamRoleArnDefault} required - > + /> - } + )} - + + + theme.zIndex.drawer + 1 }} + sx={{ color: '#fff', zIndex: theme => theme.zIndex.drawer + 1 }} open={spinning} > @@ -267,7 +355,7 @@ export const AwsEnvironmentProviderCardWidget = () => { const input = { entity, - catalog: catalogApi + catalog: catalogApi, }; return ; }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/AwsEnvironmentProviderCard/AwsEnvironmentProviderSelectorDialog.tsx b/backstage-plugins/plugins/aws-apps/src/components/AwsEnvironmentProviderCard/AwsEnvironmentProviderSelectorDialog.tsx index b752fc6c..94739b65 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/AwsEnvironmentProviderCard/AwsEnvironmentProviderSelectorDialog.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/AwsEnvironmentProviderCard/AwsEnvironmentProviderSelectorDialog.tsx @@ -7,8 +7,12 @@ import { Dialog, DialogActions, DialogContent, - DialogTitle, Grid, - IconButton, InputLabel, MenuItem, makeStyles + DialogTitle, + Grid, + IconButton, + InputLabel, + MenuItem, + makeStyles, } from '@material-ui/core'; import { Close } from '@mui/icons-material'; import React, { useEffect, useState } from 'react'; @@ -47,66 +51,74 @@ export const AwsEnvironmentProviderSelectorDialog = ({ isOpen, closeDialogHandler, selectHandler, - providersInput + providersInput, }: { isOpen: boolean; closeDialogHandler: () => void; selectHandler: (item: AWSEnvironmentProviderRecord) => void; providersInput: AWSEnvironmentProviderRecord[]; }) => { - const classes = useStyles(); - const [selectedProvider, setSelectedProvider] = useState(); + const [selectedProvider, setSelectedProvider] = + useState(); const handleChangeSelectedProvider = (event: SelectChangeEvent) => { - const selectedProvider = event.target.value as string; + const newProvider = event.target.value as string; const matchingProviders = providersInput.filter(providerRecord => { - return selectedProvider === `${providerRecord.prefix}:${providerRecord.name}` + return newProvider === `${providerRecord.prefix}:${providerRecord.name}`; }); - if (matchingProviders.length != 1) { - console.error(`Failed to find provider matching ${selectedProvider}`); + if (matchingProviders.length !== 1) { + // console.error(`Failed to find provider matching ${newProvider}`); } else { setSelectedProvider(matchingProviders[0]); } }; const localSelectHandler = () => { - // if there's a selected value - rely the item to the external caller + // if there's a selected value - relay the item to the external caller if (selectedProvider) { selectHandler(selectedProvider); } closeDialogHandler(); - } + }; if (!selectedProvider && providersInput.length > 0) { - setSelectedProvider(providersInput[0]) + setSelectedProvider(providersInput[0]); } const selectorProviders = providersInput.map(p => { const key = `${p.prefix}:${p.name}`; const title = `${p.prefix}:${p.name}`; - return ({title}) + return ( + + {title} + + ); }); const getSelectedProvider = () => { if (selectedProvider) { return `${selectedProvider?.prefix}:${selectedProvider?.name}`; } - else { - return - } - } - - useEffect(() => { + return undefined; + }; - }, []); + useEffect(() => {}, []); return ( - + Available Providers - + @@ -114,7 +126,8 @@ export const AwsEnvironmentProviderSelectorDialog = ({ Providers - {selectorItems} @@ -54,7 +61,7 @@ const EnvironmentSelector = ({ ); }; -// Extract information from hook and populate the drop down +// Extract information from hook and populate the drop-down export const EnvironmentSelectorWidget = () => { const awsAppLoadingStatus = useAsyncAwsApp(); @@ -65,7 +72,12 @@ export const EnvironmentSelectorWidget = () => { awsComponent: awsAppLoadingStatus.component, }; return ; - } else { - return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/GeneralInfoCard/GeneralInfoCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/GeneralInfoCard/GeneralInfoCard.tsx index c4868beb..fdcd72a3 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/GeneralInfoCard/GeneralInfoCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/GeneralInfoCard/GeneralInfoCard.tsx @@ -6,42 +6,56 @@ import { CodeSnippet, InfoCard, EmptyState } from '@backstage/core-components'; import { LinearProgress } from '@material-ui/core'; import { useApi } from '@backstage/core-plugin-api'; import { OPAApi, opaApiRef } from '../../api'; -import { Typography, CardContent, IconButton, Grid } from '@mui/material'; +import CardContent from '@mui/material/CardContent'; +import Grid from '@mui/material/Grid'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { SecretStringComponent } from '../common'; import { useAsyncAwsApp } from '../../hooks/useAwsApp'; -import { AWSECSAppDeploymentEnvironment, getRepoInfo, getRepoUrl } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSECSAppDeploymentEnvironment, + getRepoInfo, + getRepoUrl, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { useEntity } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; const OpaAppGeneralInfo = ({ - input: { entity, repoSecretArn, api } -}: { input: { account: string, region: string, entity: Entity, repoSecretArn: string, api: OPAApi} }) => { - + input: { entity, repoSecretArn, api }, +}: { + input: { + account: string; + region: string; + entity: Entity; + repoSecretArn: string; + api: OPAApi; + }; +}) => { const [loading, setLoading] = useState(true); - const [error, setError] = useState<{ isError: boolean; errorMsg: string | null }>({ isError: false, errorMsg: null }); - - const [secretData, setSecretData] = useState(""); - - let repoInfo = getRepoInfo(entity); + const [error, setError] = useState<{ + isError: boolean; + errorMsg: string | null; + }>({ isError: false, errorMsg: null }); + + const [secretData, setSecretData] = useState(''); + + const repoInfo = getRepoInfo(entity); const gitRepoUrl = getRepoUrl(repoInfo); -// const getGitAppUrl = () => { -// const gitAppUrl = gitHost + "/" + gitApp + ".git" -// return gitAppUrl -// } + // const getGitAppUrl = () => { + // const gitAppUrl = gitHost + "/" + gitApp + ".git" + // return gitAppUrl + // } const HandleCopyGitClone = () => { - let baseUrl = "git clone https://oauth2:" - let cloneUrl = "" - if (!repoSecretArn) - { - baseUrl = "git clone https://" + let baseUrl: string = 'git clone https://oauth2:'; + let cloneUrl: string; + if (!repoSecretArn) { + baseUrl = 'git clone https://'; cloneUrl = baseUrl + gitRepoUrl; - } - else - { - cloneUrl = baseUrl + secretData + "@" + gitRepoUrl; + } else { + cloneUrl = `${baseUrl + secretData}@${gitRepoUrl}`; } navigator.clipboard.writeText(cloneUrl); }; @@ -50,22 +64,17 @@ const OpaAppGeneralInfo = ({ navigator.clipboard.writeText(secretData || ''); }; - async function getData() { - if (!repoSecretArn) { - setSecretData(""); - } - else - { - const secrets = await api.getPlatformSecret({ - secretName: repoSecretArn, - }); - console.log(secrets) - setSecretData(secrets.SecretString || ""); - } - - } - useEffect(() => { + async function getData() { + if (!repoSecretArn) { + setSecretData(''); + } else { + const secrets = await api.getPlatformSecret({ + secretName: repoSecretArn, + }); + setSecretData(secrets.SecretString ?? ''); + } + } getData() .then(() => { @@ -73,10 +82,13 @@ const OpaAppGeneralInfo = ({ setError({ isError: false, errorMsg: '' }); }) .catch(e => { - setError({ isError: true, errorMsg: `Unexpected error occurred while retrieving secretsmanager data: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while retrieving secrets manager data: ${e}`, + }); setLoading(false); }); - }, []); + }, [api, repoSecretArn]); if (loading) { return ( @@ -95,34 +107,44 @@ const OpaAppGeneralInfo = ({ - { - repoSecretArn? - ( - <> - Repository Access Token + {repoSecretArn ? ( + <> + + Repository Access Token + - + - ): - (<>) - } - + ) : ( + <> + )} - Clone url - + + Clone url + + - + @@ -145,29 +167,31 @@ export const GeneralInfoCard = ({ appPending }: { appPending: boolean }) => { account: '', region: '', entity: entity, - repoSecretArn: entity.metadata['repoSecretArn']?.toString() || '', + repoSecretArn: entity.metadata.repoSecretArn?.toString() ?? '', api, }; return ; } - else { - if (awsAppLoadingStatus.loading) { - return ; - } else if (awsAppLoadingStatus.component) { + if (awsAppLoadingStatus.loading) { + return ; + } else if (awsAppLoadingStatus.component) { + const env = awsAppLoadingStatus.component + .currentEnvironment as AWSECSAppDeploymentEnvironment; - const env = awsAppLoadingStatus.component.currentEnvironment as AWSECSAppDeploymentEnvironment; - - const input = { - account: env.providerData.accountNumber, - region: env.providerData.region, - entity: entity, - repoSecretArn: awsAppLoadingStatus.component.repoSecretArn, - api - }; - return ; - } else { - return ; - } + const input = { + account: env.providerData.accountNumber, + region: env.providerData.region, + entity: entity, + repoSecretArn: awsAppLoadingStatus.component.repoSecretArn, + api, + }; + return ; } - + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/GenericTable/GenericTable.tsx b/backstage-plugins/plugins/aws-apps/src/components/GenericTable/GenericTable.tsx index 86fa2e0f..3ba074b6 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/GenericTable/GenericTable.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/GenericTable/GenericTable.tsx @@ -1,7 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - import React, { useEffect, useState } from 'react'; import { TableColumn, Table } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/InfrastructureCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/InfrastructureCard.tsx index 9e1e2b88..d8928f3a 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/InfrastructureCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/InfrastructureCard.tsx @@ -3,7 +3,13 @@ import { InfoCard, EmptyState } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { AWSComponent, AWSComponentType, AWSECSAppDeploymentEnvironment, AWSServiceResources, AWSResourceDeploymentEnvironment } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSComponent, + AWSComponentType, + AWSECSAppDeploymentEnvironment, + AWSServiceResources, + AWSResourceDeploymentEnvironment, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { LinearProgress, Typography } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { opaApiRef } from '../../api'; @@ -12,15 +18,18 @@ import { useAsyncAwsApp } from '../../hooks/useAwsApp'; import { ProviderType } from '../../helpers/constants'; const OpaAppInfraInfo = ({ - input: { resourceGroupArn, awsComponent } -}: { input: { resourceGroupArn: string, awsComponent: AWSComponent } }) => { - + input: { resourceGroupArn, awsComponent }, +}: { + input: { resourceGroupArn: string; awsComponent: AWSComponent }; +}) => { const api = useApi(opaApiRef); const [rscGroupData, setRscGroupData] = useState({}); const [loading, setLoading] = useState(true); - const [error, setError] = useState<{ isError: boolean; errorMsg: string | null }>({ isError: false, errorMsg: null }); - + const [error, setError] = useState<{ + isError: boolean; + errorMsg: string | null; + }>({ isError: false, errorMsg: null }); // The default set of AWS services to show on the Infrastructure card // TODO: this should be externalized to configuration @@ -37,27 +46,30 @@ const OpaAppInfraInfo = ({ 'SSM', ]; - if (ProviderType.EKS === awsComponent.currentEnvironment.providerData.providerType) { + if ( + ProviderType.EKS === + awsComponent.currentEnvironment.providerData.providerType + ) { defaultServiceFilter.push('ElasticLoadBalancingV2'); defaultServiceFilter.push('ECR'); } - async function getData() { - // Validate the resource group annotation and extract the resource group name - // so that we can build a deepLink to the resource group page in the AWS console - if (!resourceGroupArn) { - throw new Error('Missing resource group arn'); - } + useEffect(() => { + async function getData() { + // Validate the resource group annotation and extract the resource group name + // so that we can build a deepLink to the resource group page in the AWS console + if (!resourceGroupArn) { + throw new Error('Missing resource group arn'); + } - const rscGroupResources = await api.getResourceGroupResources({ - rscGroupArn: resourceGroupArn - }); + const rscGroupResources = await api.getResourceGroupResources({ + rscGroupArn: resourceGroupArn, + }); - const data = rscGroupResources ?? {}; - setRscGroupData(data); - } + const data = rscGroupResources ?? {}; + setRscGroupData(data); + } - useEffect(() => { getData() .then(() => { setLoading(false); @@ -65,12 +77,14 @@ const OpaAppInfraInfo = ({ }) .catch(e => { const statusCode = e.body.response.statusCode ?? null; - const errorMsg = statusCode === 404 ? 'Data not available at this time' - : `Unexpected error occurred while retrieving resource group data: ${e}`; + const errorMsg = + statusCode === 404 + ? 'Data not available at this time' + : `Unexpected error occurred while retrieving resource group data: ${e}`; setError({ isError: true, errorMsg }); setLoading(false); }); - }, []); + }, [api, resourceGroupArn]); const title = 'AWS Infrastructure Resources'; if (loading) { @@ -88,14 +102,18 @@ const OpaAppInfraInfo = ({ return ( - + ); }; @@ -104,28 +122,38 @@ export const InfrastructureCard = () => { const awsAppLoadingStatus = useAsyncAwsApp(); if (awsAppLoadingStatus.loading) { - return + return ; } else if (awsAppLoadingStatus.component) { let input = undefined; - if (awsAppLoadingStatus.component.componentType === AWSComponentType.AWSApp) { - const env = awsAppLoadingStatus.component.currentEnvironment as AWSECSAppDeploymentEnvironment; + if ( + awsAppLoadingStatus.component.componentType === AWSComponentType.AWSApp + ) { + const env = awsAppLoadingStatus.component + .currentEnvironment as AWSECSAppDeploymentEnvironment; input = { resourceGroupArn: env.app.resourceGroupArn, - awsComponent: awsAppLoadingStatus.component + awsComponent: awsAppLoadingStatus.component, }; - } - else if (awsAppLoadingStatus.component.componentType === AWSComponentType.AWSResource) { - const env = awsAppLoadingStatus.component.currentEnvironment as AWSResourceDeploymentEnvironment; + } else if ( + awsAppLoadingStatus.component.componentType === + AWSComponentType.AWSResource + ) { + const env = awsAppLoadingStatus.component + .currentEnvironment as AWSResourceDeploymentEnvironment; input = { resourceGroupArn: env.resource.resourceGroupArn, - awsComponent: awsAppLoadingStatus.component + awsComponent: awsAppLoadingStatus.component, }; + } else { + throw new Error('Infrastructure Card Not yet implemented!'); } - else { - throw new Error("Infrastructure Card Not yet implemented!") - } - return - } else { - return + return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ResourceDetailsDialog.tsx b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ResourceDetailsDialog.tsx index df5f6b0a..5a209f73 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ResourceDetailsDialog.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ResourceDetailsDialog.tsx @@ -9,8 +9,12 @@ import { Dialog, DialogActions, DialogContent, - DialogTitle, Grid, - IconButton, LinearProgress, makeStyles, Typography + DialogTitle, + Grid, + IconButton, + LinearProgress, + makeStyles, + Typography, } from '@material-ui/core'; import { Close } from '@mui/icons-material'; import React, { useEffect, useState } from 'react'; @@ -39,7 +43,6 @@ const useStyles = makeStyles(theme => ({ }, })); - /** * A Table component for showing AWS Resource details. The table can accommodate varying numbers of columns * with corresponding table data @@ -53,7 +56,7 @@ const ResourceDetailsTable = ({ tableData, resource, }: { - columns: TableColumn<{}>[]; + columns: TableColumn[]; tableData: {}[]; resource: AWSResource; }) => { @@ -62,7 +65,14 @@ const ResourceDetailsTable = ({ return (
- - - + + + + +
void; resource: AWSResource; prefix: string; providerName: string; }) => { - // Table column definition used for displaying basic key/value pairs - const kvColumns: TableColumn[] = [ - { - title: 'Key', - field: 'key', - highlight: true, - type: 'string', - width: '35%', - }, - { - title: 'Value', - field: 'value', - type: 'string', - }, - ]; - - // Table column definition for single-values - const singleColumn: TableColumn[] = [ - { - title: 'Value', - field: 'value', - highlight: false, - type: 'string', - }, - ]; - const classes = useStyles(); const api = useApi(opaApiRef); const [loading, setLoading] = useState(true); const [_, setError] = useState(false); - const [tableColumns, setTableColumns] = useState[]>([{}]); + const [tableColumns, setTableColumns] = useState([{}]); const [tableData, setTableData] = useState<{}[]>([{}]); - // Get the secret details - async function getData() { + useEffect(() => { + // Table column definition used for displaying basic key/value pairs + const kvColumns: TableColumn[] = [ + { + title: 'Key', + field: 'key', + highlight: true, + type: 'string', + width: '35%', + }, + { + title: 'Value', + field: 'value', + type: 'string', + }, + ]; - if (resource.resourceTypeId == 'AWS::SecretsManager::Secret') { - const secretResponse = await api.getSecret({ secretName: resource.resourceArn }); - const rawSecret = secretResponse.SecretString ?? 'unknown'; + // Table column definition for single-values + const singleColumn: TableColumn[] = [ + { + title: 'Value', + field: 'value', + highlight: false, + type: 'string', + }, + ]; - // Process the string differently depending on whether it's a JSON string or not - try { - const parsedSecret = JSON.parse(rawSecret); - setTableColumns(kvColumns); - const jsonKeys = Object.keys(parsedSecret).sort((a, b) => { - if (a < b) { return -1; } - if (a > b) { return 1; } - return 0; + // Get the secret details + async function getData() { + if (resource.resourceTypeId === 'AWS::SecretsManager::Secret') { + const secretResponse = await api.getSecret({ + secretName: resource.resourceArn, }); - const secretTableData = jsonKeys.map((key, i) => { - const value = /.*password.*/i.test(key) ? ( - - ) : ( - parsedSecret[key] - ); - return { id: i, key, value }; + const rawSecret = secretResponse.SecretString ?? 'unknown'; + + // Process the string differently depending on whether it's a JSON string or not + try { + const parsedSecret = JSON.parse(rawSecret); + setTableColumns(kvColumns); + const jsonKeys = Object.keys(parsedSecret).sort((a, b) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }); + const secretTableData = jsonKeys.map((key, i) => { + const value = /.*password.*/i.test(key) ? ( + + ) : ( + parsedSecret[key] + ); + return { id: i, key, value }; + }); + setTableData(secretTableData); + } catch { + // not a JSON string, so use the value as-is + setTableColumns(singleColumn); + setTableData([ + { value: , id: '1' }, + ]); + } + } else if (resource.resourceTypeId === 'AWS::SSM::Parameter') { + const ssmParamResponse = await api.getSSMParameter({ + ssmParamName: resource.resourceName, }); - setTableData(secretTableData); - } catch { - // not a JSON string, so just use the value as-is + // SSM Parameters are single-value and will only be displayed in a single column table setTableColumns(singleColumn); - setTableData([{ value: , id: '1' }]); + const paramValue = ssmParamResponse.Parameter?.Value ?? 'unknown'; + const secretType = ssmParamResponse.Parameter?.Type; + const secret = + secretType === 'SecureString' ? ( + + ) : ( + paramValue + ); + setTableData([{ value: secret, id: '1' }]); } - } else if (resource.resourceTypeId == 'AWS::SSM::Parameter') { - const ssmParamResponse = await api.getSSMParameter({ - ssmParamName: resource.resourceName - }); - // SSM Parameters are single-value and will only be displayed in a single column table - setTableColumns(singleColumn); - const paramValue = ssmParamResponse.Parameter?.Value ?? 'unknown'; - const secretType = ssmParamResponse.Parameter?.Type; - const secret = secretType == 'SecureString' ? : paramValue; - setTableData([{ value: secret, id: '1' }]); } - } - useEffect(() => { getData() .then(() => setLoading(false)) .catch(() => { setError(true); setLoading(false); }); - }, []); + }, [ + api, + resource.resourceArn, + resource.resourceName, + resource.resourceTypeId, + ]); if (loading) { return ; @@ -182,10 +209,17 @@ export const ResourceDetailsDialog = ({ // return the JSXElement for the details dialog box return ( - + Resource Details - + @@ -198,7 +232,11 @@ export const ResourceDetailsDialog = ({ {resource.resourceName} - + ); }; - diff --git a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ServiceComponent.tsx b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ServiceComponent.tsx index aeac09fc..11a310a1 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ServiceComponent.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/InfrastructureCard/ServiceComponent.tsx @@ -2,9 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { SubvalueCell, Table, TableColumn } from '@backstage/core-components'; -import { AWSResource, AWSServiceResources } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSResource, + AWSServiceResources, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { makeStyles, Typography } from '@material-ui/core'; -import { Link } from '@mui/material'; +import Link from '@mui/material/Link'; import React, { useState, useCallback } from 'react'; import { ResourceDetailsDialog } from './ResourceDetailsDialog'; @@ -36,9 +39,19 @@ const useStyles = makeStyles(theme => ({ * will open a dialog displaying details for the associated AWS Resource * * @param resource The AWS resource type requiring a link to display its details. + * @param prefix + * @param providerName * @returns JSXElement */ -const CustomDetailsLink = ({ resource, prefix, providerName }: { resource: AWSResource, prefix: string, providerName: string }) => { +const CustomDetailsLink = ({ + resource, + prefix, + providerName, +}: { + resource: AWSResource; + prefix: string; + providerName: string; +}) => { const [open, setOpen] = useState(false); const openDialog = () => { @@ -51,22 +64,45 @@ const CustomDetailsLink = ({ resource, prefix, providerName }: { resource: AWSRe details - + ); }; /** - * A UI component to display an AWS service and a table of it's resources. + * A UI component to display an AWS service and a table of its resources. * * @param serviceName An optional short string describing the AWS service. If the serviceName is provided, then it will be used in the header of the table; otherwise, it will not be displayed. * @param resources An array of AWS resource objects which below to the specified serviceName + * @param prefix + * @param providerName * @returns JSXElement rendering a table for an AWS service and its resources */ -export const DenseResourceTable = ({ serviceName, resources, prefix, providerName }: { serviceName?: string; resources: AWSResource[]; prefix: string; providerName: string; }) => { +export const DenseResourceTable = ({ + serviceName, + resources, + prefix, + providerName, +}: { + serviceName?: string; + resources: AWSResource[]; + prefix: string; + providerName: string; +}) => { const classes = useStyles(); - const preventRerender = useCallback((row: any): React.ReactNode => , []); + const preventRerender = useCallback( + (row: any): React.ReactNode => ( + + ), + [], + ); // Table column definition used for displaying key/value pairs of resource type and resource name const columns: TableColumn[] = [ @@ -86,13 +122,20 @@ export const DenseResourceTable = ({ serviceName, resources, prefix, providerNam ]; const resourceItems = resources.map((r, i) => { - // Array of resource types which should include a 'details' subvalue + // Array of resource types which should include a 'details' sub-value // for the user to request details about the resource // TODO: this should be redesigned to make the specification of "detail" resources more dynamic. const detailTypes = ['AWS::SecretsManager::Secret', 'AWS::SSM::Parameter']; - // subvalue is a details link to be shown beneath a table cell value - const subvalue = detailTypes.includes(r.resourceTypeId) ? : undefined; + // sub-value is a details link to be shown beneath a table cell value + const subvalue = detailTypes.includes(r.resourceTypeId) ? ( + + ) : undefined; return { id: i, @@ -128,8 +171,20 @@ export const DenseResourceTable = ({ serviceName, resources, prefix, providerNam * * @param serviceName A short string describing the AWS service. * @param resources An array of AWS resource objects which below to the specified serviceName + * @param prefix + * @param providerName */ -const Service = ({ serviceName, resources, prefix, providerName }: { serviceName: string; resources: AWSResource[], prefix: string, providerName: string }) => { +const Service = ({ + serviceName, + resources, + prefix, + providerName, +}: { + serviceName: string; + resources: AWSResource[]; + prefix: string; + providerName: string; +}) => { const classes = useStyles(); if (!resources || resources.length === 0) { @@ -138,8 +193,14 @@ const Service = ({ serviceName, resources, prefix, providerName }: { serviceName return ( <> - {serviceName} - + + {serviceName} + + ); }; @@ -149,13 +210,15 @@ const Service = ({ serviceName, resources, prefix, providerName }: { serviceName * * @param servicesObject an AWSServiceResources type providing a set of services and associated AWS resources to display * @param serviceFilter an optional array of service identifier strings which should be displayed. Service strings should match the service name used in AWS Cloudformation syntax (e.g. "AWS::ServiceName::ResourceType"). If an empty array is provided or the parameter is not provided, then all available services will be displayed. + * @param prefix + * @param providerName * @returns */ export const ServiceResourcesComponent = ({ servicesObject, serviceFilter = [], prefix, - providerName + providerName, }: { servicesObject: AWSServiceResources; serviceFilter?: string[]; @@ -163,15 +226,32 @@ export const ServiceResourcesComponent = ({ providerName: string; }) => { const svcKeys = Object.keys(servicesObject); - const filteredKeys = serviceFilter.length == 0 ? svcKeys : serviceFilter.filter(value => svcKeys.includes(value)); + const filteredKeys = + serviceFilter.length === 0 + ? svcKeys + : serviceFilter.filter(value => svcKeys.includes(value)); filteredKeys.sort((a, b) => { - if (a < b) { return -1; } - if (a > b) { return 1; } + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } return 0; }); const serviceItems = filteredKeys.map((serviceName, i) => { - return !resource.resourceName.includes('AutoDelete'))} prefix={prefix} providerName={providerName} />; + return ( + !resource.resourceName.includes('AutoDelete'), + )} + prefix={prefix} + providerName={providerName} + /> + ); }); return <>{serviceItems}; diff --git a/backstage-plugins/plugins/aws-apps/src/components/K8sAppStateCard/K8sAppStateCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/K8sAppStateCard/K8sAppStateCard.tsx index db40e061..71da1ca4 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/K8sAppStateCard/K8sAppStateCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/K8sAppStateCard/K8sAppStateCard.tsx @@ -3,16 +3,33 @@ import { InvokeCommandOutput } from '@aws-sdk/client-lambda'; import { GetParameterCommandOutput } from '@aws-sdk/client-ssm'; -import { AWSComponent, AWSEKSAppDeploymentEnvironment, AppState, AppStateType, KeyValue, getGitCredentailsSecret } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AppState, + AppStateType, + AWSComponent, + AWSEKSAppDeploymentEnvironment, + getGitCredentailsSecret, + KeyValue, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { Entity } from '@backstage/catalog-model'; import { EmptyState, InfoCard } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { useEntity } from '@backstage/plugin-catalog-react'; -import { LinearProgress, Table, TableBody, TableCell, TableRow } from '@material-ui/core'; +import { + LinearProgress, + Table, + TableBody, + TableCell, + TableRow, +} from '@material-ui/core'; import { Unstable_NumberInput as NumberInput } from '@mui/base/Unstable_NumberInput'; import AddIcon from '@mui/icons-material/Add'; import RemoveIcon from '@mui/icons-material/Remove'; -import { Button, CardContent, Divider, Grid, Typography } from '@mui/material'; +import Button from '@mui/material/Button'; +import CardContent from '@mui/material/CardContent'; +import Divider from '@mui/material/Divider'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; import { styled } from '@mui/system'; import React, { useEffect, useRef, useState } from 'react'; import { opaApiRef } from '../../api'; @@ -65,8 +82,9 @@ const StyledInput = styled('input')( color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' - }; + box-shadow: 0px 2px 4px ${ + theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)' + }; border-radius: 8px; margin: 0 8px; padding: 10px 12px; @@ -81,7 +99,9 @@ const StyledInput = styled('input')( &:focus { border-color: ${blue[400]}; - box-shadow: 0 0 0 3px ${theme.palette.mode === 'dark' ? blue[700] : blue[200]}; + box-shadow: 0 0 0 3px ${ + theme.palette.mode === 'dark' ? blue[700] : blue[200] + }; } &:focus-visible { @@ -129,40 +149,49 @@ const StyledButton = styled('button')( ); const OpaAppStateOverview = ({ - input: { env, entity, awsComponent } + input: { env, entity, awsComponent }, }: { input: { - env: AWSEKSAppDeploymentEnvironment, - entity: Entity, - awsComponent: AWSComponent - } + env: AWSEKSAppDeploymentEnvironment; + entity: Entity; + awsComponent: AWSComponent; + }; }) => { - const api = useApi(opaApiRef); const [appStateData, setAppStateData] = useState([]); const [variablesJson, setVariablesJson] = useState({}); const [appStarted, setAppStarted] = useState(false); const [appStopped, setAppStopped] = useState(false); - const [clusterNameState, setClusterNameState] = useState(""); + const [clusterNameState, setClusterNameState] = useState(''); const [loading, setLoading] = useState(true); - const [error, setError] = useState<{ isError: boolean; errorMsg: string | null }>({ isError: false, errorMsg: null }); - const { cancellablePromise } = useCancellablePromise({ rejectOnCancel: true }); + const [error, setError] = useState<{ + isError: boolean; + errorMsg: string | null; + }>({ isError: false, errorMsg: null }); + const { cancellablePromise } = useCancellablePromise({ + rejectOnCancel: true, + }); const timerRef = useRef(null); const repoInfo = awsComponent.getRepoInfo(); - + // Namespace-bound application admin role (not cluster admin role) const appAdminRoleArn = env.app.appAdminRoleArn; - const kubectlLambdaArn = env.entities.envProviderEntity?.metadata["kubectlLambdaArn"]?.toString() || ""; - let clusterNameParam, clusterName: string; + const kubectlLambdaArn = + env.entities.envProviderEntity?.metadata.kubectlLambdaArn?.toString() || ''; + let clusterNameParam; + let clusterName: string; async function fetchAppConfig() { if (!clusterName) { // console.log(`getting cluster name`); clusterNameParam = await cancellablePromise( - api.getSSMParameter({ ssmParamName: env.clusterName }) + api.getSSMParameter({ ssmParamName: env.clusterName }), ); - clusterName = clusterNameParam.Parameter?.Value?.toString().split('/')[1].toString() || ""; + clusterName = + clusterNameParam.Parameter?.Value?.toString() + .split('/')[1] + .toString() || ''; } else { // console.log(`clusterName was already cached when getting app config`); } @@ -170,18 +199,18 @@ const OpaAppStateOverview = ({ setClusterNameState(clusterName); const bodyParamVariables = { - RequestType: "Create", - ResourceType: "Custom::AWSCDK-EKS-KubernetesObjectValue", + RequestType: 'Create', + ResourceType: 'Custom::AWSCDK-EKS-KubernetesObjectValue', ResourceProperties: { - TimeoutSeconds: "5", + TimeoutSeconds: '5', ClusterName: clusterName, RoleArn: appAdminRoleArn, ObjectNamespace: env.app.namespace, InvocationType: 'RequestResponse', - ObjectType: "configmaps", + ObjectType: 'configmaps', ObjectLabels: `app.kubernetes.io/env=${env.environment.name},app.kubernetes.io/name=${entity.metadata.name}`, - JsonPath: "@" - } + JsonPath: '@', + }, }; // console.log(`calling lambda to get configs`); @@ -189,86 +218,74 @@ const OpaAppStateOverview = ({ api.invokeLambda({ functionName: kubectlLambdaArn, actionDescription: `Fetch app configs for namespace ${env.app.namespace}`, - body: JSON.stringify(bodyParamVariables) - }) + body: JSON.stringify(bodyParamVariables), + }), ); - // console.log(`got configs`); try { if (resultsVariables?.Payload) { - const payloadVariablesString = base64PayloadConvert(resultsVariables.Payload as Object); + const payloadVariablesString = base64PayloadConvert( + resultsVariables.Payload as Object, + ); const payloadVariablesJson = JSON.parse(payloadVariablesString); if (payloadVariablesJson?.Data?.Value) { - const variablesJson = JSON.parse(payloadVariablesJson.Data.Value); - // console.log(variablesJson); - return variablesJson; - } else { - return {}; + return JSON.parse(payloadVariablesJson.Data.Value); } - + return {}; } - + return null; } catch (err) { - console.log(err); throw Error("Can't parse json response"); } - } - async function fetchAppState() { - + async function fetchAppState(): Promise { if (!clusterName) { // console.log(`getting cluster name`); clusterNameParam = await cancellablePromise( - api.getSSMParameter({ ssmParamName: env.clusterName }) + api.getSSMParameter({ ssmParamName: env.clusterName }), ); - // console.log(`DONE getting cluster name`); - clusterName = clusterNameParam.Parameter?.Value?.toString().split('/')[1].toString() || ""; - // console.log(`clusterName is ${clusterName}`); + clusterName = + clusterNameParam.Parameter?.Value?.toString() + .split('/')[1] + .toString() || ''; } const bodyParam = { - RequestType: "Create", - ResourceType: "Custom::AWSCDK-EKS-KubernetesObjectValue", + RequestType: 'Create', + ResourceType: 'Custom::AWSCDK-EKS-KubernetesObjectValue', ResourceProperties: { - TimeoutSeconds: "5", + TimeoutSeconds: '5', ClusterName: clusterName, RoleArn: appAdminRoleArn, ObjectNamespace: env.app.namespace, InvocationType: 'RequestResponse', - ObjectType: "deployments", + ObjectType: 'deployments', ObjectLabels: `app.kubernetes.io/env=${env.environment.name},app.kubernetes.io/name=${entity.metadata.name}`, - JsonPath: "@" - } + JsonPath: '@', + }, }; - // console.log(bodyParam) - // console.log(`calling lambda to get manifests`); const results = await cancellablePromise( api.invokeLambda({ functionName: kubectlLambdaArn, actionDescription: `Fetch deployments for namespace ${env.app.namespace}`, - body: JSON.stringify(bodyParam) - }) + body: JSON.stringify(bodyParam), + }), ); - // console.log(`got manifests`); try { if (results?.Payload) { const payloadString = base64PayloadConvert(results.Payload as Object); const payloadJson = JSON.parse(payloadString); if (payloadJson?.Data?.Value) { - const deploymentJson = JSON.parse(payloadJson.Data.Value).items; - // console.log(deploymentJson); - return deploymentJson; - } else { - return {}; + return JSON.parse(payloadJson.Data.Value).items; } + return {}; } - + return null; } catch (err) { - console.log(err); throw Error("Can't parse json response"); } } @@ -278,13 +295,18 @@ const OpaAppStateOverview = ({ return []; } - const configMapName = appStateData.filter(appState => appState.appID === deploymentName)[0].stateObject.spec.template.spec.containers[0]?.envFrom?.[0]?.configMapRef?.name; + const configMapName = appStateData.filter( + appState => appState.appID === deploymentName, + )[0].stateObject.spec.template.spec.containers[0]?.envFrom?.[0] + ?.configMapRef?.name; if (!configMapName) { return []; } - const configMap = variablesJson.items.filter((candidateMap: any) => candidateMap.metadata.name === configMapName)?.[0]; + const configMap = variablesJson.items.filter( + (candidateMap: any) => candidateMap.metadata.name === configMapName, + )?.[0]; if (!configMap) { return []; @@ -295,8 +317,8 @@ const OpaAppStateOverview = ({ variables.push({ id: `${index}`, key: key.toString(), - value: configMap.data[key].toString() - }) + value: configMap.data[key].toString(), + }); }); return variables; }; @@ -304,14 +326,15 @@ const OpaAppStateOverview = ({ const parseState = (deploymentsJson: any): AppState[] => { // parse response JSON - let deploymentsState: AppState[] = [] + const deploymentsState: AppState[] = []; try { - Object.keys(deploymentsJson).forEach(key => { const deploymentJson = deploymentsJson[key]; - const updatedReplicas = Number.parseInt(deploymentJson.status.updatedReplicas) || 0; - const appRunning = Number.parseInt(deploymentJson.status.readyReplicas) || 0; + const updatedReplicas = + Number.parseInt(deploymentJson.status.updatedReplicas, 10) || 0; + const appRunning = + Number.parseInt(deploymentJson.status.readyReplicas, 10) || 0; const pending = Math.abs(appRunning - updatedReplicas); @@ -319,65 +342,69 @@ const OpaAppStateOverview = ({ if (pending) { appStateDescription = AppStateType.UPDATING; } else { - appStateDescription = appRunning > 0 ? AppStateType.RUNNING : AppStateType.STOPPED; + appStateDescription = + appRunning > 0 ? AppStateType.RUNNING : AppStateType.STOPPED; } const appState: AppState = { appID: deploymentJson.metadata.name, appState: appStateDescription, deploymentIdentifier: deploymentJson.metadata.uid, - desiredCount: Number.parseInt(deploymentJson.spec.replicas) || 0, + desiredCount: Number.parseInt(deploymentJson.spec.replicas, 10) || 0, pendingCount: pending, runningCount: appRunning, - lastStateTimestamp: new Date(deploymentJson.status.conditions[0].lastUpdateTime), - stateObject: deploymentJson - } + lastStateTimestamp: new Date( + deploymentJson.status.conditions[0].lastUpdateTime, + ), + stateObject: deploymentJson, + }; deploymentsState.push(appState); - }) - + }); } catch (err) { - console.log(err); + // console.log(err) } return deploymentsState || []; - } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps async function getData(appStateResults?: any) { - let isCanceled = false; let isError = false; let deploymentsJson; - let variablesJson; + let appConfig; try { if (appStateResults) { // console.log(`reusing appStateResults`); } - deploymentsJson = appStateResults ? appStateResults : await fetchAppState(); // returns array of deployments - variablesJson = await fetchAppConfig(); // return the configMaps for the app + deploymentsJson = appStateResults + ? appStateResults + : await fetchAppState(); // returns array of deployments + appConfig = await fetchAppConfig(); // return the configMaps for the app } catch (e) { if ((e as any).isCanceled) { isCanceled = true; // console.log(`got cancellation in getData`); } else { isError = true; - console.error(e); - setError({ isError: true, errorMsg: `Unexpected error occurred while retrieving event data: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while retrieving event data: ${e}`, + }); } - } if (!isCanceled && !isError) { const states = parseState(deploymentsJson); setAppStateData(states); - setVariablesJson(variablesJson); + setVariablesJson(appConfig); } - } useEffect(() => { - setAppStateData([]); // reset existing state + setAppStateData([]); // reset existing state getData() .then(() => { setLoading(false); @@ -385,7 +412,10 @@ const OpaAppStateOverview = ({ }) .catch(e => { setLoading(false); - setError({ isError: true, errorMsg: `Unexpected error occurred while retrieving app data: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while retrieving app data: ${e}`, + }); }); return () => { @@ -397,23 +427,20 @@ const OpaAppStateOverview = ({ clearTimeout(timerRef.current); // console.log(`Clearing Timeout`); } - - } - }, []); + }; + }, [getData]); function sleep(ms: number) { return new Promise(resolve => { - const resolveHandler = () => { clearTimeout(timerRef.current); resolve(null); - } + }; timerRef.current = setTimeout(resolveHandler, ms); }); } const handleStartTask = async (appState: AppState) => { - setLoading(true); // console.log(`calling lambda to set replicas to > 0, clusterNameState is ${clusterNameState}`); @@ -425,21 +452,25 @@ const OpaAppStateOverview = ({ actionDescription: `Starting app in environment ${env.environment.name}`, envName: env.environment.name, cluster: clusterNameState, - kubectlLambda: env.entities.envProviderEntity?.metadata["kubectlLambdaArn"]?.toString() || "", + kubectlLambda: + env.entities.envProviderEntity?.metadata.kubectlLambdaArn?.toString() || + '', lambdaRoleArn: appAdminRoleArn, gitAdminSecret: getGitCredentailsSecret(repoInfo), updateKey: 'spec.replicas', updateValue: appState.desiredCount || 1, repoInfo, - }) + }), ); // console.log(`DONE setting replicas to > 0`); } catch (e) { if ((e as any).isCanceled) { isCanceled = true; } else { - console.error(e); - setError({ isError: true, errorMsg: `Unexpected error occurred while starting app: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while starting app: ${e}`, + }); setLoading(false); } } @@ -449,7 +480,6 @@ const OpaAppStateOverview = ({ let localAppStarted = false; // console.log(`isCanceled is ${isCanceled} and localAppStarted is ${localAppStarted} and appStarted is ${appStarted}`); while (!isCanceled && !appStarted && !localAppStarted) { - // console.log(`sleeping waiting for app to be started, localAppStarted is ${localAppStarted} and appStarted is ${appStarted}`); await sleep(5000); // console.log(`awake ${count}, will now check app state`); @@ -457,36 +487,39 @@ const OpaAppStateOverview = ({ try { // console.log("start fetching app state"); - deploymentsJson = await fetchAppState(); + const appStateJson = await fetchAppState(); // console.log("DONE fetching app state"); - Object.keys(deploymentsJson).forEach(key => { - const currState = deploymentsJson[key]; + let isStarted = false; + + Object.keys(appStateJson).forEach(key => { + const currState = appStateJson[key]; + if (currState.metadata.uid === appState.deploymentIdentifier) { - if (Number.parseInt(currState.status.readyReplicas) > 0) { - setAppStateData([]); // reset existing state + if (Number.parseInt(currState.status.readyReplicas, 10) > 0) { + setAppStateData([]); // reset existing state setAppStarted(true); - localAppStarted = true; - // console.log(`setting appStarted to true`); + isStarted = true; } } }); + localAppStarted = isStarted; + deploymentsJson = appStateJson; + if (localAppStarted || appStarted) { - // console.log(`breaking from while loop since app was started`); + // Breaking from while loop since the app has started. break; - } else { - // console.log(`not breaking from while loop since appStarted is falsy`); } - } catch (e) { if ((e as any).isCanceled) { isCanceled = true; } else { - console.error(e); - setError({ isError: true, errorMsg: `Unexpected error occurred while retrieving app state: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while retrieving app state: ${e}`, + }); setAppStarted(true); localAppStarted = true; - // console.log(`setting appStarted to true`); setLoading(false); } break; @@ -511,45 +544,48 @@ const OpaAppStateOverview = ({ actionDescription: `Stopping app in environment ${env.environment.name}`, envName: env.environment.name, cluster: clusterNameState, - kubectlLambda: env.entities.envProviderEntity?.metadata["kubectlLambdaArn"]?.toString() || "", + kubectlLambda: + env.entities.envProviderEntity?.metadata.kubectlLambdaArn?.toString() || + '', lambdaRoleArn: appAdminRoleArn, gitAdminSecret: getGitCredentailsSecret(repoInfo), updateKey: 'spec.replicas', updateValue: 0, repoInfo, - }) + }), ); - // console.log(`DONE setting replicas to 0`); } catch (e) { if ((e as any).isCanceled) { isCanceled = true; } else { - console.error(e); - setError({ isError: true, errorMsg: `Unexpected error occurred while stopping app: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while stopping app: ${e}`, + }); setLoading(false); } } let count = 0; let localAppStopped = false; - // console.log(`isCanceled is ${isCanceled} and localAppStopped is ${localAppStopped} and appStoped is ${appStopped}`); + while (!isCanceled && !appStopped && !localAppStopped) { - // console.log(`sleeping ${count} - waiting for app to be stopped, localAppStopped is ${localAppStopped} and appStoped is ${appStopped}`); await sleep(7000); - // console.log(`DONE sleeping - app is stopped`); count++; try { - // console.log("fetching app state"); const deploymentsJson = await fetchAppState(); - // console.log("DONE - fetching app state"); + // eslint-disable-next-line no-loop-func Object.keys(deploymentsJson).forEach(key => { const currState = deploymentsJson[key]; if (currState.metadata.uid === appState.deploymentIdentifier) { - if (!currState.status.readyReplicas || Number.parseInt(currState.status.readyReplicas) === 0) { - appState.appState = AppStateType.STOPPED - appState.runningCount = 0 + if ( + !currState.status.readyReplicas || + Number.parseInt(currState.status.readyReplicas, 10) === 0 + ) { + appState.appState = AppStateType.STOPPED; + appState.runningCount = 0; localAppStopped = true; setAppStopped(true); setLoading(false); @@ -562,13 +598,14 @@ const OpaAppStateOverview = ({ // console.log(`breaking from while loop since app was stopped`); break; } - } catch (e) { if ((e as any).isCanceled) { isCanceled = true; } else { - console.error(e); - setError({ isError: true, errorMsg: `Unexpected error occurred while retrieving app state: ${e}` }); + setError({ + isError: true, + errorMsg: `Unexpected error occurred while retrieving app state: ${e}`, + }); setLoading(false); setAppStopped(true); localAppStopped = true; @@ -576,82 +613,138 @@ const OpaAppStateOverview = ({ } break; } - } }; const EnvVars = ({ appID }: { appID: string }) => { - const envVarArr = getDeploymentEnvVars(appID); if (envVarArr && envVarArr.length) { return ( <> - {envVarArr.map((envVar) => ( + {envVarArr.map(envVar => ( - {envVar.key} + + + {envVar.key} + + {envVar.value} ))} ); - } else { - return ( - - None configured - - - ); } + return ( + + + None configured + + + + ); }; - const DeploymentCard = ({ deploymentState, index, total }: { deploymentState: AppState, index: number, total: number }) => { - + const DeploymentCard = ({ + deploymentState, + index, + total, + }: { + deploymentState: AppState; + index: number; + total: number; + }) => { return ( <> - Deployment {index > 1 ? index : ""} + + Deployment {index > 1 ? index : ''} +
- { - deploymentState?.appState ? - ( - - - -
- - - Name - {deploymentState.stateObject.metadata.name} - - - Status - {deploymentState?.appState ? deploymentState?.appState : 'Not Running'} - - - Pods - {deploymentState?.runningCount + "/" + deploymentState?.desiredCount}{deploymentState?.pendingCount ? ` (${deploymentState?.pendingCount} Pending)` : ''} - - - Last Updated - {deploymentState?.lastStateTimestamp ? deploymentState?.lastStateTimestamp.toString() : ''} - - -
-
-
- ) : <> - } + {deploymentState?.appState ? ( + + + + + + + + + Name + + + + {deploymentState.stateObject.metadata.name} + + + + + + Status + + + + {deploymentState?.appState + ? deploymentState?.appState + : 'Not Running'} + + + + + + Pods + + + + {`${deploymentState?.runningCount}/${deploymentState?.desiredCount}`} + {deploymentState?.pendingCount + ? ` (${deploymentState?.pendingCount} Pending)` + : ''} + + + + + + Last Updated + + + + {deploymentState?.lastStateTimestamp + ? deploymentState?.lastStateTimestamp.toString() + : ''} + + + +
+
+
+ ) : ( + <> + )}
- Environment Variables + + Environment Variables + - + - +
@@ -662,71 +755,79 @@ const OpaAppStateOverview = ({ - { - index === total && deploymentState?.appState === AppStateType.STOPPED ? - ( -
- deploymentState.desiredCount = val || 0} - min={0} - max={10} - slots={{ - root: StyledInputRoot, - input: StyledInput, - incrementButton: StyledButton, - decrementButton: StyledButton, - }} - slotProps={{ - incrementButton: { - children: , - className: 'increment', - }, - decrementButton: { - children: , - }, - }} - /> -
) : - <> - } - { - index === total ? - ( - <> - - - **Changes to your application state will be applied directly to the cluster and not to the source code repository - ) : <> - } + {index === total && + deploymentState?.appState === AppStateType.STOPPED ? ( +
+ + (deploymentState.desiredCount = val || 0) + } + min={0} + max={10} + slots={{ + root: StyledInputRoot, + input: StyledInput, + incrementButton: StyledButton, + decrementButton: StyledButton, + }} + slotProps={{ + incrementButton: { + children: , + className: 'increment', + }, + decrementButton: { + children: , + }, + }} + /> +
+ ) : ( + <> + )} + {index === total ? ( + <> + + + + {' '} + **Changes to your application state will be applied directly + to the cluster and not to the source code repository + + + ) : ( + <> + )}
- ) - } + ); + }; if (loading) { return ( - Loading current state... + + Loading current state... + ); } @@ -739,19 +840,39 @@ const OpaAppStateOverview = ({ - Cluster Info + + Cluster Info + - + -
+
- - Cluster Name - {clusterNameState} + + + + Cluster Name + + + + {clusterNameState} + - - Namespace - {env.app.namespace} + + + + Namespace + + + + {env.app.namespace} +
@@ -760,12 +881,20 @@ const OpaAppStateOverview = ({
- { - appStateData.length ? - appStateData.map((state, index, array) => { - return () - }) : <>No Deployments Found - } + {appStateData.length ? ( + appStateData.map((state, index, array) => { + return ( + + ); + }) + ) : ( + <>No Deployments Found + )} @@ -777,22 +906,32 @@ export const K8sAppStateCard = () => { const awsAppLoadingStatus = useAsyncAwsApp(); if (awsAppLoadingStatus.loading) { - return + return ; } else if (awsAppLoadingStatus.component) { let input; - if (awsAppLoadingStatus.component.componentSubType === "aws-eks") { - const env = awsAppLoadingStatus.component.currentEnvironment as AWSEKSAppDeploymentEnvironment; + if (awsAppLoadingStatus.component.componentSubType === 'aws-eks') { + const env = awsAppLoadingStatus.component + .currentEnvironment as AWSEKSAppDeploymentEnvironment; input = { env, entity, - awsComponent: awsAppLoadingStatus.component + awsComponent: awsAppLoadingStatus.component, }; - return - } else { - return + return ; } - - } else { - return + return ( + + ); } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/LabelTable/LabelTable.tsx b/backstage-plugins/plugins/aws-apps/src/components/LabelTable/LabelTable.tsx index 282ce66f..abbdeb14 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/LabelTable/LabelTable.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/LabelTable/LabelTable.tsx @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { } from 'react'; +import React from 'react'; import { EmptyState } from '@backstage/core-components'; import { GenericTable } from '../GenericTable/GenericTable'; import { useEntity } from '@backstage/plugin-catalog-react'; @@ -18,7 +18,11 @@ const LabelTable = ({ entity }: { entity: Entity }) => { export const LabelWidget = () => { const { entity } = useEntity(); return !isLabelsAvailable(entity) ? ( - + ) : ( ); diff --git a/backstage-plugins/plugins/aws-apps/src/components/ProviderInfoCard/ProviderInfoCard.tsx b/backstage-plugins/plugins/aws-apps/src/components/ProviderInfoCard/ProviderInfoCard.tsx index 7249188b..b0bdf773 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/ProviderInfoCard/ProviderInfoCard.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/ProviderInfoCard/ProviderInfoCard.tsx @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { useEntity, } from '@backstage/plugin-catalog-react'; +import { useEntity } from '@backstage/plugin-catalog-react'; import React from 'react'; import { InfoCard, Table, TableColumn } from '@backstage/core-components'; import { Entity } from '@backstage/catalog-model'; @@ -18,7 +18,7 @@ export interface ProviderInfoProps { } const ProviderInfo = (props: ProviderInfoProps) => { - let metadata = props.entity?.metadata || {}; + const metadata = props.entity?.metadata || {}; const columns: TableColumn[] = [ { @@ -35,74 +35,74 @@ const ProviderInfo = (props: ProviderInfoProps) => { }, ]; - let items: KeyValue[] = [] + const items: KeyValue[] = []; items.push({ - key: "Prefix", - value: metadata['prefix']?.toString() || "" + key: 'Prefix', + value: metadata.prefix?.toString() || '', }); items.push({ - key: "Name", - value: metadata.name.toString() || "" + key: 'Name', + value: metadata.name.toString() || '', }); items.push({ - key: "AWS Account", - value: metadata['awsAccount']?.toString() || "" + key: 'AWS Account', + value: metadata.awsAccount?.toString() || '', }); items.push({ - key: "AWS Region", - value: metadata['awsRegion']?.toString() || "" + key: 'AWS Region', + value: metadata.awsRegion?.toString() || '', }); items.push({ - key: "Runtime", - value: metadata['envType']?.toString() || "" + key: 'Runtime', + value: metadata.envType?.toString() || '', }); items.push({ - key: "Audit Table", - value: metadata['auditTable']?.toString() || "" + key: 'Audit Table', + value: metadata.auditTable?.toString() || '', }); items.push({ - key: "VPC", - value: metadata['vpc']?.toString() || "" + key: 'VPC', + value: metadata.vpc?.toString() || '', }); - const envType = metadata['envType']?.toString() || ""; + const envType = metadata.envType?.toString() || ''; if (envType === ProviderType.ECS || envType === ProviderType.EKS) { items.push({ - key: "Cluster Name", - value: metadata['clusterName']?.toString() || "" + key: 'Cluster Name', + value: metadata.clusterName?.toString() || '', }); } if (envType === ProviderType.EKS) { items.push({ - key: "Node Type", - value: metadata['nodeType']?.toString() || "" + key: 'Node Type', + value: metadata.nodeType?.toString() || '', }); } items.push({ - key: "Operation Role", - value: metadata['operationRole']?.toString() || "" + key: 'Operation Role', + value: metadata.operationRole?.toString() || '', }); items.push({ - key: "Provisioning Role", - value: metadata['provisioningRole']?.toString() || "" + key: 'Provisioning Role', + value: metadata.provisioningRole?.toString() || '', }); if (envType === ProviderType.EKS) { items.push({ - key: "Cluster Admin Role ARN", - value: metadata['clusterAdminRole']?.toString() || "" + key: 'Cluster Admin Role ARN', + value: metadata.clusterAdminRole?.toString() || '', }); items.push({ - key: "API Endpoint Access", - value: metadata['apiAccess']?.toString() || "" + key: 'API Endpoint Access', + value: metadata.apiAccess?.toString() || '', }); items.push({ - key: "Kubectl / Helm Lambda ARN", - value: metadata['kubectlLambdaArn']?.toString() || "" + key: 'Kubectl / Helm Lambda ARN', + value: metadata.kubectlLambdaArn?.toString() || '', }); items.push({ - key: "Kubectl / Helm Lambda Role ARN", - value: metadata['kubectlLambdaAssumeRoleArn']?.toString() || "" + key: 'Kubectl / Helm Lambda Role ARN', + value: metadata.kubectlLambdaAssumeRoleArn?.toString() || '', }); } @@ -116,7 +116,7 @@ const ProviderInfo = (props: ProviderInfoProps) => { showTitle: false, header: false, filtering: false, - toolbar: false + toolbar: false, }} data={items} columns={columns} diff --git a/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceBinding.tsx b/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceBinding.tsx index 53e486aa..0a0a1353 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceBinding.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceBinding.tsx @@ -2,16 +2,46 @@ // SPDX-License-Identifier: Apache-2.0 import React, { useEffect, useState } from 'react'; -import { EmptyState, InfoCard, } from '@backstage/core-components'; -import { Button, IconButton, LinearProgress, TableBody, TableCell, TableRow, Table, TableHead, CardContent, Grid } from '@material-ui/core'; +import { EmptyState, InfoCard } from '@backstage/core-components'; +import { + Button, + CardContent, + Grid, + IconButton, + LinearProgress, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from '@material-ui/core'; import DeleteIcon from '@mui/icons-material/Delete'; import { useApi } from '@backstage/core-plugin-api'; import { opaApiRef } from '../../api'; -import { Alert, AlertTitle, Typography } from '@mui/material'; +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Typography from '@mui/material/Typography'; import { useAsyncAwsApp } from '../../hooks/useAwsApp'; -import { AWSComponent, AssociatedResources, BindResourceParams, ResourceBinding, ResourcePolicy, getGitCredentailsSecret } from '@aws/plugin-aws-apps-common-for-backstage'; -import { CompoundEntityRef, Entity, EntityRelation, parseEntityRef } from '@backstage/catalog-model'; -import { CatalogApi, EntityRefLink, catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; +import { + AssociatedResources, + AWSComponent, + BindResourceParams, + getGitCredentailsSecret, + ResourceBinding, + ResourcePolicy, +} from '@aws/plugin-aws-apps-common-for-backstage'; +import { + CompoundEntityRef, + Entity, + EntityRelation, + parseEntityRef, +} from '@backstage/catalog-model'; +import { + CatalogApi, + catalogApiRef, + EntityRefLink, + useEntity, +} from '@backstage/plugin-catalog-react'; import { ResourceSelectorDialog } from './ResourceSelectorDialog'; import Backdrop from '@mui/material/Backdrop'; import CircularProgress from '@mui/material/CircularProgress'; @@ -42,185 +72,211 @@ const ResourceBindingCard = ({ }) => { const api = useApi(opaApiRef); - const [error, setError] = useState<{ isError: boolean; errorMsg: string | null }>({ isError: false, errorMsg: null }); + const [error, setError] = useState<{ + isError: boolean; + errorMsg: string | null; + }>({ isError: false, errorMsg: null }); const [items, setItems] = useState([]); const [openDialog, setOpenDialog] = useState(false); const [spinning, setSpinning] = useState(false); const [isBindSuccessful, setIsBindSuccessful] = useState(false); - const [bindResourceMessage, setBindResourceMessage] = useState(""); - const [bindResourceRequest, setBindResourceRequest] = useState(); + const [bindResourceMessage, setBindResourceMessage] = useState(''); + const [bindResourceRequest, setBindResourceRequest] = + useState(); const repoInfo = awsComponent.getRepoInfo(); + useEffect(() => { - getBindingDetails() - - }, []); - - async function getBindingDetails() { - // find existing resource relationships - const resourceRefs: EntityRelation[] | undefined = entity.relations?.filter( - relation => parseEntityRef(relation?.targetRef).kind === 'resource')!; - - const resourcesEntities = await Promise.all(resourceRefs.map(async (entityRef: { targetRef: string | CompoundEntityRef; }) => { - const entity = await catalog.getEntityByRef(entityRef.targetRef); - return entity; - })); - - //select view for only current environment - const currentEnvironment = awsComponent.currentEnvironment.environment.name; - - const matchedResources = resourcesEntities.filter(entity => { - const appData = entity!.metadata["appData"] as any; - return appData && appData[currentEnvironment] - }) - - let resources: ResourceBinding[] = []; - - matchedResources.forEach(et => { - const appData = et!.metadata["appData"] as any; - const envAppData = appData[currentEnvironment] as any; - const providers = Object.keys(envAppData) - providers.forEach(p => { - const providerAppData = envAppData[p] as any; - if (et!.metadata['resourceType'] === "aws-rds") { - - const associatedRDSResources: AssociatedResources = - { - resourceArn: providerAppData['DbAdminSecretArn'], - resourceType: "aws-db-secret", - resourceName: `${et!.metadata.name}-secret` - } + async function getBindingDetails() { + // find existing resource relationships + const resourceRefs: EntityRelation[] | undefined = + entity.relations?.filter( + relation => parseEntityRef(relation?.targetRef).kind === 'resource', + )!; + + const resourcesEntities = await Promise.all( + resourceRefs.map( + async (entityRef: { targetRef: string | CompoundEntityRef }) => { + return await catalog.getEntityByRef(entityRef.targetRef); + }, + ), + ); + + // select view for only current environment + const currentEnvironment = + awsComponent.currentEnvironment.environment.name; + + const matchedResources = resourcesEntities.filter(resourceEntity => { + const appData = resourceEntity!.metadata.appData as any; + return appData && appData[currentEnvironment]; + }); - resources.push( - { + const resources: ResourceBinding[] = []; + + matchedResources.forEach(et => { + const appData = et!.metadata.appData as any; + const envAppData = appData[currentEnvironment] as any; + const providers = Object.keys(envAppData); + providers.forEach(p => { + const providerAppData = envAppData[p] as any; + if (et!.metadata.resourceType === 'aws-rds') { + const associatedRDSResources: AssociatedResources = { + resourceArn: providerAppData.DbAdminSecretArn, + resourceType: 'aws-db-secret', + resourceName: `${et!.metadata.name}-secret`, + }; + + resources.push({ resourceName: et!.metadata.name, - resourceType: et!.metadata['resourceType']?.toString() || "", + resourceType: et!.metadata.resourceType?.toString() || '', provider: p, - resourceArn: providerAppData['Arn'], - id: providerAppData['Arn'], - entityRef: "resource:default/" + et!.metadata.name, - associatedResources: [associatedRDSResources] - }) - } - else { - resources.push( - { + resourceArn: providerAppData.Arn, + id: providerAppData.Arn, + entityRef: `resource:default/${et!.metadata.name}`, + associatedResources: [associatedRDSResources], + }); + } else { + resources.push({ resourceName: et!.metadata.name, - resourceType: et!.metadata['resourceType']?.toString() || "", + resourceType: et!.metadata.resourceType?.toString() || '', provider: p, - resourceArn: providerAppData['Arn'], - id: providerAppData['Arn'], - entityRef: "resource:default/" + et!.metadata.name - } - ) - } - }) - }) - // console.log(resources) - setItems(resources) + resourceArn: providerAppData.Arn, + id: providerAppData.Arn, + entityRef: `resource:default/${et!.metadata.name}`, + }); + } + }); + }); + // console.log(resources) + setItems(resources); + } - } + getBindingDetails(); + }, [ + awsComponent.currentEnvironment.environment.name, + catalog, + entity.relations, + ]); async function bindResource(item: ResourceBinding): Promise { - let policies: ResourcePolicy[] = []; + const policies: ResourcePolicy[] = []; - if (item.resourceType === "aws-rds") { - const rdsPolicy = RDS_POLICY.replace("@@@PLACEHOLDER@@@", item.resourceArn); + if (item.resourceType === 'aws-rds') { + const rdsPolicy = RDS_POLICY.replace( + '@@@PLACEHOLDER@@@', + item.resourceArn, + ); policies.push({ policyFileName: `statement-rds-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, policyContent: rdsPolicy, - policyResource: item.resourceType + policyResource: item.resourceType, }); if (item.associatedResources && item.associatedResources.length > 0) { item.associatedResources.forEach(ar => { - if (ar.resourceType === "aws-db-secret") { - const secretPolicy = SECRET_POLICY.replace("@@@PLACEHOLDER@@@", ar.resourceArn); + if (ar.resourceType === 'aws-db-secret') { + const secretPolicy = SECRET_POLICY.replace( + '@@@PLACEHOLDER@@@', + ar.resourceArn, + ); policies.push({ policyFileName: `statement-secrets-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, policyContent: secretPolicy, - policyResource: item.resourceType + policyResource: item.resourceType, }); } - }) + }); } - } else if (item.resourceType === "aws-db-secret") { - const secretPolicy = SECRET_POLICY.replace("@@@PLACEHOLDER@@@", item.resourceArn); + } else if (item.resourceType === 'aws-db-secret') { + const secretPolicy = SECRET_POLICY.replace( + '@@@PLACEHOLDER@@@', + item.resourceArn, + ); policies.push({ policyFileName: `statement-secrets-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, policyContent: secretPolicy, - policyResource: item.resourceType + policyResource: item.resourceType, }); - } - else if (item.resourceType === "aws-secretsmanager") { - const secretPolicy = SECRET_POLICY.replace("@@@PLACEHOLDER@@@", item.resourceArn); + } else if (item.resourceType === 'aws-secretsmanager') { + const secretPolicy = SECRET_POLICY.replace( + '@@@PLACEHOLDER@@@', + item.resourceArn, + ); policies.push({ policyFileName: `statement-secrets-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, policyContent: secretPolicy, - policyResource: item.resourceType + policyResource: item.resourceType, }); - } else if (item.resourceType === "aws-s3") { - const s3Policy = S3_POLICY.replace("@@@PLACEHOLDER@@@", `"${item.resourceArn}","${item.resourceArn}/*"`); + } else if (item.resourceType === 'aws-s3') { + const s3Policy = S3_POLICY.replace( + '@@@PLACEHOLDER@@@', + `"${item.resourceArn}","${item.resourceArn}/*"`, + ); policies.push({ policyFileName: `statement-s3-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, policyContent: s3Policy, - policyResource: item.resourceType + policyResource: item.resourceType, }); } - const params: BindResourceParams = { providerName: item.provider, envName: awsComponent.currentEnvironment.environment.name, appName: entity.metadata.name, resourceName: item.resourceName, resourceEntityRef: item.id, - policies + policies, }; - return api.bindResource({ repoInfo, params, gitAdminSecret: getGitCredentailsSecret(repoInfo) }) + return api.bindResource({ + repoInfo, + params, + gitAdminSecret: getGitCredentailsSecret(repoInfo), + }); } async function removeResource(item: ResourceBinding): Promise { - let policies: ResourcePolicy[] = []; + const policies: ResourcePolicy[] = []; - if (item.resourceType === "aws-rds") { + if (item.resourceType === 'aws-rds') { policies.push({ policyFileName: `statement-rds-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, - policyContent: "", - policyResource: item.resourceType + policyContent: '', + policyResource: item.resourceType, }); if (item.associatedResources && item.associatedResources.length > 0) { item.associatedResources.forEach(ar => { - if (ar.resourceType === "aws-db-secret") { + if (ar.resourceType === 'aws-db-secret') { policies.push({ policyFileName: `statement-secrets-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, - policyContent: "", - policyResource: item.resourceType + policyContent: '', + policyResource: item.resourceType, }); } - }) + }); } - } else if (item.resourceType === "aws-db-secret") { + } else if (item.resourceType === 'aws-db-secret') { policies.push({ policyFileName: `statement-secrets-${awsComponent.currentEnvironment.environment.name}-${item.provider}-${item.resourceName}`, - policyContent: "", - policyResource: item.resourceType + policyContent: '', + policyResource: item.resourceType, }); } const params: BindResourceParams = { - providerName: item.provider, envName: awsComponent.currentEnvironment.environment.name, appName: entity.metadata.name, resourceName: item.resourceName, resourceEntityRef: item.entityRef!, - policies + policies, }; - return api.unBindResource({ repoInfo, params, gitAdminSecret: getGitCredentailsSecret(repoInfo) }) + return api.unBindResource({ + repoInfo, + params, + gitAdminSecret: getGitCredentailsSecret(repoInfo), + }); } - const handleClickAdd = async () => { setOpenDialog(true); }; @@ -228,46 +284,47 @@ const ResourceBindingCard = ({ const closeDialog = () => setOpenDialog(false); const selectResourceHandler = (item: ResourceBinding) => { - setSpinning(true) - setBindResourceRequest(item) - bindResource(item).then(results => { - setBindResourceMessage(results.message) - setIsBindSuccessful(true) - - setSpinning(false) - }).catch(err => { - setIsBindSuccessful(false) - setBindResourceMessage(err) - console.log(err); - setError(err) - setSpinning(false) - }) - - } + setSpinning(true); + setBindResourceRequest(item); + bindResource(item) + .then(results => { + setBindResourceMessage(results.message); + setIsBindSuccessful(true); + + setSpinning(false); + }) + .catch(err => { + setIsBindSuccessful(false); + setBindResourceMessage(err); + setError(err); + setSpinning(false); + }); + }; const deleteClick = async (index: number) => { - const deletedItem = items.at(index) - setSpinning(true) - removeResource(deletedItem!).then(results => { - setBindResourceMessage(results.message) - setIsBindSuccessful(true) - - //remove from table - const resourceData = items.slice(); - resourceData.splice(index, 1); - setItems(resourceData); - setSpinning(false) - }).catch(err => { - setIsBindSuccessful(false) - setBindResourceMessage(err) - console.log(err); - setError(err) - setSpinning(false) - }) - } + const deletedItem = items.at(index); + setSpinning(true); + removeResource(deletedItem!) + .then(results => { + setBindResourceMessage(results.message); + setIsBindSuccessful(true); + + // remove from table + const resourceData = items.slice(); + resourceData.splice(index, 1); + setItems(resourceData); + setSpinning(false); + }) + .catch(err => { + setIsBindSuccessful(false); + setBindResourceMessage(err); + setError(err); + setSpinning(false); + }); + }; const handleCloseAlert = () => { - setBindResourceMessage(""); + setBindResourceMessage(''); }; const deleteIcon = (index: number) => ( @@ -294,43 +351,76 @@ const ResourceBindingCard = ({ - { - items.map((record, index) => { - return ( - - {record.id} - - - {record.resourceType} - {deleteIcon(index)} - - ) - }) - } + {items.map((record, index) => { + return ( + + {record.id} + + + + + {' '} + + {record.resourceType} + {deleteIcon(index)} + + ); + })} {isBindSuccessful && !!bindResourceMessage && ( Success - {bindResourceRequest?.resourceName!} was successfully scheduled! - {!!bindResourceMessage && (<>

{bindResourceMessage})} + {bindResourceRequest?.resourceName!} was + successfully scheduled! + {!!bindResourceMessage && ( + <> +
+
+ {bindResourceMessage} + + )}
)} {!isBindSuccessful && !!bindResourceMessage && ( Error - Failed to schedule {bindResourceRequest?.resourceName!} Binding. - {!!bindResourceMessage && (<>

{bindResourceMessage})} + Failed to schedule{' '} + {bindResourceRequest?.resourceName!} Binding. + {!!bindResourceMessage && ( + <> +
+
+ {bindResourceMessage} + + )}
)}
- - - + + + theme.zIndex.drawer + 1 }} + sx={{ color: '#fff', zIndex: theme => theme.zIndex.drawer + 1 }} open={spinning} > @@ -351,10 +441,15 @@ export const ResourceBindingCardWidget = () => { const input = { awsComponent: awsAppLoadingStatus.component, entity, - catalog: catalogApi + catalog: catalogApi, }; return ; - } else { - return ; } + return ( + + ); }; diff --git a/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceSelectorDialog.tsx b/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceSelectorDialog.tsx index b38232ac..bd592924 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceSelectorDialog.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/ResourceBindingCard/ResourceSelectorDialog.tsx @@ -1,15 +1,26 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { AssociatedResources, ResourceBinding } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AssociatedResources, + ResourceBinding, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { CatalogApi } from '@backstage/plugin-catalog-react'; import { Button, Dialog, DialogActions, DialogContent, - DialogTitle, Grid, - IconButton, makeStyles, Radio, Table, TableBody, TableCell, TableHead, TableRow + DialogTitle, + Grid, + IconButton, + makeStyles, + Radio, + Table, + TableBody, + TableCell, + TableHead, + TableRow, } from '@material-ui/core'; import { Close } from '@mui/icons-material'; import React, { useEffect, useState } from 'react'; @@ -41,23 +52,22 @@ const useStyles = makeStyles(theme => ({ * with corresponding table data * @param columns An array of Column descriptors. See material-table.com/#/docs/all-props for available fields * @param tableData An array of data objects to display in the table - * @param resource The AWS resource type to display its details. Only SSM Parameters and SecretsManager secrets are supported * @returns */ const ResourceSelectorTable = ({ tableData, - selectedRowCallback + selectedRowCallback, }: { tableData: ResourceBinding[]; selectedRowCallback: (item: ResourceBinding) => void; }) => { - /*const classes = */ useStyles(); + /* const classes = */ useStyles(); const [selectedRadio, setSelectedRadio] = useState(); const selectedRow = (item: ResourceBinding, index: number) => { - selectedRowCallback(item) - setSelectedRadio(index) - } + selectedRowCallback(item); + setSelectedRadio(index); + }; return ( @@ -71,11 +81,15 @@ const ResourceSelectorTable = ({ - {tableData.map((row, index) => ( - selectedRow(row, index)} /> + selectedRow(row, index)} + /> {row.resourceName} {row.resourceType} @@ -83,7 +97,6 @@ const ResourceSelectorTable = ({ {row.resourceArn} ))} -
); @@ -102,7 +115,7 @@ export const ResourceSelectorDialog = ({ selectHandler, catalog, currentEnvironment, - associatedResources + associatedResources, }: { isOpen: boolean; closeDialogHandler: () => void; @@ -111,137 +124,142 @@ export const ResourceSelectorDialog = ({ currentEnvironment: string; associatedResources: ResourceBinding[]; }) => { - - const classes = useStyles(); - // @ts-ignore - const [loading, setLoading] = useState(true); - // @ts-ignore - const [error, setError] = useState(false); + const [, setLoading] = useState(true); + const [, setError] = useState(false); const [tableData, setTableData] = useState([]); const [selectedResource, setSelectedResource] = useState(); const localSelectHandler = () => { - // if there's a selected value - rely the item to the external caller + // if there's a selected value - relay the item to the external caller if (selectedResource) { selectHandler(selectedResource); } closeDialogHandler(); - } + }; const rowSelectedHandler = (item: ResourceBinding) => { - setSelectedResource(item) - } + setSelectedResource(item); + }; - const isResourceAlreadyBind = (resourceArn: string, associatedResources: ResourceBinding[]) => { - let result: boolean = false - associatedResources.forEach(r => { + const isResourceAlreadyBind = ( + resourceArn: string, + resourceBindings: ResourceBinding[], + ) => { + let result: boolean = false; + resourceBindings.forEach(r => { if (r.resourceArn === resourceArn) { result = true; } - }) - return result - } + }); + return result; + }; - async function getData() { - const tableData: ResourceBinding[] = [] + useEffect(() => { + async function getData() { + const resourceBindings: ResourceBinding[] = []; - // search the catalog for resources within the same environment and provider - const allResources = await catalog.getEntities({ filter: { 'kind': "resource", 'spec.type': 'aws-resource' } }); - const matchedResources = allResources.items.filter(entity => { - const appData = entity.metadata["appData"] as any; - return appData && appData[currentEnvironment] && entity.metadata.name - }) + // search the catalog for resources within the same environment and provider + const allResources = await catalog.getEntities({ + filter: { kind: 'resource', 'spec.type': 'aws-resource' }, + }); + const matchedResources = allResources.items.filter(entity => { + const appData = entity.metadata.appData as any; + return appData && appData[currentEnvironment] && entity.metadata.name; + }); - matchedResources.forEach(et => { - const etNamespace = et.metadata.namespace || 'default'; - const etName = et.metadata.name; - const id = `resource:${etNamespace}/${etName}`; + matchedResources.forEach(et => { + const etNamespace = et.metadata.namespace || 'default'; + const etName = et.metadata.name; + const id = `resource:${etNamespace}/${etName}`; - const appData = et.metadata["appData"] as any; - const envAppData = appData[currentEnvironment] as any; - // find all providers - for multi providers - const providers = Object.keys(envAppData) - providers.forEach(p => { - const providerAppData = envAppData[p] as any; - if (isResourceAlreadyBind(providerAppData['Arn'], associatedResources)) { - return; - } - if (et.metadata['resourceType'] === "aws-rds") { - //Handler for aws-rds with associated resources - const associatedRDSResources: AssociatedResources = - { - resourceArn: providerAppData['DbAdminSecretArn'], - resourceType: "aws-db-secret", - resourceName: `${etName}-secret` + const appData = et.metadata.appData as any; + const envAppData = appData[currentEnvironment] as any; + // find all providers - for multi providers + const providers = Object.keys(envAppData); + providers.forEach(p => { + const providerAppData = envAppData[p] as any; + if (isResourceAlreadyBind(providerAppData.Arn, associatedResources)) { + return; } + if (et.metadata.resourceType === 'aws-rds') { + // Handler for aws-rds with associated resources + const associatedRDSResources: AssociatedResources = { + resourceArn: providerAppData.DbAdminSecretArn, + resourceType: 'aws-db-secret', + resourceName: `${etName}-secret`, + }; - tableData.push( - { + resourceBindings.push({ resourceName: etName, - resourceType: et.metadata['resourceType']?.toString() || "", + resourceType: et.metadata.resourceType?.toString() || '', provider: p, - resourceArn: providerAppData['Arn'], + resourceArn: providerAppData.Arn, id, - associatedResources: [associatedRDSResources] - }) - } - else if (et.metadata['resourceType'] === "aws-s3") { - // Custom S3 bucket resource handler - add resource policy - const associatedS3Resources: AssociatedResources = - { - resourceArn: providerAppData['Arn'], - resourceType: "aws-s3", - resourceName: `${etName}-secret` - } + associatedResources: [associatedRDSResources], + }); + } else if (et.metadata.resourceType === 'aws-s3') { + // Custom S3 bucket resource handler - add resource policy + const associatedS3Resources: AssociatedResources = { + resourceArn: providerAppData.Arn, + resourceType: 'aws-s3', + resourceName: `${etName}-secret`, + }; - tableData.push( - { + resourceBindings.push({ resourceName: etName, - resourceType: et.metadata['resourceType']?.toString() || "", + resourceType: et.metadata.resourceType?.toString() || '', provider: p, - resourceArn: providerAppData['Arn'], + resourceArn: providerAppData.Arn, id, - associatedResources: [associatedS3Resources] - }) - } - else { - // General AWS resource handler - tableData.push( - { + associatedResources: [associatedS3Resources], + }); + } else { + // General AWS resource handler + resourceBindings.push({ resourceName: etName, - resourceType: et.metadata['resourceType']?.toString() || "", + resourceType: et.metadata.resourceType?.toString() || '', provider: p, - resourceArn: providerAppData['Arn'], + resourceArn: providerAppData.Arn, id, - }) - } - }) - }) - setTableData(tableData) - } + }); + } + }); + }); + setTableData(resourceBindings); + } - useEffect(() => { getData() .then(() => setLoading(false)) .catch(() => { setError(true); setLoading(false); }); - }, [associatedResources]); + }, [associatedResources, catalog, currentEnvironment]); // return the JSXElement for the details dialog box return ( - + Available Resources - + - + @@ -255,4 +273,3 @@ export const ResourceSelectorDialog = ({ ); }; - diff --git a/backstage-plugins/plugins/aws-apps/src/components/common/SecretStringComponent.tsx b/backstage-plugins/plugins/aws-apps/src/components/common/SecretStringComponent.tsx index cd46fedc..41b49fcc 100644 --- a/backstage-plugins/plugins/aws-apps/src/components/common/SecretStringComponent.tsx +++ b/backstage-plugins/plugins/aws-apps/src/components/common/SecretStringComponent.tsx @@ -1,9 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { - IconButton -} from '@material-ui/core'; +import { IconButton } from '@material-ui/core'; import { Visibility, VisibilityOff } from '@mui/icons-material'; import React, { useState } from 'react'; @@ -22,7 +20,9 @@ export const SecretStringComponent = ({ secret }: { secret: string }) => { <> {/* */} {hidden ? secret.replaceAll(/./g, '●') : secret} - {hidden ? : } + + {hidden ? : } + {/* */} ); diff --git a/backstage-plugins/plugins/aws-apps/src/helpers/constants.ts b/backstage-plugins/plugins/aws-apps/src/helpers/constants.ts index aca63cb2..c068a9a4 100644 --- a/backstage-plugins/plugins/aws-apps/src/helpers/constants.ts +++ b/backstage-plugins/plugins/aws-apps/src/helpers/constants.ts @@ -1,18 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { StackStatus } from "@aws-sdk/client-cloudformation"; +import { StackStatus } from '@aws-sdk/client-cloudformation'; export enum ExtraStackDeployStatus { - STAGED = "STAGED", - UNSTAGED = "UNSTAGED" + STAGED = 'STAGED', + UNSTAGED = 'UNSTAGED', } export enum ProviderType { - ECS = "ecs", - EKS = "eks", - SERVERLESS = "serverless", - GENAI_SERVERLESS = "gen-ai-serverless" + ECS = 'ecs', + EKS = 'eks', + SERVERLESS = 'serverless', + GENAI_SERVERLESS = 'gen-ai-serverless', } export type DeployStackStatus = StackStatus | ExtraStackDeployStatus; diff --git a/backstage-plugins/plugins/aws-apps/src/helpers/date-utils.ts b/backstage-plugins/plugins/aws-apps/src/helpers/date-utils.ts index 4f508c89..3831e317 100644 --- a/backstage-plugins/plugins/aws-apps/src/helpers/date-utils.ts +++ b/backstage-plugins/plugins/aws-apps/src/helpers/date-utils.ts @@ -6,5 +6,5 @@ export function formatWithTime(date: Date): string { const month = date.getMonth() + 1; const year = date.getFullYear(); const zone = date.toString().slice(date.toString().lastIndexOf('(')); - return `${month}/${day}/${year} ${date.toLocaleTimeString()} ${zone}` -} + return `${month}/${day}/${year} ${date.toLocaleTimeString()} ${zone}`; +} diff --git a/backstage-plugins/plugins/aws-apps/src/helpers/util.ts b/backstage-plugins/plugins/aws-apps/src/helpers/util.ts index 40e70492..24dfc8e6 100644 --- a/backstage-plugins/plugins/aws-apps/src/helpers/util.ts +++ b/backstage-plugins/plugins/aws-apps/src/helpers/util.ts @@ -3,10 +3,10 @@ export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); -export const base64PayloadConvert = (payload:Object) => { - let str = ""; - Object.values(payload).forEach(k=> { - str+=String.fromCharCode(k) - }) - return str; -} \ No newline at end of file +export const base64PayloadConvert = (payload: Object) => { + let str = ''; + Object.values(payload).forEach(k => { + str += String.fromCharCode(k); + }); + return str; +}; diff --git a/backstage-plugins/plugins/aws-apps/src/hooks/custom-hooks.ts b/backstage-plugins/plugins/aws-apps/src/hooks/custom-hooks.ts index ada5320b..1541f95f 100644 --- a/backstage-plugins/plugins/aws-apps/src/hooks/custom-hooks.ts +++ b/backstage-plugins/plugins/aws-apps/src/hooks/custom-hooks.ts @@ -4,13 +4,11 @@ import { Entity } from '@backstage/catalog-model'; export function useLabelsFromEntity(entity: Entity): Record { - const labels = entity?.metadata?.labels ?? {}; - - return labels; + return entity?.metadata?.labels ?? {}; } -export function useAnnotationsFromEntity(entity: Entity): Record { - const annotations = entity?.metadata?.annotations ?? {}; - - return annotations; +export function useAnnotationsFromEntity( + entity: Entity, +): Record { + return entity?.metadata?.annotations ?? {}; } diff --git a/backstage-plugins/plugins/aws-apps/src/hooks/useAppFromEntity.ts b/backstage-plugins/plugins/aws-apps/src/hooks/useAppFromEntity.ts index bf66fd87..84125551 100644 --- a/backstage-plugins/plugins/aws-apps/src/hooks/useAppFromEntity.ts +++ b/backstage-plugins/plugins/aws-apps/src/hooks/useAppFromEntity.ts @@ -5,8 +5,7 @@ import { OPAAppData } from '../types'; import { Entity } from '@backstage/catalog-model'; export function useAppFromEntity(entity: Entity): OPAAppData { - const appData = (entity.metadata.annotations as OPAAppData) ?? ''; // ToDo Validate entity as AWS Entity, has proper data (i.e repo, association with env etc.) - return appData; + return (entity.metadata.annotations as OPAAppData) ?? ''; } diff --git a/backstage-plugins/plugins/aws-apps/src/hooks/useAwsApp.tsx b/backstage-plugins/plugins/aws-apps/src/hooks/useAwsApp.tsx index 07b07622..dab72ebe 100644 --- a/backstage-plugins/plugins/aws-apps/src/hooks/useAwsApp.tsx +++ b/backstage-plugins/plugins/aws-apps/src/hooks/useAwsApp.tsx @@ -3,16 +3,38 @@ import React, { ReactNode, createContext } from 'react'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - useEntity, -} from '@backstage/plugin-catalog-react'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Entity, EntityRelation, parseEntityRef } from '@backstage/catalog-model'; +import { + Entity, + EntityRelation, + parseEntityRef, +} from '@backstage/catalog-model'; // TODO: consider moving AWSEnvironmentEntityV1 AWSEnvironmentProviderEntityV1 to common plugin -import { AWSEnvironmentEntityV1, AWSEnvironmentProviderEntityV1 } from '@aws/plugin-aws-apps-common-for-backstage'; -import { AWSComponent, AWSComponentType, AWSDeploymentEnvironment, AWSECSAppDeploymentEnvironment, AWSEKSAppDeploymentEnvironment, AWSResourceDeploymentEnvironment, AWSServerlessAppDeploymentEnvironment, AwsDeploymentEnvironments, CloudFormationStack, ComponentStateType, GenericAWSEnvironment, IRepositoryInfo, getRepoInfo } from '@aws/plugin-aws-apps-common-for-backstage'; -import { ProviderType, ExtraStackDeployStatus, DeployStackStatus } from '../helpers/constants'; +import { + AWSEnvironmentEntityV1, + AWSEnvironmentProviderEntityV1, +} from '@aws/plugin-aws-apps-common-for-backstage'; +import { + AWSComponent, + AWSComponentType, + AWSDeploymentEnvironment, + AWSECSAppDeploymentEnvironment, + AWSEKSAppDeploymentEnvironment, + AWSResourceDeploymentEnvironment, + AWSServerlessAppDeploymentEnvironment, + AwsDeploymentEnvironments, + CloudFormationStack, + ComponentStateType, + GenericAWSEnvironment, + IRepositoryInfo, + getRepoInfo, +} from '@aws/plugin-aws-apps-common-for-backstage'; +import { + ProviderType, + ExtraStackDeployStatus, + DeployStackStatus, +} from '../helpers/constants'; import { opaApiRef } from '../api'; import { formatWithTime } from '../helpers/date-utils'; @@ -31,14 +53,14 @@ export type AwsComponentHookLoadingStatus = { type EntityNameAndRef = { envName: string; targetRef: string; -} +}; type EntityLookupResponse = EntityNameAndRef & { entity: Entity | undefined; -} +}; type MultiEntityNameAndRef = { envName: string; targetRefs: string[]; -} +}; // Private / internal variables let _app_name: string | null = null; @@ -55,92 +77,117 @@ const _setCurrentProvider = (envName: string, providerName: string) => { _refresh(); // calls getData function }; -/** +/** * Loads AWS App deployment data and returns response that will allow * consumers to get the data and see whether it is still being loaded * or if there was an error loading it. * @public -*/ + */ export const useAwsComponentFromContext = (): AwsComponentHookLoadingStatus => { - const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); const config = useApi(configApiRef); const api = useApi(opaApiRef); - api.setPlatformParams(entity.metadata.name, config.getString('backend.platformRegion')); - if (entity.metadata.name != _app_name) - { + api.setPlatformParams( + entity.metadata.name, + config.getString('backend.platformRegion'), + ); + if (entity.metadata.name !== _app_name) { // switched app reset references of drop down env selector _envProviderEntityMap = null; _change_to_env_name = null; _change_to_env_provider_name = null; } - _app_name=entity.metadata.name + _app_name = entity.metadata.name; // Look up environment and environment provider entities from the catalog async function getCatalogData(): Promise { if (!entity.relations) { throw new Error(`Entity ${entity.metadata.name} has no relations`); } - const envRefs: EntityRelation[] = entity.relations - .filter(relation => parseEntityRef(relation.targetRef).kind === 'awsenvironment') + const envRefs: EntityRelation[] = entity.relations.filter( + relation => parseEntityRef(relation.targetRef).kind === 'awsenvironment', + ); if (!envRefs.length) { - throw new Error(`Entity ${entity.metadata.name} does not have an AWS environment.`); + throw new Error( + `Entity ${entity.metadata.name} does not have an AWS environment.`, + ); } // Get all application environment entities const envEntities: EntityLookupResponse[] = await Promise.all( - envRefs.map(async (relation: EntityRelation): Promise => { - const envEntity = await catalogApi.getEntityByRef(relation.targetRef); - return { - envName: envEntity?.metadata.name || parseEntityRef(relation.targetRef).name, - targetRef: relation.targetRef, - entity: envEntity, - } - }) + envRefs.map( + async (relation: EntityRelation): Promise => { + const envEntity = await catalogApi.getEntityByRef(relation.targetRef); + return { + envName: + envEntity?.metadata.name || + parseEntityRef(relation.targetRef).name, + targetRef: relation.targetRef, + entity: envEntity, + }; + }, + ), ); // Get the targetRef for each environment's providers const envProviderRefs = envEntities .filter(envLookupResponse => !!envLookupResponse.entity?.relations) .map(envLookupResponse => { - const envProviderRefs: EntityRelation[] | undefined = + const entityRelations: EntityRelation[] | undefined = envLookupResponse.entity?.relations?.filter( - relation => parseEntityRef(relation?.targetRef).kind === 'awsenvironmentprovider') + relation => + parseEntityRef(relation?.targetRef).kind === + 'awsenvironmentprovider', + ); return { envName: envLookupResponse.envName, - targetRefs: envProviderRefs ? envProviderRefs.map(ref => ref.targetRef) : [] - } + targetRefs: entityRelations + ? entityRelations.map(ref => ref.targetRef) + : [], + }; }) .filter(envProviderMap => !!envProviderMap.targetRefs.length) - .reduce((acc: EntityNameAndRef[], envProviderEntities: MultiEntityNameAndRef) => { - envProviderEntities.targetRefs.forEach(targetRef => { - acc.push({ envName: envProviderEntities.envName, targetRef }); - }); - return acc; - }, []); + .reduce( + ( + acc: EntityNameAndRef[], + envProviderEntities: MultiEntityNameAndRef, + ) => { + envProviderEntities.targetRefs.forEach(targetRef => { + acc.push({ envName: envProviderEntities.envName, targetRef }); + }); + return acc; + }, + [], + ); // Get a map of environment provider entities. The map key is the environment name. The // map value is an array of provider entities for that environment. Note, each environment // can have more than one provider entity in a multi-region or multi-account scenario. - const envProviderEntityMap: EnvEntityMap = (await Promise.all( - envProviderRefs.map(async (providerRef): Promise => { - const envProviderEntity = await catalogApi.getEntityByRef(providerRef.targetRef); - return { - envName: providerRef.envName, - targetRef: providerRef.targetRef, - entity: envProviderEntity, - } - }) - )).reduce((acc, envProviderEntity) => { + const envProviderEntityMap: EnvEntityMap = ( + await Promise.all( + envProviderRefs.map( + async (providerRef): Promise => { + const envProviderEntity = await catalogApi.getEntityByRef( + providerRef.targetRef, + ); + return { + envName: providerRef.envName, + targetRef: providerRef.targetRef, + entity: envProviderEntity, + }; + }, + ), + ) + ).reduce((acc, envProviderEntity) => { const typedAcc: EnvEntityMap = acc; if (!typedAcc[envProviderEntity.envName]) { return { - ...typedAcc, [envProviderEntity.envName]: [envProviderEntity.entity] + ...typedAcc, + [envProviderEntity.envName]: [envProviderEntity.entity], }; - } else { - typedAcc[envProviderEntity.envName].push(envProviderEntity.entity!); - return typedAcc; } + typedAcc[envProviderEntity.envName].push(envProviderEntity.entity!); + return typedAcc; }, {}); // set values into cache @@ -151,309 +198,381 @@ export const useAwsComponentFromContext = (): AwsComponentHookLoadingStatus => { } function getProviderAppData(deployEnv: AWSDeploymentEnvironment): any { - const appData = entity.metadata["appData"] as any; + const appData = entity.metadata.appData as any; const envAppData = appData[deployEnv.environment.name] as any; return envAppData[deployEnv.providerData.name] as any; } - async function populateServerlessState(svAppDeployEnv: AWSServerlessAppDeploymentEnvironment): Promise { - + async function populateServerlessState( + svAppDeployEnv: AWSServerlessAppDeploymentEnvironment, + ): Promise { let defaultDeployStatus = ExtraStackDeployStatus.UNSTAGED; const providerAppData = getProviderAppData(svAppDeployEnv); - const bucketName = providerAppData["BuildBucketName"]; + const bucketName = providerAppData.BuildBucketName; try { await api.doesS3FileExist({ bucketName, fileName: 'packaged.yaml' }); defaultDeployStatus = ExtraStackDeployStatus.STAGED; - // console.log('S3 HEAD response'); - // console.log(JSON.stringify(templateFileExistsResponse, null, 2)); - } catch (e) { - console.error(e); + // TODO: Replace this: + // console.error(e); } const appStack: CloudFormationStack = { - stackName: providerAppData["AppStackName"] || "", + stackName: providerAppData.AppStackName || '', stackDeployStatus: defaultDeployStatus, - } + }; svAppDeployEnv.app = { logGroupNames: [], - resourceGroupArn: providerAppData["AppResourceGroup"] || "", - cloudFormationStackName: providerAppData["StackName"] || "", + resourceGroupArn: providerAppData.AppResourceGroup || '', + cloudFormationStackName: providerAppData.StackName || '', appStack, s3BucketName: bucketName, - links: [] - } + links: [], + }; try { - if (defaultDeployStatus === ExtraStackDeployStatus.STAGED) { - const stackDetails = await api.getStackDetails({ stackName: appStack.stackName }); + const stackDetails = await api.getStackDetails({ + stackName: appStack.stackName, + }); // console.log("Stack Details"); // console.log(JSON.stringify(stackDetails, null, 2)); - appStack.stackDeployStatus = (stackDetails.StackStatus || ExtraStackDeployStatus.STAGED) as DeployStackStatus; - appStack.creationTime = stackDetails.CreationTime ? formatWithTime(new Date(stackDetails.CreationTime)) : undefined; - appStack.lastUpdatedTime = stackDetails.LastUpdatedTime ? formatWithTime(new Date(stackDetails.LastUpdatedTime)) : undefined; + appStack.stackDeployStatus = (stackDetails.StackStatus || + ExtraStackDeployStatus.STAGED) as DeployStackStatus; + appStack.creationTime = stackDetails.CreationTime + ? formatWithTime(new Date(stackDetails.CreationTime)) + : undefined; + appStack.lastUpdatedTime = stackDetails.LastUpdatedTime + ? formatWithTime(new Date(stackDetails.LastUpdatedTime)) + : undefined; if (stackDetails.Outputs) { - const logGroupsArrayOutput = stackDetails.Outputs.filter(output => output.OutputKey === 'LogGroupsArray')[0] ?? null; + const logGroupsArrayOutput = + stackDetails.Outputs.filter( + output => output.OutputKey === 'LogGroupsArray', + )[0] ?? null; if (logGroupsArrayOutput) { - svAppDeployEnv.app.logGroupNames.push(...JSON.parse(logGroupsArrayOutput.OutputValue as string)); + svAppDeployEnv.app.logGroupNames.push( + ...JSON.parse(logGroupsArrayOutput.OutputValue as string), + ); } - const apiUrlOutput = stackDetails.Outputs.filter(output => output.OutputKey === 'ApiGatewayEndpointWithPath')[0] ?? null; + const apiUrlOutput = + stackDetails.Outputs.filter( + output => output.OutputKey === 'ApiGatewayEndpointWithPath', + )[0] ?? null; if (apiUrlOutput) { svAppDeployEnv.app.links = [ { - title: "Go to app", + title: 'Go to app', url: apiUrlOutput.OutputValue as string, // icon: 'kind:api' - } + }, ]; } } } } catch (e) { - console.log('Failed to get stack. This is expected if the stack has not yet been deployed'); - console.error(e); + // TODO: Replace this: + // console.log( + // 'Failed to get stack. This is expected if the stack has not yet been deployed', + // ); + // console.error(e); } - } - function populateEcsState(ecsAppDeployEnv: AWSECSAppDeploymentEnvironment, providerEntity: Entity): void { - ecsAppDeployEnv.clusterName = providerEntity.metadata['clusterName']?.toString() || ""; + function populateEcsState( + ecsAppDeployEnv: AWSECSAppDeploymentEnvironment, + providerEntity: Entity, + ): void { + ecsAppDeployEnv.clusterName = + providerEntity.metadata.clusterName?.toString() || ''; const providerAppData = getProviderAppData(ecsAppDeployEnv); ecsAppDeployEnv.app = { - ecrArn: providerAppData["EcrRepositoryArn"] || "", - logGroupName: providerAppData["TaskLogGroup"] || "", - resourceGroupArn: providerAppData["AppResourceGroup"] || "", - serviceArn: providerAppData["EcsServiceArn"] || "", - taskDefArn: providerAppData["EcsTaskDefinitionArn"] || "", - cloudFormationStackName: providerAppData["StackName"] || "", - taskExecutionRoleArn: providerAppData["TaskExecutionRoleArn"] || "", + ecrArn: providerAppData.EcrRepositoryArn || '', + logGroupName: providerAppData.TaskLogGroup || '', + resourceGroupArn: providerAppData.AppResourceGroup || '', + serviceArn: providerAppData.EcsServiceArn || '', + taskDefArn: providerAppData.EcsTaskDefinitionArn || '', + cloudFormationStackName: providerAppData.StackName || '', + taskExecutionRoleArn: providerAppData.TaskExecutionRoleArn || '', links: [ { - title: "Go to app", - url: providerAppData["AlbEndpoint"] || "", + title: 'Go to app', + url: providerAppData.AlbEndpoint || '', // icon: 'kind:api' - } - ] - } + }, + ], + }; } - function populateEksState(eksAppDeployEnv: AWSEKSAppDeploymentEnvironment, providerEntity: Entity): void { - eksAppDeployEnv.clusterName = providerEntity.metadata['clusterName']?.toString() || ""; + function populateEksState( + eksAppDeployEnv: AWSEKSAppDeploymentEnvironment, + providerEntity: Entity, + ): void { + eksAppDeployEnv.clusterName = + providerEntity.metadata.clusterName?.toString() || ''; const providerAppData = getProviderAppData(eksAppDeployEnv); eksAppDeployEnv.app = { - appAdminRoleArn: providerAppData["AppAdminRoleArn"] as string || "", - ecrArn: providerAppData["EcrRepositoryArn"] as string || "", - namespace: providerAppData["Namespace"] as string || "", + appAdminRoleArn: (providerAppData.AppAdminRoleArn as string) || '', + ecrArn: (providerAppData.EcrRepositoryArn as string) || '', + namespace: (providerAppData.Namespace as string) || '', // Must match Fluent Bit configurations. See "log_group_template" setting in the EKS provider IaC. - logGroupName: providerAppData["LogGroup"] || `/aws/apps/${eksAppDeployEnv.providerData.prefix}-${eksAppDeployEnv.providerData.name}/${providerAppData["Namespace"]}`, - - resourceGroupArn: providerAppData["AppResourceGroup"] || "", - cloudFormationStackName: providerAppData["StackName"] || "", - links: [] - } + logGroupName: + providerAppData.LogGroup || + `/aws/apps/${eksAppDeployEnv.providerData.prefix}-${eksAppDeployEnv.providerData.name}/${providerAppData.Namespace}`, - if (providerAppData["AlbEndpoint"]) { + resourceGroupArn: providerAppData.AppResourceGroup || '', + cloudFormationStackName: providerAppData.StackName || '', + links: [], + }; + + if (providerAppData.AlbEndpoint) { eksAppDeployEnv.app.links.push({ - title: "Go to app", - url: providerAppData["AlbEndpoint"] || "", + title: 'Go to app', + url: providerAppData.AlbEndpoint || '', // icon: 'kind:api' }); } - } - function populateResourceState(resourceDeployEnv: AWSResourceDeploymentEnvironment): void { - resourceDeployEnv.resource.resourceName = entity.metadata.name + function populateResourceState( + resourceDeployEnv: AWSResourceDeploymentEnvironment, + ): void { + resourceDeployEnv.resource.resourceName = entity.metadata.name; const providerAppData = getProviderAppData(resourceDeployEnv); - resourceDeployEnv.resource.arn = providerAppData['Arn']?.toString() || ""; - resourceDeployEnv.resource.resourceGroupArn = providerAppData['ResourceGroup']?.toString() || ""; - resourceDeployEnv.resource.cloudFormationStackName = providerAppData['StackName']?.toString() || ""; + resourceDeployEnv.resource.arn = providerAppData.Arn?.toString() || ''; + resourceDeployEnv.resource.resourceGroupArn = + providerAppData.ResourceGroup?.toString() || ''; + resourceDeployEnv.resource.cloudFormationStackName = + providerAppData.StackName?.toString() || ''; } // Retrieve AWS App Deployment data from the back end async function getData(): Promise { - // We load catalog data if we've never loaded it before for the current // application entity or if we want to refresh the data. // We do NOT load catalog data again if we only want to switch the current // environment provider - //TODO: Feature optimization to cache app related data - make sure the key of the app matches. multi -app-> multi-env->multi-providers.. - //if (!_envEntities) { - await getCatalogData(); -// } + // TODO: Feature optimization to cache app related data - make sure the key of the app matches. multi -app-> multi-env->multi-providers.. + // if (!_envEntities) { + await getCatalogData(); + // } const envEntities = _envEntities!; const envProviderEntityMap = _envProviderEntityMap!; const componentType = getComponentType(entity); // Construct environment and components data types from environment/provider entities - const deployEnvs: AwsDeploymentEnvironments = envEntities.reduce((acc, envEntity) => { - const envProviders = envProviderEntityMap[envEntity.envName]; - let envProvider; - - if (envEntity.envName === _change_to_env_name) { - envProvider = envProviders - .filter((providerEntity: Entity) => providerEntity.metadata.name === _change_to_env_provider_name)[0]; - - if (!envProvider) { - console.error(`Could not filter based on env name ${_change_to_env_name} and provider name ${_change_to_env_provider_name}`) + const deployEnvs: AwsDeploymentEnvironments = envEntities.reduce( + (acc, envEntity) => { + const envProviders = envProviderEntityMap[envEntity.envName]; + let envProvider; + + if (envEntity.envName === _change_to_env_name) { + envProvider = envProviders.filter( + (providerEntity: Entity) => + providerEntity.metadata.name === _change_to_env_provider_name, + )[0]; + + if (!envProvider) { + // TODO: Replace this: + // console.error( + // `Could not filter based on env name ${_change_to_env_name} and provider name ${_change_to_env_provider_name}`, + // ); + envProvider = envProviders[0]; + } + } else { envProvider = envProviders[0]; } - } else { - envProvider = envProviders[0]; - } - - const awsDeploymentEnvironment: AWSDeploymentEnvironment = { - environment: { - accountType: envEntity.entity?.metadata['envTypeAccount']?.toString() || "", - category: envEntity.entity?.metadata['category']?.toString() || "", - classification: envEntity.entity?.metadata['classification']?.toString() || "", - description: envEntity.entity?.metadata['description']?.toString() || "", - envType: envEntity.entity?.metadata['environmentType']?.toString() || "", - level: parseInt(envEntity.entity?.metadata['level']?.toString() || "0", 10), - name: envEntity.entity?.metadata['name'].toString() || "", - regionType: envEntity.entity?.metadata['envTypeRegion']?.toString() || "", - }, - providerData: { - accountNumber: envProvider.metadata['awsAccount']?.toString() || "", - region: envProvider.metadata['awsRegion']?.toString() || "", - prefix: envProvider.metadata['prefix']?.toString() || "", - auditTable: envProvider.metadata['auditTable']?.toString() || "", - description: envProvider.metadata['description']?.toString() || "", - name: envProvider.metadata['name'], - operationRoleSsmKey: envProvider.metadata['operationRole']?.toString() || "", - provisioningRoleSsmKey: envProvider.metadata['provisioningRole']?.toString() || "", - providerType: envProvider.metadata['envType']?.toString().toLowerCase() || "", - vpcSsmKey: envProvider.metadata['vpc']?.toString() || "", - cloudFormationStackName: envProvider.metadata['StackName']?.toString() || "", - terraformWorkspace: envProvider.metadata['TerraformWorkspace']?.toString() || "", - terraformStateBucket: envProvider.metadata['TerraformStateBucket']?.toString() || "", - terraformStateTable: envProvider.metadata['TerraformStateTable']?.toString() || "", - }, - entities: { - envEntity: envEntity.entity as AWSEnvironmentEntityV1, - envProviderEntity: envProvider as AWSEnvironmentProviderEntityV1 - }, - app: { - cloudFormationStackName: "", // will be updated later below when app data is fetched from entity - links: [] - }, - resource: {} - } + const awsDeploymentEnvironment: AWSDeploymentEnvironment = { + environment: { + accountType: + envEntity.entity?.metadata.envTypeAccount?.toString() || '', + category: envEntity.entity?.metadata.category?.toString() || '', + classification: + envEntity.entity?.metadata.classification?.toString() || '', + description: + envEntity.entity?.metadata.description?.toString() || '', + envType: + envEntity.entity?.metadata.environmentType?.toString() || '', + level: parseInt( + envEntity.entity?.metadata.level?.toString() || '0', + 10, + ), + name: envEntity.entity?.metadata.name.toString() || '', + regionType: + envEntity.entity?.metadata.envTypeRegion?.toString() || '', + }, + providerData: { + accountNumber: envProvider.metadata.awsAccount?.toString() || '', + region: envProvider.metadata.awsRegion?.toString() || '', + prefix: envProvider.metadata.prefix?.toString() || '', + auditTable: envProvider.metadata.auditTable?.toString() || '', + description: envProvider.metadata.description?.toString() || '', + name: envProvider.metadata.name, + operationRoleSsmKey: + envProvider.metadata.operationRole?.toString() || '', + provisioningRoleSsmKey: + envProvider.metadata.provisioningRole?.toString() || '', + providerType: + envProvider.metadata.envType?.toString().toLowerCase() || '', + vpcSsmKey: envProvider.metadata.vpc?.toString() || '', + cloudFormationStackName: + envProvider.metadata.StackName?.toString() || '', + terraformWorkspace: + envProvider.metadata.TerraformWorkspace?.toString() || '', + terraformStateBucket: + envProvider.metadata.TerraformStateBucket?.toString() || '', + terraformStateTable: + envProvider.metadata.TerraformStateTable?.toString() || '', + }, + entities: { + envEntity: envEntity.entity as AWSEnvironmentEntityV1, + envProviderEntity: envProvider as AWSEnvironmentProviderEntityV1, + }, + app: { + cloudFormationStackName: '', // will be updated later below when app data is fetched from entity + links: [], + }, + resource: {}, + }; - // now adjust more specific provider data types - const providerType = envEntity.entity?.metadata['environmentType']?.toString().toLowerCase() || "N/A"; + // now adjust more specific provider data types + const providerType = + envEntity.entity?.metadata.environmentType + ?.toString() + .toLowerCase() || 'N/A'; - if (providerType === "N/A") { - throw new Error("Environment Entity not set properly - please configure environmentType"); - } + if (providerType === 'N/A') { + throw new Error( + 'Environment Entity not set properly - please configure environmentType', + ); + } - if (providerType === ProviderType.ECS && componentType === "aws-app") { - populateEcsState(awsDeploymentEnvironment as AWSECSAppDeploymentEnvironment, envProvider); - } else if (providerType === ProviderType.EKS && componentType === "aws-app") { - populateEksState(awsDeploymentEnvironment as AWSEKSAppDeploymentEnvironment, envProvider); - } else if (providerType === ProviderType.SERVERLESS && componentType === "aws-app") { - // Must handle this later, since it will require async calls that cannot be made here - } else if (componentType === "aws-resource") { - populateResourceState(awsDeploymentEnvironment as AWSResourceDeploymentEnvironment); - if (entity.metadata["resourceType"] === "aws-rds") { - (awsDeploymentEnvironment as AWSResourceDeploymentEnvironment).resource.resourceType = "database"; + if (providerType === ProviderType.ECS && componentType === 'aws-app') { + populateEcsState( + awsDeploymentEnvironment as AWSECSAppDeploymentEnvironment, + envProvider, + ); + } else if ( + providerType === ProviderType.EKS && + componentType === 'aws-app' + ) { + populateEksState( + awsDeploymentEnvironment as AWSEKSAppDeploymentEnvironment, + envProvider, + ); + } else if ( + providerType === ProviderType.SERVERLESS && + componentType === 'aws-app' + ) { + // Must handle this later, since it will require async calls that cannot be made here + } else if (componentType === 'aws-resource') { + populateResourceState( + awsDeploymentEnvironment as AWSResourceDeploymentEnvironment, + ); + if (entity.metadata.resourceType === 'aws-rds') { + ( + awsDeploymentEnvironment as AWSResourceDeploymentEnvironment + ).resource.resourceType = 'database'; + } } - } - return { - ...acc, - [envEntity.envName.toLowerCase()]: awsDeploymentEnvironment - }; - }, {}); + return { + ...acc, + [envEntity.envName.toLowerCase()]: awsDeploymentEnvironment, + }; + }, + {}, + ); // now build an AWS component matching the app and the deployed environment - function getComponentType(entity: Entity): AWSComponentType { - if (entity.kind==="AWSEnvironment") { - return AWSComponentType.AWSEnvironment - } else if (entity.kind==="AWSEnvironmentProvider") { - return AWSComponentType.AWSProvider + function getComponentType(e: Entity): AWSComponentType { + if (e.kind === 'AWSEnvironment') { + return AWSComponentType.AWSEnvironment; + } else if (e.kind === 'AWSEnvironmentProvider') { + return AWSComponentType.AWSProvider; } - let componentType: string = entity.spec?.type?.toString() || "" - if (componentType === 'aws-resource') { - return AWSComponentType.AWSResource - } else if (componentType === 'aws-app') { - return AWSComponentType.AWSApp - } else if (componentType === 'aws-organization') { - return AWSComponentType.AWSOrganization - } else { - return AWSComponentType.Default + const type: string = e.spec?.type?.toString() || ''; + if (type === 'aws-resource') { + return AWSComponentType.AWSResource; + } else if (type === 'aws-app') { + return AWSComponentType.AWSApp; + } else if (type === 'aws-organization') { + return AWSComponentType.AWSOrganization; } + return AWSComponentType.Default; } - function getLowerEnvironment(envs: AwsDeploymentEnvironments): GenericAWSEnvironment { + function getLowerEnvironment( + envs: AwsDeploymentEnvironments, + ): GenericAWSEnvironment { let lowest: GenericAWSEnvironment = Object.values(envs).at(0)!; Object.values(envs).forEach(env => { if (lowest && lowest.environment.level > env.environment.level) lowest = env; - }) + }); return lowest; } - const getRepoInfoImpl = () : IRepositoryInfo => { + const getRepoInfoImpl = (): IRepositoryInfo => { return getRepoInfo(entity); - } - - const getComponentStateType = () : ComponentStateType => { - if (entity.metadata['componentState'] ===undefined) - { - throw Error ("Error: Entity of type Component must have componentState in metadata.") + }; + + const getComponentStateType = (): ComponentStateType => { + if (entity.metadata.componentState === undefined) { + throw Error( + 'Error: Entity of type Component must have componentState in metadata.', + ); } - const componentState = entity.metadata['componentState']?.toString() || ""; - switch (componentState) - { - case "cloudformation": - return ComponentStateType.CLOUDFORMATION - case "terraform-cloud": - return ComponentStateType.TERRAFORM_CLOUD - case "terraform-aws": + const componentState = entity.metadata.componentState?.toString() || ''; + switch (componentState) { + case 'cloudformation': + return ComponentStateType.CLOUDFORMATION; + case 'terraform-cloud': + return ComponentStateType.TERRAFORM_CLOUD; + case 'terraform-aws': return ComponentStateType.TERRAFORM_AWS; - default: - throw Error (`Unsupported component state ${componentState}`); + default: + throw Error(`Unsupported component state ${componentState}`); } - } + }; const awsComponent: AWSComponent = { - componentName: entity.metadata['name'], + componentName: entity.metadata.name, componentType, componentState: getComponentStateType(), - componentSubType: entity.spec? entity.spec['subType']!.toString(): "", - iacType: entity.metadata['iacType']?.toString() || "", - repoSecretArn: entity.metadata['repoSecretArn']?.toString() || "", - getRepoInfo:getRepoInfoImpl, + componentSubType: entity.spec ? entity.spec.subType!.toString() : '', + iacType: entity.metadata.iacType?.toString() || '', + repoSecretArn: entity.metadata.repoSecretArn?.toString() || '', + getRepoInfo: getRepoInfoImpl, platformRegion: config.getString('backend.platformRegion'), environments: deployEnvs, - currentEnvironment: !!_change_to_env_name ? deployEnvs[_change_to_env_name.toLowerCase()] : getLowerEnvironment(deployEnvs), + currentEnvironment: !!_change_to_env_name + ? deployEnvs[_change_to_env_name.toLowerCase()] + : getLowerEnvironment(deployEnvs), setCurrentProvider: (envName: string, providerName: string) => { _setCurrentProvider(envName, providerName); - } + }, }; if (!awsComponent.currentEnvironment) { if (_change_to_env_name) { - console.log(`Attempting to set current environment to ${_change_to_env_name}`); + // TODO Replace or remove this: + // console.log(`Attempting to set current environment to ${_change_to_env_name}`,); } - console.log(`Failed to retrieve currentEnvironment from data set:`); - console.log(deployEnvs); } api.setBackendParams({ @@ -461,15 +580,27 @@ export const useAwsComponentFromContext = (): AwsComponentHookLoadingStatus => { awsAccount: awsComponent.currentEnvironment.providerData.accountNumber, awsRegion: awsComponent.currentEnvironment.providerData.region, prefix: awsComponent.currentEnvironment.providerData.prefix, - providerName: awsComponent.currentEnvironment.providerData.name + providerName: awsComponent.currentEnvironment.providerData.name, }); - if (awsComponent.currentEnvironment.providerData.providerType === ProviderType.SERVERLESS && componentType === "aws-app") { - await populateServerlessState(awsComponent.currentEnvironment as AWSServerlessAppDeploymentEnvironment); + if ( + awsComponent.currentEnvironment.providerData.providerType === + ProviderType.SERVERLESS && + componentType === 'aws-app' + ) { + await populateServerlessState( + awsComponent.currentEnvironment as AWSServerlessAppDeploymentEnvironment, + ); } - if (awsComponent.currentEnvironment.providerData.providerType === ProviderType.GENAI_SERVERLESS && componentType === "aws-app") { + if ( + awsComponent.currentEnvironment.providerData.providerType === + ProviderType.GENAI_SERVERLESS && + componentType === 'aws-app' + ) { // consider populating different GenAI-related data here - await populateServerlessState(awsComponent.currentEnvironment as AWSServerlessAppDeploymentEnvironment); + await populateServerlessState( + awsComponent.currentEnvironment as AWSServerlessAppDeploymentEnvironment, + ); } return awsComponent as AWSComponent; @@ -481,10 +612,7 @@ export const useAwsComponentFromContext = (): AwsComponentHookLoadingStatus => { error, loading, retry: refresh, - } = useAsyncRetry( - () => getData(), - [catalogApi, entity], - ); + } = useAsyncRetry(() => getData(), [catalogApi, entity]); _refresh = refresh; @@ -492,7 +620,9 @@ export const useAwsComponentFromContext = (): AwsComponentHookLoadingStatus => { }; // Utilize React context to hold app deployment data retrieval status -const AwsAppContext = createContext({ loading: true }); +const AwsAppContext = createContext({ + loading: true, +}); /** * Properties for the AsyncAwsAppProvider component. @@ -516,9 +646,7 @@ export const AsyncAwsAppProvider = (props: AsyncAwsAppProviderProps) => { const { children, component, loading, error, refresh } = props; const value = { component, loading, error, refresh }; return ( - - {children} - + {children} ); }; @@ -548,7 +676,7 @@ export const AwsAppProvider = (props: AwsAppProviderProps) => ( ); /** - * Grab the current app's AWS deployment data, provides loading state + * Grab the current app's AWS deployment data, provides loading state * and errors, and the ability to refresh. * * @public diff --git a/backstage-plugins/plugins/aws-apps/src/hooks/useCancellablePromise.ts b/backstage-plugins/plugins/aws-apps/src/hooks/useCancellablePromise.ts index e6dda059..a1a9c676 100644 --- a/backstage-plugins/plugins/aws-apps/src/hooks/useCancellablePromise.ts +++ b/backstage-plugins/plugins/aws-apps/src/hooks/useCancellablePromise.ts @@ -1,19 +1,19 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { useRef, useEffect } from "react"; +import { useRef, useEffect } from 'react'; type CancellablePromise = { promise: Promise; cancel: VoidFunction; -} +}; /** * Wraps a promise so that it is cancellable. Rejects promise when cancellation * occurs. - * + * * Example use: - * try { // invoke code that returns a cancellable promise } + * try { // invoke code that returns a cancellable promise } * catch (e) { * if ((e as any).isCanceled) { * // handle cancellation @@ -33,15 +33,15 @@ export function makeCancellableWithErrors(promise: Promise): { const wrappedPromise = new Promise((resolve, reject) => { promise - .then((val) => (isCanceled ? reject({ isCanceled }) : resolve(val))) - .catch((error) => (isCanceled ? reject({ isCanceled }) : reject(error))); + .then(val => (isCanceled ? reject({ isCanceled }) : resolve(val))) + .catch(error => (isCanceled ? reject({ isCanceled }) : reject(error))); }); return { promise: wrappedPromise, cancel() { isCanceled = true; - } + }, }; } @@ -57,13 +57,12 @@ export function makeCancellable(promise: Promise): { cancel(): void; } { let isCanceled = false; - const wrappedPromise = - new Promise((resolve, reject) => { - // Suppress resolution and rejection if canceled - promise - .then((val) => (!isCanceled && resolve(val))) - .catch((error) => (!isCanceled && reject(error))); - }); + const wrappedPromise = new Promise((resolve, reject) => { + // Suppress resolution and rejection if canceled + promise + .then(val => !isCanceled && resolve(val)) + .catch(error => !isCanceled && reject(error)); + }); return { promise: wrappedPromise, cancel() { @@ -74,23 +73,25 @@ export function makeCancellable(promise: Promise): { /** * Returns a function that wraps a promise so that it is canceled automatically - * when the component unmounts. - * + * when the component unmounts. + * * @param options - Optional. Allows control of whether promise is rejected when * cancellation occurs. Defaults to false. * @returns a function that wraps a promise so that it is canceled automatically - * when the component unmounts. + * when the component unmounts. */ export function useCancellablePromise({ rejectOnCancel = false }): { cancellablePromise: (p: Promise) => Promise; } { - const cancellable = rejectOnCancel ? makeCancellableWithErrors : makeCancellable; + const cancellable = rejectOnCancel + ? makeCancellableWithErrors + : makeCancellable; const emptyPromise = Promise.resolve(true); // test if the input argument is a cancelable promise generator if (cancellable(emptyPromise).cancel === undefined) { throw new Error( - "promise wrapper argument must provide a cancel() function" + 'promise wrapper argument must provide a cancel() function', ); } diff --git a/backstage-plugins/plugins/aws-apps/src/index.ts b/backstage-plugins/plugins/aws-apps/src/index.ts index 981245bd..0d468eb2 100644 --- a/backstage-plugins/plugins/aws-apps/src/index.ts +++ b/backstage-plugins/plugins/aws-apps/src/index.ts @@ -26,5 +26,5 @@ export { EntityAppConfigCard, EntityAuditTable, EntityEnvironmentSelector, - EntityAwsEnvironmentProviderSelectorCard + EntityAwsEnvironmentProviderSelectorCard, } from './plugin'; diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsAppPage/AwsAppPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsAppPage/AwsAppPage.tsx index 1226db60..609f6b39 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsAppPage/AwsAppPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsAppPage/AwsAppPage.tsx @@ -1,12 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { GenericAWSEnvironment, readOpaAppAuditPermission } from '@aws/plugin-aws-apps-common-for-backstage'; +import { + GenericAWSEnvironment, + readOpaAppAuditPermission, +} from '@aws/plugin-aws-apps-common-for-backstage'; import { Entity } from '@backstage/catalog-model'; import { EmptyState } from '@backstage/core-components'; import { EntityLayout, EntitySwitch } from '@backstage/plugin-catalog'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; -import { RequirePermission, usePermission } from '@backstage/plugin-permission-react'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; +import { + RequirePermission, + usePermission, +} from '@backstage/plugin-permission-react'; import { isGitlabAvailable } from '@immobiliarelabs/backstage-plugin-gitlab'; import { Grid, LinearProgress } from '@material-ui/core'; import React, { ReactNode } from 'react'; @@ -31,8 +37,11 @@ const isCicdApplicable = (entity: Entity) => { return isGitlabAvailable(entity) || isGithubActionsAvailable(entity); }; -export function isAppType(appType: string, env: GenericAWSEnvironment): (entity: Entity) => boolean { - return (/*entity: Entity*/): boolean => { +export function isAppType( + appType: string, + env: GenericAWSEnvironment, +): (entity: Entity) => boolean { + return (/* entity: Entity*/): boolean => { // ecs or eks or serverless return env.providerData.providerType === appType; }; @@ -98,7 +107,10 @@ export function AwsAppPage(_props: AwsAppPageProps) { {!loadingPermission && canReadAudit && ( - }> + } + > {auditContent} @@ -125,7 +137,10 @@ export function AwsAppPage(_props: AwsAppPageProps) { {!loadingPermission && canReadAudit && ( - }> + } + > {auditContent} @@ -152,7 +167,10 @@ export function AwsAppPage(_props: AwsAppPageProps) { {!loadingPermission && canReadAudit && ( - }> + } + > {auditContent} @@ -167,26 +185,36 @@ export function AwsAppPage(_props: AwsAppPageProps) { const env = awsAppLoadingStatus.component.currentEnvironment; return ( - {AwsECSAppEntityPage} - {AwsEKSAppEntityPage} - {AwsServerlessAppEntityPage} - {AwsServerlessAppEntityPage} + + {AwsECSAppEntityPage} + + + {AwsEKSAppEntityPage} + + + {AwsServerlessAppEntityPage} + + + {AwsServerlessAppEntityPage} + -

Application Type "{env.providerData.providerType}" Is Not Supported At This Time

+

+ Application Type "{env.providerData.providerType}" Is Not Supported + At This Time +

); - } else { - if (awsAppLoadingStatus.error) { - console.log(awsAppLoadingStatus.error); - } - - return ( - - ); } + // if (awsAppLoadingStatus.error) { + // console.log(awsAppLoadingStatus.error); + // } + + return ( + + ); } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsComponentPage/AwsComponentPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsComponentPage/AwsComponentPage.tsx index cf53b7f4..e652092e 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsComponentPage/AwsComponentPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsComponentPage/AwsComponentPage.tsx @@ -2,14 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import { AsyncAwsAppProvider, useAwsComponentFromContext } from '../../hooks/useAwsApp' -import { AwsAppPage } from '../AwsAppPage/AwsAppPage' -import { AwsResourcePage } from '../AwsResourcePage/AwsResourcePage' -import { AwsPendingPage } from '../AwsPendingPage/AwsPendingPage' -import { EntityEnvironmentSelector } from '../../plugin'; import { - useEntity, -} from '@backstage/plugin-catalog-react'; + AsyncAwsAppProvider, + useAwsComponentFromContext, +} from '../../hooks/useAwsApp'; +import { AwsAppPage } from '../AwsAppPage/AwsAppPage'; +import { AwsResourcePage } from '../AwsResourcePage/AwsResourcePage'; +import { AwsPendingPage } from '../AwsPendingPage/AwsPendingPage'; +import { EntityEnvironmentSelector } from '../../plugin'; +import { useEntity } from '@backstage/plugin-catalog-react'; export interface AwsComponentPageProps { componentType: string; @@ -19,31 +20,26 @@ export interface AwsComponentPageProps { export function AwsComponentPage({ componentType }: AwsComponentPageProps) { const { entity } = useEntity(); - const isApp = componentType === "aws-app"; - const isResource = componentType === "aws-resource"; - const isComponentReady = entity.metadata["appData"] !== undefined; + const isApp = componentType === 'aws-app'; + const isResource = componentType === 'aws-resource'; + const isComponentReady = entity.metadata.appData !== undefined; return ( - { - //Before loading page - check if context exist - or AWS provisioning has not yet complete. - //if it's not ready - load an alternate pending page which only has general info and repo information - isComponentReady ? ( - isApp ? ( - - - - ) : isResource ? ( - - - - ) : -
No AWS matching page to render: {componentType}
- ) : - ( - - ) - } + {isComponentReady && isApp && ( + + + + )} + {isComponentReady && isResource && ( + + + + )} + {isComponentReady && !isApp && !isResource && ( +
No AWS matching page to render: {componentType}
+ )} + {!isComponentReady && }
- ) + ); } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsECSAppPage/AwsECSAppPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsECSAppPage/AwsECSAppPage.tsx index f5fd69e9..8e38d30a 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsECSAppPage/AwsECSAppPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsECSAppPage/AwsECSAppPage.tsx @@ -4,14 +4,16 @@ import React from 'react'; import { Grid } from '@material-ui/core'; import { EntityAboutCard } from '@backstage/plugin-catalog'; -import { EntityGeneralInfoCard, EntityAppStateCard, EntityInfrastructureInfoCard, EntityAppConfigCard, EntityAppLinksCard } from '../../plugin'; import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; + EntityGeneralInfoCard, + EntityAppStateCard, + EntityInfrastructureInfoCard, + EntityAppConfigCard, + EntityAppLinksCard, +} from '../../plugin'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -interface AwsECSAppPageProps { - -} +interface AwsECSAppPageProps {} /** @public */ export function AwsECSAppPage(_props: AwsECSAppPageProps) { @@ -22,7 +24,11 @@ export function AwsECSAppPage(_props: AwsECSAppPageProps) {
- + @@ -34,7 +40,7 @@ export function AwsECSAppPage(_props: AwsECSAppPageProps) { - + @@ -42,10 +48,5 @@ export function AwsECSAppPage(_props: AwsECSAppPageProps) { ); - - return ( - <> - {awsEcsAppViewContent} - - ); + return <>{awsEcsAppViewContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsECSEnvironmentProviderPage/AwsECSEnvironmentProviderPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsECSEnvironmentProviderPage/AwsECSEnvironmentProviderPage.tsx index 31f8e89f..5e257b2e 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsECSEnvironmentProviderPage/AwsECSEnvironmentProviderPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsECSEnvironmentProviderPage/AwsECSEnvironmentProviderPage.tsx @@ -2,9 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { Entity } from '@backstage/catalog-model'; -import { EntityAboutCard, EntityLayout, EntityLinksCard } from '@backstage/plugin-catalog'; +import { + EntityAboutCard, + EntityLayout, + EntityLinksCard, +} from '@backstage/plugin-catalog'; import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; @@ -32,15 +36,18 @@ export function AwsECSEnvironmentProviderPage(/* {children}: AwsEnvironmentProvi
- + - + - @@ -54,9 +61,9 @@ export function AwsECSEnvironmentProviderPage(/* {children}: AwsEnvironmentProvi - - - + + + ); diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSAppPage/AwsEKSAppPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSAppPage/AwsEKSAppPage.tsx index a17b70e3..9c22ae51 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSAppPage/AwsEKSAppPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSAppPage/AwsEKSAppPage.tsx @@ -1,14 +1,15 @@ import React from 'react'; import { Grid } from '@material-ui/core'; import { EntityAboutCard } from '@backstage/plugin-catalog'; -import { EntityGeneralInfoCard, EntityAppLinksCard, EntityInfrastructureInfoCard, EntityK8sAppStateCard } from '../../plugin'; import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; + EntityGeneralInfoCard, + EntityAppLinksCard, + EntityInfrastructureInfoCard, + EntityK8sAppStateCard, +} from '../../plugin'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -interface AwsEKSAppPageProps { - -} +interface AwsEKSAppPageProps {} /** @public */ export function AwsEKSAppPage(_props: AwsEKSAppPageProps) { @@ -18,7 +19,11 @@ export function AwsEKSAppPage(_props: AwsEKSAppPageProps) { - + @@ -35,9 +40,5 @@ export function AwsEKSAppPage(_props: AwsEKSAppPageProps) { ); - return ( - <> - {awsEKSAppViewContent} - - ); + return <>{awsEKSAppViewContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSEnvironmentProviderPage/AwsEKSEnvironmentProviderPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSEnvironmentProviderPage/AwsEKSEnvironmentProviderPage.tsx index 470a1791..079ca298 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSEnvironmentProviderPage/AwsEKSEnvironmentProviderPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsEKSEnvironmentProviderPage/AwsEKSEnvironmentProviderPage.tsx @@ -1,7 +1,11 @@ import { Entity } from '@backstage/catalog-model'; -import { EntityAboutCard, EntityLayout, EntityLinksCard } from '@backstage/plugin-catalog'; +import { + EntityAboutCard, + EntityLayout, + EntityLinksCard, +} from '@backstage/plugin-catalog'; import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; @@ -29,10 +33,14 @@ export function AwsEKSEnvironmentProviderPage(/* {children}: AwsEnvironmentProvi - + - + @@ -50,9 +58,9 @@ export function AwsEKSEnvironmentProviderPage(/* {children}: AwsEnvironmentProvi - - - + + + ); diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentPage/AwsEnvironmentPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentPage/AwsEnvironmentPage.tsx index 24f643fe..adbbf298 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentPage/AwsEnvironmentPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentPage/AwsEnvironmentPage.tsx @@ -1,24 +1,29 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { EntityAboutCard, EntityLinksCard, EntityLayout } from '@backstage/plugin-catalog'; +import { + EntityAboutCard, + EntityLinksCard, + EntityLayout, +} from '@backstage/plugin-catalog'; import { Grid } from '@material-ui/core'; import React, { ReactNode } from 'react'; -import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; export interface AwsEnvironmentPageProps { - children?: ReactNode + children?: ReactNode; } import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -import { EntityAwsEnvironmentProviderSelectorCard, EntityDeleteEnvironmentCard, EntityEnvironmentInfoCard } from '../../plugin'; +import { + EntityAwsEnvironmentProviderSelectorCard, + EntityDeleteEnvironmentCard, + EntityEnvironmentInfoCard, +} from '../../plugin'; /** @public */ -export function AwsEnvironmentPage(/*{children}: AwsEnvironmentPageProps */) { - +export function AwsEnvironmentPage(/* {children}: AwsEnvironmentPageProps */) { const managementContent = ( @@ -35,7 +40,11 @@ export function AwsEnvironmentPage(/*{children}: AwsEnvironmentPageProps */) { - + diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage.tsx index d84a8d97..4bf85260 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage.tsx @@ -11,14 +11,17 @@ import { ProviderType } from '../../helpers/constants'; import { AwsEKSEnvironmentProviderPage } from '../AwsEKSEnvironmentProviderPage/AwsEKSEnvironmentProviderPage'; export interface AwsEnvironmentProviderPageProps { - children?: ReactNode + children?: ReactNode; } -export function isProviderType(providerType: string, entity: Entity): (entity: Entity) => boolean { +export function isProviderType( + providerType: string, + entity: Entity, +): (entity: Entity) => boolean { return (): boolean => { - return entity.metadata["envType"]?.toString().toLowerCase() === providerType; + return entity.metadata.envType?.toString().toLowerCase() === providerType; }; -}; +} /** @public */ export function AwsEnvironmentProviderPage(/* {children}: AwsEnvironmentProviderPageProps */) { @@ -35,11 +38,16 @@ export function AwsEnvironmentProviderPage(/* {children}: AwsEnvironmentProvider - + -

Environment Provider Type "{entity.metadata["envType"]?.toString()}" Is Not Supported At This Time

+

+ Environment Provider Type "{entity.metadata.envType?.toString()}" Is + Not Supported At This Time +

); diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsPendingPage/AwsPendingPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsPendingPage/AwsPendingPage.tsx index 851a355e..9b564859 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsPendingPage/AwsPendingPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsPendingPage/AwsPendingPage.tsx @@ -2,10 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import { Entity } from '@backstage/catalog-model'; -import { EntityAboutCard, EntityLayout, EntityLinksCard } from '@backstage/plugin-catalog'; +import { + EntityAboutCard, + EntityLayout, + EntityLinksCard, +} from '@backstage/plugin-catalog'; import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; import { useEntity } from '@backstage/plugin-catalog-react'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; import { isGitlabAvailable } from '@immobiliarelabs/backstage-plugin-gitlab'; import { Grid } from '@material-ui/core'; import React from 'react'; @@ -26,7 +30,7 @@ export function AwsPendingPage(_props: AwsPendingPageProps) { isResource = entity.spec.type === 'aws-resource'; } - const AwsPendingEntityPage = ( + return ( <> @@ -35,7 +39,11 @@ export function AwsPendingPage(_props: AwsPendingPageProps) {
- + @@ -51,6 +59,4 @@ export function AwsPendingPage(_props: AwsPendingPageProps) { ); - - return AwsPendingEntityPage; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsRDSResourcePage/AwsRDSResourcePage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsRDSResourcePage/AwsRDSResourcePage.tsx index 64443fe3..0953a02f 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsRDSResourcePage/AwsRDSResourcePage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsRDSResourcePage/AwsRDSResourcePage.tsx @@ -4,13 +4,11 @@ import { Grid } from '@material-ui/core'; import React, { ReactNode } from 'react'; import { EntityAboutCard, EntityLinksCard } from '@backstage/plugin-catalog'; -import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; import { EntityInfrastructureInfoCard } from '../../plugin'; interface AwsRDSResourcePageProps { - children?: ReactNode + children?: ReactNode; } /** @public */ @@ -21,7 +19,11 @@ export function AwsRDSResourcePage(_props: AwsRDSResourcePageProps) { - + @@ -31,9 +33,5 @@ export function AwsRDSResourcePage(_props: AwsRDSResourcePageProps) {
); - return ( - <> - {rdsContent} - - ); + return <>{rdsContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsResourcePage/AwsResourcePage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsResourcePage/AwsResourcePage.tsx index 7c1ba573..94cedf4c 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsResourcePage/AwsResourcePage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsResourcePage/AwsResourcePage.tsx @@ -3,25 +3,28 @@ import { Entity } from '@backstage/catalog-model'; import { EntityLayout, EntitySwitch } from '@backstage/plugin-catalog'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; import { isGitlabAvailable } from '@immobiliarelabs/backstage-plugin-gitlab'; import { Grid } from '@material-ui/core'; import React, { ReactNode } from 'react'; import { CICDContent } from '../../components/CICDContent/CICDContent'; import { EntityDeleteAppCard } from '../../plugin'; import { AwsRDSResourcePage } from '../AwsRDSResourcePage/AwsRDSResourcePage'; -import {AwsS3ResourcePage} from '../AwsS3ResourcePage/AwsS3ResourcePage' -import {AwsSecretsManagerResourcePage} from '../AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage' +import { AwsS3ResourcePage } from '../AwsS3ResourcePage/AwsS3ResourcePage'; +import { AwsSecretsManagerResourcePage } from '../AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage'; interface AwsResourcePageProps { children: ReactNode; } -export function isResourceType(resourceType: string): (entity: Entity) => boolean { +export function isResourceType( + resourceType: string, +): (entity: Entity) => boolean { return (entity: Entity): boolean => { let subType = 'N/A'; - if (entity?.metadata?.['resourceType']) subType = entity?.metadata?.['resourceType'].toString(); - return subType == resourceType; + if (entity?.metadata?.resourceType) + subType = entity?.metadata?.resourceType.toString(); + return subType === resourceType; }; } @@ -44,7 +47,7 @@ export function AwsResourcePage(_props: AwsResourcePageProps) { {_props.children} - + @@ -61,7 +64,7 @@ export function AwsResourcePage(_props: AwsResourcePageProps) { {_props.children} - + @@ -78,7 +81,7 @@ export function AwsResourcePage(_props: AwsResourcePageProps) { {_props.children} - + @@ -92,9 +95,15 @@ export function AwsResourcePage(_props: AwsResourcePageProps) { return ( - {AwsRDSResourceEntityPage} - {AwsS3ResourceEntityPage} - {AwsSecretsResourceEntityPage} + + {AwsRDSResourceEntityPage} + + + {AwsS3ResourceEntityPage} + + + {AwsSecretsResourceEntityPage} + {/* {AwsSQSEntityPage} diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsS3ResourcePage/AwsS3ResourcePage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsS3ResourcePage/AwsS3ResourcePage.tsx index 58362488..276fb669 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsS3ResourcePage/AwsS3ResourcePage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsS3ResourcePage/AwsS3ResourcePage.tsx @@ -4,13 +4,11 @@ import { Grid } from '@material-ui/core'; import React, { ReactNode } from 'react'; import { EntityAboutCard, EntityLinksCard } from '@backstage/plugin-catalog'; -import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; import { EntityInfrastructureInfoCard } from '../../plugin'; interface AwsS3ResourcePageProps { - children?: ReactNode + children?: ReactNode; } /** @public */ @@ -21,7 +19,11 @@ export function AwsS3ResourcePage(_props: AwsS3ResourcePageProps) {
- + @@ -31,9 +33,5 @@ export function AwsS3ResourcePage(_props: AwsS3ResourcePageProps) { ); - return ( - <> - {rdsContent} - - ); + return <>{rdsContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage.tsx index 6bf43c38..906c01c4 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsSecretsManagerResourcePage/AwsSecretsManagerResourcePage.tsx @@ -4,24 +4,28 @@ import { Grid } from '@material-ui/core'; import React, { ReactNode } from 'react'; import { EntityAboutCard, EntityLinksCard } from '@backstage/plugin-catalog'; -import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; import { EntityInfrastructureInfoCard } from '../../plugin'; interface AwsSecretsManagerResourcePageProps { - children?: ReactNode + children?: ReactNode; } /** @public */ -export function AwsSecretsManagerResourcePage(_props: AwsSecretsManagerResourcePageProps) { +export function AwsSecretsManagerResourcePage( + _props: AwsSecretsManagerResourcePageProps, +) { const rdsContent = ( - + @@ -31,9 +35,5 @@ export function AwsSecretsManagerResourcePage(_props: AwsSecretsManagerResourceP ); - return ( - <> - {rdsContent} - - ); + return <>{rdsContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessAppPage/AwsServerlessAppPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessAppPage/AwsServerlessAppPage.tsx index e74eb93d..df28f08e 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessAppPage/AwsServerlessAppPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessAppPage/AwsServerlessAppPage.tsx @@ -3,26 +3,30 @@ import React from 'react'; import { EntityAboutCard } from '@backstage/plugin-catalog'; -import { EntityAppLinksCard, EntityAppStateCardCloudFormation, EntityGeneralInfoCard, EntityInfrastructureInfoCard } from '../../plugin'; -import { Grid } from '@material-ui/core'; import { - EntityCatalogGraphCard -} from '@backstage/plugin-catalog-graph'; - -interface AwsServerlessAppPageProps { + EntityAppLinksCard, + EntityAppStateCardCloudFormation, + EntityGeneralInfoCard, + EntityInfrastructureInfoCard, +} from '../../plugin'; +import { Grid } from '@material-ui/core'; +import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -} +interface AwsServerlessAppPageProps {} /** @public */ export function AwsServerlessAppPage(_props: AwsServerlessAppPageProps) { - const awsServerlessRestApiAppViewContent = ( - + @@ -39,9 +43,5 @@ export function AwsServerlessAppPage(_props: AwsServerlessAppPageProps) { ); - return ( - <> - {awsServerlessRestApiAppViewContent} - - ); + return <>{awsServerlessRestApiAppViewContent}; } diff --git a/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessEnvironmentProviderPage/AwsServerlessEnvironmentProviderPage.tsx b/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessEnvironmentProviderPage/AwsServerlessEnvironmentProviderPage.tsx index 8f54db02..7f4d7a36 100644 --- a/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessEnvironmentProviderPage/AwsServerlessEnvironmentProviderPage.tsx +++ b/backstage-plugins/plugins/aws-apps/src/pages/AwsServerlessEnvironmentProviderPage/AwsServerlessEnvironmentProviderPage.tsx @@ -2,9 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { Entity } from '@backstage/catalog-model'; -import { EntityAboutCard, EntityLayout, EntityLinksCard } from '@backstage/plugin-catalog'; +import { + EntityAboutCard, + EntityLayout, + EntityLinksCard, +} from '@backstage/plugin-catalog'; import { EntityCatalogGraphCard } from '@backstage/plugin-catalog-graph'; -import { isGithubActionsAvailable } from '@backstage/plugin-github-actions'; +import { isGithubActionsAvailable } from '@backstage-community/plugin-github-actions'; import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; @@ -32,10 +36,14 @@ export function AwsServerlessEnvironmentProviderPage(/* {children}: AwsEnvironme - + - + @@ -53,9 +61,9 @@ export function AwsServerlessEnvironmentProviderPage(/* {children}: AwsEnvironme - - - + + + ); diff --git a/backstage-plugins/plugins/aws-apps/src/plugin.ts b/backstage-plugins/plugins/aws-apps/src/plugin.ts index b1f00d8c..8e7003f5 100644 --- a/backstage-plugins/plugins/aws-apps/src/plugin.ts +++ b/backstage-plugins/plugins/aws-apps/src/plugin.ts @@ -12,17 +12,21 @@ import { Entity } from '@backstage/catalog-model'; import { rootRouteRef } from './routes'; import { OPAApiClient, opaApiRef } from './api'; -export const isOPAAppAvailable = (entity: Entity) => entity?.spec?.type === 'aws-app'; -export const isAnnotationsAvailable = (entity: Entity) => entity?.metadata?.annotations; +export const isOPAAppAvailable = (entity: Entity) => + entity?.spec?.type === 'aws-app'; +export const isAnnotationsAvailable = (entity: Entity) => + entity?.metadata?.annotations; export const isLabelsAvailable = (entity: Entity) => entity?.metadata?.labels; +/** @public */ export const opaPlugin = createPlugin({ id: 'aws-apps', apis: [ createApiFactory({ api: opaApiRef, deps: { configApi: configApiRef, fetchApi: fetchApiRef }, - factory: ({ configApi, fetchApi }) => new OPAApiClient({ configApi, fetchApi }), + factory: ({ configApi, fetchApi }) => + new OPAApiClient({ configApi, fetchApi }), }), ], routes: { @@ -30,228 +34,321 @@ export const opaPlugin = createPlugin({ }, }); +/** @public */ export const EntityLabelTable = opaPlugin.provide( createComponentExtension({ name: 'EntityLabelTable', component: { - lazy: () => import('./components/LabelTable/LabelTable').then(m => m.LabelWidget), + lazy: () => + import('./components/LabelTable/LabelTable').then(m => m.LabelWidget), }, }), ); +/** @public */ export const EntityAuditTable = opaPlugin.provide( createComponentExtension({ name: 'EntityAuditTable', component: { - lazy: () => import('./components/AuditTable/AuditTable').then(m => m.AuditWidget), + lazy: () => + import('./components/AuditTable/AuditTable').then(m => m.AuditWidget), }, }), ); +/** @public */ export const EntityEnvironmentSelector = opaPlugin.provide( createComponentExtension({ name: 'EnvironmentSelector', component: { - lazy: () => import('./components/EnvironmentSelector/EnvironmentSelector').then(m => m.EnvironmentSelectorWidget), + lazy: () => + import('./components/EnvironmentSelector/EnvironmentSelector').then( + m => m.EnvironmentSelectorWidget, + ), }, }), ); +/** @public */ export const EntityAnnotationTypeTable = opaPlugin.provide( createComponentExtension({ name: 'EntityAnnotationTypeTable', component: { - lazy: () => import('./components/AnnotationTypeTable/AnnotationTypeTable').then(m => m.AnnotationWidget), + lazy: () => + import('./components/AnnotationTypeTable/AnnotationTypeTable').then( + m => m.AnnotationWidget, + ), }, }), ); +/** @public */ export const EntityAppStateCard = opaPlugin.provide( createComponentExtension({ name: 'AppStateCard', component: { - lazy: () => import('./components/AppStateCard/AppStateCard').then(m => m.AppStateCard), + lazy: () => + import('./components/AppStateCard/AppStateCard').then( + m => m.AppStateCard, + ), }, }), ); +/** @public */ export const EntityK8sAppStateCard = opaPlugin.provide( createComponentExtension({ name: 'K8sAppStateCard', component: { - lazy: () => import('./components/K8sAppStateCard/K8sAppStateCard').then(m => m.K8sAppStateCard), + lazy: () => + import('./components/K8sAppStateCard/K8sAppStateCard').then( + m => m.K8sAppStateCard, + ), }, }), ); +/** @public */ export const EntityAppStateCardCloudFormation = opaPlugin.provide( createComponentExtension({ name: 'AppStateCardCloudFormation', component: { - lazy: () => import('./components/AppStateCardCloudFormation/AppStateCardCloudFormation').then(m => m.AppStateCard), + lazy: () => + import( + './components/AppStateCardCloudFormation/AppStateCardCloudFormation' + ).then(m => m.AppStateCard), }, }), ); +/** @public */ export const EntityGeneralInfoCard = opaPlugin.provide( createComponentExtension({ name: 'GeneralInfoCard', component: { - lazy: () => import('./components/GeneralInfoCard/GeneralInfoCard').then(m => m.GeneralInfoCard), + lazy: () => + import('./components/GeneralInfoCard/GeneralInfoCard').then( + m => m.GeneralInfoCard, + ), }, }), ); +/** @public */ export const EntityAppPromoCard = opaPlugin.provide( createComponentExtension({ name: 'AppPromoCard', component: { - lazy: () => import('./components/AppPromoCard/AppPromoCard').then(m => m.AppPromoWidget), + lazy: () => + import('./components/AppPromoCard/AppPromoCard').then( + m => m.AppPromoWidget, + ), }, }), ); +/** @public */ export const EntityAppLinksCard = opaPlugin.provide( createComponentExtension({ name: 'AppLinksCard', component: { - lazy: () => import('./components/AppLinksCard/AppLinksCard').then(m => m.AppLinksCard), + lazy: () => + import('./components/AppLinksCard/AppLinksCard').then( + m => m.AppLinksCard, + ), }, }), ); +/** @public */ export const AppCatalogPage = opaPlugin.provide( createComponentExtension({ name: 'AppCatalogPage', component: { - lazy: () => import('./components/AppCatalogPage/AppCatalogPage').then(m => m.AppCatalogPage), + lazy: () => + import('./components/AppCatalogPage/AppCatalogPage').then( + m => m.AppCatalogPage, + ), }, }), ); +/** @public */ export const EntityCloudwatchLogsTable = opaPlugin.provide( createComponentExtension({ name: 'EntityCloudwatchLogsTable', component: { - lazy: () => import('./components/CloudwatchLogsTable/CloudwatchLogsTable').then(m => m.CloudwatchLogsWidget), + lazy: () => + import('./components/CloudwatchLogsTable/CloudwatchLogsTable').then( + m => m.CloudwatchLogsWidget, + ), }, }), ); +/** @public */ export const EntityInfrastructureInfoCard = opaPlugin.provide( createComponentExtension({ name: 'InfrastructureInfoCard', component: { - lazy: () => import('./components/InfrastructureCard/InfrastructureCard').then(m => m.InfrastructureCard), + lazy: () => + import('./components/InfrastructureCard/InfrastructureCard').then( + m => m.InfrastructureCard, + ), }, }), ); - +/** @public */ export const EntityProviderInfoCard = opaPlugin.provide( createComponentExtension({ name: 'ProviderInfoCard', component: { - lazy: () => import('./components/ProviderInfoCard/ProviderInfoCard').then(m => m.ProviderInfoCard), + lazy: () => + import('./components/ProviderInfoCard/ProviderInfoCard').then( + m => m.ProviderInfoCard, + ), }, }), ); +/** @public */ export const EntityEnvironmentInfoCard = opaPlugin.provide( createComponentExtension({ name: 'EnvironmentInfoCard', component: { - lazy: () => import('./components/EnvironmentInfoCard/EnvironmentInfoCard').then(m => m.EnvironmentInfoCard), + lazy: () => + import('./components/EnvironmentInfoCard/EnvironmentInfoCard').then( + m => m.EnvironmentInfoCard, + ), }, }), ); +/** @public */ export const EntityAppConfigCard = opaPlugin.provide( createComponentExtension({ name: 'AppConfigCard', component: { - lazy: () => import('./components/AppConfigCard/AppConfigCard').then(m => m.AppConfigCard), + lazy: () => + import('./components/AppConfigCard/AppConfigCard').then( + m => m.AppConfigCard, + ), }, }), ); +/** @public */ export const EntityDeleteAppCard = opaPlugin.provide( createComponentExtension({ name: 'DeleteAppCard', component: { - lazy: () => import('./components/DeleteComponentCard/DeleteComponentCard').then(m => m.DeleteComponentCard), + lazy: () => + import('./components/DeleteComponentCard/DeleteComponentCard').then( + m => m.DeleteComponentCard, + ), }, }), ); +/** @public */ export const EntityDeleteProviderCard = opaPlugin.provide( createComponentExtension({ name: 'DeleteProviderCard', component: { - lazy: () => import('./components/DeleteProviderCard/DeleteProviderCard').then(m => m.DeleteProviderCard), + lazy: () => + import('./components/DeleteProviderCard/DeleteProviderCard').then( + m => m.DeleteProviderCard, + ), }, }), ); +/** @public */ export const EntityDeleteEnvironmentCard = opaPlugin.provide( createComponentExtension({ name: 'DeleteEnvironmentCard', component: { - lazy: () => import('./components/DeleteEnvironmentCard/DeleteEnvironmentCard').then(m => m.DeleteEnvironmentCard), + lazy: () => + import('./components/DeleteEnvironmentCard/DeleteEnvironmentCard').then( + m => m.DeleteEnvironmentCard, + ), }, }), ); +/** @public */ export const EntityResourceBindingCard = opaPlugin.provide( createComponentExtension({ name: 'ResourceBindingCard', component: { - lazy: () => import('./components/ResourceBindingCard/ResourceBinding').then(m => m.ResourceBindingCardWidget), + lazy: () => + import('./components/ResourceBindingCard/ResourceBinding').then( + m => m.ResourceBindingCardWidget, + ), }, }), ); +/** @public */ export const EntityAwsEnvironmentProviderSelectorCard = opaPlugin.provide( createComponentExtension({ name: 'AwsEnvironmentProviderSelectorCard', component: { - lazy: () => import('./components/AwsEnvironmentProviderCard/AwsEnvironmentProviderCard').then(m => m.AwsEnvironmentProviderCardWidget), + lazy: () => + import( + './components/AwsEnvironmentProviderCard/AwsEnvironmentProviderCard' + ).then(m => m.AwsEnvironmentProviderCardWidget), }, }), ); +/** @public */ export const AwsAppPage = opaPlugin.provide( createComponentExtension({ name: 'AwsAppPage', component: { - lazy: () => import('./pages/AwsAppPage/AwsAppPage').then(m => m.AwsAppPage), + lazy: () => + import('./pages/AwsAppPage/AwsAppPage').then(m => m.AwsAppPage), }, }), ); +/** @public */ export const AwsComponentPage = opaPlugin.provide( createComponentExtension({ name: 'AwsComponentPage', component: { - lazy: () => import('./pages/AwsComponentPage/AwsComponentPage').then(m => m.AwsComponentPage), + lazy: () => + import('./pages/AwsComponentPage/AwsComponentPage').then( + m => m.AwsComponentPage, + ), }, }), ); +/** @public */ export const AwsEnvironmentPage = opaPlugin.provide( createComponentExtension({ name: 'AwsEnvironmentPage', component: { - lazy: () => import('./pages/AwsEnvironmentPage/AwsEnvironmentPage').then(m => m.AwsEnvironmentPage), + lazy: () => + import('./pages/AwsEnvironmentPage/AwsEnvironmentPage').then( + m => m.AwsEnvironmentPage, + ), }, }), ); +/** @public */ export const AwsEnvironmentProviderPage = opaPlugin.provide( createComponentExtension({ name: 'AwsEnvironmentProviderPage', component: { - lazy: () => import('./pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage').then(m => m.AwsEnvironmentProviderPage), + lazy: () => + import( + './pages/AwsEnvironmentProviderPage/AwsEnvironmentProviderPage' + ).then(m => m.AwsEnvironmentProviderPage), }, }), );