I am creating a zip file for deployment to lambda using https://github.com/Tim-B/grunt-aws-lambda but when deploying to aws lambda I need to create the function first in amazon console. Can we create a function using grunt instead of amazon console? Thank you.
You can create the function from grunt using AWS JavaScript SDK for Lambda.
Use the createFunction method as shown below.
/* This example creates a Lambda function. */
var params = {
Code: {
},
Description: "",
FunctionName: "MyFunction",
Handler: "souce_file.handler_name", // is of the form of the name of your source file and then name of your function handler
MemorySize: 128,
Publish: true,
Role: "arn:aws:iam::123456789012:role/service-role/role-name", // replace with the actual arn of the execution role you created
Runtime: "nodejs4.3",
Timeout: 15,
VpcConfig: {
}
};
lambda.createFunction(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
/*
data = {
CodeSha256: "",
CodeSize: 123,
Description: "",
FunctionArn: "arn:aws:lambda:us-west-2:123456789012:function:MyFunction",
FunctionName: "MyFunction",
Handler: "source_file.handler_name",
LastModified: "2016-11-21T19:49:20.006+0000",
MemorySize: 128,
Role: "arn:aws:iam::123456789012:role/service-role/role-name",
Runtime: "nodejs4.3",
Timeout: 123,
Version: "1",
VpcConfig: {
}
}
*/
});
Note: You can fill the code parameter with code or use addition attributes to refer a code zipped and uploaded to S3.
E.g.
Code: { /* required */
S3Bucket: 'STRING_VALUE',
S3Key: 'STRING_VALUE',
S3ObjectVersion: 'STRING_VALUE',
ZipFile: new Buffer('...') || 'STRING_VALUE'
},
Also make sure to give required permission to the IAM User and setup JavaScript SDK credentials to run the code.
Related
We've created a Custom Botbuilder Adapter to connect to the Vonage API called botbuilder-adapter-vonage-js. To test the Adapter's basic functionality, with a basic bot reply, we send an SMS to the Vonage number and should get an SMS reply back "Hello Back", but instead receive the error below:
[onTurnError] unhandled error: TypeError: bot.reply is not a function
Not sure how to actually debug the Custom Adapter to find where it is broken.
Would be great to find someone familiar with either the Botkit Core library and Botkit Platform Adapters could help with this. I've attached the Express Server (webhook-server.js) below.
// webhook-server.js
require('dotenv').config();
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
const SendMessagesAPI = require('./Vonage-SEND-messages-api');
const VonageAdapter = require('botbuilder-adapter-vonage-js');
const Botkit = require('botkit');
const {
BotFrameworkAdapter,
InspectionMiddleware,
MemoryStorage,
InspectionState,
UserState,
ConversationState,
} = require('botbuilder');
const { MicrosoftAppCredentials } = require('botframework-connector');
// This bot's main dialog.
const { IntersectionBot } = require('./bot');
const { Message } = require('#vonage/server-sdk');
const creds = {
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH,
};
const config = {
to_number: process.env.TO_NUMBER,
from_number: process.env.FROM_NUMBER,
// enable_incomplete: true
};
// Create Adapter
const adapter = new VonageAdapter(creds, config);
// Create the Storage provider and the various types of BotState.
const memoryStorage = new MemoryStorage();
const inspectionState = new InspectionState(memoryStorage);
const userState = new UserState(memoryStorage);
const conversationState = new ConversationState(memoryStorage);
// Create and add the InspectionMiddleware to the adapter.
adapter.use(
new InspectionMiddleware(
inspectionState,
userState,
conversationState,
new MicrosoftAppCredentials(
process.env.MicrosoftAppId,
process.env.MicrosoftAppPassword
)
)
);
app.post('/webhooks/dlr', (req, res) => {
res.status(200).end();
});
// Catch-all for errors.
adapter.onTurnError = async (, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights. See https://aka.ms/bottelemetry for telemetry
// configuration instructions.
console.error(`\n [onTurnError] unhandled error: ${error}`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await .sendTraceActivity(
'OnTurnError Trace',
`${error}`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await .sendActivity('The bot encountered an error or bug.');
await .sendActivity(
'To continue to run this bot, please fix the bot source code.'
);
// Clear out state
await conversationState.clear();
};
// Create the main dialog.
const bot = new IntersectionBot(conversationState, userState);
// Listen for incoming requests.
app.post('/webhooks/inbound', (req, res) => {
console.log('/webhooks/inbound req.body', req.body);
adapter.processActivity(req, res, async () => {
console.log(context);
// [onTurnError] unhandled error: TypeError: Cannot read property 'from' of undefined
// await bot.run();
// [onTurnError] unhandled error: TypeError: .reply is not a function
// await .reply('I heard a message!');
// [onTurnError] unhandled error: TypeError: bot.reply is not a function
await bot.reply('Hello Back!');
});
res.status(200).end();
});
app.post('/webhooks/status', (req, res) => {
res.status(200).end();
});
app.listen(port, () => {
console.log(`🌏 Server running at http://localhost:${port}`);
});
Response
🌏 Server running at http://localhost:3000
/webhooks/inbound req.body {
message_uuid: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
to: { type: 'sms', number: '12018994297' },
from: { type: 'sms', number: '15754947000' },
timestamp: '2021-08-27T21:14:51.228Z',
usage: { price: '0.0057', currency: 'EUR' },
message: {
content: { type: 'text', text: 'Hello' },
sms: { num_messages: '1' }
},
direction: 'inbound'
}
TurnContext {
_respondedRef: { responded: false },
_turnState: TurnContextStateCollection(2) [Map] {
'httpStatus' => 200,
Symbol(state) => { state: [Object], hash: '{}' }
},
_onSendActivities: [],
_onUpdateActivity: [],
_onDeleteActivity: [],
_turn: 'turn',
_locale: 'locale',
bufferedReplyActivities: [],
_adapter: VonageAdapter {
middleware: MiddlewareSet { middleware: [Array] },
BotIdentityKey: Symbol(BotIdentity),
OAuthScopeKey: Symbol(OAuthScope),
name: 'Vonage Adapter',
middlewares: null,
botkit_worker: [class VonageBotWorker extends BotWorker],
credentials: {
apiKey: '4f2ff535',
apiSecret: 'jtYzPbh3MXr8M1Hr',
applicationId: '978500cf-7ea8-4d7b-ac54-2b42f67b28a2',
privateKey: './private.key'
},
options: {},
to_number: '15754947000',
from_number: '12018994297',
enable_incomplete: undefined,
turnError: [AsyncFunction (anonymous)]
},
_activity: {
id: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
timestamp: 2021-08-27T21:14:39.573Z,
channelId: 'vonage-sms',
conversation: { id: '15754947000' },
from: { id: '15754947000' },
recipient: { id: '12018994297' },
text: 'Hello',
channelData: {
message_uuid: 'e93a3007-f7a5-436a-8ba7-c46d64343d80',
to: [Object],
from: [Object],
timestamp: '2021-08-27T21:14:51.228Z',
usage: [Object],
message: [Object],
direction: 'inbound'
},
type: 'message'
}
}
[onTurnError] unhandled error: TypeError: bot.reply is not a function
Botkit version:
Messaging Platform: Vonage
Node version: v14.16.1
Os: MAC
It looks like your custom adapter is built using both Botkit and BotFramework similar to other Botkit adapters. However, your bot's implementation aligns more with a bot built only for BotFramework yet you are trying to call the reply() method that belongs to a Botkit bot.
For example, in Botkit's 'botbuilder-adapter-twilio-sms' adapter, you are presented with two ways to utilize the adapter. In the first, under Botkit Basics, an adapter is created which is then consumed by Botkit to form the controller. This then allows you to access the reply() method callable from a Botkit bot.
In the second, under BotBuilder Basics, an adapter is created which is then utilized within the Express server's /api/messages endpoint. Inbound messages are passed to the adapter's processActivity() method where the bot then responds using the sendActivity() method, callable from within a BotFramework adapter's context.
Narrowing down which implementation you intend to use I believe will alleviate the error you are receiving.
I have an AppSync GraphQL API defined. The API is using Cognito UserPools as primary authentication. This part works fine.
I am trying to execute the request from the Nodejs Lambda function using axios and aws4. The mutation I am trying to hit is configure something like this in my schema (omitting most of the schema):
AppSyncSchema:
Type: AWS::AppSync::GraphQLSchema
Properties:
ApiId: !GetAtt MyApi.ApiId
Definition: |
type Something {
someId: ID!
}
input SomethingInput {
someId: ID!
}
type Mutation {
doSomething(input: SomethingInput!): Something #aws_iam
}
I have configure the Lambda function execution role to have appsync:GraphQL permission:
"Action": [
"appsync:GraphQL"
],
"Resource": "arn:aws:appsync:eu-west-2:xxxx:apis/yyyy/*",
"Effect": "Allow",
"Sid": "AllowAppSyncExecution"
From different articles online, I have put together some request that my Nodejs Typescript function then tries to execute:
const doSomething = `mutation doSomething($input: SomethingInput!) {
doSomething(input: $input) {
someId
}
}`;
const data = {
operationName: 'doSomething',
query: doSomething,
variables: {
input: { someId: 'abc' },
},
};
const body = JSON.stringify(data);
const signOptions = {
method: 'POST',
url: 'https://zzzz.appsync-api.eu-west-2.amazonaws.com/graphql,
host: 'zzzz.appsync-api.eu-west-2.amazonaws.com',
path: '/graphql',
region: process.env.AWS_REGION,
service: 'appsync',
headers: {
'content-type': 'application/json',
},
body, // used by aws4
data, // used by axios
};
const creds = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
sessionToken: process.env.AWS_SESSION_TOKEN,
};
const signed = aws4.sign(signOptions, creds);
delete signed.headers.Host;
delete signed.headers['Content-Length'];
console.log(signed);
console.log('Got signed headers');
try {
await axios(signed);
} catch (err) {
console.error('Fialed to execute AppSync request', err);
throw err;
}
I am always getting a Request failed with status code 401 response back. The AppSync logs don't reveal much more:
{
"logType": "RequestSummary",
"requestId": "11111111",
"graphQLAPIId": "some id",
"statusCode": 401,
"latency": 7079000
}
What am I missing in configuring this correctly? Is there something I need to add to allow AWS IAM only for a specific mutation or do you see any issue with the way I am trying to execute the request?
These are the articles I was referencing:
manually-triggering-appsync-apollo-subscriptions
calling-amazon-api-gateway-authenticated-methods-with-axios-and-aws4
Got it working. I had to add additional authentication provider to the AWS::AppSync::GraphQLApi. In CloudFormation, this looks like an additional property:
Type: AWS::AppSync::GraphQLApi
Properties:
Name: !Ref MyApi
AuthenticationType: AMAZON_COGNITO_USER_POOLS
UserPoolConfig:
UserPoolId: !Ref MyPool
AwsRegion: !Ref MyRegion
DefaultAction: ALLOW
AdditionalAuthenticationProviders:
- AuthenticationType: AWS_IAM
After making this work, I got a response from AppSync GraphQL but it contained GraphQL errors for all fields in the response object:
Not Authorized to access someId on type Something
To get around this I had to also allow IAM on this type in the GraphQL Schema:
type Something #aws_cognito_user_pools #aws_iam {
someId: ID!
}
I am new to AWS and I've slowly been trying to perform different actions. I recently set up an API that allows me to query a dynamodb table and now I am trying to set up an api that will allow me to update a value in the table with the current temperature. This data will come from a script running on a raspberry pi.
I've been wading through so many tutorials but I haven't gotten this quite locked down. I am able to write to the db using a hard-coded python script so I know my db and roles is set up correctly. I am now trying to create a node-based lambda function that will accept parms from the URL and put the values into the table. I am missing something.
First, do I need to map the values in the api? Some guides do it, others do not. Like I said, ideally I want to pass them in as URL parms.
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
exports.handler = (event, context, callback) => {
dynamodb.putItem({
TableName: "temperature",
Item: {
"tempid": {
S: event.queryStringParameters["tempid"]
}
}
}, function(err, data) {
if (err) {
console.log(err, err.stack);
callback(null, {
statusCode: '500',
body: err
});
} else {
callback(null, {
statusCode: '200',
body: 'Result from ' + event.queryStringParameters["tempid"] + '!'
});
}
})
};
When I test it in the api using "tempid=hotttub1" in the query string I get this error:
START RequestId: 1beb4572-65bf-4ab8-81a0-c217677c3acc Version: $LATEST
2020-07-09T14:02:05.773Z 1beb4572-65bf-4ab8-81a0-c217677c3acc INFO { tempid: 'hottub1' }
2020-07-09T14:02:05.774Z 1beb4572-65bf-4ab8-81a0-c217677c3acc ERROR Invoke Error {"errorType":"TypeError","errorMessage":"Cannot read property 'tempid' of undefined","stack":["TypeError: Cannot read property 'tempid' of undefined"," at Runtime.exports.handler (/var/task/index.js:11:47)"," at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"]}
EDIT
If I print out event I can see that the value is coming in and I am apparently referencing it wrong. Still looking.
{
"tempid": "hottub1"
}
It needed to be in this format:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
exports.handler = (event, context, callback) => {
console.info("EVENT\n" + JSON.stringify(event.tempid, null, 2))
var temperatureid = JSON.stringify(event.tempid, null, 2)
dynamodb.putItem({
TableName: "temperature",
Item: {
"tempid": {
S: temperatureid
}
}
GetClientId:
Type: "AWS::Lambda::Function"
Properties:
Handler: index.handler
Role: !GetAtt LambdaESCognitoRole.Arn
Code:
ZipFile: !Sub |
var AWS = require('aws-sdk');
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var response = require('cfn-response');
var responseData = {};
exports.handler = async (event, context) => {
console.log(JSON.stringify(event, null, 2));
var params = {
UserPoolId: event.ResourceProperties.UserPoolId
};
await cognitoidentityserviceprovider.listUserPoolClients(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data); // successful response
responseData = {'ClientId': data.UserPoolClients[0].ClientId};
}
}).promise();
response.send(event, context, response.SUCCESS, responseData);
return;
}
Runtime: nodejs8.10
CallGetClientId:
Type: 'Custom::CallGetClientId'
Version: 1.0
Properties:
ServiceToken: !GetAtt GetClientId.Arn
UserPoolId: !Ref CognitoUserPool
IdentityPoolRoleMapping:
Type: "AWS::Cognito::IdentityPoolRoleAttachment"
Properties:
IdentityPoolId: !Ref CognitoIdentityPool
Roles:
authenticated: !GetAtt AuthenticatedRole.Arn
unauthenticated: !GetAtt UnauthenticatedRole.Arn
RoleMappings:
"cognito-identity-provider":
IdentityProvider: !Join ['', [ !GetAtt CognitoUserPool.ProviderName, ':', !GetAtt CallGetClientId.ClientId ]] #Need to get the ClientID here
AmbiguousRoleResolution: Deny
Type: Rules
RulesConfiguration:
Rules:
- Claim: "custom:groups"
MatchType: "Contains"
RoleARN: !GetAtt AuthenticatedRole.Arn
Value: "user"
- Claim: "custom:groups"
MatchType: "Contains"
RoleARN: !GetAtt AuthenticatedAdminRole.Arn
Value: "admin"
I see two ways to resolve the issue.
One - use the cfnresponse.send(...responseData) parameter. See here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html#w2ab1c17c25c14b9c11
My example:
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, responseData['ClientSecret'])
Once you returned data from Lambda, you can refer to it in CFN template with !GetAtt:
Value: !GetAtt HapiUserPoolClientPostProc.ClientSecret
Two - I use custom resources as a components "post-processor", i.e. create the resources, and update their parameters with a custom resource after. This order will be guaranteed by custom resource lambda input parameters (dependency).
My example was to feed in Cognito AppClient callback URLs from my ElasticBeanstalk WebApp. So I create both the UserPool AppClient and the EB webapp, then a post-processor custom resource lambda takes the URL from EB and updates the CallbackURL in Cognito.
Hope this helps.
With the following AWS SAM template process.env is an empty object. I expect it to contain the environment variable from the template defined as dbURL.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: "An example RESTful service"
Resources:
ExampleFunction:
Type: "AWS::Serverless::Function"
Properties:
Runtime: "nodejs6.10"
Handler: "/dist/getTickets/index.handler"
Events:
RootDeveloperHub:
Type: "Api"
Properties:
Path: "/new"
Method: "any"
Environment:
Variables:
dbURL: "dbURL_Value"
handler
exports.handler = (event, context, callback) => {
// logs {}
console.log(process.env)
}
Things I've ruled out:
webpack react process.env always empty (windows 10) - I don't have a DefinePlugin configuration. I'm also using mac.
I should have tested the code I posted...
The handler works when it is set up as
exports.handler = (event, context, callback) => {
// logs {}
console.log(process.env)
}
The fix for my code was to add the following to my webpack config.
{
...
target: 'node'
}