How to read and use data from DynamboDB in an Alexa skill - javascript

I am building an Alexa skill that retrieves some data from a DynamoDB table and then needs to use it in another function or as speech output. My idea was to use async/await but I'm not sure how to do it.
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-2'});
var docClient = new AWS.DynamoDB.DocumentClient({apiVersion: '2012-08-10'});
var params = {
TableName: 'someTableName',
Key: {'UserID': '1'}
};
var getDataPromise = docClient.get(params).promise();
getDataPromise.then(
function(data) {
console.log(data.Item); // I need to return data.Item to somehow use in .speechoutput()
}
).catch(
function(err) {
console.log(err);
}
);
If I create an async function, I'm not sure where I would put await or how I would return the data to .speechoutput(). I have tried putting await in front of the docClient function, but it doesn't seem to work.
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-2'});
var docClient = new AWS.DynamoDB.DocumentClient({apiVersion: '2012-08-10'});
async function getData() {
var params = {
TableName: 'someTableName',
Key: {'UserID': '1'}
};
var getDataPromise = await docClient.get(params).promise();
getDataPromise.then(
function(data) {
console.log(data.Item); // I need to return data.Item to somehow use in .speechoutput()
}
).catch(
function(err) {
console.log(err);
}
);
}
getData();

Looks like you might not be getting async/await.
Await makes JS wait until a promise is resolved and gets whatever is returned on resolution.
getDataPromise contains the result of the docClient.getParams() operation once it resolves, which should be the data object.
AFAICT, your code fails because you are trying to "then" a plain old data object.

Related

Calling a function that contains a promise from another function

I am trying to learn how to use promises in NodeJs and I am using the AWS-SDK library to access an S3 object. My goal is to call the readFromS3() function from within the init() function and print out the contents of the file. However, I am not getting the results I want as shown below by the first console.log statement inside the init(). I understand that the promise is not complete and wanted your suggestions on how I can block execution until the news object is NOT null ??
const AWS = require('aws-sdk');
const S3 = new AWS.S3({});
const CONFIG = {
init() {
const news = CONFIG.readFromS3();
console.log('These are the file contents ' + JSON.stringify(news));
console.log('THIS IS THE END. This should only print after news have been read from S3');
},
readFromS3() {
// set parameters for reading S3 files
const options = {
Bucket: 'my-bucket',
Key: 'myFile.txt'
};
// create a promise to read from S3
const readS3Promise = S3.getObject(options).promise();
// start reading from s3
readS3Promise
.then(function(data) {
return JSON.parse(data.Body);
});
})
.catch(function(error) {
console.log('ERROR: Cannot read from S3');
throw error;
});
}
};
CONFIG.init();
However, My output currently is like this unfortunately:
These are the SPECS undefined
THIS IS THE END. This should only print after news have been read from S3
{... // JSON data from S3 printed out
You need to return your promise from read method and await that call inside init function so you can get output. something like this
const AWS = require('aws-sdk');
const S3 = new AWS.S3({});
const CONFIG = {
async init() {
const news = await CONFIG.readFromS3();
console.log('These are the file contents ' + JSON.stringify(news));
console.log('THIS IS THE END. This should only print after news have been read from S3');
},
readFromS3() {
// set parameters for reading S3 files
const options = {
Bucket: 'my-bucket',
Key: 'myFile.txt'
};
// create a promise to read from S3
const readS3Promise = S3.getObject(options).promise();
// start reading from s3
return readS3Promise
.then(function(data) {
return JSON.parse(data.Body);
});
})
.catch(function(error) {
console.log('ERROR: Cannot read from S3');
throw error;
});
}
};
CONFIG.init();

Key element does not match the schema with DocumentClient

I have a Lambda that I have created using the AWS Toolkit for Visual Studio Code. I have a small lambda.js file that exports the handler for API Gateway events.
const App = require('./app');
const AWS = require('aws-sdk');
exports.handler = async (event, context) => {
let config = {
aws_region: 'us-east-1',
//endpoint: 'http://localhost:8000',
};
let dynamo = new AWS.DynamoDB.DocumentClient();
let app = new App(dynamo, config, event);
try {
let response = await app.run();
return response;
} catch(err) {
return err;
}
};
The app.js file represents the logic of my Lambda. This is broken up to improve testing.
const AWS = require('aws-sdk');
class App {
constructor(datastore, configuration, event) {
this.datastore = datastore;
this.httpEvent = event;
this.configuration = configuration;
}
async run() {
AWS.config.update({
region: this.configuration.aws_region,
endpoint: this.configuration.endpoint,
});
let params = {
TableName: 'test',
Key: {
'year': 2015,
'title': 'The Big New Movie',
},
};
const getRequest = this.datastore.get(params);
// EXCEPTION THROWN HERE
var result = await getRequest.promise();
let body = {
location: this.configuration.aws_region,
url: this.configuration.endpoint,
data: result
};
return {
'statusCode': 200,
'body': JSON.stringify(body)
};
}
}
module.exports = App;
I can write the following mocha test and get it to pass.
'use strict';
const AWS = require('aws-sdk');
const chai = require('chai');
const sinon = require('sinon');
const App = require('../app');
const expect = chai.expect;
var event, context;
const dependencies = {
// sinon.stub() prevents calls to DynamoDB and allows for faking of methods.
dynamo: sinon.stub(new AWS.DynamoDB.DocumentClient()),
configuration: {
aws_region: 'us-west-2',
endpoint: 'http://localhost:5000',
},
};
describe('Tests handler', function () {
// Reset test doubles for isolating individual test cases
this.afterEach(sinon.reset);
it('verifies successful response', async () => {
dependencies.dynamo.get.returns({ promise: sinon.fake.resolves('foo bar')});
let app = new App(dependencies.dynamo, dependencies.configuration, event);
const result = await app.run()
expect(result).to.be.an('object');
expect(result.statusCode).to.equal(200);
expect(result.body).to.be.an('string');
console.log(result.body);
let response = JSON.parse(result.body);
expect(response).to.be.an('object');
expect(response.location).to.be.equal(dependencies.configuration.aws_region);
expect(response.url).to.be.equal(dependencies.configuration.endpoint);
expect(response.data).to.be.equal('foo bar');
});
});
However, when I run the Lambda locally using the Debug Locally via the Code Lens option in VS Code the results from the AWS.DynamoDB.DocumentClient.get call throws an exception.
{
"message":"The provided key element does not match the schema",
"code":"ValidationException",
...
"statusCode":400,
"retryable":false,
"retryDelay":8.354173589804192
}
I have a table created in the us-east-1 region where the non-test code is configured to go to. I've confirmed the http endpoint being hit by the DocumentClient as being dynamodb.us-east-1.amazonaws.com. The table name is correct and I have a hash key called year and a sort key called title. Why would this not find the key correctly? I pulled this example from the AWS documentation, created a table to mirror what the key was and have not had any luck.
The issue is that the Key year was provided a number value while the table was expecting it to be a string.
Wrapping the 2015 in quotes let the Document know that this was a string and it could match to the schema in Dynamo.
let params = {
TableName: 'test',
Key: {
'year': '2015',
'title': 'The Big New Movie',
},
};

How to make a async/await function in AWS Lambda when using aws-sdk

I am using AWS lambda to get some data from cloudwatch metric and below is my code in lambda
var AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-1'});
var cloudwatch = new AWS.CloudWatch({apiVersion: '2010-08-01'});
exports.handler = async function(event, context) {
console.log('==== start ====');
const connection_params = {
// params
};
cloudwatch.getMetricStatistics(connection_params, function(err, data) {
if (err){
console.log(err, err.stack);
} else {
console.log(data);
active_connection = data.Datapoints[0].Average.toFixed(2);
}
console.log(`DDDDDDD ${active_connection}`);
});
console.log('AAAA');
};
I always get 'AAAA' first and then get 'DDDD${active_connection }'.
But what i want is get 'DDDD${active_connection }' first and then 'AAAA'.
I tried to use like
cloudwatch.getMetricStatistics(connection_params).then(() => {})
but show
cloudwatch.getMetricStatistics(...).then is not a function
Try writing your code like this,
With then
const x = cloudwatch.getMetricStatistics(connection_params).promise();
x.then((response) => do something);
With async/await
const x = await cloudwatch.getMetricStatistics(connection_params).promise();
You could use util#promisify Docs
const util = require("util");
const cloudwatch = new AWS.CloudWatch();
const getMetricStatistics = util.promisify(cloudwatch.getMetricStatistics.bind(cloudwatch));
getMetricStatistics(connection_params).then(() => {})

Streaming video from AWS Kinesis to JS client

I'm trying to consume the video from an AWS Kinesis stream. The stream is visible in the AWS console, but I cannot consume it in the JS application I'm trying to create.
I've been following this tutorial, but cannot get the streaming URL.
My code is here:
import React, { Component} from 'react'
import ReactPlayer from 'react-player'
import AWS from "aws-sdk";
import { STREAM_NAME, ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION } from '../secrets'
var streamName = STREAM_NAME;
// Step 1: Configure SDK Clients
var options = {
accessKeyId: ACCESS_KEY_ID,
secretAccessKey: SECRET_ACCESS_KEY,
region: REGION
}
var kinesisVideo = new AWS.KinesisVideo(options);
var kinesisVideoArchivedContent = new AWS.KinesisVideoArchivedMedia(options);
// Step 2: Get a data endpoint for the stream
kinesisVideo.getDataEndpoint({
StreamName: streamName,
APIName: "GET_HLS_STREAMING_SESSION_URL"
}, function(err, response) {
if (err) { return console.error(err); }
console.log('Data endpoint: ' + response.DataEndpoint);
kinesisVideoArchivedContent.endpoint = new AWS.Endpoint(response.DataEndpoint);
});
// Step 3: Get an HLS Streaming Session URL
console.log('Fetching HLS Streaming Session URL');
var playbackMode = 'LIVE'; // 'LIVE' or 'ON_DEMAND'
//var startTimestamp = new Date('START_TIMESTAMP'); // For ON_DEMAND only
//var endTimestamp = new Date('END_TIMESTAMP'); // For ON_DEMAND only
var fragmentSelectorType = 'SERVER_TIMESTAMP'; // 'SERVER_TIMESTAMP' or 'PRODUCER_TIMESTAMP'
const SESSION_EXPIRATION_SECONDS = 60*60
console.log(kinesisVideo)
const hlsUrl = kinesisVideoArchivedContent.getHLSStreamingSessionURL({
StreamName: streamName,
//StreamARN: "arn:aws:kinesisvideo:us-east-1:635420739373:stream/mr-pinchers-dot-org/1561848963391",
PlaybackMode: playbackMode,
HLSFragmentSelector: {
FragmentSelectorType: fragmentSelectorType,
TimestampRange: playbackMode === 'LIVE' ? undefined : {
// StartTimestamp: startTimestamp,
// EndTimestamp: endTimestamp
}
},
Expires: parseInt(SESSION_EXPIRATION_SECONDS)
}, function(err, response) {
if (err) { return console.error("Darn", err); }
console.log('HLS Streaming Session URL: ' + response.HLSStreamingSessionURL, response);
}
)
console.log("here", hlsUrl)
class Home extends Component {
render () {
return <ReactPlayer url={hlsUrl} playing={true} />
}
}
export default Home
The response I'm getting in Step 3 (response.HLSStreamingSessionURL) is undefined.
Step 2 runs fine, and I get an endpoint back, so I'm confident that it's not a permissions problem.
Part of me thinks that I should be using some async/await calls but I'm not sure, still pretty new to JS and all that async stuff so didn't know how to incorporate it into this.
I've spent quite a bit of time trying to figure this out but the documentation on Kinesis is still pretty light, although if someone has a good resource for it, please let me know.
This is basic JavaScript async behavior. You're executing step 3 before step 2 is complete. You can't use the response before it's happened.
You can fix this by starting step 3 when step 2 has completed, as follows:
kinesisVideo.getDataEndpoint({
StreamName: streamName,
APIName: "GET_HLS_STREAMING_SESSION_URL"
}, function(err, response) {
if (err) { return console.error(err); }
console.log('Data endpoint: ' + response.DataEndpoint);
kinesisVideoArchivedContent.endpoint = new AWS.Endpoint(response.DataEndpoint);
var playbackMode = 'LIVE';
var fragmentSelectorType = 'SERVER_TIMESTAMP';
const SESSION_EXPIRATION_SECONDS = 60*60
kinesisVideoArchivedContent.getHLSStreamingSessionURL({...});
// remainder of code here
});
Or you can use async/await and promise variants of the AWS SDK methods like so:
(async () => {
const kv_response = await kv.getDataEndpoint({...}).promise();
// ...
const hls_response = await kvac.getHLSStreamingSessionURL({...}).promise();
})();
Note that await may only be used inside an async function, hence the anonymous async wrapper.

Node.js: Awaiting a Require

I'm new the Node.js and I've been working with a sample project by a third party provider and I'm trying to use Azure Key Vault to store configuration values.
I'm having trouble getting a process to wait before executing the rest. I'll try to detail as much as I know.
The sample project has a file named agent.js which is the start page/file. On line 16 (agent_config = require('./config/config.js')[process.env.LP_ACCOUNT][process.env.LP_USER]) it calls a config file with values. I'm trying to set these value using Key Vault. I've tried many combinations of calling functions, and even implementing async / await but the value for agent_config always contains a [Promise] object and not the data returned by Key Vault.
If I'm right, this is because the Key Vault itself uses async / await too and the config file returns before the Key Vault values are returned.
How can Key Vault be added/implemented in a situation like this?
Here's what I've tried:
First updated agent.js to
let agent_config = {};
try {
agent_config = require('./config/config.js')['123']['accountName'];
} catch (ex) {
log.warn(`[agent.js] Error loading config: ${ex}`)
}
console.log(agent_config);
Test 1
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
Results
{ accountsId: undefined }
Test 2
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' }).then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
async function config() {
module.exports = {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
config();
Results
{}
Test 3
Made getValue an async function and wrapped it around another function (tried without the wrapping and didn't work either)
./config/config.js
const KeyVault = require('azure-keyvault');
const msRestAzure = require('ms-rest-azure');
const KEY_VAULT_URI = 'https://' + '{my vault}' + '.vault.azure.net/' || process.env['KEY_VAULT_URI'];
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
module.exports = {
'123': {
'accountName': {
accountId: getValue('mySecretName', '')
}
}
};
config();
Results
{ accountId: { <pending> } }
Other
I've tried many others ways like module.exports = async (value) =< {...} (found through other questions/solutions without success.
I'm starting to think I need to do some "waiting" on agent.js but I haven't found good info on this.
Any help would be great!
One issue is that your getValue function is not returning anything as your returns need to be explicit.
(and without the promise being returned, there's nothing to await on)
async function getValue(secretName, secretVersion) {
return msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then((credentials) => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion).then(
function (response) {
return response.Value;
});
});
}
You could also get away with less explicit returns using arrow functions..
const getValue = async (secretName, secretVersion) =>
msRestAzure.loginWithAppServiceMSI({ resource: 'https://vault.azure.net' })
.then(credentials => {
const client = new KeyVault.KeyVaultClient(credentials);
return client.getSecret(KEY_VAULT_URI, secretName, secretVersion)
.then(response => response.Value);
});
Introducing the Azure Key Vault read, which is async, means your whole config read is async. There' nothing you can do to get around that. This will mean that the code that uses the config will need to handle it appropriately. You start by exporting an async function that will return the config..
async function getConfig() {
return {
'123': {
'accountName': {
accountId: await getValue('mySecretName', '')
}
}
};
}
module.exports = getConfig;
In your agent code you call that function. This will mean that your agent code will need to be wrapped in a function too, so maybe something like this..
const Bot = require('./bot/bot.js');
const getConfig = require('./config/config.js');
getConfig().then(agentConfig => {
const agent = new Bot(agentConfig);
agent.on(Bot.const.CONNECTED, data => {
log.info(`[agent.js] CONNECTED ${JSON.stringify(data)}`);
});
});
The package azure-keyvault has been deprecated in favor of the new packages to deal with Keyvault keys, secrets and certificates separately. For your scenario, you can use the new #azure/keyvault-secrets package to talk to Key Vault and the new #azure/identity package to create the credential.
const { SecretClient } = require("#azure/keyvault-secrets");
const { DefaultAzureCredential } = require("#azure/identity");
async function getValue(secretName, secretVersion) {
const credential = new DefaultAzureCredential();
const client = new SecretClient(KEY_VAULT_URI, credential);
const secret = await client.getSecret(secretName);
return secret.value;
}
The DefaultAzureCredential assumes that you have set the below env variables
AZURE_TENANT_ID: The tenant ID in Azure Active Directory
AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant
AZURE_CLIENT_SECRET: The client secret for the registered application
To try other credentials, see the readme for #azure/identity
If you are moving from the older azure-keyvault package, checkout the migration guide to understand the major changes

Categories

Resources