From 06e0f9d9e065ab8995f7db460b04873e32993042 Mon Sep 17 00:00:00 2001 From: Scott Schreckengaust Date: Mon, 21 Oct 2024 09:56:35 -0700 Subject: [PATCH] feat: add destroy and create options for generate physical name (#758) * feat: add destroy and create options for generate physical name FIXES: 734 --------- Signed-off-by: Scott Schreckengaust Signed-off-by: github-actions Co-authored-by: github-actions Co-authored-by: Alain Krok --- .../bedrock/data-sources/s3-data-source.ts | 7 ++++- src/common/helpers/utils.ts | 29 +++++++++++++++++-- test/common/helpers/utils.test.ts | 12 ++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/cdk-lib/bedrock/data-sources/s3-data-source.ts b/src/cdk-lib/bedrock/data-sources/s3-data-source.ts index 294054b49..f766b55fa 100644 --- a/src/cdk-lib/bedrock/data-sources/s3-data-source.ts +++ b/src/cdk-lib/bedrock/data-sources/s3-data-source.ts @@ -99,7 +99,12 @@ export class S3DataSource extends DataSourceNew { // Assign attributes this.knowledgeBase = props.knowledgeBase; this.dataSourceType = DataSourceType.S3; - this.dataSourceName = props.dataSourceName ?? generatePhysicalNameV2(this, 's3-ds', { maxLength: 40, lower: true, separator: '-' });; + + // Turns out chunking and parsing are not replace so pass + const chunkingStrategy = props.chunkingStrategy; + const parsingStrategy = props.parsingStrategy; + const theseAreNotReplacable = { chunkingStrategy, parsingStrategy }; + this.dataSourceName = props.dataSourceName ?? generatePhysicalNameV2(this, 's3-ds', { maxLength: 40, lower: true, separator: '-', destroyCreate: theseAreNotReplacable });; this.bucket = props.bucket; this.kmsKey = props.kmsKey; diff --git a/src/common/helpers/utils.ts b/src/common/helpers/utils.ts index c551057a4..189a04bf0 100644 --- a/src/common/helpers/utils.ts +++ b/src/common/helpers/utils.ts @@ -10,6 +10,7 @@ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * and limitations under the License. */ +import { createHash } from 'crypto'; import * as cdk from 'aws-cdk-lib'; import { IConstruct } from 'constructs'; import { CfnNagSuppressRule } from '../../patterns/gen-ai/aws-rag-appsync-stepfn-kendra/types'; @@ -76,6 +77,12 @@ export interface GeneratePhysicalNameV2Options extends cdk.UniqueResourceNameOpt * @default false */ lower?: boolean; + + /** + * This object is hashed for uniqueness and can force a destroy instead of a replace. + * @default: undefined + */ + destroyCreate?: any; } /** * @internal This is an internal core function and should not be called directly by Solutions Constructs clients. @@ -102,20 +109,36 @@ export function generatePhysicalNameV2( */ options?: GeneratePhysicalNameV2Options, ): string { + function objectToHash(obj: any): string { + // Nothing to hash if undefined + if (obj === undefined) { return ''; } + + // Convert the object to a JSON string + const jsonString = JSON.stringify(obj); + + // Create a SHA-256 hash + const hash = createHash('sha256'); + + // Update the hash with the JSON string and get the digest in hexadecimal format + // Shorten it (modeled after seven characters like git commit hash shortening) + return hash.update(jsonString).digest('hex').slice(0, 7); + } const { maxLength = 256, lower = false, separator = '', allowedSpecialCharacters = undefined, + destroyCreate = undefined, } = options ?? {}; - if (maxLength < (prefix + separator).length) { + const hash = objectToHash(destroyCreate); + if (maxLength < (prefix + hash + separator).length) { throw new Error('The prefix is longer than the maximum length.'); } const uniqueName = cdk.Names.uniqueResourceName( scope, - { maxLength: maxLength - (prefix + separator).length, separator, allowedSpecialCharacters }, + { maxLength: maxLength - (prefix + hash + separator).length, separator, allowedSpecialCharacters }, ); - const name = `${prefix}${separator}${uniqueName}`; + const name = `${prefix}${hash}${separator}${uniqueName}`; if (name.length > maxLength) { throw new Error(`The generated name is longer than the maximum length of ${maxLength}`); } diff --git a/test/common/helpers/utils.test.ts b/test/common/helpers/utils.test.ts index de602aeb3..d1e033ada 100644 --- a/test/common/helpers/utils.test.ts +++ b/test/common/helpers/utils.test.ts @@ -281,6 +281,18 @@ describe('generatePhysicalNameV2', () => { }).toThrow(new RegExp('^The generated name is longer than the maximum length of')); }); + test('hash for a more unique name', () => { + const hashedName = generatePhysicalNameV2( + testResourceB, + 'test', + { + destroyCreate: { one: 'XXX', two: true, three: undefined }, + }); + + expect(hashedName).not.toMatch(new RegExp('^test' + testResourceB.stack.stackName)); + expect(hashedName).toMatch(new RegExp('^test' + '[0-9a-f]{7}' + testResourceB.stack.stackName)); + expect(hashedName).toEqual('test0221ffeTestStackAB27595CD3'); + }); describe('kendra general utils', () => { describe('addCfnSuppressRules', () => {