Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudfront): parameterise CloudFront Functions via find/replace #30492

Open
1 of 2 tasks
SydneyUni-Jim opened this issue Jun 8, 2024 · 4 comments · May be fixed by #30621
Open
1 of 2 tasks

feat(cloudfront): parameterise CloudFront Functions via find/replace #30492

SydneyUni-Jim opened this issue Jun 8, 2024 · 4 comments · May be fixed by #30621
Labels
@aws-cdk/aws-cloudfront Related to Amazon CloudFront effort/small Small work item – less than a day of effort feature-request A feature should be added or improved. p3

Comments

@SydneyUni-Jim
Copy link
Contributor

SydneyUni-Jim commented Jun 8, 2024

Describe the feature

CloudFront Functions (not Lambda@Edge) don't provide a way to parameterise the code, in the way environment variables can with Lambda@Edge, for example. Allow code to be replaced with values from the CDK app during synth.

Use Case

  • Inject the Key Value Store (KVS) ID into the function when the KVS is deployed as part of the CDK app.
  • Change the function for different environments, dev vs prod for example.

Proposed Solution

In aws-cdk-lib/aws-cloudfront/lib/function.ts:

  • Add these interfaces.
    export interface FindReplace {
      readonly find: string
      readonly replace: string | cdk.Token
      readonly all?: boolean
    }
    
    export interface FunctionCodeOptions {
      readonly findReplace?: FindReplace[]
    }
  • Add a protected findReplace(code) method to the abstract class FunctionCode, which returns the code after doing the find/replaces of the options.
    • If replace is a Token, the code is replace with the result of cdk.Token.asString(replace)
    • If all is truthy, all occurrences are replaced. If all is falsey only the first occurrence is replaced.
  • Change the render methods of InlineCode and FileCode to call findReplace before returning their respective values.
  • Change FileCodeOptions to extend FunctionCodeOptions.
  • Change InlineCode's constructor to take a second options parameter, being a FunctionCodeOptions object.
    • The options parameter is a private property.
  • Change FunctionCode.fromInline(code) to FunctionCode.fromInline(code, options).
    • The new second parameter is optional, defaulting to an empty object.
    • Pass options to InlineCode's constructor.

This is a naïve find (all) and replace. It does not attempt to parse the function code to find appropriate tokens. It does not attempt to replace code meaningfully. There is no guarantee that syntactically valid code remains syntactically valid after the find/replace.

Proof of concept

This is the code I'm currently using to get the KVS ID into a CloudFront Function. The proposed solution is a generalisation of this.

interface FileCodeWithKvsIdSubstitutionOptions extends cloudfront.FileCodeOptions {
  readonly keyValueStore: cloudfront.IKeyValueStore
}

class FileCodeWithKvsIdSubstitution extends cloudfront.FunctionCode {

  constructor(private options: FileCodeWithKvsIdSubstitutionOptions) {
    super()
  }

  public render(): string {
    return (
      fs.readFileSync(this.options.filePath, { encoding: 'utf-8' })
      .replace('«KVS_ID»', cdk.Token.asString(this.options.keyValueStore.keyValueStoreId))
    )
  }

}

Example of using the proposed solution

Given this code for a CloudFront Function.

import cf from 'cloudfront'

// __IS_PROD__ anywhere in the code is replaced with true or false during CDK synth 

const kvs = cf.kvs('__KVS_ID__')  // __KVS_ID__ is replaced during CDK synth

async function handler(event) {
  console.log('is production: __IS_PROD__')
  // …
  if (__IS_PROD__) {
   // …
  }
  // …
  const v = __IS_PROD__ ? x : y
  // …
}

The use of double underscore delimiters is not part of the proposed solution. It would up to the CDK builder to create code that can be unambiguously found. If any delimiters are used, those delimiters would be part of the value for find.

__KVS_ID__ and __IS_PROD__ could be replaced in the code with this.

new cloudfront.Function(this, 'Function', {
  code: FunctionCode.fromFile({
    filePath: 'cloudfront-function.js',
    findReplace: [
      { find: '__KVS_ID__',  replace: keyValueStore.keyValueStoreId },
      { find: '__IS_PROD__', replace: `${ !!isProd }`, all: true },            // replace has to be string
    ],
  }),
})

If the KVS id is 61736184-1ad9-4b1b-9466-6ca1fb629685 and isProd is false, the code that would be sent to CloudFront would be this.

import cf from 'cloudfront'

// false anywhere in the code is replaced with true or false during CDK synth 

const kvs = cf.kvs('61736184-1ad9-4b1b-9466-6ca1fb629685')  // 61736184-1ad9-4b1b-9466-6ca1fb629685 is replaced during CDK synth

async function handler(event) {
  console.log('is production: false')
  // …
  if (false) {
   // …
  }
  // …
  const v = false ? x : y
  // …
}

Other Information

The Key Value Store can be used for parameterisation. But there's costs associated with that. And there's the conundrum of how to get the KVS ID into the CloudFront Function to begin with.

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

CDK version used

2.145.0

Environment details (OS name and version, etc.)

NodeJS/iron

@SydneyUni-Jim SydneyUni-Jim added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Jun 8, 2024
@github-actions github-actions bot added the @aws-cdk/aws-cloudfront Related to Amazon CloudFront label Jun 8, 2024
@SydneyUni-Jim
Copy link
Contributor Author

SydneyUni-Jim commented Jun 8, 2024

It would be great if find could be a JavaScript regular expression or a string. And it would be great if replace could be any, with findReplace converting anything not a string into a string. But I don't know if that's possible with JSII.

@SydneyUni-Jim
Copy link
Contributor Author

Possible implementation of findReplace.

protected findReplace(code: string, findReplaceArr?: FindReplace[]): string {
  if (!findReplaceArr?.length) return code
  function reducer(acc: string, opt: FindReplace) {
    const r: string = typeof opt.replace === 'string' ? opt.replace : cdk.Token.asString(opt.replace)
    return opt.all ? acc.replaceAll(opt.find, r) : acc.replace(opt.find, r)
  }
  return findReplaceArr.reduce(reducer, code)
}

@khushail khushail added investigating This issue is being investigated and/or work is in progress to resolve the issue. and removed needs-triage This issue or PR still needs to be triaged. labels Jun 12, 2024
@khushail khushail self-assigned this Jun 12, 2024
@khushail
Copy link
Contributor

Hi @SydneyUni-Jim , thanks for reaching out. I see this readme doc talks about Key-value store ,quite similar to what you have proposed in solution. I might not fully comprehend about the solution but we are open to suggestions and please feel free to submit a PR !

@khushail khushail added p3 effort/small Small work item – less than a day of effort and removed investigating This issue is being investigated and/or work is in progress to resolve the issue. labels Jun 12, 2024
@khushail khushail removed their assignment Jun 12, 2024
@SydneyUni-Jim
Copy link
Contributor Author

HI @khushail. Thanks for pointing out the docs. Unfortunately that only associates the Key Value Store with the function. It doesn't make the store's id available to the function's code. I will work on a PR.

@SydneyUni-Jim SydneyUni-Jim changed the title feat(aws-cloudfront): Parameterise CloudFront Functions via find/replace feat(cloudfront): Parameterise CloudFront Functions via find/replace Jun 22, 2024
@SydneyUni-Jim SydneyUni-Jim changed the title feat(cloudfront): Parameterise CloudFront Functions via find/replace feat(cloudfront): parameterise CloudFront Functions via find/replace Jun 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-cloudfront Related to Amazon CloudFront effort/small Small work item – less than a day of effort feature-request A feature should be added or improved. p3
Projects
None yet
2 participants