Skip to content
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

Add CustomPlugin and WorkerConfiguration resources along with tagging support for all resources #14

Conversation

divymamg
Copy link
Contributor

@divymamg divymamg commented Apr 19, 2024

Issue #, if available:

Description of changes:

  1. Add AWS::KafkaConnect::CustomPlugin and AWS::KafkaConnect::WorkerConfiguration CFN resources.
  2. Add tagging support to all of the resources.
  3. Upgrade JDK version from 8 to 17.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@divymamg divymamg force-pushed the feature/add-cfn-resources-and-tags-support branch 2 times, most recently from fff467f to 98189da Compare May 30, 2024 16:04
@divymamg divymamg force-pushed the feature/add-cfn-resources-and-tags-support branch from 98189da to 44b3ed9 Compare May 30, 2024 16:07
@@ -36,7 +36,7 @@ public BaseHandlerException translateToCfnException(
}

if (exception instanceof BadRequestException) {
return new CfnInvalidRequestException(ResourceModel.TYPE_NAME, exception);
return new CfnInvalidRequestException(exception.getMessage(), exception);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only this exception changed from resource type to exception message? I see there are other exceptions that being handled differently

*/
public static Map<String, String> generateTagsToAdd(final Map<String, String> previousTags, final Map<String, String> desiredTags) {
return desiredTags.entrySet().stream()
.filter(e -> !previousTags.containsKey(e.getKey()) || !Objects.equals(previousTags.get(e.getKey()), e.getValue()))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the e reads like a exception by a glance, consider use a proper name such as desiredTag

}

/**
* Request to add tags to a resource
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request to remove tags from a resource

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't check all the comments in details, please review the accuracy and correctness of the comments, especially the content in docs

@@ -79,6 +83,8 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
.then(progress ->
verifyUpdatable(proxy, proxyClient, progress, "AWS-KafkaConnect-Connector::PreUpdateCheck"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does verifyUpdatable also check if tags got changed or not? if customer only changed tags, it should be treated as updatable and move to the next step.

Comment on lines +176 to +196
if (!removedTags.isEmpty()) {

final UntagResourceRequest untagResourceRequest = Translator.untagResourceRequest(desiredModel, removedTags);
try {
proxyClient.injectCredentialsAndInvokeV2(untagResourceRequest, proxyClient.client()::untagResource);
logger.log(String.format("Removed %d tags", removedTags.size()));
} catch (final AwsServiceException e) {
throw exceptionTranslator.translateToCfnException(e, identifier);
}
}

// calculate tags to update based on Tags (key + value)
if (!addedTags.isEmpty()) {
final TagResourceRequest tagResourceRequest = Translator.tagResourceRequest(desiredModel, addedTags);
try {
proxyClient.injectCredentialsAndInvokeV2(tagResourceRequest, proxyClient.client()::tagResource);
logger.log(String.format("Added %d tags", addedTags.size()));
} catch (final AwsServiceException e) {
throw exceptionTranslator.translateToCfnException(e, identifier);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this logic here idempotent and handle partial failures well?

Let's say when tags being removed successfully but then the add tags failed, what happens?


return describeConnectorResponse;

private ProgressEvent<ResourceModel, CallbackContext> verifyNonCreateOnlyFieldsHaveToBeUpdated(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: verifyUpdatableFieldsGotChanged? the current name is a bit confusing.

throw exceptionTranslator.translateToCfnException(e, identifier);
}

logger.log(String.format("%s [%s] created successfully.", ResourceModel.TYPE_NAME, identifier));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how informative is this by using just name as logging identifier, better to include customer account ID as well. Although it's not part of request body, it should be accessible from request context or similar parts.

Comment on lines +163 to +167
case DELETING:
throw new CfnResourceConflictException(
ResourceModel.TYPE_NAME,
model.getCustomPluginArn(),
String.format("Another process is deleting this %s", ResourceModel.TYPE_NAME));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When got to the stage where checking if resource is stablised or not, it means the create request got a successful response. Given the locking mechanism we have, deleting shouldn't be a possible case to handle, why do we think this case is necessary to be handled?

successMessagePattern,
ResourceModel.TYPE_NAME,
describeCustomPluginRequest.customPluginArn(),
customPluginState == null ? "unknown" : customPluginState.toString()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason to handle the customPluginState to be null case? It seems implying describeCustomPlugin call doesn't handle exception properly and setting state to null, if that's not the case, we can remove this check. Another way to look at this, if pluginArn can't be null then pluginState is the same

Comment on lines +80 to +82
if (describeCustomPluginRequest.customPluginArn() == null) {
throw new CfnNotFoundException(ResourceModel.TYPE_NAME, null);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case custom plugin Arn will be null? It looks like to me it's a parameter that customers provid and then we use it to describe the resource. If so, here's is more appropriate to throw a 400 instead of 404.

Comment on lines +117 to +119
} catch (final AwsServiceException e) {
throw exceptionTranslator.translateToCfnException(e, identifier);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although we are validating the resource state by describing the resource, it's still a best effort which covers most cases. The edge case here is that the the resource can be deleted/updated after passes the validation phase, therefore, additional logics are needed.

  1. If resource is being deleted, it can choose make the operation idempotent and move on. Another option is to fail the request. Better to check how other services are handling this case
  2. If resource is being updated, we need to fail the operation.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to tackle this is to move the validation resource after the delete call has been dispatched.


return ProgressEvent.<ResourceModel, CallbackContext>builder()
.resourceModels(models)
.nextToken(nextToken)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this progressEvent handles nextToken automatically, and make additional call if token exist and stops when token is null?

return Collections.emptyMap();
}
return tags.stream()
.filter(tag -> tag.getValue() != null)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not support key only tags? Please test this in API

desiredModel,
callbackContext)
.translateToServiceRequest(translator::translateToReadRequest)
.makeServiceCall(this::validateResourceExists)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the delete comment, the validateResource logic is likely need to be expanded and move to after the updateAPI call dispatched and handles the resource to be sure we are verifying against the real status of the resource and avoid any race conditions

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to repeat similar comments, but some previous comment on code pattern applies to other resources as well, please verify them even though they don't have a comment

@Tvli Tvli merged commit 71c22ab into aws-cloudformation:main Sep 27, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants