|
1 | | -## My Project |
| 1 | +## AWS CloudFormation Java Plugin Test Framework |
2 | 2 |
|
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. |
4 | 6 |
|
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 | + <!-- for sts support to assume role setup above --> |
| 81 | + <dependency> |
| 82 | + <groupId>software.amazon.awssdk</groupId> |
| 83 | + <artifactId>sts</artifactId> |
| 84 | + <version>2.10.91</version> |
| 85 | + <scope>test</scope> |
| 86 | + </dependency> |
| 87 | + <!-- for kms key handling support --> |
| 88 | + <dependency> |
| 89 | + <groupId>software.amazon.awssdk</groupId> |
| 90 | + <artifactId>kms</artifactId> |
| 91 | + <version>2.10.91</version> |
| 92 | + </dependency> |
| 93 | + <dependency> |
| 94 | + <groupId>software.amazon.cloudformation.test</groupId> |
| 95 | + <artifactId>cloudformation-cli-java-plugin-testing-support</artifactId> |
| 96 | + <version>1.0.0</version> |
| 97 | + <scope>test</scope> |
| 98 | + </dependency> |
| 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) -> 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) |
6 | 263 |
|
7 | | -* Change the title in this README |
8 | | -* Edit your repository description on GitHub |
9 | 264 |
|
10 | 265 | ## License |
11 | 266 |
|
12 | 267 | This project is licensed under the Apache-2.0 License. |
13 | | - |
|
0 commit comments