Serverless Framework function HTTP trigger event configuration - refactoring

Say I have a serverless app that uses multiple Lambda functions triggered by HTTP event, where they all follow a similar URL format (same path parameter) - for example:
Function 1 path is: events/{id}
Function 2 path is: events/{id}/attendees
Function 3 path is: events/{id}/join
My serverless.yml is starting to look like:
...
functions:
get_event:
handler: handler.getEvent
events:
- http:
path: events/{id}
method: get
cors: true
request:
parameters:
paths:
id: true
get_event_attendees:
handler: handler.getEventAttendees
events:
- http:
path: events/{id}/attendees
method: get
cors: true
request:
parameters:
paths:
id: true
join_event:
handler: handler.joinEvent
events:
- http:
path: events/{id}/join
method: post
cors: true
request:
parameters:
paths:
id: true
What is a good way to refactor these http event declarations to reduce the redundancy?
I'm aware of the serverless variables syntax, but unsure of how I can make something more like a function, that accepts as arguments the different path parameters, and returns the appropriate YML.
I believe doing something like:
custom:
httpRequest:
parameters:
paths:
id: true
...
functions:
get_event:
handler: handler.getEvent
events:
- http:
path: events/{id}
method: get
request: ${self:custom:httpRequest}
get_event_attendees:
handler: handler.getEventAttendees
events:
- http:
path: events/{id}/attendees
method: get
request: ${self:custom:httpRequest}
...
only works if all the HTTP functions I ever write only have that one path parameter id.

Personally I wouldn't worry about it. There is some level of verbosity needed because of the sheer number of elements that can be configured but the redundancy in no way negatively affects the performance of your application. And while it may seem redundant, what you really have is the granularity to affect change very uniquely across your functions as well as the clarity to see what the differences are between them.
If you want to clean up the serverless.yml overall look at breaking out into separate files perhaps for all the function definitions or perhaps per event https://serverless.com/framework/docs/providers/aws/guide/variables/#reference-variables-in-other-files
That will probably server you better by keeping related entities together but segregated so there's less cognitive load when looking at the base serverless.yml file.

Related

How to rename AWS Lambda Function Name without changing its Function URL with SAM?

I am working with the following AWS SAM Template.
Resources:
PaymentFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: payment_function
CodeUri: PaymentFunction/
Description: 'A lambda function to do a payment'
...
...
...
FunctionUrlConfig:
AuthType: NONE
Cors:
AllowOrigins:
- "*"
AllowHeaders:
- "*"
AllowMethods:
- "*"
Outputs:
PaymentFunctionUrl:
Value:
Fn::GetAtt: PaymentFunctionUrl.FunctionUrl
When I deploy this function with aws deploy command. I get the following function url
https://{random_string}.lambda-url.{aws_region}.on.aws/
whenever I change the LogicalResourceId i.e. PaymentFunction or actual function name i.e. payment_function, it creates a new {random_string}. that means a new function URL.
Is it possible to change the function_name without changing function URL?
I don't think it is possible. The CFN docs for the FunctionName property of an AWS Lambda function clearly states that the updating of the name requires Replacement of the resource. This means that the old resource will be deleted and a new resource will be created with a new function URL.
Changing the LogicalResourceId of any cfn resource will automatically create a new resource in your AWS account and delete the old one. For Lambda functions, this always results in a different function URL.
If you want to invoke lambdas using URLs that you have more control over (and can easily change the lambda function that gets executed for each path), have a look at the REST API of the Amazon API Gateway service.

YAML file requires different amounts of indentation for different nodes

I'm attempting to write a YAML config script for a load testing utility called Artillery.
The YAML syntax is not making any sense to me though. Artillery appears to deserialize the YAML to a Javascript object syntax so it expects nodes in the YAML file to have a certain structure.
config:
target: https://mhhs-prod-webapp.azurewebsites.net/
phases:
- duration: 60
arrivalRate: 50
scenarios:
- flow:
- get:
url: '/'
Given the above file though it fails complaining that 'get' must be of type object. In the file above the get node has a child node url of key value type, so I'm expecting it to be of type object.
After much trial and error I've managed to get it to work using the following layout.
config:
target: https://mhhs-prod-webapp.azurewebsites.net/
phases:
- duration: 60
arrivalRate: 50
scenarios:
- flow:
- get:
url: '/'
But the bizarre thing is that this file is identical to the previous one apart from the fact that the url node is indented 4 spaces instead of the 2 spaces of indentation used throughout the rest of the file.
Is this correct? I'm having trouble finding an explanation of YAML that is understandable but so far I haven't come across anything that suggests that different amounts of indentation do completely different things.
Hohoho, I understand how you feel about YAML indentations.
It's like Python-based indentation but with a mix of JSON.
In your code:
config:
...
scenarios:
- flow:
- get:
url: '/'
"url" looks like a child of "flow" since it has an indent like so.
It can go in a couple of routes.
Route #1: (Where "url" is under "flow:")
...
scenarios:
- flow:
- get:
- url: '/'
Route #2: (Where "url" is under "get:")
...
scenarios:
- flow:
- get:
- url: '/'
Route #3: (Where "url" is under~ uhm...)
config:
...
scenarios:
...
url: '/'

How to add environment variables in template.yaml in a secured way?

When creating Lambda function through the SAM CLI using template.yaml, I have to pass few environment variables, and they shouldn't be exposed on GitHub. Is there any way I can refer the environment variables in template.yaml through the .env file?
I didnt find any sources for the same.
Sample code snippet from template.yaml:
Properties:
CodeUri: student /
FunctionName: list
Handler: index.listHandler
Runtime: nodejs14.x
Environment:
Variables:
MONGODB_URI: mongodb://username:pwd
There are few options here.
Add them to the Parameters section of the template (be sure to add the NoEcho option) and pass them in at the time of deploying.
A slightly better option is to use Secrets Manager to store the value and then use dynamic references in the template. CloudFormation will retrieve the values from Secrets Manager for you, at the time you deploy.
A better option is to not pass them as environment variables at all (since anyone with permissions to view the function will be able to see the value). Instead, use Secrets Manager to store the value and look up the value in the code. If you decide to use this approach be sure to cache the value so that you can at least reuse it between warm starts of the lambda.
One more option is to encrypt the value using KMS, and pass in the encrypted (Base64 encoded) value to the function. You'll need to call KMS decrypt to get the decrypted value. This operation is pretty fast, and isn't likely to be throttled. I would still cache the value to help speed things up between warm starts.
By extension of #Jason's answer 2. here a full working example:
template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: My test secrets manager dynamic reference SAM template/ Cloudformation stack
Resources:
# lambdas
myLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-myLambda
Runtime: nodejs12.x
Handler: index.handler
CodeUri: ./src/handlers/myLambda
MemorySize: 128
Timeout: 10
Environment:
Variables:
someSecret: '{{resolve:secretsmanager:somePreviouslyStoredSecret}}'
src/handlers/myLambda/index.js
const { someSecret } = process.env;
exports.handler = (event, context, callback) => {
if (someSecret) callback(null, `secret: ${someSecret}`);
callback(`Unexpected error, secret: ${someSecret}`);
};

How to add item to YML collection conditionally using Serverless framework?

Given I have the following defined in a YML file for a resource:
Type: AWS::Cognito::UserPoolClient
CallbackURLs:
- "my-first-url"
How can I add a second item when only certain conditions are met? Unfortuantely it is not possible to simply set it to an empty string or null as it fails deployment validation. E.G something like:
Type: AWS::Cognito::UserPoolClient
CallbackURLs:
- "my-first-url"
- myCondition ? "" : undefined // Omit item
Is this possible in any way at all? Happy to use Plugin solutions etc.
You can use a CloudFormation condition like Fn::If to conditionally create stack resources. The CloudFormation documentation about conditions has all the details but somehting like this should get you started:
resources:
Conditions:
my_condition: !Equals [value_1, value_2]
Resources:
MyUserPool:
Type: AWS::Cognito::UserPoolClient
CallbackURLs:
- "my-first-url"
- !If [my_condition, "...", !Ref "AWS::NoValue"]
Replace the content of my_condition with your condition. It is referenced later in the Fn::If (the example uses the shorthand for Fn::If).
The AWS::NoValue is a pseudo parameter which can be used as a return value to remove the corresponding property. It should work here to remove the list item but I'm not sure about it, you'll need to test.

How to change one value in an associative array referenced by Node

So I apologize in advance if I use yaml terminology wrong I am pretty new to it.
So I have this item in a list that is an associative array and I would like to use a node to repeat it multiple times in the file but I need to change one value in a sub array in it and I don't know how to do it without overwriting the entire array.
So here is the item in the list
- &def_service
type: service
name: Remote Service
config:
machine: ''
version: '1.0.0'
apikey: VALUE_I_WANT_TO_CHANGE
and I what I've tried to do is
- <<: *def_service
config:
apikey: NEW_VALUE
but that just overwrites the entire array so config is just
{config:{apikey:NEW_VALUE}}
I would be very grateful for an answer here I am pretty stuck.
Ok so an answer that occurred to me maybe not the best answer is just to introduce another variable for the config array like this.
- &def_service
type: service
name: Remote Service
config: &service_config
machine: ''
version: '1.0.0'
apikey: VALUE_I_WANT_TO_CHANGE
so to reference it I did this
- <<: *def_service
config:
<<: *service_config
apikey: NEW_VALUE
YAML is not a programming language. It is designed for data representation, not for data transformation.
The merge key you use (<<) is not part of the YAML specification. It is part of the YAML type repository, which is outdated (since it is defined for YAML 1.1). Therefore, your question is highly dependendent on the YAML processor you use. One processor might implement it while another does not.
Since you have a specific problem, it would probably be better to write YAML tailored to your problem, and then handle it in your code (assuming that you are in charge of the code). Something like this:
- !config_base
&def_service
type: service
name: Remote Service
config: &service_config
machine: ''
version: '1.0.0'
apikey: VALUE_I_WANT_TO_CHANGE
- !config_child
base: *def_service
substitutions:
config:
apikey: NEW_VALUE
You can then write code that does the substitution inside your deserialized YAML structure.

Resources