Skip to content

Commit 0a9fe08

Browse files
Merge pull request #1 from dchakrav-github/master
Adding Testing Support for Java based handlers
2 parents ba6bd66 + 48f9910 commit 0a9fe08

15 files changed

+1927
-6
lines changed

.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# macOS
2+
.DS_Store
3+
._*
4+
5+
# Maven outputs
6+
.classpath
7+
8+
# IntelliJ
9+
*.iml
10+
.idea
11+
out.java
12+
out/
13+
.settings
14+
.project
15+
16+
# auto-generated files
17+
target/
18+
19+
# our logs
20+
rpdk.log

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v2.4.0
4+
hooks:
5+
- id: check-case-conflict
6+
- id: detect-private-key
7+
- id: end-of-file-fixer
8+
- id: mixed-line-ending
9+
args:
10+
- --fix=lf
11+
- id: trailing-whitespace
12+
- id: pretty-format-json
13+
args:
14+
- --autofix
15+
- --indent=2
16+
- --no-sort-keys
17+
- id: check-merge-conflict

README.md

Lines changed: 260 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,267 @@
1-
## My Project
1+
## AWS CloudFormation Java Plugin Test Framework
22

3-
TODO: Fill this README out!
3+
This provides an easier foundation for testing handlers for CRUD along with integated support for KMS. Developers
4+
can easily write sequence of CRUD lifecycle test with expectations and will be tested. There is also a mock based
5+
test based for local unit testing.
46

5-
Be sure to:
7+
The framework leverages support for [Named Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) that allows
8+
developers to test using roles and credentials, to test the exact way in which they expect to work inside CloudFormation for their handlers. Here is the
9+
sample for now this can be used for injecting credentials using the role based profile specified.
10+
11+
**Sample AWS Named Profile Setup**
12+
13+
```
14+
~/.aws/credentials
15+
...
16+
[cfn-assume-role]
17+
aws_access_key_id=[YOUR_ACCESS_KEY_ID]
18+
aws_secret_access_key=[YOUR_SECRET_ACCESS_KEY]
19+
...
20+
21+
~/.aws/config
22+
[profile cfn-integration]
23+
role_arn = arn:aws:iam::<AWS_ACCOUNT_ID>:role/<ROLE_NAME>
24+
source_profile = cfn-assume-role
25+
```
26+
27+
**Using the named profile for testing**
28+
29+
<b>How to setup IAM managed policies, user and role credentials for above setup</b>
30+
31+
<ol>
32+
<li><u>Create a Managed Policy for the user</u>
33+
Here the credentials section has an user credentials that is provided with only sts:assumeRole
34+
permission. Here is the policy that is associated with cfn-assume-role user in the account.
35+
<pre>
36+
{
37+
"Version": "2012-10-17",
38+
"Statement": [
39+
{
40+
"Sid": "VisualEditor0",
41+
"Effect": "Allow",
42+
"Action": "sts:AssumeRole",
43+
"Resource": "*"
44+
}
45+
]
46+
}
47+
</pre>
48+
</li>
49+
<li><u>Create a Managed Policy for the services you are testing with</u>
50+
This is needed to test all integration for CRUD+L needed for logs. You can always narrow it down further.
51+
Recommended approach is to define the above policies as customer managed policies in IAM in the account and
52+
associate with the role and users as appropriate. This is an example policy to test, replace
53+
[INSERT_YOUR_SERVICE] with the service you are integrating with and need KMS integration. E.g.
54+
use _logs_ as the service name for integrating with CloudWatch Logs service.
55+
<pre>
56+
{
57+
"Version": "2012-10-17",
58+
"Statement": [
59+
{
60+
"Sid": "VisualEditor0",
61+
"Effect": "Allow",
62+
"Action": [
63+
"kms:*",
64+
"[INSERT_YOUR_SERVICE]:*"
65+
],
66+
"Resource": "*"
67+
}
68+
]
69+
}
70+
</pre>
71+
</li>
72+
<li><u>Create a user cfn-assume-role with Managed Policy create in Step (1)</u>
73+
Download the access_key, secret_key for this user and add it to the credentials file under
74+
cfn-assume-role
75+
</li>
76+
<li><u>Create cfn-integration role with the reference to the managed policy we created above.</u></li>
77+
<li><u>Update your poml.xml</u>
78+
Here is how to use this for unit testing. First add the dependency to you maven <u>pom.xml</u>
79+
<pre><code>
80+
&lt;!-- for sts support to assume role setup above --&gt;
81+
&lt;dependency&gt;
82+
&lt;groupId&gt;software.amazon.awssdk&lt;/groupId&gt;
83+
&lt;artifactId&gt;sts&lt;/artifactId&gt;
84+
&lt;version&gt;2.10.91&lt;/version&gt;
85+
&lt;scope&gt;test&lt;/scope&gt;
86+
&lt;/dependency&gt;
87+
&lt;!-- for kms key handling support --&gt;
88+
&lt;dependency&gt;
89+
&lt;groupId&gt;software.amazon.awssdk&lt;/groupId&gt;
90+
&lt;artifactId&gt;kms&lt;/artifactId&gt;
91+
&lt;version&gt;2.10.91&lt;/version&gt;
92+
&lt;/dependency&gt;
93+
&lt;dependency&gt;
94+
&lt;groupId&gt;software.amazon.cloudformation.test&lt;/groupId&gt;
95+
&lt;artifactId&gt;cloudformation-cli-java-plugin-testing-support&lt;/artifactId&gt;
96+
&lt;version&gt;1.0.0&lt;/version&gt;
97+
&lt;scope&gt;test&lt;/scope&gt;
98+
&lt;/dependency&gt;
99+
</code></pre>
100+
</li>
101+
</ol>
102+
103+
<b>How to use it?</b>
104+
<p>
105+
Sample code illustrating how to use this setup with KMS. To make scheduling the key for delete in case of abort to
106+
testing the key is aliased using the alias name [KEY_ALIAS](src/main/software/amazon/cloudformation/test/KMSKeyEnabledServiceIntegrationTestBase.java)
107+
The test when it runs to completion will automatically move the KMS key for delete. If test is rerun
108+
the KMS key will be made active again for the duration of he test run and disable and scheduled to be deleted.
109+
Regardless of how many times we run these tests there is only one key with the alias managed in the account.
110+
111+
To ensure that this test does not run for build environments like Travis etc. we enable is using system properties using
112+
{@link org.junit.jupiter.api.condition.EnabledIfSystemProperty}. To run the test with maven we would
113+
use
114+
115+
```sh
116+
mvn -Ddesktop=true test
117+
```
118+
119+
to run the test code shown below
120+
121+
```java
122+
123+
package software.amazon.logs.loggroup;
124+
125+
import org.junit.jupiter.api.*;
126+
import static org.assertj.core.api.Assertions.assertThat;
127+
128+
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
129+
import software.amazon.cloudformation.proxy.*;
130+
import software.amazon.cloudformation.test.*;
131+
132+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // we order the tests to follows C to U to D
133+
@ExtendWith(InjectProfileCredentials.class) // extend with profile based credentials
134+
@EnabledIfSystemProperty(named = "desktop", matches = "true")
135+
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // IMP PER_CLASS
136+
public class LifecycleTest extends KMSKeyEnabledServiceIntegrationTestBase {
137+
138+
//
139+
// At the annotation software.amazon.cloudformation.test.annotations.InjectSessionCredentials to the
140+
// constructor. This will inject the role's credentials
141+
//
142+
public LifecycleTest(@InjectSessionCredentials(profile = "cfn-integration") AwsSessionCredentials awsCredentials) {
143+
super(awsCredentials, ((apiCall, provided) -&gt; override));
144+
}
145+
...
146+
...
147+
@Order(300)
148+
@Test
149+
void addValidKMS() {
150+
final ResourceModel current = ResourceModel.builder().arn(model.getArn())
151+
.logGroupName(model.getLogGroupName()).retentionInDays(model.getRetentionInDays()).build();
152+
// Access a KMS key. The test ensures to only create one key and recycles despite any number of runs
153+
String kmsKeyId = getKmsKeyId();
154+
String kmsKeyArn = getKmsKeyArn();
155+
// Add your service to use KMS key
156+
addServiceAccess("logs", kmsKeyId);
157+
model.setKMSKeyArn(kmsKeyArn);
158+
ProgressEvent<ResourceModel, CallbackContext> event = new UpdateHandler()
159+
.handleRequest(getProxy(), createRequest(model, current), null, getLoggerProxy());
160+
assertThat(event.isSuccess()).isTrue();
161+
model = event.getResourceModel();
162+
}
163+
...
164+
...
165+
}
166+
```
167+
168+
**Example Lifecycle Testing with KMS Support**
169+
170+
```java
171+
@ExtendWith(InjectProfileCredentials.class)
172+
@EnabledIfSystemProperty(named = "desktop", matches = "true")
173+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
174+
public class CRUDLifecycleTest extends CRUDLifecycleTestBase<ResourceModel, CallbackContext> {
175+
176+
private final ReadHandler readHandler = new ReadHandler();
177+
private final DeleteHandler deleteHandler = new DeleteHandler();
178+
private final UpdateHandler updateHandler = new UpdateHandler();
179+
private final CreateHandler createHandler = new CreateHandler();
180+
private final Map<Action, HandlerInvoke<ResourceModel, CallbackContext>> handlers =
181+
ImmutableMap.<Action, HandlerInvoke<ResourceModel, CallbackContext>>builder()
182+
.put(Action.CREATE, createHandler::handleRequest)
183+
.put(Action.READ, readHandler::handleRequest)
184+
.put(Action.UPDATE, updateHandler::handleRequest)
185+
.put(Action.DELETE, deleteHandler::handleRequest)
186+
.build();
187+
188+
private final String logGroupName = "logGroup-TEST-DELETE-" + UUID.randomUUID().toString();
189+
190+
static final Delay override = Constant.of().delay(Duration.ofSeconds(1))
191+
.timeout(Duration.ofSeconds(10)).build();
192+
public CRUDLifecycleTest(@InjectSessionCredentials(profile = "cfn-integ") AwsSessionCredentials sessionCredentials) {
193+
super(sessionCredentials, ((apiCall, provided) -> override));
194+
}
195+
196+
@Override
197+
protected List<ResourceModels> testSeed() {
198+
final String kmsKeyId = getKmsKeyId();
199+
final String kmsKeyArn = getKmsKeyArn();
200+
List<ResourceModels> testGroups = new ArrayList<>(1);
201+
202+
testGroups.add(
203+
newStepBuilder()
204+
.group("simple_normal")
205+
.create(ResourceModel.builder().logGroupName(logGroupName).build())
206+
.update(ResourceModel.builder().logGroupName(logGroupName).retentionInDays(7).build())
207+
.delete(ResourceModel.builder().logGroupName(logGroupName).build()));
208+
209+
testGroups.add(
210+
newStepBuilder()
211+
.group("complex_with_failed_kms")
212+
.create(ResourceModel.builder().logGroupName(logGroupName).build())
213+
.createFail(ResourceModel.builder().logGroupName(logGroupName).build())
214+
.updateFail(ResourceModel.builder().logGroupName(logGroupName).retentionInDays(10).build())
215+
.update(ResourceModel.builder().logGroupName(logGroupName).retentionInDays(7).build())
216+
.updateFail(
217+
ResourceModel.builder().logGroupName(logGroupName).retentionInDays(7)
218+
.kMSKey("kmsKeyDoesNotExist").build())
219+
//
220+
// can not access logs service
221+
//
222+
.updateFail(() -> {
223+
removeServiceAccess("logs", kmsKeyId, Region.US_EAST_2);
224+
return ResourceModel.builder().logGroupName(logGroupName).retentionInDays(7)
225+
.kMSKey(kmsKeyArn).build();
226+
})
227+
.update(() -> {
228+
addServiceAccess("logs", kmsKeyId, Region.US_EAST_2);
229+
return ResourceModel.builder().logGroupName(logGroupName).retentionInDays(7)
230+
.kMSKey(kmsKeyArn).build();
231+
})
232+
.delete(ResourceModel.builder().logGroupName(logGroupName).build()));
233+
return testGroups;
234+
}
235+
236+
@Override
237+
protected Map<Action, HandlerInvoke<ResourceModel, CallbackContext>> handlers() {
238+
return handlers;
239+
}
240+
241+
@Override
242+
protected CallbackContext context() {
243+
return new CallbackContext();
244+
}
245+
}
246+
247+
```
248+
249+
**See Also**
250+
251+
*Lifecycle Testing*
252+
253+
These classes use the annotation and named profiles described above to make testing against live AWS services easy.
254+
255+
[software.amazon.cloudformation.test.KMSKeyEnabledServiceIntegrationTestBase](src/main/software/amazon/cloudformation/test/KMSKeyEnabledServiceIntegrationTestBase.java)
256+
[software.amazon.cloudformation.test.AbstractLifecycleTestBase](src/main/software/amazon/cloudformation/test/AbstractLifecycleTestBase.java)
257+
258+
*Local Unit Testing*
259+
260+
This class provides the step plumbing
261+
262+
[software.amazon.cloudformation.test.AbstractMockTestBase](src/main/software/amazon/cloudformation/test/AbstractMockTestBase.java)
6263

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
9264

10265
## License
11266

12267
This project is licensed under the Apache-2.0 License.
13-

buildspec.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 0.2
2+
phases:
3+
install:
4+
runtime-versions:
5+
java: openjdk8
6+
python: 3.7
7+
commands:
8+
- pip install pre-commit cloudformation-cli-java-plugin
9+
build:
10+
commands:
11+
- pre-commit run --all-files
12+
- mvn clean verify --no-transfer-progress

licenseHeader

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/

lombok.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lombok.addLombokGeneratedAnnotation = true

0 commit comments

Comments
 (0)