AWS::Lambda serverless invoke local function not reflecting my new wcode - node.js

Not sure what is happening with my code.
It never executes my updated code on my local. If I update my code and run sls invoke local , it still runs the old code. ALso, it does not send out SES EMail.
For some reason, it always executes the code which was already deployed to AWS platform rather than executing my local code. This is confusing.
Below is my serverless.yml:
service: lambda-test
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: ap-southeast-1
iamRoleStatements:
- Effect: Allow
Action:
- lambda:InvokeFunction
- lambda:InvokeAsync
Resource: "*"
functions:
hello:
handler: handler.hello
environment:
events:
- http:
path: /
method: get
trulyYours:
handler: handler.trulyYours
environment:
events:
- http:
path: /trulyYours
method: get
sendRegistrationEmail:
handler: handler.sendRegistrationEmail
environment:
events:
- http:
path: /sendRegistrationEmail
method: get
plugins:
- serverless-offline
I am not sure if I should continue to edit code in AWS web console itself or try
setting up local dev environment. Been tying since last two days, but turning out to be useless to spend time.
'use strict';
var aws = require("aws-sdk");
var nodeMailer = require("nodemailer");
//aws.config.loadFromPath('aws_config.json');
var ses = new aws.SES();
var s3 = new aws.S3();
module.exports.hello = async (event, context) => {
console.log("executing lambda function 'hello' XX...");
// return {
// statusCode: 200,
// body: JSON.stringify({
// message: 'v1.0',
// }),
// };
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
//
exports.trulyYours = async (event, context, callback) => {
console.log('Lambda trulyYours Received event:', JSON.stringify(event, null, 2));
//context.succeed('Hello from' + event.name);
return {
statusCode: 200,
body: JSON.stringify({
message: 'hello from trulyYours' + event.name,
}),
};
}
/*function trulyYours (foo, bar) {
// MyLambdaFunction logic here
}*/
module.exports.sendRegistrationEmail = (event, context) => {
console.log("Executing sendRegistrationEmail...");
var lambda = new aws.Lambda({
region: 'ap-southeast-1' //change to your region
});
var params = {
FunctionName: 'lambda-test-dev-trulyYours', // the lambda function we are going to invoke
InvocationType: 'RequestResponse',
LogType: 'Tail',
Payload: '{ "name" : "Alexa" }'
};
lambda.invoke(params, function (err, data) {
if (err) {
context.fail(err);
} else if (data.Payload) {
context.succeed('Lambda trulyYours ' + data.Payload);
}
});
//
// return {
// statusCode: 200,
// body: JSON.stringify({
// message: 'sent email successfully',
// }),
// };
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

Try to create a serverless-local.yml, for example :
config:
region: eu-west-1
environment:
VAR: local
database:
hostname: localhost
port: 8000
username: root
password: toor
database: db
url: http://localhost:3000
and in you serverless.yml in provider add this line :
stage: ${opt:stage, 'dev'}
then in you terminal try this cli :
sls invoke local -f functionName -s local

Related

Use Redis with AWS SAM (Redis Client Error)

Right now what I'm trying to do is that every time a request is made, a query is made to the Redis service. The problem is that when using a basic configuration, it would not be working. The error is the following:
INFO Redis Client Error Error: connec at TCPConnectWrap.afterConnect [as oncomplete] (node} port: 6379127.0.0.1',
I have as always running redis-server with its corresponding credentials listening to port 127.0.0.1:6379. I know that AWS SAM runs with a container, and the issue is probably due to a network configuration, but the only command that AWS SAM CLI provides me is --host. How could i fix this?
my code is the following, although it is not very relevant:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { createClient } from 'redis';
import processData from './src/lambda-data-dictionary-read/core/service/controllers/processData';
export async function lambdaHandler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
const body: any = await processData(event.queryStringParameters);
const url = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
const client = createClient({
url,
});
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
console.log('----', value, '----');
const response: APIGatewayProxyResult = {
statusCode: 200,
body,
};
if (body.error) {
return {
statusCode: 404,
body,
};
}
return response;
}
My template.yaml:
Transform: AWS::Serverless-2016-10-31
Description: >
lambda-data-dictionary-read
Sample SAM Template for lambda-data-dictionary-read
Globals:
Function:
Timeout: 0
Resources:
IndexFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: app/
Handler: index.lambdaHandler
Runtime: nodejs16.x
Timeout: 10
Architectures:
- x86_64
Environment:
Variables:
ENV: !Ref develope
REDIS_URL: !Ref redis://127.0.0.1:6379
Events:
Index:
Type: Api
Properties:
Path: /api/lambda-data-dictionary-read
Method: get
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: 'es2020'
Sourcemap: true
UseNpmCi: true
Im using:
"scripts": {
"dev": "sam build --cached --beta-features && sam local start-api --port 8080 --host 127.0.0.1"
}

How can I write a json file in S3 from a SAM Lambda function with Node

I am trying to save a json file from a Lambda function with Node but the file is not showing in my s3 bucket. From what I found, I have to make some change in my template.yaml file but I don't know what it is. I'm getting a positive response from the api post call but nothing happens in the bucket.
How can I fiz this?
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
webhook
Sample SAM Template for webhook
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: webhook_nath/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /webhook
Method: POST
Metadata: # Manage esbuild properties
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Sourcemap: true
EntryPoints:
- app.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import aws from 'aws-sdk';
const s3 = new aws.S3();
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
let response: APIGatewayProxyResult;
try {
s3.putObject({
Body: JSON.stringify(event.body),
Bucket: 'new-bucket-nath',
Key: 'file_name.json',
ContentType:'application/json'
}).promise();
response = {
statusCode: 200,
body: JSON.stringify({
message: 'worked',
}),
};
} catch (err) {
console.log(err);
response = {
statusCode: 500,
body: JSON.stringify({
message: 'some error happened',
}),
};
}
return response;
};
The call to s3.putObject needs an await.
‘await s3.putObject‘

How do I reference Cloudformation Parameters as variables in an embedded Node.js script?

I have a CloudFormation template creating a Cloudwatch Synthetics Canary. Part of the template has a Lambda embedded written in Node.js 2: syn-nodejs-2.0. I have a few parameters that are being passed into the CFT and I want to pass them into the node script to use the values of the website I'm trying to test. I'm pretty sure I can do this with something like this:
{ "Ref" : "${Param}" }
where ${Param} is the Cloudformation parameter I'm trying to reference, but that doesn't seem to work for me. Maybe I have a small syntax issue, or maybe I'm off base in my logic, I'm not really sure. The ultimate goal is to read the variables stored in SSM, but I haven't gotten to that point yet. Here is my code. The problem spot I'm having is near the end:
Parameters:
CanaryName:
Type: String
Default: my-canary
MaxLength: 21
HostName:
Type: String
Default: foo.bar.net
MaxLength: 128
Path:
Type: String
Default: /v1/status
MaxLength: 256
Port:
Type: Number
Default: 443
Resources:
CloudWatchSyntheticsRole:
Type: AWS::IAM::Role
Properties:
RoleName:
Fn::Sub: CloudWatchSyntheticsRole-${CanaryName}-${AWS::Region}
Description: CloudWatch Synthetics lambda execution role for running canaries
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition: {}
RolePermissions:
Type: AWS::IAM::Policy
Properties:
Roles:
- Ref: CloudWatchSyntheticsRole
PolicyName:
Fn::Sub: CloudWatchSyntheticsPolicy-${CanaryName}-${AWS::Region}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetBucketLocation
Resource:
- Fn::Sub: arn:aws:s3:::${ResultsBucket}/*
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
- logs:CreateLogGroup
Resource:
- Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/cwsyn-test-*
- Effect: Allow
Action:
- s3:ListAllMyBuckets
Resource: '*'
- Effect: Allow
Resource: '*'
Action: cloudwatch:PutMetricData
Condition:
StringEquals:
cloudwatch:namespace: CloudWatchSynthetics
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: "arn:aws:secretsmanager:*:MYACCOUNT:secret:*"
ResultsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Sub: cw-syn-results-${AWS::AccountId}-${AWS::Region}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
Canary:
Type: AWS::Synthetics::Canary
Properties:
Name:
Fn::Sub: ${CanaryName}
Code:
Handler: exports.handler
Script: |
var synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const https = require('https');
const http = require('http');
const apiCanaryBlueprint = async function () {
const postData = "";
const verifyRequest = async function (requestOption) {
return new Promise((resolve, reject) => {
log.info("Making request with options: " + JSON.stringify(requestOption));
let req
if (requestOption.port === 443) {
req = https.request(requestOption);
} else {
req = http.request(requestOption);
}
req.on('response', (res) => {
log.info(`Status Code: ${res.statusCode}`)
log.info(`Response Headers: ${JSON.stringify(res.headers)}`)
// If the response status code is not a 2xx success code
if (res.statusCode < 200 || res.statusCode > 299) {
reject("Failed: " + requestOption.path);
}
res.on('data', (d) => {
log.info("Response: " + d);
});
res.on('end', () => {
resolve();
})
});
req.on('error', (error) => {
reject(error);
});
if (postData) {
req.write(postData);
}
req.end();
});
}
secret = "MYSECREYKEY";
const headers = {"Authorization":"Basic ${secret}"}
headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' ');
// PROBLEM SPOT: HOW TO ACCESS THE CFT PARAMETERS?
const requestOptions = `"hostname" : { "!Ref" : "$HostName" }, "method" : "GET", "path" : { "!Ref" : "$Path" }, "port" : { "!Ref" : "$Port" }`
requestOptions['headers'] = headers;
await verifyRequest(requestOptions);
};
exports.handler = async () => {
return await apiCanaryBlueprint();
};
You can use the intrinsic function Fn::Sub to substitute ${param} inside a inline code block:
Code: !Sub |
console.log('handle event: ${param}');
Handler: exports.handler
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html#w2aac25c28c59c11
The documentation also describes how to escape the node.js string interpolation: ${!Literal}
Probably not the best answer, but you could instead create those variables in parameter store, and then reference those in the code.
here is a way to do that: How to access the aws parameter store from a lambda using node.js and aws-sdk
I believe that since you are adding the code inline all references to the CF template would not work, so using something external would work.

AWS SNS publish only works locally, but not from lambda

This is the code that runs successfully on local to send sms
const snsParams = {
Message: "Hello World",
PhoneNumber: normalizedPhoneNumber,
};
const sms = messager.publish(snsParams).promise();
sms.then(data => {
console.log('Success!', data);
}).catch(err => {
console.log('Error!', err);
});
However, this does not fire from my deployed lambda.
My serverless.yml contains the necessary iamRoleStatements like this
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- arn:aws:dynamodb:us-west-1:*:table/${app-name}-${self:custom.stage}
- arn:aws:dynamodb:us-west-1:*:table/${app-name}-${self:custom.stage}/*
- Effect: Allow
Action:
- sns:*
Resource: "*"
I've also checked from the IAM Management console to see that SNS is included in my lambda role
How can I make this work? What am I missing?
I think the problem could be in promises, because the line
messager.publish(snsParams).promise(); creates only a promise and there is no waiting when the promise is executed.
You can change your code so:
exports.handler = async function(event) {
...
const snsParams = {
Message: "Hello World",
PhoneNumber: normalizedPhoneNumber,
};
try {
const sms = await messager.publish(snsParams).promise();
console.log('Success!', sms);
} catch (err) {
console.log('Error!', err);
}
}

How to call an AWS Step Function using the definitions in the serverless-step-functions plugin?

I'm using Serverless Framework to create my Lambda functions and the serverless-step-functions plugin to define my step functions.
Is it possible to call an step function directly from one of the lambda functions using the name defined into the serverless.yml file?
I was trying to solve the same problem and this question and the self answer were very helpful. However, I want to add another answer with more details and a working example to help future readers.
There are two things that you may need:
1- Start a State Machine
2- Invoke one specific function from a State Machine (usually for testing purposes)
The following demo uses both cases.
First, we need to configure the serverless.yml file to declare the State Machine, the Lambda functions and the correct IAM permissions.
service: test-state-machine
provider:
name: aws
runtime: nodejs4.3
region: us-east-1
stage: dev
environment:
AWS_ACCOUNT: 1234567890 # use your own AWS ACCOUNT number here
# define the ARN of the State Machine
STEP_FUNCTION_ARN: "arn:aws:states:${self:provider.region}:${self:provider.environment.AWS_ACCOUNT}:stateMachine:${self:service}-${self:provider.stage}-lambdaStateMachine"
# define the ARN of function step that we want to invoke
FUNCTION_ARN: "arn:aws:lambda:${self:provider.region}:${self:provider.environment.AWS_ACCOUNT}:function:${self:service}-${self:provider.stage}-stateMachineFirstStep"
functions:
# define the Lambda function that will start the State Machine
lambdaStartStateMachine:
handler: handler.lambdaStartStateMachine
role: stateMachine # we'll define later in this file
# define the Lambda function that will execute an arbitrary step
lambdaInvokeSpecificFuncFromStateMachine:
handler: handler.lambdaInvokeSpecificFuncFromStateMachine
role: specificFunction # we'll define later in this file
stateMachineFirstStep:
handler: handler.stateMachineFirstStep
# define the State Machine
stepFunctions:
stateMachines:
lambdaStateMachine:
Comment: "A Hello World example"
StartAt: firstStep
States:
firstStep:
Type: Task
Resource: stateMachineFirstStep
End: true
# define the IAM permissions of our Lambda functions
resources:
Resources:
stateMachine:
Type: AWS::IAM::Role
Properties:
RoleName: stateMachine
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: stateMachine
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: "Allow"
Action:
- "states:StartExecution"
Resource: "${self:provider.environment.STEP_FUNCTION_ARN}"
specificFunction:
Type: AWS::IAM::Role
Properties:
RoleName: specificFunction
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: specificFunction
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource: "${self:provider.environment.FUNCTION_ARN}"
package:
exclude:
- node_modules/**
- .serverless/**
plugins:
- serverless-step-functions
Define the Lambda functions inside the handler.js file.
const AWS = require('aws-sdk');
module.exports.lambdaStartStateMachine = (event, context, callback) => {
const stepfunctions = new AWS.StepFunctions();
const params = {
stateMachineArn: process.env.STEP_FUNCTION_ARN,
input: JSON.stringify({ "msg": "some input" })
};
// start a state machine
stepfunctions.startExecution(params, (err, data) => {
if (err) {
callback(err, null);
return;
}
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'started state machine',
result: data
})
};
callback(null, response);
});
};
module.exports.lambdaInvokeSpecificFuncFromStateMachine = (event, context, callback) => {
const lambda = new AWS.Lambda();
const params = {
FunctionName: process.env.FUNCTION_ARN,
Payload: JSON.stringify({ message: 'invoked specific function' })
};
// invoke a specific function of a state machine
lambda.invoke(params, (err, data) => {
if (err) {
callback(err, null);
return;
}
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'invoke specific function of a state machine',
result: data
})
};
callback(null, response);
});
};
module.exports.stateMachineFirstStep = (event, context, callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'state machine first step',
input: event
}),
};
callback(null, response);
};
Deploy executing:
serverless deploy stepf
serverless deploy
Test using:
serverless invoke -f lambdaStartStateMachine
serverless invoke -f lambdaInvokeSpecificFuncFromStateMachine
Solved using serverless environment variables:
environment:
MYFUNCTION_ARN: "arn:aws:states:${self:provider.region}:${self:provider.environment.AWS_ACCOUNT}:stateMachine:${self:service}-${self:provider.stage}-myFunction"
In the function:
var params = {
stateMachineArn: process.env.MYFUNCTION_ARN
};
Here is how you solve it nowadays.
In your serverless.yml, define your stepFunctions and also Outputs:
# define your step functions
stepFunctions:
stateMachines:
myStateMachine:
name: stateMachineSample
events:
- http:
path: my-trigger
method: GET
# make it match your step functions definition
Outputs:
myStateMachine:
Value:
Ref: StateMachineSample
Then you can set your state machine ARN as an environment using ${self:resources.Outputs.fipeStateMachine.Value}.

Resources