From c91c8274a0d0c407fff50c429d5ff7094f3424ab Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:35:03 +0800 Subject: [PATCH 1/4] style(env var): change naming of var technically we mock both create and get calls, changing the var name to reflect this --- .env-example | 2 +- src/config/config.ts | 6 +++--- src/services/identity/LaunchClient.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.env-example b/.env-example index da44309d5..8009bb927 100644 --- a/.env-example +++ b/.env-example @@ -26,7 +26,7 @@ export SITE_CREATE_FORM_KEY="" # generate your own access key and secret access key from AWS export AWS_ACCESS_KEY_ID="" export AWS_SECRET_ACCESS_KEY="" -export MOCK_AMPLIFY_CREATE_DOMAIN_CALLS="true" +export MOCK_AMPLIFY_DOMAIN_ASSOCIATION_CALLS="true" # Required to run end-to-end tests export E2E_TEST_REPO="e2e-test-repo" diff --git a/src/config/config.ts b/src/config/config.ts index 6c7ccaa1a..f8e370a77 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -172,9 +172,9 @@ const config = convict({ format: String, default: "", }, - mockAmplifyCreateDomainAssociationCalls: { - doc: "Mock createDomainAssociation calls to Amplify ", - env: "MOCK_AMPLIFY_CREATE_DOMAIN_ASSOCIATION_CALLS", + mockAmplifyDomainAssociationCalls: { + doc: "Mock domain association calls to Amplify", + env: "MOCK_AMPLIFY_DOMAIN_ASSOCIATION_CALLS", format: "required-boolean", default: true, }, diff --git a/src/services/identity/LaunchClient.ts b/src/services/identity/LaunchClient.ts index c8d1101be..02e387924 100644 --- a/src/services/identity/LaunchClient.ts +++ b/src/services/identity/LaunchClient.ts @@ -119,7 +119,7 @@ class LaunchClient { } private shouldMockAmplifyDomainCalls(): boolean { - return config.get("aws.amplify.mockAmplifyCreateDomainAssociationCalls") + return config.get("aws.amplify.mockAmplifyDomainAssociationCalls") } private getSubDomains( From 8a4b3779e715751b6c84af41a843c85160da8dc4 Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:19:20 +0800 Subject: [PATCH 2/4] fix(mockDomainAssociation): change wrong usage of env var --- src/services/infra/InfraService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/infra/InfraService.ts b/src/services/infra/InfraService.ts index 588796517..e5fb628dd 100644 --- a/src/services/infra/InfraService.ts +++ b/src/services/infra/InfraService.ts @@ -228,8 +228,9 @@ export default class InfraService { ) // Get DNS records from Amplify - const isTestEnv = - config.get("env") === "test" || config.get("env") === "dev" + const isTestEnv = config.get( + "aws.amplify.mockAmplifyDomainAssociationCalls" + ) // since we mock values during development, we don't have to await for the dns records if (!isTestEnv) { /** From 2d578ec33223e8d749e315abc8586827ecb9e24a Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:39:10 +0800 Subject: [PATCH 3/4] feat(launchClient): Create Delete Domain calls + typing --- src/services/identity/LaunchClient.ts | 48 ++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/services/identity/LaunchClient.ts b/src/services/identity/LaunchClient.ts index 02e387924..322aafd59 100644 --- a/src/services/identity/LaunchClient.ts +++ b/src/services/identity/LaunchClient.ts @@ -7,11 +7,16 @@ import { GetDomainAssociationCommandInput as AmplifySDKGetDomainAssociationCommandInput, GetDomainAssociationCommandOutput, SubDomainSetting, + DeleteDomainAssociationCommandInput as AmplifySDKDeleteDomainAssociationCommandInput, + DeleteDomainAssociationCommand, + NotFoundException, } from "@aws-sdk/client-amplify" import { SubDomain } from "aws-sdk/clients/amplify" import { config } from "@config/config" +import { AmplifyError } from "@root/types" + // stricter typing to interact with Amplify SDK type CreateDomainAssociationCommandInput = { [K in keyof AmplifySDKCreateDomainAssociationCommandInput]: NonNullable< @@ -25,6 +30,19 @@ type GetDomainAssociationCommandInput = { > } +type DeleteDomainAssociationCommandInput = { + [K in keyof AmplifySDKDeleteDomainAssociationCommandInput]: NonNullable< + AmplifySDKDeleteDomainAssociationCommandInput[K] + > +} + +export type AmplifyDomainNotFoundException = AmplifyError | NotFoundException + +export const isAmplifyDomainNotFoundException = ( + obj: unknown +): obj is AmplifyDomainNotFoundException => + obj instanceof AmplifyError || obj instanceof NotFoundException + class LaunchClient { private readonly amplifyClient: InstanceType @@ -68,6 +86,14 @@ class LaunchClient { domainName, }) + createDeleteDomainAssociationCommandInput = ( + appId: string, + domainName: string + ): DeleteDomainAssociationCommandInput => ({ + appId, + domainName, + }) + sendGetDomainAssociationCommand = ( input: GetDomainAssociationCommandInput ): Promise => { @@ -142,13 +168,33 @@ class LaunchClient { return subDomains } + async sendDeleteDomainAssociationCommand( + input: DeleteDomainAssociationCommandInput + ): Promise { + if (this.shouldMockAmplifyDomainCalls()) { + this.mockDeleteDomainAssociationOutput(input) + } + await this.amplifyClient.send(new DeleteDomainAssociationCommand(input)) + } + + mockDeleteDomainAssociationOutput( + input: DeleteDomainAssociationCommandInput + ) { + if (!this.mockDomainAssociations.has(input.domainName)) { + throw new AmplifyError( + `NotFoundException: Domain association ${input.domainName} not found.` + ) + } + this.mockDomainAssociations.delete(input.domainName) + } + private mockGetDomainAssociationOutput( input: GetDomainAssociationCommandInput ): Promise { const isSubDomainCreated = true // this is a `get` call, assume domain has already been created const subDomainSettings = this.mockDomainAssociations.get(input.domainName) if (!subDomainSettings) { - throw new Error( + throw new AmplifyError( `NotFoundException: Domain association ${input.domainName} not found.` ) } From c9a831559e53b2184cb10f1ed6b5d2659362cad5 Mon Sep 17 00:00:00 2001 From: Kishore <42832651+kishore03109@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:40:08 +0800 Subject: [PATCH 4/4] feat(launches Service ): call delete domain before create to remove ... redundant associations --- src/services/identity/LaunchesService.ts | 39 +++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/services/identity/LaunchesService.ts b/src/services/identity/LaunchesService.ts index 85c56a3f5..cf2ec1c71 100644 --- a/src/services/identity/LaunchesService.ts +++ b/src/services/identity/LaunchesService.ts @@ -11,7 +11,9 @@ import logger from "@logger/logger" import { Deployment, Launch, Repo, User, Redirection } from "@database/models" import { RedirectionTypes } from "@root/constants/constants" import { AmplifyError } from "@root/types/index" -import LaunchClient from "@services/identity/LaunchClient" +import LaunchClient, { + isAmplifyDomainNotFoundException, +} from "@services/identity/LaunchClient" export type SiteLaunchCreateParams = { userId: number @@ -159,6 +161,41 @@ export class LaunchesService { throw siteIdResult.error } + try { + // Check if association already exists + const getDomainAssociationCommandInput = this.launchClient.createGetDomainAssociationCommandInput( + appIdResult.value, + domainName + ) + + const getDomainAssociationResult = await this.launchClient.sendGetDomainAssociationCommand( + getDomainAssociationCommandInput + ) + const hasDomainAssociationFailed = + getDomainAssociationResult?.domainAssociation?.domainStatus === "FAILED" + if (hasDomainAssociationFailed) { + // safe to delete and retry + const deleteDomainAssociationCommandInput = this.launchClient.createDeleteDomainAssociationCommandInput( + appIdResult.value, + domainName + ) + await this.launchClient.sendDeleteDomainAssociationCommand( + deleteDomainAssociationCommandInput + ) + } + } catch (error: unknown) { + const isExpectedNotFoundError = isAmplifyDomainNotFoundException(error) + if (!isExpectedNotFoundError) { + return err( + new AmplifyError( + `Unable to connect to Amplify for: ${repoName}, ${error}`, + repoName, + appIdResult.value + ) + ) + } + } + const launchAppOptions = this.launchClient.createDomainAssociationCommandInput( appIdResult.value, domainName,