alexa / alexa-skills-kit-sdk-for-nodejs

The Alexa Skills Kit SDK for Node.js helps you get a skill up and running quickly, letting you focus on skill logic instead of boilerplate code.
Apache License 2.0
3.12k stars 736 forks source link

Using the DynamoDbPersistenceAdapter results in module initialization error #498

Closed stefan-spittank closed 5 years ago

stefan-spittank commented 5 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:

Expected Behavior

Using the DynamoDbPersistenceAdapter to have a way to store information session independent.

Current Behavior

The usage of the DynamoDbPersistenceAdapter results in an error, so that the lambda for the skill won't run. In the cloud watch logs I see the following error:

module initialization error: AskSdk.DynamoDbPersistenceAdapter Error at Object.createAskSdkError (/var/task/index.js:6609:15) at Response. (/var/task/index.js:3191:32) at Request. (/var/task/index.js:20379:18) at Request.callListeners (/var/task/index.js:22131:20) at Request.emit (/var/task/index.js:22098:10) at Request.emit (/var/task/index.js:20709:14) at Request.transition (/var/task/index.js:20042:10) at AcceptorStateMachine.runTo (/var/task/index.js:25761:12) at /var/task/index.js:25772:10 at Request. (/var/task/index.js:20058:9)

Steps to Reproduce (for bugs)

What I've done so far:

I've setup the DynamoDbPersistenceAdapter in the index.js where I configure the skill:

import {DynamoDbPersistenceAdapter} from 'ask-sdk-dynamodb-persistence-adapter';

const persistenceAdapter = new DynamoDbPersistenceAdapter({
    tableName: 'RecipeState',
    createTable: true
});

const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
    .addRequestHandlers(
        //skipped
    )
    .addErrorHandlers(ErrorHandler)
    .withPersistenceAdapter(persistenceAdapter)
    .withApiClient(new Alexa.DefaultApiClient())
    .lambda();

In the handler I'm trying to use the persistent Adapter, e.g. with: const attributes = await handlerInput.attributesManager.getPersistentAttributes(); I also added a new policy to the role my lambda is executed with:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "dynamodb:CreateTable",
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:PutItem",
                "dynamodb:GetItem",
                "dynamodb:UpdateItem",
                "dynamodb:UpdateTable"
            ],
            "Resource": "*"
        }
    ]
}

though I now event tried to use the policy AmazonDynamoDBFullAccess.

I tried with and without uploading my local node_modules folder.

The lambda runs in europe, so I tried adding:

const AWS = require("aws-sdk");

AWS.config.update({region: 'eu-west-1'});

Context

Using the DynamoDbPersistenceAdapter to have a way to store information session independent

Your Environment

        "ask-sdk": "^2.0.0",
        "ask-sdk-core": "^2.3.0",
        "ask-sdk-dynamodb-persistence-adapter": "^2.3.0",
        "ask-sdk-model": "^1.11.1",

Node.js and NPM Info

stefan-spittank commented 5 years ago

Hmm, there is a test button in the lambda management console. If I execute this I get a more descriptive error:

{
  "errorMessage": "Could not create table (RecipeState): Missing credentials in config",
  "errorType": "AskSdk.DynamoDbPersistenceAdapter Error",
  "stackTrace": [
    "Response.<anonymous> (/var/task/index.js:3191:32)",
    "Request.<anonymous> (/var/task/index.js:20379:18)",
    "Request.callListeners (/var/task/index.js:22131:20)",
    "Request.emit (/var/task/index.js:22098:10)",
    "Request.emit (/var/task/index.js:20709:14)",
    "Request.transition (/var/task/index.js:20042:10)",
    "AcceptorStateMachine.runTo (/var/task/index.js:25761:12)",
    "/var/task/index.js:25772:10",
    "Request.<anonymous> (/var/task/index.js:20058:9)"
  ]
}

Not sure which credentials in which config are missing, though.

Logging the config in the index.js verifies that the credentials are missing:

console.log('AWS.config.credentials' ,AWS.config.credentials);
console.log('AWS.config.region' ,AWS.config.region);

2018-12-30T11:49:31.903Z f843d1b9-0c28-11e9-9214-65238ed765ad AWS.config.credentials null 2018-12-30T11:49:31.904Z f843d1b9-0c28-11e9-9214-65238ed765ad AWS.config.region null

stefan-spittank commented 5 years ago

The docs at: https://docs.aws.amazon.com/de_de/sdk-for-javascript/v2/developer-guide/loading-node-credentials-lambda.html

state, that:

The execution role provides the Lambda function with the credentials it needs to run and to invoke other web services. As a result, you do not need to provide credentials to the Node.js code you write within a Lambda function.

Just to get some progress I tried it anyway. Additionally to the role I already created I created a new user with that role, dowloaded it's credentials and tried to load the aws-config from a config file: AWS.config.loadFromPath('./aws-config.json');

However this results in a new error:

{
  "errorMessage": "AWS.FileSystemCredentials is not a constructor",
  "errorType": "TypeError",
  "stackTrace": [
    "Module../src/index.js (/var/task/index.js:52471:12)",
    "__webpack_require__ (/var/task/index.js:20:30)",
    "./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js.module.exports (/var/task/index.js:84:18)",
    "Object.<anonymous> (/var/task/index.js:87:10)",
    "Module._compile (module.js:652:30)",
    "Object.Module._extensions..js (module.js:663:10)",
    "Module.load (module.js:565:32)",
    "tryModuleLoad (module.js:505:12)",
    "Function.Module._load (module.js:497:3)"
  ]
}

I even tried to set the credentials from code (bad idea I know, just to see whether this works):

AWS.config.update({
    region: 'eu-west-1'
    credentials: new AWS.Credentials('(skipped)', '(skipped)')
});

But with this the lambda times out.

I somehow suspect my lambda running in EU/Ireland to be the root of the problem. That seems to be the major difference to the examples I've found so far...

stefan-spittank commented 5 years ago

I created completely new / empty lambda in region us-east-1, updated all my cli stuff and ran into the same problem. Probably not related to the region? Wouldn't be a solution anyway, since I can't set the endpoint of my voice interaction model to a lamda in that region (if I try I get the error "The trigger setting for the Lambda arn:aws:lambda:us-east-1:668867983508:function:newRecipe is invalid. Error code: SkillManifestError").

tianrenz commented 5 years ago

Hi @pixelos-cc ,

I've tried to replicate the error and was unable to do so. Could you try the following code snippet and pass the credential inline?

const persistenceAdapter = new DynamoDbPersistenceAdapter({
    tableName: 'RecipeState',
    createTable: true
    dynamoDBClient : new DynamoDB({
      credentials: ...
    })
});

Regards
stefan-spittank commented 5 years ago

Hi @tianrenz,

thanks for your reply. I've spent now several days on this issue and finally got it working.

Long story short: I think this issue can be closed, it a configuration error on my side, though I still not fully understand what exactly happend.

The long version (maybe it helps someone else having a similar issue).

I said I could replicate this issue with a new lambda. This was true but not the whole story. I created a new lambda and uploaded my existing code to this lambad with the cli.

I realized that this only proofed that the lambda configuration is not an issue. So I created yet another lambda, configured it and created a simple skill handler with the PersisteneAdapter in the online editor. It worked. But as soon as I uploaded my real code it was broken again.

Step by step I stripped to my code to one single file and this worked. So it seemed to be bundling issue? I've spent the last two days on creating a configuration which allows me to work with async await, import/exports, jest and flow.

This repo was a big help: https://github.com/buildbreakdo/lambda-starter

Though I hat to change the way I export my handler to:

export const handler = skillBuilder...

My final webpack.config:

module.exports = {
    target: 'node',
    entry: [ './src/index.js' ],
    output: {
        path: OUT_DIR,
        filename: 'main.js',
        library: 'main',
        libraryTarget: 'commonjs2',
    },
    externals: [ 'aws-sdk' ],
    plugins: [
        new webpack.IgnorePlugin(/^pg-native$/),
        new webpack.DefinePlugin({
            'process.env.BROWSER': false,
            __DEV__: process.env.NODE_ENV !== 'production',
        }),
    ],
    module: {
        rules: [
            {
                test: /\.(mjs|js|jsx)$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    configFile: './babel.config.js'
                }
            }
        ],
    },
    mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
};

The main difference to the non working version?

Which of these changes fixed my problem and why is beyound me. Maybe the trick was excluding the the aws-sdk because the used library in aws differs from the one I installed from npm?

If the bundling would have been completely broken the error would have been clear, but only connecting to the db somehow loses a configuration sounds weird. Anyhow feel free to close this issue if you agree that there is no error on your side.

tianrenz commented 5 years ago

Hi @pixelos-cc ,

Glad the issue got resolved. We've noticed that the configuration of webpack can sometimes cause problems with aws-sdk credential configuration. My best guess is the webpack removed some logic in aws-sdk when doing the bundling. But adding the target as node should fix the problem.

Please see this issue fore some reference info.

Regards