-
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
Best practices for building company default constructs #3235
Comments
A few thoughts/patterns/ideas (definitely not to be taken as guidance): InheritanceYou could technically inherit from export class MyCompanyFunction extends lambda.Function {
constructor(scope: Construct, id: string, props: MyCompanyFunctionProps) {
// you might need a scope to create constructs before the "super" call so
// you can do something like this, which will create an "inner" scope for you:
const inner = new Construct(scope, id + '+inner');
const specialRole = new iam.Role(inner, 'Role', ...);
super(scope, id, { role, ...props });
// now, "this" is a lambda.Function
this.node.applyAspect(new Tag('foo', 'bar'));
}
} Base StackWe are starting to see value in vending a common base stack construct within the team/company to offer access to shared resources such as a shared VPC, a domain name, etc. It is a also a good place to override the standard behavior of how various stack-level attributes are implemented. For example, you could override the |
Copy @NetaNir |
I can see the inheritance case.. some constructs might be too complex for that (multiple resources needing to be accessed in the stack) but for something like lambda that has potential... I'll have a play with the stack base for sure (we already overwrite |
I've stuck with a construct for now... my lambda is currently looking like: import * as cdk from "@aws-cdk/core";
// For... lambda!
import * as lambda from '@aws-cdk/aws-lambda';
// For cron
import events = require('@aws-cdk/aws-events');
import targets = require('@aws-cdk/aws-events-targets');
// For Secrets permissions
import { Role, PolicyStatement } from '@aws-cdk/aws-iam';
interface XXILamdaProps {
assetPath: string, // what are we deploying
environment?: object, // { TABLE: ..., FOO: 'bar'}
handler?: string,
description?: string,
timeout?: number, // seconds
secretAccess?: string,
role?: Role, // if we need to assume a specific role
cron?: events.CronOptions
}
export class Lambda extends cdk.Construct {
public readonly function: lambda.Function;
constructor(scope: cdk.Construct, id: string, props: XXILamdaProps) {
super(scope, id);
let env: any = props.environment ? props.environment : {};
env.DEPLOY_ENV = process.env.DEPLOY_ENV; // required for our stacks
let constructId = id + '+' + env.DEPLOY_ENV;
let role: any = props.role ? props.role : undefined;
let lam = new lambda.Function(scope, constructId, {
code: lambda.Code.asset(props.assetPath),
handler: props.handler ? props.handler : "index.handler",
runtime: lambda.Runtime.NODEJS_10_X,
environment: props.environment,
description: props.description,
role: role,
timeout: cdk.Duration.seconds(props.timeout ? props.timeout : 60)
});
if (props.secretAccess) {
// Access secrets
lam.addToRolePolicy(new PolicyStatement({
resources: ['*'], // TODO: lock down the supplied string
actions: ['secretsmanager:getSecretValue']
}));
}
if (props.cron && process.env.DEPLOY_ENV === 'production') {
// Add cloud watch cron job, but NOT on dev!
const rule = new events.Rule(this, constructId + '+Rule', {
schedule: events.Schedule.cron(props.cron)
});
rule.addTarget(
new targets.LambdaFunction(lam)
);
}
this.function = lam;
}
} Usage: const someLam = new XXX.Lambda(this, 'ANAME', {
assetPath: __dirname + '/lambda/foo',
environment: {
SOME: 'stuff'
},
secretAccess: 'path/to/secret',
cron: {
hour: '5',
minute: '0'
},
}).function; Not bad for:
|
I don't know if this is the appropriate place to put these, but I'm looking at this issue too. I have the following basic cases for far:
My issue is our developers are writing code in .net, javascript and python so we really need to expose these constructors/classes to all languages that CDK allows us to use. So before I go and learn java/type script, I'd like to know if that is the case with the above code. Question is constructors or inheritance? |
I've frequently seen the props param extended in examples the way that the initial poster did, including in the CDK documentation. But, I'm wondering if this is a preferred method compared to making an additional constructor parameter for the additional properties? I'm concerned about conflict with future props additions or that some special handling will happen on the props fields for some constructs? |
something i've been doing is creating factory functions to initialize constructs with common defaults. makes it easy to override properties as needed.
import _ from 'lodash'
export type scopePlus = cdk.App | cdk.Construct
export function createBucket({scope, id, bucketProps = {}, accessLogBucket}: {
scope: scopePlus,
id: string,
bucketProps?: BucketProps
accessLogBucket?: IBucket
}) {
bucketProps = _.defaults({}, bucketProps, {
encryption: BucketEncryption.KMS_MANAGED,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL
})
const bucket = new Bucket(scope, id, bucketProps)
if (!_.isUndefined(accessLogBucket)) {
let logFilePrefix = `${id}/`
addS3AccessLogs({srcBucket: bucket, destBucket: accessLogBucket, logFilePrefix})
}
return bucket
} |
In my current organization, we use Service Catalog to enforce company defaults and requirements around various aws services. We restrict end users IAM permissions, to be able to primarily operate only via Service Catalog. |
Another pattern that an Enterprise needs is an easy way to scale out the same resource. For example, provision multiple s3 buckets, provision n x ec2 instances. There should be a construct to be able to scale out/in resources. |
Duplicate aws/aws-cdk-rfcs#25 |
It has been a while now since your posted this @lapair How have things been working out for the last two years since you posted this @lapair ? Still using the way you described above? What is your learnings? |
IMHO I prefer the inheritance approach. It requires less maintenance and allows the most freedom. We also have a standard Aspect application that enforces all organization standards so users aren't constrained to just constructs that we have written.
|
Having built a few production stacks we are seeing patterns in the component parts we use, and want to enforce internal best practices: timeouts / env values / roles etc as well as keep actual stack logic as small/focused as possible. I was discussing this with @eladb on gitter...
I've been playing with something like:
lib/our-company/lambda.ts
(with alib/our-company/index.ts
so we can import all constructs with a simple single import):Then assuming
stacks/NAME/index.ts
:Other sorts of defaults we are thinking of:
Additionally we could build feature factory constructs... 'swagger file + lambda functions' = apigateway (I've basically used https://gist.github.com/abbottdev/17379763ebc14a5ecbf2a111ffbcdd86 - from #1461 - and mundged it to hide the implementation of parsing the swagger file).
Some of this will include differences we want between production and development environments - but the goal being standards and best practices (for our company) over all.
Feedback / thoughts would be most appreciated.
The text was updated successfully, but these errors were encountered: