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

Cloudformation custom resource handler #1260

Open
GentileFulvio opened this issue Dec 3, 2024 · 4 comments
Open

Cloudformation custom resource handler #1260

GentileFulvio opened this issue Dec 3, 2024 · 4 comments

Comments

@GentileFulvio
Copy link

GentileFulvio commented Dec 3, 2024

Is your feature request related to a problem? Please describe.
We are currently implementing custom resources for out platform. We added our own custom wrapper implementation to make handling these custom resources less bloated inside our code and noticed that middy doesn't have anything for custom resources already.

Describe the solution you'd like
It would be nice to have an abstraction which handles custom resources for us, the following is what our abstraction looks like, we are then using the created handler with middy so we can leverage the middleware there as well

interface ExpectedResourceOutput {
	SecretArn: string;
}

const myHandler: CustomResourceHandler<ExpectedResourceOutput> = async (event) => {
	// Code goes here

	return {
		// This is automatically added to the output in cloudformation
		SecretArn: "Hello-world",
	};
};

// The handler wrapper is responsible to catch errors in the handler and automatically report to cloudformation
// It is also responsible to report the success and add the outputs on the cloudformation resource
export const handler = createSNSCustomResourceHandler({
	create: myHandler,
});

The return value of the MyHandler function will be used as the output of the custom resource. Note that in this implementation we are creating a custom resource via sns, the handler should be able to handle requests via sns and to lambda directly

Describe alternatives you've considered
We currently created our own wrapper to simplify the code

Additional context
I'm willing to try and work out something in my free time as a suggestion. I was just wondering if this is something that should be added to middy or not

@willfarrell
Copy link
Member

I was reading through https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html. In the event examples included I saw that some properties are returned from the request, along with Status as a fixed value that could be put into a middleware to trim down the handler response a little bit. Is this what you were envisioning.

{
   "Status" : "SUCCESS",
   "RequestId" : "(copied from request)",
   "LogicalResourceId" : "(copied from request)",
   "StackId" : "(copied from request)",
   "...
}

@GentileFulvio
Copy link
Author

GentileFulvio commented Dec 13, 2024

I had something like this in mind where we can abstract the reporting to the responseUrl so the developer simply needs to define create, update, delete functions where the reuturn value are automatically used as data to report to cloudformation. The abstraction of my handler currently looks like this

const createCustomResourceHandler = <TEvent = unknown, TResult = unknown>(
	handlers: Handlers<TResult>,
	parser: EventParser<TEvent>,
) => {
	return async (event: TEvent, context: Context): Promise<void> => {
                // This part I added because I wanted to be able to handle both direct lambda integration as sns integration as wel;
                // When the event comes from sns we need to parse the event ourselves first
		const parsedEvent = parser(event);
		const { delete: deleteHandler, create: createHandler, update: updateHandler } = handlers;

		const functionDistributor: Record<
			CloudFormationCustomResourceEvent["RequestType"],
			CustomResourceHandler<unknown> | undefined
		> = {
			Create: createHandler,
			Delete: deleteHandler,
			Update: updateHandler,
		};


		const handler = functionDistributor[parsedEvent.RequestType];

		if (!handler) {
			// eslint-disable-next-line no-console -- Logger will be added in future pr once middleware is introduced
			console.warn(`No handler found, skipping [${parsedEvent.RequestType}]`);
			return send(parsedEvent, context, SUCCESS, {});
		}

		return handler(parsedEvent, context)
			.then(async (result) => {
				return send(parsedEvent, context, SUCCESS, result ?? {});
			})
			.catch(async (error: unknown) => {
				// eslint-disable-next-line no-console -- Need this until we have all necessary middlware introduced
				console.error(`Failed to execute handler for [${parsedEvent.RequestType}]`, error);
				return send(parsedEvent, context, FAILED, {});
			});
	};
};

Thanks to this, the code to create a custom resource handler is simply as mentioned above.
Do notice that my approach is rather a wrapper for the handler and not really middy middleware, but I could adjust accordingly to better fit the middy approach

@willfarrell
Copy link
Member

Based on what you you have, maybe a router pattern is in order. This would allow you to define all 3 operations within the same lambda. Let me think on how to best do this for a bit, as it would likely still need to deviate from how routers typically pass in routes.

@willfarrell
Copy link
Member

Take a look at: #1268

Let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants