-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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(aws-s3objectlambda): add L2 construct for S3 Object Lambda #15833
Conversation
cf82e8e
to
af83e6c
Compare
I wonder if the construct should implement |
b8b70b1
to
1cce38a
Compare
1cce38a
to
5504bc1
Compare
Thanks so much for submitting this pull request. I am marking this pull request as We use +1s to help prioritize our work, and are happy to revaluate this pull request based on community feedback. You can reach out to the cdk.dev community on Slack to solicit support for reprioritization. |
924c117
to
ebfd5f2
Compare
/** | ||
* An S3 Object Lambda for intercepting and transforming `GetObject` requests. | ||
*/ | ||
export class ObjectLambda extends CoreConstruct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@duarten thanks for the PR and sorry it took so long to review! The main comment I have is that I think this PR should introduce an AccessPoint
construct instead of ObjectLambda
since that is the name of the corresponding L1. Also instead of extending CoreConstruct
this should extend Resource
. I would recommend taking a look at the example here which has the base setup we would be looking for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, @corymhall! Isn't an AccessPoint
different than an ObjectLambda
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah and they are in different libraries, so you would use them like this.
import * as objectlambda from '@aws-cdk/aws-s3objectlambda';
import * as s3 from '@aws-cdk/aws-s3';
new objectlambda.AccessPoint();
new s3.AccessPoint();
In this case S3ObjectLambda
is the name of the library and AccessPoint
is the name of the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, makes sense! I'll update the PR asap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
5504bc1
to
f9175ac
Compare
Pull request has been modified.
f9175ac
to
37fafd3
Compare
37fafd3
to
6823642
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@duarten updates look good! Just some minor comments, and we need to add
- README documentation
- unit tests
/** | ||
* The name of the access point access point. | ||
*/ | ||
readonly accessPointName: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be optional and I think the doc should say The name of the s3objectlambda access point.
* | ||
* @default - No data. | ||
*/ | ||
readonly payload?: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is json data can we allow object input that we convert to string?
public virtualHostedUrlForObject(key?: string, options?: s3.VirtualHostedStyleUrlOptions): string { | ||
const domainName = options?.regional ?? true ? this.regionalDomainName : this.domainName; | ||
const prefix = `https://${domainName}`; | ||
if (typeof key !== 'string') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are just checking to see if key is provided you can do
if (!key) {}
public abstract readonly accessPointArn: string | ||
public abstract readonly accessPointCreationDate: string | ||
|
||
protected abstract readonly name: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected abstract readonly name: string; | |
public abstract readonly accessPointName: string; |
Let's just go ahead and make this public.
objectLambdaConfiguration: { | ||
allowedFeatures, | ||
cloudWatchMetricsEnabled: props.cloudWatchMetricsEnabled, | ||
supportingAccessPoint: supporting.getAtt('Arn').toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supportingAccessPoint: supporting.getAtt('Arn').toString(), | |
supportingAccessPoint: supporting.attrArn, |
/** | ||
* The Lambda function used to transform objects. | ||
*/ | ||
readonly fn: lambda.IFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readonly fn: lambda.IFunction | |
readonly handler: lambda.IFunction |
lets call this handler
instead.
], | ||
}, | ||
}); | ||
this.accessPoint.addDependsOn(supporting); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that this is required since an implicit dependency should be added by the GetAttr.
} | ||
|
||
/** Implement the {@link IAccessPoint.domainName} field. */ | ||
get domainName(): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should either be here or on the abstract class, you don't need them both places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, forgot to remove them.
Pull request has been modified.
Thanks for all the feedback! :) I've added the documentation, but I'm struggling a bit with the unit tests. Because now we're doing |
015d52a
to
10b3fc7
Compare
10b3fc7
to
2a517ce
Compare
|
||
constructor(scope: Construct, id: string, props: AccessPointProps) { | ||
super(scope, id, { | ||
physicalName: props.accessPointName ?? core.Lazy.string({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I wasn't thinking on this one. We don't need to have CDK generate a name so we can just do
physicalName: props.accessPointName,
* | ||
* @default - No data. | ||
*/ | ||
readonly payload?: Record<string, unknown>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do { [key: string]: any }
instead. I think that is the convention we use elsewhere.
@duarten it's looking really good! Just two minor comments.
Unit tests with ids can be tricky. If you need to validate a value that is only available after synth (you don't know what it will be when writing the test), I usually do something like this which will give you a failing test with the content of the template. expect(Template.from_stack(stack).toJSON()).toEqual({}); |
2a517ce
to
60ba8ae
Compare
Pull request has been modified.
08b63e8
to
8d57302
Compare
I added the unit test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! Just a couple last comments.
allowedFeatures.push('GetObject-Range'); | ||
} | ||
|
||
this.accessPoint = new CfnAccessPoint(this, 'LambdaAccessPoint', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const accessPoint = new CfnAccessPoint(this, 'Resource', {
The default resource should have the id of Resource
. Also can you just assign accessPoint
to a const
.
}, | ||
"awslint": { | ||
"exclude": [ | ||
"attribute-tag:@aws-cdk/aws-s3objectlambda.AccessPoint.accessPointName" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this I get a lint error.
regional: false, | ||
}), | ||
}); | ||
expect(Template.fromStack(stack).toJSON()).toEqual({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you update these tests to just use the assertions
methods like
Template.fromStack(stack).hasResourceProperties('AWS::S3ObjectLambda::AccessPoint', {
ObjectLambdaConfiguration: {},
});
Sorry if my comment about using expect(Template.fromStack(stack).toJSON())
was misleading. I was meaning that that method can be useful to find out logical ids of resources if the normal test output isn't giving you what you need.
Also to make things easier you might want to just create the resources in a beforeEach
and then have a separate test to assert individual things. Combined with using the assertions
methods each test can be smaller and validate a smaller piece of the template, which makes it easier to review.
let stack: cdk.Stack;
let bucket: s3.Bucket;
let handler: lambda.Function;
beforeEach(() => {
stack = new cdk.Stack();
bucket = new s3.Bucket(stack, 'MyBucket');
handler = new lambda.Function(stack, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.hello',
code: new lambda.InlineCode('def hello(): pass'),
});
});
test('can create a valid access point');
test('regional virtual hosted URL');
test('non-regional virtual hosted URL');
...etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
8d57302
to
006b146
Compare
Pull request has been modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@duarten looks great!
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
This PR adds an L2 construct for the S3 Object Lambda.
To avoid a circular dependency, the construct lives outside of the aws-s3 package.
Fixes #13675
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license