Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ export interface CcApiContextQuery extends ContextLookupRoleOptions {
readonly dummyValue?: any;

/**
* When True, the context provider don't throw an error and return the dummyValue if the resource was not found.
* Ignore an error and return the `dummyValue` instead if the resource was not found.
* @default false
*/
readonly ignoreErrorOnMissingContext?: boolean;
readonly ignoreFailedLookup?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1049,8 +1049,8 @@
"dummyValue": {
"description": "The value to return if the context value was not found. (Default - None)"
},
"ignoreErrorOnMissingContext": {
"description": "When True, the context provider don't throw an error and return the dummyValue if the resource was not found.",
"ignoreFailedLookup": {
"description": "Ignore an error and return the `dummyValue` instead if the resource was not found.",
"default": false,
"type": "boolean"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/cloud-assembly-schema/schema/version.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"schemaHash": "4c069fb88b4e8d69ffb38c74a978955d21d2fb20022266d298f1d9855f45ba78",
"revision": 43
"schemaHash": "5fe173a6d0d4d783245c472e0c03c2b6d7d216ace91b35635b8f7a3dacd2fd7e",
"revision": 42
}
58 changes: 31 additions & 27 deletions packages/aws-cdk/lib/context-providers/cc-api-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-pr
import type { ContextProviderPlugin } from '../api/plugin';
import { ContextProviderError } from '../toolkit/error';
import { findJsonValue, getResultObj } from '../util';
import { ResourceNotFoundException } from '@aws-sdk/client-cloudcontrol';

export class CcApiContextProviderPlugin implements ContextProviderPlugin {
constructor(private readonly aws: SdkProvider) {
Expand All @@ -19,8 +20,18 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
public async getValue(args: CcApiContextQuery) {
const cloudControl = (await initContextProviderSdk(this.aws, args)).cloudControl();

const result = await this.findResources(cloudControl, args);
return result;
try {
const result = await this.findResources(cloudControl, args);
return result;
} catch (err) {
if (err instanceof ResourceNotFoundException) {
const dummyObject = this.getDummyObject(args);
if (dummyObject) {
return [dummyObject];
}
}
throw err;
}
}

private async findResources(cc: ICloudControlClient, args: CcApiContextQuery): Promise<{[key: string]: any} []> {
Expand All @@ -31,20 +42,12 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
throw new ContextProviderError(`Neither exactIdentifier nor propertyMatch is specified. Failed to find resources using CC API for type ${args.typeName}.`);
}

try {
if (args.exactIdentifier) {
// use getResource to get the exact indentifier
return await this.getResource(cc, args.typeName, args.exactIdentifier, args.propertiesToReturn);
} else {
// use listResource
return await this.listResources(cc, args.typeName, args.propertyMatch!, args.propertiesToReturn);
}
} catch (err) {
const dummyValue = this.getDummyValueIfErrorIgnored(args);
if (dummyValue) {
return [getResultObj(dummyValue, 'dummy-id', args.propertiesToReturn)];
}
throw err;
if (args.exactIdentifier) {
// use getResource to get the exact indentifier
return await this.getResource(cc, args.typeName, args.exactIdentifier, args.propertiesToReturn, args.ignoreFailedLookup);
} else {
// use listResource
return await this.listResources(cc, args.typeName, args.propertyMatch!, args.propertiesToReturn, args.ignoreFailedLookup);
}
}

Expand All @@ -60,6 +63,7 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
typeName: string,
exactIdentifier: string,
propertiesToReturn: string[],
ignoreFailedLookup?: boolean,
): Promise<{[key: string]: any}[]> {
const resultObjs: {[key: string]: any}[] = [];
try {
Expand All @@ -76,6 +80,9 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
throw new ContextProviderError(`Could not get resource ${exactIdentifier}.`);
}
} catch (err) {
if (err instanceof ResourceNotFoundException && ignoreFailedLookup) {
throw err;
}
throw new ContextProviderError(`Encountered CC API error while getting resource ${exactIdentifier}. Error: ${err}`);
}
return resultObjs;
Expand All @@ -93,6 +100,7 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
typeName: string,
propertyMatch: Record<string, unknown>,
propertiesToReturn: string[],
ignoreFailedLookup?: boolean,
): Promise<{[key: string]: any}[]> {
const resultObjs: {[key: string]: any}[] = [];

Expand Down Expand Up @@ -128,22 +136,18 @@ export class CcApiContextProviderPlugin implements ContextProviderPlugin {
}
});
} catch (err) {
if (err instanceof ResourceNotFoundException && ignoreFailedLookup) {
throw err;
}
throw new ContextProviderError(`Could not get resources ${JSON.stringify(propertyMatch)}. Error: ${err}`);
}
return resultObjs;
}

private getDummyValueIfErrorIgnored(args: CcApiContextQuery): Record<string, any> | undefined {
if (!args.ignoreErrorOnMissingContext) {
return undefined;
}
if (!Array.isArray(args.dummyValue) || args.dummyValue.length === 0) {
return undefined;
}
const dummyValue = args.dummyValue[0];
if (typeof dummyValue !== 'object' || dummyValue === null) {
return undefined;
private getDummyObject(args: CcApiContextQuery): Record<string, any> | undefined {
if (!Array.isArray(args.dummyValue) || args.dummyValue.length === 0 || typeof args.dummyValue[0] !== 'object' || args.dummyValue[0] === null) {
throw new ContextProviderError(`dummyValue must be an array with at least one object. Failed to get dummy object for type ${args.typeName}.`);
}
return dummyValue;
return getResultObj(args.dummyValue[0], 'dummy-id', args.propertiesToReturn);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the if (!args.ignoreErrorOnMissingContext) block in this since this function is called when err is an instance of ResourceNotFoundException and ignoreFailedLookup is true.

Also moved getResultObj(args.dummyValue[0], 'dummy-id', args.propertiesToReturn) into this function from getValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more changes to getDummyObject (and also changed to getDummyObjects).

c315497

8d9d94c

}
}
102 changes: 82 additions & 20 deletions packages/aws-cdk/test/context-providers/cc-api-provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GetResourceCommand, ListResourcesCommand } from '@aws-sdk/client-cloudcontrol';
import { GetResourceCommand, InvalidRequestException, ListResourcesCommand, ResourceNotFoundException } from '@aws-sdk/client-cloudcontrol';
import { CcApiContextProviderPlugin } from '../../lib/context-providers/cc-api-provider';
import { mockCloudControlClient, MockSdkProvider, restoreSdkMocksToDefault } from '../util/mock-sdk';

Expand Down Expand Up @@ -244,7 +244,7 @@ test('error by specifying neither exactIdentifier or propertyMatch', async () =>
describe('dummy value', () => {
test('returns dummy value when CC API getResource fails', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN
const results = await provider.getValue({
Expand All @@ -253,7 +253,7 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
dummyValue: [
{
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
Expand All @@ -273,7 +273,7 @@ describe('dummy value', () => {

test('returns dummy value when CC API listResources fails', async () => {
// GIVEN
mockCloudControlClient.on(ListResourcesCommand).rejects('No data found');
mockCloudControlClient.on(ListResourcesCommand).rejects(createResourceNotFoundException());

// WHEN
const results = await provider.getValue({
Expand All @@ -282,7 +282,7 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
propertyMatch: { 'StorageEncrypted': 'true' },
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
dummyValue: [
{
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
Expand All @@ -300,9 +300,55 @@ describe('dummy value', () => {
});
});

test('throws error when CC API fails and ignoreErrorOnMissingContext is not provided', async () => {
test('throws error when CC API getResource fails but the error is not ResourceNotFoundException', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createOtherError());

// WHEN/THEN
await expect(
provider.getValue({
account: '123456789012',
region: 'us-east-1',
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreFailedLookup: true,
dummyValue: [
{
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
StorageEncrypted: 'true',
},
],
}),
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
});

test('throws error when CC API listResources fails but the error is not ResourceNotFoundException', async () => {
// GIVEN
mockCloudControlClient.on(ListResourcesCommand).rejects(createOtherError());

// WHEN/THEN
await expect(
provider.getValue({
account: '123456789012',
region: 'us-east-1',
typeName: 'AWS::RDS::DBInstance',
propertyMatch: { 'StorageEncrypted': 'true' },
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreFailedLookup: true,
dummyValue: [
{
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
StorageEncrypted: 'true',
},
],
}),
).rejects.toThrow('Could not get resources {"StorageEncrypted":"true"}.');
});

test('throws error when CC API fails and ignoreFailedLookup is not provided', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN/THEN
await expect(
Expand All @@ -322,9 +368,9 @@ describe('dummy value', () => {
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
});

test('throws error when CC API fails and ignoreErrorOnMissingContext is false', async () => {
test('throws error when CC API fails and ignoreFailedLookup is false', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN/THEN
await expect(
Expand All @@ -334,7 +380,7 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: false,
ignoreFailedLookup: false,
dummyValue: [
{
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
Expand All @@ -357,14 +403,14 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
}),
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
});

test('throws error when CC API fails and dummyValue is not an array', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN/THEN
await expect(
Expand All @@ -374,18 +420,18 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
dummyValue: {
DBInstanceArn: 'arn:aws:rds:us-east-1:123456789012:db:dummy-instance',
StorageEncrypted: 'true',
},
}),
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
).rejects.toThrow('dummyValue must be an array with at least one object. Failed to get dummy object for type AWS::RDS::DBInstance.');
});

test('throws error when CC API fails and dummyValue is an empty array', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN/THEN
await expect(
Expand All @@ -395,15 +441,15 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
dummyValue: [],
}),
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
).rejects.toThrow('dummyValue must be an array with at least one object. Failed to get dummy object for type AWS::RDS::DBInstance.');
});

test('throws error when CC API fails and dummyValue is not an object array', async () => {
// GIVEN
mockCloudControlClient.on(GetResourceCommand).rejects('No data found');
mockCloudControlClient.on(GetResourceCommand).rejects(createResourceNotFoundException());

// WHEN/THEN
await expect(
Expand All @@ -413,12 +459,28 @@ describe('dummy value', () => {
typeName: 'AWS::RDS::DBInstance',
exactIdentifier: 'bad-identifier',
propertiesToReturn: ['DBInstanceArn', 'StorageEncrypted'],
ignoreErrorOnMissingContext: true,
ignoreFailedLookup: true,
dummyValue: [
'not an object',
],
}),
).rejects.toThrow('Encountered CC API error while getting resource bad-identifier.');
).rejects.toThrow('dummyValue must be an array with at least one object. Failed to get dummy object for type AWS::RDS::DBInstance.');
});
});
/* eslint-enable */

function createResourceNotFoundException() {
return new ResourceNotFoundException({
$metadata: {},
message: 'Resource not found',
Message: 'Resource not found'
});
}

function createOtherError() {
return new InvalidRequestException({
$metadata: {},
message: 'Other error',
Message: 'Other error'
});
}
Loading