Skip to content

Commit

Permalink
Merge branch 'master' into kimxogus/aws-ec2/s3-interface-endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
kimxogus authored Sep 15, 2021
2 parents b952a2c + 23d9b6a commit bbd5600
Show file tree
Hide file tree
Showing 75 changed files with 6,062 additions and 940 deletions.
41 changes: 41 additions & 0 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,47 @@ assert.hasResourceProperties('Foo::Bar', Match.objectLike({
}});
```
### Serialized JSON
Often, we find that some CloudFormation Resource types declare properties as a string,
but actually expect JSON serialized as a string.
For example, the [`BuildSpec` property of `AWS::CodeBuild::Project`][Pipeline BuildSpec],
the [`Definition` property of `AWS::StepFunctions::StateMachine`][StateMachine Definition],
to name a couple.
The `Match.serializedJson()` matcher allows deep matching within a stringified JSON.
```ts
// Given a template -
// {
// "Resources": {
// "MyBar": {
// "Type": "Foo::Bar",
// "Properties": {
// "Baz": "{ \"Fred\": [\"Waldo\", \"Willow\"] }"
// }
// }
// }
// }

// The following will NOT throw an assertion error
assert.hasResourceProperties('Foo::Bar', {
Baz: Match.serializedJson({
Fred: Match.arrayWith(["Waldo"]),
}),
});

// The following will throw an assertion error
assert.hasResourceProperties('Foo::Bar', {
Baz: Match.serializedJson({
Fred: ["Waldo", "Johnny"],
}),
});
```
[Pipeline BuildSpec]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-source.html#cfn-codebuild-project-source-buildspec
[StateMachine Definition]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#cfn-stepfunctions-statemachine-definition
## Capturing Values
This matcher APIs documented above allow capturing values in the matching entry
Expand Down
41 changes: 41 additions & 0 deletions packages/@aws-cdk/assertions/lib/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export abstract class Match {
return new NotMatch('not', pattern);
}

/**
* Matches any string-encoded JSON and applies the specified pattern after parsing it.
* @param pattern the pattern to match after parsing the encoded JSON.
*/
public static serializedJson(pattern: any): Matcher {
return new SerializedJson('serializedJson', pattern);
}

/**
* Matches any non-null value at the target.
*/
Expand Down Expand Up @@ -265,6 +273,39 @@ class ObjectMatch extends Matcher {
}
}

class SerializedJson extends Matcher {
constructor(
public readonly name: string,
private readonly pattern: any,
) {
super();
};

public test(actual: any): MatchResult {
const result = new MatchResult(actual);
if (getType(actual) !== 'string') {
result.push(this, [], `Expected JSON as a string but found ${getType(actual)}`);
return result;
}
let parsed;
try {
parsed = JSON.parse(actual);
} catch (err) {
if (err instanceof SyntaxError) {
result.push(this, [], `Invalid JSON string: ${actual}`);
return result;
} else {
throw err;
}
}

const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern);
const innerResult = matcher.test(parsed);
result.compose(`(${this.name})`, innerResult);
return result;
}
}

class NotMatch extends Matcher {
constructor(
public readonly name: string,
Expand Down
51 changes: 48 additions & 3 deletions packages/@aws-cdk/assertions/test/match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,63 @@ describe('Matchers', () => {
expectFailure(matcher, {}, ['Missing key at /foo']);
});
});

describe('serializedJson()', () => {
let matcher: Matcher;

test('all types', () => {
matcher = Match.serializedJson({ Foo: 'Bar', Baz: 3, Boo: true, Fred: [1, 2] });
expectPass(matcher, '{ "Foo": "Bar", "Baz": 3, "Boo": true, "Fred": [1, 2] }');
});

test('simple match', () => {
matcher = Match.serializedJson({ Foo: 'Bar' });
expectPass(matcher, '{ "Foo": "Bar" }');

expectFailure(matcher, '{ "Foo": "Baz" }', ['Expected Bar but received Baz at (serializedJson)/Foo']);
expectFailure(matcher, '{ "Foo": 4 }', ['Expected type string but received number at (serializedJson)/Foo']);
expectFailure(matcher, '{ "Bar": "Baz" }', [
'Unexpected key at (serializedJson)/Bar',
'Missing key at (serializedJson)/Foo',
]);
});

test('nested matcher', () => {
matcher = Match.serializedJson(Match.objectLike({
Foo: Match.arrayWith(['Bar']),
}));

expectPass(matcher, '{ "Foo": ["Bar"] }');
expectPass(matcher, '{ "Foo": ["Bar", "Baz"] }');
expectPass(matcher, '{ "Foo": ["Bar", "Baz"], "Fred": "Waldo" }');

expectFailure(matcher, '{ "Foo": ["Baz"] }', ['Missing element [Bar] at pattern index 0 at (serializedJson)/Foo']);
expectFailure(matcher, '{ "Bar": ["Baz"] }', ['Missing key at (serializedJson)/Foo']);
});

test('invalid json string', () => {
matcher = Match.serializedJson({ Foo: 'Bar' });

expectFailure(matcher, '{ "Foo"', [/invalid JSON string/i]);
});
});
});

function expectPass(matcher: Matcher, target: any): void {
expect(matcher.test(target).hasFailed()).toEqual(false);
const result = matcher.test(target);
if (result.hasFailed()) {
fail(result.toHumanStrings()); // eslint-disable-line jest/no-jasmine-globals
}
}

function expectFailure(matcher: Matcher, target: any, expected: (string | RegExp)[] = []): void {
const result = matcher.test(target);
expect(result.failCount).toBeGreaterThan(0);
const actual = result.toHumanStrings();
if (expected.length > 0) {
expect(actual.length).toEqual(expected.length);
if (expected.length > 0 && actual.length !== expected.length) {
// only do this if the lengths are different, so as to display a nice failure message.
// otherwise need to use `toMatch()` to support RegExp
expect(actual).toEqual(expected);
}
for (let i = 0; i < expected.length; i++) {
const e = expected[i];
Expand Down
17 changes: 16 additions & 1 deletion packages/@aws-cdk/aws-batch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ For more information on **AWS Batch** visit the [AWS Docs for Batch](https://doc

## Compute Environment

At the core of AWS Batch is the compute environment. All batch jobs are processed within a compute environment, which uses resource like OnDemand or Spot EC2 instances.
At the core of AWS Batch is the compute environment. All batch jobs are processed within a compute environment, which uses resource like OnDemand/Spot EC2 instances or Fargate.

In **MANAGED** mode, AWS will handle the provisioning of compute resources to accommodate the demand. Otherwise, in **UNMANAGED** mode, you will need to manage the provisioning of those resources.

Expand Down Expand Up @@ -74,6 +74,21 @@ const spotEnvironment = new batch.ComputeEnvironment(stack, 'MySpotEnvironment',
});
```

### Fargate Compute Environment

It is possible to have AWS Batch submit jobs to be run on Fargate compute resources. Below is an example of how this can be done:

```ts
const vpc = new ec2.Vpc(this, 'VPC');

const fargateSpotEnvironment = new batch.ComputeEnvironment(stack, 'MyFargateEnvironment', {
computeResources: {
type: batch.ComputeResourceType.FARGATE_SPOT,
vpc,
},
});
```

### Understanding Progressive Allocation Strategies

AWS Batch uses an [allocation strategy](https://docs.aws.amazon.com/batch/latest/userguide/allocation-strategies.html) to determine what compute resource will efficiently handle incoming job requests. By default, **BEST_FIT** will pick an available compute instance based on vCPU requirements. If none exist, the job will wait until resources become available. However, with this strategy, you may have jobs waiting in the queue unnecessarily despite having more powerful instances available. Below is an example of how that situation might look like:
Expand Down
Loading

0 comments on commit bbd5600

Please sign in to comment.