Use input parameter within lambda schedule in Cloudformation - go

I'm trying to enable/disable lambda scheduled event based on environment, and not having much luck. If the env is prod, it should enable the schedule, and if it's dev it should disable it. However, the schedule status doesn't obey the condition. It's currently enabled in dev, but the template doesn't disable it. If I set the Enabled property to false manually, the schedule status does change to disable. So not sure where I'm going wrong (or if what I'm trying to do is possible). Any help is greatly appreciated!
Parameters:
env:
Type: String
Conditions:
isProd:
!Equals [!Ref env, prod]
Resources:
func:
Type: AWS::Serverless::Function
Properties:
Events:
ScheduledEvent:
Type: Schedule
Properties:
Schedule: rate(5 minutes)
Enabled: !If [isProd, true, false]

Okay, so after a bit more searching, it looks like setting the Enabled property with conditional statements doesn't work so nice. The workaround I've put in place is to use conditional statements in the Schedule property.
ScheduledEvent:
Type: Schedule
Properties:
Schedule: !If [isProd, "rate(5 minutes)", "cron(00 00 01 01 ? 1970)"]
Thanks to these posts for point me in the right direction:
Conditionally enabling an event schedule in AWS sam template file
Dynamically change event properties on aws cloudformation templates

Related

How to optionally apply environment configuration?

I want to optionally apply a VPC configuration based on whether an environment variable is set.
Something like this:
custom:
vpc:
securityGroupIds:
- ...
subnetIds:
- ...
functions:
main:
...
vpc: !If
- ${env:USE_VPC}
- ${self:custom.vpc}
- ~
I'd also like to do similar for alerts (optionally add emails to receive alerts) and other fields too.
How can this be done?
I've tried the above configuration and a variety of others but just receive various different errors
For example:
Configuration error:
at 'functions.main.vpc': must have required property 'securityGroupIds'
at 'functions.main.vpc': must have required property 'subnetIds'
at 'functions.main.vpc': unrecognized property 'Fn::If'
Currently, the best way to achieve such behavior is to use JS/TS-based configuration instead of YAML. With TS/JS, you get full power of a programming language to shape your configuration however you want, including use of such conditional checks to exclude certain parts of the configuration. It's not documented too well, but you can use this as a starting point: https://github.com/serverless/examples/tree/v3/legacy/aws-nodejs-typescript
In general, you can do whatever you want, as long as you export a valid object (or a promise that resolves to a valid object) with serverless configuration.

How can I make an optional email settings in my Jelastic manifest and return the value to the user?

Let's say I have a Jelastic manifest where, in the settings section, I ask the user for an email address (to setup some service). Because the user using Jelastic already is logged on Jelastic with an email, it might make sense to propose to the user that she uses that email. It saves her typing during the manifest setup. So, ideally, what I would like to do is this:
settings:
fields:
- name: email
caption: Email
type: string
default: ${user.email}
Then, in the manifest success text, I would like to display it, along with other credentials for the deployed services in the installed Jelastic environment:
success:
text: |
**Email**: ${settings.email}
The email is typically used as a username in a service deployed on the new Jelastic environment and in the success text it would be displayed near the corresponding password.
This above is all nice, but it doesn't work. Indeed, I cannot use the ${user.email} in the default field for the email settings.
Currently, the only viable way I've found to make it work is as follows:
settings:
fields:
- name: useJelasticEmailAsEmail
type: toggle
caption: Use Jelastic Email
value: true
hidden: false
showIf:
false:
name: email
caption: email
type: string
required: true
Then, I can use it to install a service like so:
onInstall:
- script: 'return { result: 0, email: ${settings.useJelasticEmailAsEmail} ? "${user.email}" : "${settings.email}" };'
- install:
jps: my-service-manifest.yml
settings:
username: ${response.email}
However, after installation of my-service-manifest.yml, I have no access to the actually used email anymore, therefore I cannot use the ${response.email} in my success text, except maybe if my-service-manifest.yml returned the email somehow (possibly with a return statement?).
The thing is, I may have many services to install like that, but only one requires the email. Additionally, it might be that for some reason that service requiring the email must be installed first. In such a case, with the above solution, I'd need to propagate the email through all services in order to get it out to the success text. The email would be consumed by the first service and somehow returned to the next. The next service does not need the email, but must propagate it, therefore it should take it as settings argument and return it to the next service, and so on. This is not very nice to have a manifest taking arguments it doesn't need, therefore that solution is probably not the right one.
I find that very complicated for a very simple problem. There must be something easier to do, right? Can someone give me a hint?
I'd need a solution for a manifest where I have many of such optional parameters. Currently, I have an issue with the email and also with secrets. In my use-cases, it makes sense that these secrets be provided by me directly in the settings or that they be automatically generated with e.g. ${fn.password(20)}. For some installations I need to set them myself, for some other installations I need to let them be automatically generated.
So, the working solution I have found goes as follows, assuming I have 2 optional parameters:
in the settings section of my jps manifest, I do
settings:
fields:
- name: useOptionalParam1
type: toggle
caption: Use optional param1
value: true
hidden: false
showIf:
false:
name: param1
caption: Param1
type: string
required: true
- name: useOptionalParam2
type: toggle
caption: Use optional param2
value: true
hidden: false
showIf:
false:
name: param2
caption: Param2
type: string
required: true
define values for those params in the globals section (for the email, this is not necessary, because it is defined as user.email):
globals:
OPTIONAL_PARAM1: ${fn.password(20)}
OPTIONAL_PARAM2: ${fn.password(20)}
define an action like this:
actions:
getOptionalParams:
- script: 'return { result: 0, param1: ${settings.useOptionalParam1} ? "${globals.OPTIONAL_PARAM1}" : "${settings.param1}", param2: ${settings.useOptionalParam2} ? "${globals.OPTIONAL_PARAM2}" : "${settings.param2}" };'
call the action wherever necessary; for example, before the success text:
# settings, globals, nodes sections
[...]
onInstall:
- myVariousInstalls
- getOptionalParams
success:
text: |
**PARAM1**: ${response.param1}
**PARAM2**: ${response.param2}
Note the last call to getOptionalParams at the very end of the onInstall section. It is there to provide the response object to the success section. Interestingly, that works fine. The getOptionalParams action can also be called anywhere in the myVariousInstalls action. It must be called before an operation, for example like this:
actions:
[...]
myVariousInstalls:
- getOptionalParams
- install:
jps: path/to/manifest
settings:
param1: ${response.param1}
In so doing, I solve my initial problem.

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 assign values to my CloudFormation template written in YAML while deploying

I have written a CloudFormation template in YAML to deploy my AWS Lambda functions. I have to deploy multiple Lambda function and I want to be able to changes the key-value pair at run-time, so that I don't have to copy the entire thing again and again to make changes before deployment.
I was reading about the set builtin of Linux, but didn't find it of much help
NameOfMyLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '#My_Function_Name'
Handler: app.lambda_handler
Runtime: python3.7
MemorySize: 256
Role: !GetAtt MyExecutionRole.Arn
CodeUri: 'path/to/my/python/file'
In the above given code, I want to be able to change the "FunctionName" and "Role" at time of deployment.
Your exact use case isn't completely clear (how are you running your cloudformation?), but it sounds like you want to use CloudFormation Parameters possibly combined with Nested stacks
This would allow you to reference your cloudformation template multiple times from within an outer template, passing different parameters each time.

Resources