From f9dbcd883ddcbebf40d0dd257371b667cefd8f8d Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Tue, 24 Dec 2024 18:27:17 +0100 Subject: [PATCH] chore: wraps no outputs found error from backend output client --- .changeset/chilly-pillows-live.md | 8 ++ .../forms/generate_forms_command.test.ts | 54 +++++++ .../generate/forms/generate_forms_command.ts | 114 +++++++-------- .../unified_client_config_generator.test.ts | 33 +++++ .../src/unified_client_config_generator.ts | 136 +++++++++--------- .../create_graphql_document_generator.test.ts | 31 ++++ .../create_graphql_models_generator.test.ts | 31 ++++ .../create_graphql_types_generator.test.ts | 31 ++++ .../get_backend_output_with_error_handling.ts | 112 +++++++-------- .../src/lambda_function_log_streamer.test.ts | 17 +++ .../src/lambda_function_log_streamer.ts | 7 +- 11 files changed, 388 insertions(+), 186 deletions(-) create mode 100644 .changeset/chilly-pillows-live.md diff --git a/.changeset/chilly-pillows-live.md b/.changeset/chilly-pillows-live.md new file mode 100644 index 0000000000..da53db4b3b --- /dev/null +++ b/.changeset/chilly-pillows-live.md @@ -0,0 +1,8 @@ +--- +'@aws-amplify/model-generator': patch +'@aws-amplify/client-config': patch +'@aws-amplify/sandbox': patch +'@aws-amplify/backend-cli': patch +--- + +wraps no outputs found error from backend output client diff --git a/packages/cli/src/commands/generate/forms/generate_forms_command.test.ts b/packages/cli/src/commands/generate/forms/generate_forms_command.test.ts index d5bcd498c7..de53013e1b 100644 --- a/packages/cli/src/commands/generate/forms/generate_forms_command.test.ts +++ b/packages/cli/src/commands/generate/forms/generate_forms_command.test.ts @@ -344,6 +344,60 @@ void describe('generate forms command', () => { ); }); + void it('throws user error if the stack outputs are undefined', async () => { + const fakeSandboxId = 'my-fake-app-my-fake-username'; + const backendIdResolver = { + resolveDeployedBackendIdentifier: mock.fn(() => + Promise.resolve({ + namespace: fakeSandboxId, + name: fakeSandboxId, + type: 'sandbox', + }) + ), + resolveBackendIdentifier: mock.fn(() => + Promise.resolve({ + namespace: fakeSandboxId, + name: fakeSandboxId, + type: 'sandbox', + }) + ), + } as BackendIdentifierResolver; + const formGenerationHandler = new FormGenerationHandler({ + awsClientProvider, + }); + + const fakedBackendOutputClient = { + getOutput: mock.fn(() => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'stack outputs are undefined' + ); + }), + }; + + const generateFormsCommand = new GenerateFormsCommand( + backendIdResolver, + () => fakedBackendOutputClient, + formGenerationHandler + ); + + const parser = yargs().command( + generateFormsCommand as unknown as CommandModule + ); + const commandRunner = new TestCommandRunner(parser); + await assert.rejects( + () => commandRunner.runCommand('forms'), + (error: TestCommandError) => { + assert.strictEqual(error.error.name, 'AmplifyOutputsNotFoundError'); + assert.strictEqual( + error.error.message, + 'Amplify outputs not found in stack metadata' + ); + return true; + } + ); + }); + void it('throws user error if credentials are expired when getting backend outputs', async () => { const fakeSandboxId = 'my-fake-app-my-fake-username'; const backendIdResolver = { diff --git a/packages/cli/src/commands/generate/forms/generate_forms_command.ts b/packages/cli/src/commands/generate/forms/generate_forms_command.ts index b9597e769a..1977c74f34 100644 --- a/packages/cli/src/commands/generate/forms/generate_forms_command.ts +++ b/packages/cli/src/commands/generate/forms/generate_forms_command.ts @@ -82,64 +82,64 @@ export class GenerateFormsCommand try { output = await backendOutputClient.getOutput(backendIdentifier); } catch (error) { - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS - ) { - throw new AmplifyUserError( - 'DeploymentInProgressError', - { - message: 'Deployment is currently in progress.', - resolution: 'Re-run this command once the deployment completes.', - }, - error - ); + if (BackendOutputClientError.isBackendOutputClientError(error)) { + switch (error.code) { + case BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS: + throw new AmplifyUserError( + 'DeploymentInProgressError', + { + message: 'Deployment is currently in progress.', + resolution: + 'Re-run this command once the deployment completes.', + }, + error + ); + case BackendOutputClientErrorType.NO_STACK_FOUND: + throw new AmplifyUserError( + 'StackDoesNotExistError', + { + message: 'Stack does not exist.', + resolution: + 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', + }, + error + ); + case BackendOutputClientErrorType.NO_OUTPUTS_FOUND: + throw new AmplifyUserError( + 'AmplifyOutputsNotFoundError', + { + message: 'Amplify outputs not found in stack metadata', + resolution: `Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists. + If this is a new sandbox or branch deployment, wait for the deployment to be successfully finished and try again.`, + }, + error + ); + case BackendOutputClientErrorType.CREDENTIALS_ERROR: + throw new AmplifyUserError( + 'CredentialsError', + { + message: + 'Unable to get backend outputs due to invalid credentials.', + resolution: + 'Ensure your AWS credentials are correctly set and refreshed.', + }, + error + ); + case BackendOutputClientErrorType.ACCESS_DENIED: + throw new AmplifyUserError( + 'AccessDeniedError', + { + message: + 'Unable to get backend outputs due to insufficient permissions.', + resolution: + 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', + }, + error + ); + default: + throw error; + } } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.NO_STACK_FOUND - ) { - throw new AmplifyUserError( - 'StackDoesNotExistError', - { - message: 'Stack does not exist.', - resolution: - 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.CREDENTIALS_ERROR - ) { - throw new AmplifyUserError( - 'CredentialsError', - { - message: - 'Unable to get backend outputs due to invalid credentials.', - resolution: - 'Ensure your AWS credentials are correctly set and refreshed.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.ACCESS_DENIED - ) { - throw new AmplifyUserError( - 'AccessDeniedError', - { - message: - 'Unable to get backend outputs due to insufficient permissions.', - resolution: - 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', - }, - error - ); - } - throw error; } diff --git a/packages/client-config/src/unified_client_config_generator.test.ts b/packages/client-config/src/unified_client_config_generator.test.ts index 496d59df9d..b82c962096 100644 --- a/packages/client-config/src/unified_client_config_generator.test.ts +++ b/packages/client-config/src/unified_client_config_generator.test.ts @@ -692,6 +692,39 @@ void describe('UnifiedClientConfigGenerator', () => { ); }); + void it('throws user error if the stack outputs are undefined', async () => { + const outputRetrieval = mock.fn(() => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'stack outputs are undefined' + ); + }); + const modelSchemaAdapter = new ModelIntrospectionSchemaAdapter( + stubClientProvider + ); + + const configContributors = new ClientConfigContributorFactory( + modelSchemaAdapter + ).getContributors('1.3'); + + const clientConfigGenerator = new UnifiedClientConfigGenerator( + outputRetrieval, + configContributors + ); + + await assert.rejects( + () => clientConfigGenerator.generateClientConfig(), + (error: AmplifyUserError) => { + assert.strictEqual( + error.message, + 'Amplify outputs not found in stack metadata' + ); + assert.ok(error.resolution); + return true; + } + ); + }); + void it('throws user error if the stack is missing metadata', async () => { const outputRetrieval = mock.fn(() => { throw new BackendOutputClientError( diff --git a/packages/client-config/src/unified_client_config_generator.ts b/packages/client-config/src/unified_client_config_generator.ts index fc33c316cc..068b2c0349 100644 --- a/packages/client-config/src/unified_client_config_generator.ts +++ b/packages/client-config/src/unified_client_config_generator.ts @@ -39,78 +39,72 @@ export class UnifiedClientConfigGenerator implements ClientConfigGenerator { try { output = await this.fetchOutput(); } catch (error) { - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS - ) { - throw new AmplifyUserError( - 'DeploymentInProgressError', - { - message: 'Deployment is currently in progress.', - resolution: 'Re-run this command once the deployment completes.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.NO_STACK_FOUND - ) { - throw new AmplifyUserError( - 'StackDoesNotExistError', - { - message: 'Stack does not exist.', - resolution: - 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.METADATA_RETRIEVAL_ERROR - ) { - throw new AmplifyUserError( - 'NonAmplifyStackError', - { - message: 'Stack was not created with Amplify.', - resolution: - 'Ensure the CloudFormation stack ID references a main stack created with Amplify, then re-run this command.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.CREDENTIALS_ERROR - ) { - throw new AmplifyUserError( - 'CredentialsError', - { - message: - 'Unable to get backend outputs due to invalid credentials.', - resolution: - 'Ensure your AWS credentials are correctly set and refreshed.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.ACCESS_DENIED - ) { - throw new AmplifyUserError( - 'AccessDeniedError', - { - message: - 'Unable to get backend outputs due to insufficient permissions.', - resolution: - 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', - }, - error - ); + if (BackendOutputClientError.isBackendOutputClientError(error)) { + switch (error.code) { + case BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS: + throw new AmplifyUserError( + 'DeploymentInProgressError', + { + message: 'Deployment is currently in progress.', + resolution: + 'Re-run this command once the deployment completes.', + }, + error + ); + case BackendOutputClientErrorType.NO_STACK_FOUND: + throw new AmplifyUserError( + 'StackDoesNotExistError', + { + message: 'Stack does not exist.', + resolution: + 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', + }, + error + ); + case BackendOutputClientErrorType.METADATA_RETRIEVAL_ERROR: + throw new AmplifyUserError( + 'NonAmplifyStackError', + { + message: 'Stack was not created with Amplify.', + resolution: + 'Ensure the CloudFormation stack ID references a main stack created with Amplify, then re-run this command.', + }, + error + ); + case BackendOutputClientErrorType.NO_OUTPUTS_FOUND: + throw new AmplifyUserError( + 'AmplifyOutputsNotFoundError', + { + message: 'Amplify outputs not found in stack metadata', + resolution: `Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists. + If this is a new sandbox or branch deployment, wait for the deployment to be successfully finished and try again.`, + }, + error + ); + case BackendOutputClientErrorType.CREDENTIALS_ERROR: + throw new AmplifyUserError( + 'CredentialsError', + { + message: + 'Unable to get backend outputs due to invalid credentials.', + resolution: + 'Ensure your AWS credentials are correctly set and refreshed.', + }, + error + ); + case BackendOutputClientErrorType.ACCESS_DENIED: + throw new AmplifyUserError( + 'AccessDeniedError', + { + message: + 'Unable to get backend outputs due to insufficient permissions.', + resolution: + 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', + }, + error + ); + } } - throw error; } const backendOutput = unifiedBackendOutputSchema.parse(output); diff --git a/packages/model-generator/src/create_graphql_document_generator.test.ts b/packages/model-generator/src/create_graphql_document_generator.test.ts index d3071f0d02..5df2c3a43c 100644 --- a/packages/model-generator/src/create_graphql_document_generator.test.ts +++ b/packages/model-generator/src/create_graphql_document_generator.test.ts @@ -100,6 +100,37 @@ void describe('model generator factory', () => { ); }); + void it('throws an error if outputs do not exist', async () => { + const fakeBackendOutputClient = { + getOutput: mock.fn(() => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'stack outputs are undefined' + ); + }), + }; + mock.method( + BackendOutputClientFactory, + 'getInstance', + () => fakeBackendOutputClient + ); + const generator = createGraphqlDocumentGenerator({ + backendIdentifier: { stackName: 'stackThatDoesNotHaveOutputs' }, + awsClientProvider, + }); + await assert.rejects( + () => generator.generateModels({ targetFormat: 'javascript' }), + (error: AmplifyUserError) => { + assert.strictEqual( + error.message, + 'Amplify outputs not found in stack metadata' + ); + assert.ok(error.resolution); + return true; + } + ); + }); + void it('throws an error if credentials are expired when getting backend outputs', async () => { const fakeBackendOutputClient = { getOutput: mock.fn(() => { diff --git a/packages/model-generator/src/create_graphql_models_generator.test.ts b/packages/model-generator/src/create_graphql_models_generator.test.ts index 615ac7e655..86aeb3deb2 100644 --- a/packages/model-generator/src/create_graphql_models_generator.test.ts +++ b/packages/model-generator/src/create_graphql_models_generator.test.ts @@ -105,6 +105,37 @@ void describe('models generator factory', () => { ); }); + void it('throws an error if stack outputs are undefined', async () => { + const fakeBackendOutputClient = { + getOutput: mock.fn(() => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'stack outputs are undefined' + ); + }), + }; + mock.method( + BackendOutputClientFactory, + 'getInstance', + () => fakeBackendOutputClient + ); + const generator = createGraphqlModelsGenerator({ + backendIdentifier: { stackName: 'stackThatDoesNotHaveOutputs' }, + awsClientProvider, + }); + await assert.rejects( + () => generator.generateModels({ target: 'javascript' }), + (error: AmplifyUserError) => { + assert.strictEqual( + error.message, + 'Amplify outputs not found in stack metadata' + ); + assert.ok(error.resolution); + return true; + } + ); + }); + void it('throws an error if credentials are expired when getting backend outputs', async () => { const fakeBackendOutputClient = { getOutput: mock.fn(() => { diff --git a/packages/model-generator/src/create_graphql_types_generator.test.ts b/packages/model-generator/src/create_graphql_types_generator.test.ts index 50981b498d..ab889cff59 100644 --- a/packages/model-generator/src/create_graphql_types_generator.test.ts +++ b/packages/model-generator/src/create_graphql_types_generator.test.ts @@ -99,6 +99,37 @@ void describe('types generator factory', () => { ); }); + void it('throws an AmplifyUserError if stack outputs are undefined', async () => { + const fakeBackendOutputClient = { + getOutput: mock.fn(() => { + throw new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'stack outputs are undefined' + ); + }), + }; + mock.method( + BackendOutputClientFactory, + 'getInstance', + () => fakeBackendOutputClient + ); + const generator = createGraphqlTypesGenerator({ + backendIdentifier: { stackName: 'stackThatDoesNotHaveOutputs' }, + awsClientProvider, + }); + await assert.rejects( + () => generator.generateTypes({ target: 'json' }), + (error: AmplifyUserError) => { + assert.strictEqual( + error.message, + 'Amplify outputs not found in stack metadata' + ); + assert.ok(error.resolution); + return true; + } + ); + }); + void it('throws an AmplifyUserError if credentials are expired when getting backend outputs', async () => { const fakeBackendOutputClient = { getOutput: mock.fn(() => { diff --git a/packages/model-generator/src/get_backend_output_with_error_handling.ts b/packages/model-generator/src/get_backend_output_with_error_handling.ts index fc45ed295f..6dba118eae 100644 --- a/packages/model-generator/src/get_backend_output_with_error_handling.ts +++ b/packages/model-generator/src/get_backend_output_with_error_handling.ts @@ -16,63 +16,63 @@ export const getBackendOutputWithErrorHandling = async ( try { return await backendOutputClient.getOutput(backendIdentifier); } catch (error) { - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS - ) { - throw new AmplifyUserError( - 'DeploymentInProgressError', - { - message: 'Deployment is currently in progress.', - resolution: 'Re-run this command once the deployment completes.', - }, - error - ); + if (BackendOutputClientError.isBackendOutputClientError(error)) { + switch (error.code) { + case BackendOutputClientErrorType.DEPLOYMENT_IN_PROGRESS: + throw new AmplifyUserError( + 'DeploymentInProgressError', + { + message: 'Deployment is currently in progress.', + resolution: 'Re-run this command once the deployment completes.', + }, + error + ); + case BackendOutputClientErrorType.NO_STACK_FOUND: + throw new AmplifyUserError( + 'StackDoesNotExistError', + { + message: 'Stack does not exist.', + resolution: + 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', + }, + error + ); + case BackendOutputClientErrorType.NO_OUTPUTS_FOUND: + throw new AmplifyUserError( + 'AmplifyOutputsNotFoundError', + { + message: 'Amplify outputs not found in stack metadata', + resolution: `Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists. + If this is a new sandbox or branch deployment, wait for the deployment to be successfully finished and try again.`, + }, + error + ); + case BackendOutputClientErrorType.CREDENTIALS_ERROR: + throw new AmplifyUserError( + 'CredentialsError', + { + message: + 'Unable to get backend outputs due to invalid credentials.', + resolution: + 'Ensure your AWS credentials are correctly set and refreshed.', + }, + error + ); + case BackendOutputClientErrorType.ACCESS_DENIED: + throw new AmplifyUserError( + 'AccessDeniedError', + { + message: + 'Unable to get backend outputs due to insufficient permissions.', + resolution: + 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', + }, + error + ); + default: + throw error; + } } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.NO_STACK_FOUND - ) { - throw new AmplifyUserError( - 'StackDoesNotExistError', - { - message: 'Stack does not exist.', - resolution: - 'Ensure the CloudFormation stack ID or Amplify App ID and branch specified are correct and exists, then re-run this command.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.CREDENTIALS_ERROR - ) { - throw new AmplifyUserError( - 'CredentialsError', - { - message: 'Unable to get backend outputs due to invalid credentials.', - resolution: - 'Ensure your AWS credentials are correctly set and refreshed.', - }, - error - ); - } - if ( - BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.ACCESS_DENIED - ) { - throw new AmplifyUserError( - 'AccessDeniedError', - { - message: - 'Unable to get backend outputs due to insufficient permissions.', - resolution: - 'Ensure you have permissions to call cloudformation:GetTemplateSummary.', - }, - error - ); - } - throw error; } }; diff --git a/packages/sandbox/src/lambda_function_log_streamer.test.ts b/packages/sandbox/src/lambda_function_log_streamer.test.ts index dfd98eb119..a6e24b1a4f 100644 --- a/packages/sandbox/src/lambda_function_log_streamer.test.ts +++ b/packages/sandbox/src/lambda_function_log_streamer.test.ts @@ -168,6 +168,23 @@ void describe('LambdaFunctionLogStreamer', () => { assert.strictEqual(lambdaClientSendMock.mock.callCount(), 0); }); + void it('return early if backend output client throws with outputs do not exist', async () => { + backendOutputClientMock.getOutput.mock.mockImplementationOnce(() => { + return Promise.reject( + new BackendOutputClientError( + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + 'Stack outputs are undefined' + ) + ); + }); + await classUnderTest.startStreamingLogs(testSandboxBackendId, { + enabled: true, + }); + + // No lambda calls to retrieve tags + assert.strictEqual(lambdaClientSendMock.mock.callCount(), 0); + }); + void it('calls logs monitor with all the customer defined functions and conversation handlers if no function name filter is provided', async () => { await classUnderTest.startStreamingLogs(testSandboxBackendId, { enabled: true, diff --git a/packages/sandbox/src/lambda_function_log_streamer.ts b/packages/sandbox/src/lambda_function_log_streamer.ts index 12858111e7..aac9f2a833 100644 --- a/packages/sandbox/src/lambda_function_log_streamer.ts +++ b/packages/sandbox/src/lambda_function_log_streamer.ts @@ -47,10 +47,13 @@ export class LambdaFunctionLogStreamer { sandboxBackendId ); } catch (error) { - // If stack does not exist, we do not want to go further to start streaming logs + // If stack does not exist or hasn't deployed successfully, we do not want to go further to start streaming logs if ( BackendOutputClientError.isBackendOutputClientError(error) && - error.code === BackendOutputClientErrorType.NO_STACK_FOUND + [ + BackendOutputClientErrorType.NO_STACK_FOUND, + BackendOutputClientErrorType.NO_OUTPUTS_FOUND, + ].some((code) => (error as BackendOutputClientError).code === code) ) { this.enabled = false; return;