Skip to content

Commit

Permalink
Merge branch 'master' into huijbers/collapse-action-roles
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored May 24, 2022
2 parents 6585236 + de027e2 commit cd1f7ec
Show file tree
Hide file tree
Showing 42 changed files with 921 additions and 393 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ const integ = new IntegTest(app, 'Destinations', {
testCases: [stack],
});

integ.assert.invokeFunction({
integ.assertions.invokeFunction({
functionName: stack.fn.functionName,
invocationType: InvocationType.EVENT,
payload: JSON.stringify({ status: 'OK' }),
});

const message = integ.assert.awsApiCall('SQS', 'receiveMessage', {
const message = integ.assertions.awsApiCall('SQS', 'receiveMessage', {
QueueUrl: stack.queue.queueUrl,
WaitTimeSeconds: 20,
});
Expand Down
8 changes: 7 additions & 1 deletion packages/@aws-cdk/aws-lambda/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,12 @@ export class Function extends FunctionBase {
}
}

if (props.description && !Token.isUnresolved(props.description)) {
if (props.description.length > 256) {
throw new Error(`Function description can not be longer than 256 characters but has ${props.description.length} characters.`);
}
}

const managedPolicies = new Array<iam.IManagedPolicy>();

// the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Expand Down Expand Up @@ -1115,7 +1121,7 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett
for (const subnetId of subnetIds) {
if (publicSubnetIds.has(subnetId) && !allowPublicSubnet) {
throw new Error('Lambda Functions in a public subnet can NOT access the internet. ' +
'If you are aware of this limitation and would still like to place the function int a public subnet, set `allowPublicSubnet` to true');
'If you are aware of this limitation and would still like to place the function in a public subnet, set `allowPublicSubnet` to true');
}
}

Expand Down
25 changes: 25 additions & 0 deletions packages/@aws-cdk/aws-lambda/test/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,31 @@ describe('function', () => {
}).not.toThrow();
});

test('Error when function description is longer than 256 chars', () => {
const stack = new cdk.Stack();
expect(() => new lambda.Function(stack, 'MyFunction', {
code: lambda.Code.fromInline('foo'),
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'index.handler',
description: 'a'.repeat(257),
})).toThrow(/Function description can not be longer than 256 characters/);
});

test('No error when function name is Tokenized and Unresolved', () => {
const stack = new cdk.Stack();
expect(() => {
const realFunctionDescription = 'a'.repeat(257);
const tokenizedFunctionDescription = cdk.Token.asString(new cdk.Intrinsic(realFunctionDescription));

new lambda.Function(stack, 'foo', {
code: new lambda.InlineCode('foo'),
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_14_X,
description: tokenizedFunctionDescription,
});
}).not.toThrow();
});

describe('FunctionUrl', () => {
test('addFunctionUrl creates a function url with default options', () => {
// GIVEN
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-lambda/test/integ.bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ const integ = new IntegTest(app, 'Bundling', {
stackUpdateWorkflow: false,
});

const invoke = integ.assert.invokeFunction({
const invoke = integ.assertions.invokeFunction({
functionName: stack.functionName,
});
invoke.assert(ExpectedResult.objectLike({
invoke.expect(ExpectedResult.objectLike({
Payload: '200',
}));
app.synth();
20 changes: 10 additions & 10 deletions packages/@aws-cdk/aws-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,15 +522,15 @@ by deploying with CDK version `1.126.0` or later **before** switching this value

```ts
const bucket = new s3.Bucket(this, 'MyBucket', {
transferAcceleration: true,
transferAcceleration: true,
});
```

To access the bucket that is enabled for Transfer Acceleration, you must use a special endpoint. The URL can be generated using method `transferAccelerationUrlForObject`:

```ts
const bucket = new s3.Bucket(this, 'MyBucket', {
transferAcceleration: true,
transferAcceleration: true,
});
bucket.transferAccelerationUrlForObject('objectname');
```
Expand All @@ -540,14 +540,14 @@ bucket.transferAccelerationUrlForObject('objectname');
[Intelligent Tiering](https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering.html) can be configured to automatically move files to glacier:

```ts
new s3.Bucket(this, 'MyBucket', {
intelligentTieringConfigurations: [{
name: 'foo',
prefix: 'folder/name',
archiveAccessTierTime: cdk.Duration.days(90),
deepArchiveAccessTierTime: cdk.Duration.days(180),
tags: [{key: 'tagname', value: 'tagvalue'}]
}],
new s3.Bucket(this, 'MyBucket', {
intelligentTieringConfigurations: [{
name: 'foo',
prefix: 'folder/name',
archiveAccessTierTime: cdk.Duration.days(90),
deepArchiveAccessTierTime: cdk.Duration.days(180),
tags: [{key: 'tagname', value: 'tagvalue'}]
}],
});

```
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-servicecatalog/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.d.ts
tsconfig.json
node_modules
product-stack-snapshots
*.generated.ts
dist
.jsii
Expand Down
100 changes: 100 additions & 0 deletions packages/@aws-cdk/aws-servicecatalog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enables organizations to create and manage catalogs of products for their end us
- [Product](#product)
- [Creating a product from a local asset](#creating-a-product-from-local-asset)
- [Creating a product from a stack](#creating-a-product-from-a-stack)
- [Creating a Product from a stack with a history of previous versions](#creating-a-product-from-a-stack-with-a-history-of-all-previous-versions)
- [Adding a product to a portfolio](#adding-a-product-to-a-portfolio)
- [TagOptions](#tag-options)
- [Constraints](#constraints)
Expand Down Expand Up @@ -184,6 +185,105 @@ const product = new servicecatalog.CloudFormationProduct(this, 'Product', {
});
```

### Creating a Product from a stack with a history of previous versions

The default behavior of Service Catalog is to overwrite each product version upon deployment.
This applies to Product Stacks as well, where only the latest changes to your Product Stack will
be deployed.
To keep a history of the revisions of a ProductStack available in Service Catalog,
you would need to define a ProductStack for each historical copy.

You can instead create a `ProductStackHistory` to maintain snapshots of all previous versions.
The `ProductStackHistory` can be created by passing the base `productStack`,
a `currentVersionName` for your current version and a `locked` boolean.
The `locked` boolean which when set to true will prevent your `currentVersionName`
from being overwritten when there is an existing snapshot for that version.

```ts
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';

class S3BucketProduct extends servicecatalog.ProductStack {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);

new s3.Bucket(this, 'BucketProduct');
}
}

const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
currentVersionName: 'v1',
currentVersionLocked: true
});
```

We can deploy the current version `v1` by using `productStackHistory.currentVersion()`

```ts
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';

class S3BucketProduct extends servicecatalog.ProductStack {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);

new s3.Bucket(this, 'BucketProductV2');
}
}

const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
currentVersionName: 'v2',
currentVersionLocked: true
});

const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', {
productName: "My Product",
owner: "Product Owner",
productVersions: [
productStackHistory.currentVersion(),
],
});
```

Using `ProductStackHistory` all deployed templates for the ProductStack will be written to disk,
so that they will still be available in the future as the definition of the `ProductStack` subclass changes over time.
**It is very important** that you commit these old versions to source control as these versions
determine whether a version has already been deployed and can also be deployed themselves.

After using `ProductStackHistory` to deploy version `v1` of your `ProductStack`, we
make changes to the `ProductStack` and update the `currentVersionName` to `v2`.
We still want our `v1` version to still be deployed, so we reference it by calling `productStackHistory.versionFromSnapshot('v1')`.

```ts
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';

class S3BucketProduct extends servicecatalog.ProductStack {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);

new s3.Bucket(this, 'BucketProductV2');
}
}

const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
currentVersionName: 'v2',
currentVersionLocked: true
});

const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', {
productName: "My Product",
owner: "Product Owner",
productVersions: [
productStackHistory.currentVersion(),
productStackHistory.versionFromSnapshot('v1')
],
});
```

### Adding a product to a portfolio

You add products to a portfolio to organize and distribute your catalog at scale. Adding a product to a portfolio creates an association,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ class CloudFormationAssetTemplate extends CloudFormationTemplate {
*/
class CloudFormationProductStackTemplate extends CloudFormationTemplate {
/**
* @param stack A service catalog product stack.
*/
* @param productStack A service catalog product stack.
*/
constructor(public readonly productStack: ProductStack) {
super();
}
Expand Down
7 changes: 6 additions & 1 deletion packages/@aws-cdk/aws-servicecatalog/lib/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Constant for the default directory to store ProductStack snapshots.
*/
export const DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY = 'product-stack-snapshots';

/**
* The language code.
* Used for error and logging messages for end users.
Expand All @@ -18,4 +23,4 @@ export enum MessageLanguage {
* Chinese
*/
ZH = 'zh'
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-servicecatalog/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './cloudformation-template';
export * from './portfolio';
export * from './product';
export * from './product-stack';
export * from './product-stack-history';
export * from './tag-options';

// AWS::ServiceCatalog CloudFormation Resources:
Expand Down
118 changes: 118 additions & 0 deletions packages/@aws-cdk/aws-servicecatalog/lib/product-stack-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as fs from 'fs';
import * as path from 'path';
import { Names } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CloudFormationTemplate } from './cloudformation-template';
import { DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY } from './common';
import { CloudFormationProductVersion } from './product';
import { ProductStack } from './product-stack';

// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Properties for a ProductStackHistory.
*/
export interface ProductStackHistoryProps {
/**
* The ProductStack whose history will be retained as a snapshot
*/
readonly productStack: ProductStack;

/**
* The current version name of the ProductStack.
*/
readonly currentVersionName: string;

/**
* If this is set to true, the ProductStack will not be overwritten if a snapshot is found for the currentVersionName.
*/
readonly currentVersionLocked: boolean

/**
* The description of the product version
* @default - No description provided
*/
readonly description?: string;

/**
* Whether the specified product template will be validated by CloudFormation.
* If turned off, an invalid template configuration can be stored.
* @default true
*/
readonly validateTemplate?: boolean;

/**
* The directory where template snapshots will be stored
* @default 'product-stack-snapshots'
*/
readonly directory?: string
}

/**
* A Construct that contains a Service Catalog product stack with its previous deployments maintained.
*/
export class ProductStackHistory extends CoreConstruct {
private readonly props: ProductStackHistoryProps
constructor(scope: Construct, id: string, props: ProductStackHistoryProps) {
super(scope, id);
props.productStack._setParentProductStackHistory(this);
this.props = props;
}

/**
* Retains product stack template as a snapshot when deployed and
* retrieves a CloudFormationProductVersion for the current product version.
*/
public currentVersion() : CloudFormationProductVersion {
return {
cloudFormationTemplate: CloudFormationTemplate.fromProductStack(this.props.productStack),
productVersionName: this.props.currentVersionName,
description: this.props.description,
};
}

/**
* Retrieves a CloudFormationProductVersion from a previously deployed productVersionName.
*/
public versionFromSnapshot(productVersionName: string) : CloudFormationProductVersion {
const productStackSnapshotDirectory = this.props.directory || DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY;
const templateFileKey = `${Names.uniqueId(this)}.${this.props.productStack.artifactId}.${productVersionName}.product.template.json`;
const templateFilePath = path.join(productStackSnapshotDirectory, templateFileKey);
if (!fs.existsSync(templateFilePath)) {
throw new Error(`Template ${templateFileKey} cannot be found in ${productStackSnapshotDirectory}`);
}
return {
cloudFormationTemplate: CloudFormationTemplate.fromAsset(templateFilePath),
productVersionName: productVersionName,
description: this.props.description,
};
}

/**
* Writes current template generated from Product Stack to a snapshot directory.
*
* @internal
*/
public _writeTemplateToSnapshot(cfn: string) {
const productStackSnapshotDirectory = this.props.directory || DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY;
if (!fs.existsSync(productStackSnapshotDirectory)) {
fs.mkdirSync(productStackSnapshotDirectory);
}
const templateFileKey = `${Names.uniqueId(this)}.${this.props.productStack.artifactId}.${this.props.currentVersionName}.product.template.json`;
const templateFilePath = path.join(productStackSnapshotDirectory, templateFileKey);
if (fs.existsSync(templateFilePath)) {
const previousCfn = fs.readFileSync(templateFilePath).toString();
if (previousCfn !== cfn && this.props.currentVersionLocked) {
throw new Error(`Template has changed for ProductStack Version ${this.props.currentVersionName}.
${this.props.currentVersionName} already exist in ${productStackSnapshotDirectory}.
Since locked has been set to ${this.props.currentVersionLocked},
Either update the currentVersionName to deploy a new version or deploy the existing ProductStack snapshot.
If ${this.props.currentVersionName} was unintentionally synthesized and not deployed,
delete the corresponding version from ${productStackSnapshotDirectory} and redeploy.`);
}
}
fs.writeFileSync(templateFilePath, cfn);
}
}
Loading

0 comments on commit cd1f7ec

Please sign in to comment.