Using string functions in Cloudformation for Lambda Code - string

I have a cloudformation snippet that looks roughly like this:
"LambdaScale": {
"Type": "AWS::Lambda::Function",
"Properties": {
...
"Code": {
"S3Bucket": {
{
"Ref": "LambdaBucket"
}
},
"S3Key": {
"Fn::Join": [
"/",
[
{
"Ref": "LambdaDirectoryKey"
},
"some_func.zip"
]
]
}
},
...
}
But when I try to run this, I get the following error:
An error occurred (ValidationError) when calling the CreateStack
operation: Template format error:
[/Resources/LambdaScale/Type/Code/S3Bucket] map keys must be strings;
received a map instead
Reading this, it makes me think that the S3Bucket and S3Key properties are expecting a string literal, and do not support the string manipulation functions. Can this really be true? If so, that is a huge barrier for deploying these templates on different environments.
Is there perhaps a workaround I have not considered? Thanks for any advice!

You have:
"S3Bucket": {
{
"Ref": "LambdaBucket"
}
},
it should probably be:
"S3Bucket": {
"Ref": "LambdaBucket"
},

Related

Sap Commerce Cloud cannot generate build with custom addon

I've installed in my local environment customgigyaaddon (an extended version of gigyaloginaddon), injected in my customstorefront and added to localextensions.xml and compiled without any problems, however, when I try to generate a build of this same version in sap commerce cloud, it throws me the following exception :
/opt/workspace/build/commerce-
suite/hybris/bin/platform/resources/ant/antmacros.xml:587:
java.lang.IllegalArgumentException: cannot merge namespace ((customgigyaaddon)) into
((<merged>)) due to duplicate type code 'gigyaconfig2cmssite' : GigyaConfig2CMSSite::
((customgigyaaddon))::YRelation[customgigyaaddon-items.xml:14(RelationTypeTagListener)]
<>GigyaConfig2CMSSite::((gigyaservices))::YRelation[gigyaservices-
items.xml:65(RelationTypeTagListener)]
My localextensions-cloud.xml is exactly the same as my localextensions.xml and in the manifest.json I've declared the addon injection as follows:
{
"commerceSuiteVersion":"2105",
"enableImageProcessingService": true,
"extensionPacks" : [
{
"name" : "hybris-commerce-integrations",
"version" : "2108.2"
}
],
"extensions":[
"sapymktcommon",
"sapymktclickstream",
"sapymktrecommendation",
"sapymktrecommendationbuffer",
"sapymktrecommendationwebservices",
"cloudmediaconversion",
"mediaconversionbackoffice",
"sapymktsegmentation",
"sapymktsegmentationb2b",
"sapymktsegmentationwebservices",
"personalizationymkt",
"integrationbackoffice",
"outboundsyncbackoffice",
"sapymktdatareplication",
"assistedservicestorefront"
],
"useConfig":{
"properties":[
{
"location":"config/local-dev.properties",
"persona":"development"
},
{
"location":"config/local-stag.properties",
"persona":"staging"
},
{
"location":"config/local-prd.properties",
"persona":"production"
}
],
"extensions":{
"location":"/config/localextensions-cloud.xml"
},
"solr":{
"location":"customSolr"
}
},
"storefrontAddons":[
{
"addons":["commerceorgsamplesaddon","smarteditaddon","textfieldconfiguratortemplateaddon","customerticketingaddon","orderselfserviceaddon","consignmenttrackingaddon","marketplaceaddon","notificationaddon","customerinterestsaddon","stocknotificationaddon","customaddon","configurablebundleaddon","sapymktrecommendationaddon"],
"storefronts":["customstorefront"],
"template":"yacceleratorstorefront"
},
{
"addons":["customgigyaaddon"],
"storefronts":["customstorefront"],
"template":"yacceleratorstorefront"
}
],
"aspects":[
{
"name":"backoffice",
"webapps":[
{
"name":"mediaweb",
"contextPath":"/medias"
},
{
"name":"backoffice",
"contextPath":"/backoffice"
},
{
"name":"hac",
"contextPath":"/hac"
},
{
"name":"dathubadapter",
"contextPath":"/datahubadapter"
},
{
"name":"oauth2",
"contextPath":"/authorizationserver"
},
{
"name": "customstorefront",
"contextPath": "/valet"
}
{
"name": "personalizationsmartedit",
"contextPath": "/personalizationsmartedit"
},
{
"name": "personalizationpromotionssmartedit",
"contextPath": "/personalizationpromotionssmartedit"
},
{
"name": "personalizationwebservices",
"contextPath": "/personalizationwebservices"
},
{
"name": "personalizationsearchsmartedit",
"contextPath": "/personalizationsearchsmartedit"
},
{
"name":"previewwebservices",
"contextPath":"/previewwebservices"
},
{
"name":"permissionswebservices",
"contextPath":"/permissionswebservices"
},
{
"name":"ycommercewebservices",
"contextPath":"/rest"
}
]
}
],
"webapps":[
{
"name":"customstorefront",
"contextPath":"/custom"
}
]
},
{
"name":"backgroundProcessing",
"properties":[
{
"key":"cluster.node.groups",
"value":"integration,yHotfolderCandidate"
}
],
"webapps":[
{
"name":"hac",
"contextPath":"/hac"
},
{
"name":"mediaweb",
"contextPath":"/medias"
}
]
}
]
}
],
"properties": [
{
"key":"configFile",
"value":"/opt/hybris/bin/custom/resources/update-config.json"
}
]
}
Looking the exception it looks like there is already an relation gigyaconfig2cmssite declared in gigyaservices-items.xml, however this is not true since this specific relation is only declared in customgigyaservices-items.xml.
Am I missing something? I feel really lost
if you analyzed the issue as follows:customgigyaaddon-items.xml and gigyaservices-items.xml, both contain the entry.
Otherwise, have you tried to ant clean all the platform? Also, set generate and auto-create as false and removed the extends.

AJV validate schema with JSONPath strings

I'm trying to create a JSON schema that can support validating JSON objects with property values that can either be regular JSON types OR strings representing valid JSONpath expressions.
So for example, given this schema:
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"type": "number"
}
}
}
Either of these JSON objects could be valid:
{
"age": 30
}
{
"age" "$.age"
}
I've gotten stuck trying to add a custom keyword called jsonPath like this:
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"type": "number",
"jsonPath": true
}
}
}
ajv.addKeyword('jsonPath', {
valid: true,
compile: () => data => {
return /^\$./.test(data)
}
})
Ideally I would love to just be able to check if a given property value is a valid JSONPath string and if so, approve it. Otherwise let ajv run it's own validation.
Thanks for any help!
I don't know if you can prevent other keywords from running. There are multiple ways to apply checks in JSON Schema to the same location, so this would likely be pretty difficult and probably not something that's supported by ajv.
You could build this into your schema.
{
"$schema": "http://json-schema.org/draft-07/schema",
"properties": {
"age": {
"anyOf": [
{
"type": "number"
},
{
"pattern": "REGEX FOR JSON PATH"
}
]
}
}
}
You could de-duplicate the regex by using definitions and referencing it using $ref.

How to get Session Entities to work as part of Dialogflow detect intent

I have an Entity that is supposed to be updated on a per-session basis with user-specific information. This had worked when I was using Dialogflow v1, and I thought it had worked with v2, but I'm now having significant problems with it.
I believe I am setting the Session Entity information correctly, but for the Intent that uses it, it only matches when a value from the Developer Entity is used.
How can I get it to use the Session Entity? Am I doing something wrong when updating it? Am I using the wrong Session ID? Is there a way I can better verify or test that I'm using the correct ID or that I'm updating the Entity correctly? Is this just a bug?
Documentation of everything follows.
The project is configured to use v2 and to allow for beta features, although I've tried this without the beta features as well.
There are only three Intents. A Fallback Intent to capture failures, a Welcome Intent that gets the welcome event, and the "entry" intent that is supposed to capture the entry code which should match the "code" Entity. All of them use a webhook for fulfillment.
The Fallback Intent
The Welcome Intent
The "entry" Intent
As shown in the "entry" Intent, it uses the "code" Entity, which is the only Developer Entity in the system
The code has most of the Dialogflow specific work in a separate module that uses the "dialogflow" module from npm to set the Session Entity. (Note this is different than the dialogflow-fulfillment module, which is used to handle fulfillment. I'm using the multivocal library for fulfillment, but that shouldn't matter.) (It also uses firebase functions to run on, but I don't think any of these are relevant.)
From the package.json:
"dependencies": {
"dialogflow": "^0.9.0",
"firebase-admin": "~7.0.0",
"firebase-functions": "^2.2.0",
"multivocal": "^0.11.1"
},
This is imported as dialogflow, specifying the API version to use:
const dialogflow = require('dialogflow').v2beta1;
The functions I show below call envToConfig(env) which takes the environment (a multivocal concept that just stores relevant information, including the Dialogflow parent and certificate information) and returns the configuration that needs to be passed to dialogflow.SessionEntityTypesClient( config ). Given no errors are thrown in further calls, it appears to work correctly.
The makeEntityType( name, entityMap ) function takes a map of values to be used for the entities in a SessionEntityType and returns an object that will be used to build a full SessionEntityType. The name provided here is the display name.
function makeEntityType( name, entityMap ){
let ret = {
displayName: name,
entities: []
};
Object.keys( entityMap ).map( key => {
let val = entityMap[key];
let entity = {
value: key,
synonyms: [key, ...val]
};
ret.entities.push( entity );
});
return ret;
}
exports.makeEntityType = makeEntityType;
The result from this is passed to setSessionEntity( env, entityType ) along with the multivocal environment, which contains some information we use in the session. It makes sure the name and entityOverrideMode are set correctly in the entityType and then tries to create it. I've tried using PATCH as well, and it behaves the same way. It also does a bunch of logging, that I'll show later when it runs to prove it actually works.
function setSessionEntity( env, entityType ){
const config = envToConfig( env );
const client = new dialogflow.SessionEntityTypesClient( config );
let parent = env.dialogflow.parent;
if( entityType.displayName && !entityType.name ){
entityType.name = `${parent}/entityTypes/${entityType.displayName}`;
}
if( !entityType.entityOverrideMode ){
entityType.entityOverrideMode = 'ENTITY_OVERRIDE_MODE_OVERRIDE';
}
console.log('setSessionEntity parent',parent);
const request = {
parent: parent,
sessionEntityType: entityType
};
console.log('setSessionEntity request',JSON.stringify(request,null,1));
return client.createSessionEntityType( request )
.then( create => {
console.log('setSessionEntity created',JSON.stringify(create,null,1));
return Promise.resolve( env );
})
.catch( err => {
console.error('setSessionEntity problem creating',err);
return Promise.resolve( env );
})
}
exports.setSessionEntity = setSessionEntity;
For debugging, I also have a function that lists the session entities:
function listSessionEntities( env ){
let parent = env.dialogflow && env.dialogflow.parent;
console.log('listSessionEntities parent', parent);
if( !parent ){
return Promise.resolve( env );
}
const config = envToConfig( env );
const client = new dialogflow.SessionEntityTypesClient( config );
const request = {
parent: parent
};
return client.listSessionEntityTypes(request)
.then( result => {
console.log('listSessionEntities', JSON.stringify(result,null,1));
})
.catch( err => {
console.log('listSessionEntities err', err);
})
.then( () => Promise.resolve( env ) );
}
exports.listSessionEntities = listSessionEntities;
The code that calls this imports it as Dialogflow:
const Dialogflow = require('./dialogflow');
As part of all the webhook calls, the listSessionEntities() function is called before any specific handler is:
function debugSessionEntities( env ){
return Dialogflow.listSessionEntities( env );
}
When the Welcome Intent is triggered, it sets the "code" Session Entity to have two new types that should override the type that was defined in the "code" Developer Entity:
function handleWelcome( env ){
const entityType = Dialogflow.makeEntityType('code',{
'alpha': [],
'bravo': []
});
return Dialogflow.setSessionEntity( env, entityType )
.then( env => Multivocal.handleDefault( env ) );
}
When I run this through the simulator, it doesn't pick up on the Session Entity Types that are set, but do still respond to the Developer Entity Type. (Using a real device works the same way.)
In the simulator, this is what it reports in the Request tab for the Welcome Intent:
{
"responseId": "55a9eb06-ce05-48f9-8a56-b993fa512aee",
"queryResult": {
"queryText": "GOOGLE_ASSISTANT_WELCOME",
"action": "multivocal.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "Hello! How can I help you?",
"fulfillmentMessages": [
{
"text": {
"text": [
"Greetings! How can I assist?"
]
}
}
],
"outputContexts": [
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/google_assistant_welcome"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/actions_capability_screen_output"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/actions_capability_audio_output"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/actions_capability_account_linking"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/google_assistant_input_type_keyboard"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/actions_capability_media_response_audio"
},
{
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/contexts/actions_capability_web_browser"
}
],
"intent": {
"name": "projects/session-test-XXXXX/agent/intents/ca79c951-4d75-4b2b-acd4-7dac2f81856e",
"displayName": "welcome"
},
"intentDetectionConfidence": 1,
"languageCode": "en-us"
},
"originalDetectIntentRequest": {
"source": "google",
"version": "2",
"payload": {
"isInSandbox": true,
"surface": {
"capabilities": [
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.ACCOUNT_LINKING"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name": "actions.capability.WEB_BROWSER"
}
]
},
"requestType": "SIMULATOR",
"inputs": [
{
"rawInputs": [
{
"query": "Talk to my test app",
"inputType": "KEYBOARD"
}
],
"intent": "actions.intent.MAIN"
}
],
"user": {
"userStorage": "{\"UserId\":\"ABwppHHd40lIZ1o0bRERAKlHNtNcS2qFtz7NbRQnb31AQDFuV41VPFQivXwwpQGtv_5SlsZNp0N3kxalIIXXXXXX\",\"NumVisits\":1}",
"lastSeen": "2019-05-18T19:12:38Z",
"locale": "en-US",
"userId": "ABwppHHd40lIZ1o0bRERAKlHNtNcS2qFtz7NbRQnb31AQDFuV41VPFQivXwwpQGtv_5SlsZNp0N3kxalIIXXXXXX"
},
"conversation": {
"conversationId": "ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX",
"type": "NEW"
},
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.WEB_BROWSER"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
}
]
}
},
"session": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX"
}
Most notable from that is the session attribute set at the bottom from that. The code uses this as the parent and session values when building the
The request objects for the other two Intents are similar, and all have the same value for session. None of the response objects are notable in any way.
When the Welcome Intent is triggered, the call to listSessionEntities(), unsurprisingly, shows there aren't any yet:
info: listSessionEntities parent projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX
info: listSessionEntities [
[],
null,
null
]
The parent appears to have the correct value from the session however.
When the handler for the Welcome Intent goes and creates the Session Entity, things appear to work ok:
info: setSessionEntity request {
"parent": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX",
"sessionEntityType": {
"displayName": "code",
"entities": [
{
"value": "alpha",
"synonyms": [
"alpha"
]
},
{
"value": "bravo",
"synonyms": [
"bravo"
]
}
],
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/entityTypes/code",
"entityOverrideMode": "ENTITY_OVERRIDE_MODE_OVERRIDE"
}
}
info: setSessionEntity created [
{
"entities": [
{
"synonyms": [
"alpha"
],
"value": "alpha"
},
{
"synonyms": [
"bravo"
],
"value": "bravo"
}
],
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/entityTypes/code",
"entityOverrideMode": "ENTITY_OVERRIDE_MODE_OVERRIDE"
},
null,
null
]
The parent appears to be the same as the session, and the name appears to follow the correct format, including the additional part that has the "/entityTypes/" followed by the display name.
When I try calling it with the code "alpha", which should trigger the "entry" Intent, it instead triggers the Fallback Intent. The call to listSessionEntities() seems to show the "code" Entity with the Entity Types we expect, even tho there was no match for "alpha".
info: listSessionEntities parent projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX
info: listSessionEntities [
[
{
"entities": [
{
"synonyms": [
"alpha"
],
"value": "alpha"
},
{
"synonyms": [
"bravo"
],
"value": "bravo"
}
],
"name": "projects/session-test-XXXXX/agent/sessions/ABwppHFGTpcFtHOOo6ehQfKys9AnH14V5-RhzrNKsea6y6L5zgv4eN-j3IesfuqSsKMc7qRt1CAOhkUYA9XXXXXX/entityTypes/code",
"entityOverrideMode": "ENTITY_OVERRIDE_MODE_OVERRIDE"
}
],
null,
null
]
Again, everything looks correct. When I try it again with "zulu", it shows the same thing for the call to listSessionEntites(), but this time it matches the "entry" Intent, since "zulu" is one of the Entity Types for "code" that is defined as a Developer Entity.
This is where I am stuck. Everything looks correct. It looks like the Session Entity should be set correctly for this session. It looks like it should be using those values. But it never seems to do so.
What is going on? All help would be greatly appreciated. (Did you even read till the end of the question? If so - thank you! I know it's long, but wanted to be as complete as possible.)
This appears to be a bug - I've gotten feedback from other developers that they're seeing the same problem, sometimes on previously working code.
A bug has been opened at https://issuetracker.google.com/issues/133166381 to track the issue. Star it to indicate you have similar problems and to track progress.

Using Cloudformation to Deploy Lamba, Including a Parameter that the function will have access to

We have an API that will be used to provision certain resources in AWS using Cloud Formation. This includes a Lambda function that will send events to S3, with the bucket being configurable. The thing is, we will know the bucket name when we provision the lambda, not within the lambda code itself.
As far as I can tell, there is no way to inject the S3 bucket name at the time of provisioning, in the Cloud Formation Template itself. Is that true?
The only solution I can see is to generate the function code on the fly, and embed that into the Cloud Formation template. This would make us unable to use any NPM dependencies along with the function code. Is there a better option?
So, I realized I had never updated this question with my eventual solution. I ended up embedding a proxy lambda function into the cloudformation template, which enabled me to inject template parameters.
Example:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates a function to relay messages from a Kinesis instance to S3",
"Parameters": {
"S3Bucket" : {
"Type": "String",
"Description": "The name of the S3 bucket where the data will be stored"
},
"S3Key": {
"Type": "String",
"Description": "The key of the directory where the data will be stored"
}
},
"Resources": {
"mainLambda": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Description" : "Writes events to S3",
"Role" : { "Ref": "LambdaRoleARN" },
"Runtime" : "nodejs4.3",
"Code" : {
"S3Bucket": "streams-resources",
"S3Key": "astro-bass/${GIT_COMMIT}/lambda/astro-bass.zip"
}
}
},
"lambdaProxy": {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Handler" : "index.handler",
"Runtime" : "nodejs",
"Code" : {
"ZipFile": { "Fn::Join": ["", [
"var AWS = require('aws-sdk');",
"var lambda = new AWS.Lambda();",
"exports.handler = function(event, context) {",
"event.bundledParams = ['",
{ "Ref": "S3Bucket" },
"','",
{ "Ref": "S3Key" },
"'];",
"lambda.invoke({",
"FunctionName: '",
{ "Ref": "mainLambda" },
"',",
"Payload: JSON.stringify(event, null, 2),",
"InvocationType: 'Event'",
"}, function(err, data) {",
"if(err) {",
"context.fail(err);",
"}",
"context.done();",
"});",
"};"
]]}
}
}
},
},
...
}
The proxy function had the parameters injected into its code (s3bucket/key), and then it invokes the main lambda with a modified event object. It's a little unorthodox but struck me as much cleaner than the other available solutions, such as parse stacknames/etc. Worked well thus far.
Note that this solution only works currently with the legacy node environment. Not an issue, but worrisome in terms of the longevity of this solution.
UPDATE:
We ran into limitations with the previous solution and had to devise yet another one. We ended up with an off-label usage of the description field to embed configuration values. Here is our Lambda
'use strict';
var aws = require('aws-sdk');
var lambda = new aws.Lambda({apiVersion: '2014-11-11'});
let promise = lambda.getFunctionConfiguration({ FunctionName: process.env['AWS_LAMBDA_FUNCTION_NAME'] }).promise();
exports.handler = async function getTheConfig(event, context, cb) {
try {
let data = await promise;
cb(null, JSON.parse(data.Description).bucket);
} catch(e) {
cb(e);
}
};
Then, in the description field, you can embed a simple JSON snipped like so:
{
"bucket": "bucket-name"
}
Moreover, this structure, using the promise outside of the handler, limits the request to only occurring when the container is spawned - not for each individual lambda execution.
Not quite the cleanest solution, but the most functional one we've found.
There is no way of passing parameters to a Lambda function beside the event itself at the moment.
If you are creating a Lambda function with CloudFormation you could use the following workaround:
Use the Lambda function name to derive the CloudFormation stack name.
Use the CloudFormation stack name to access resources, or parameters of the stack when executing the Lambda function.
I would suggest doing it like this.
First create an index.js file and add this code.
var AWS = require('aws-sdk');
const s3 = new AWS.S3();
const https = require('https');
exports.handler = (event, context, callback) => {
const options = {
hostname: process.env.ApiUrl,
port: 443,
path: '/todos',
method: 'GET'
};
const req = https.request(options, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
});
req.on('error', (e) => {
console.error(e);
});
req.end();
};
Zip the index.js file and upload it to an S3 bucket in the same region as your lambda function.
Then use this Cloudformation template make sure you specific the correct bucket name.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ApiWorkflow",
"Metadata": {
},
"Parameters": {
"ApiUrl": {
"Description": "Specify the api url",
"Type": "String",
"Default": "jsonplaceholder.typicode.com"
},
},
"Mappings": {
},
"Conditions": {
},
"Resources": {
"lambdaVodFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "lamdba-exec-tests",
"S3Key": "index.js.zip"
},
"Handler": "index.handler",
"Role": "arn:aws:iam::000000000:role/BasicLambdaExecRole",
"Runtime": "nodejs10.x",
"FunctionName": "ApiWorkflow",
"MemorySize": 128,
"Timeout": 5,
"Description": "Texting Lambda",
"Environment": {
"Variables": {
"ApiUrl": {
"Ref": "ApiUrl"
},
"Test2": "Hello World"
}
},
}
}
},
"Outputs": {
"ApiUrl": {
"Description": "Set api url",
"Value": {
"Ref": "ApiUrl"
}
}
}
}
You should see in the template Environmental variables you can access these in your NodeJS Lambda function like this.
process.env.ApiUrl

Getting started with FortuneJS

Evening All,
I'm hoping that this is me making a school-boy error. Trying to 'get started' with fortuneJS but having an issue whilst trying to create a new resource.
So, I've setup fortune as described on their homepage. I've opted to use their JSON API plugin, again setup as per their repo.
I'm using Postman to test out the server I've created and I can create a 'user' resource no problem with the following:
{
"data": {
"type": "user",
"attributes": {
"name": "Andrew"
}
}
}
That works fine and I get a response as follows:
{
"data": {
"type": "users",
"id": "37446bbc",
"attributes": {
"name": "Andrew"
},
"relationships": {
"posts": {
"links": {
"self": "http://localhost:1337/users/37446bbc/relationships/posts",
"related": "http://localhost:1337/users/37446bbc/posts"
},
"data": []
}
},
"links": {
"self": "http://localhost:1337/users/37446bbc"
}
}
}
Now, I need to try and create the 'post' resource. Following the JSON API specification I'm posting the following payload:
{
"data": {
"type": "posts",
"attributes": {
"message": "This is my first post"
},
"relationships": {
"author": {
"data": { "type": "users", "id": "37446bbc" }
}
}
}
}
All I get back from that is:
{
"errors": [
{
"title": "Error",
"detail": "An internal server error occurred."
}
]
}
I've debugged by placing a console log of the 'error' on line 118 in node_modules/fortune/dist/lib/dispatch/index.js which shows this error:
[TypeError: Cannot set property 'user' of undefined]
Any advice or guidance you can offer would be greatly appreciated. Hopefully it's just me!
I'm using babel-node app.js to get this running. To save you time, I've thrown up the code onto a public repo
It seems that the actual error message needs to be relayed to the client. You can do so by attaching a catch to the HTTP listener:
const server = http.createServer((request, response) =>
fortune.net.http(store)(request, response)
.catch(error => console.log(error.stack)))
What I found when I recreated the steps was this:
Error: A related record for the field "author" was not found.
However, since it is a generic Error and not a typed error that Fortune uses internally, it is hidden from the client.
So by setting the correct value for the author id, it was able to create a record with that association successfully.
Future versions will be patched so that this informative error message gets shown.
The originally posted question works fine, the version of node was causing an error. Upgrading to v4.2.1 resolved the issue.

Resources