Building Elastic Beanstalk with Node.js SDK - node.js

Has anyone created an elastic beanstalk application with the AWS javascript sdk? I've been able to update existing applications using grunt, that works really well. But as part of a continuous integration/continuous deployment project, we want to also create the app when it's not there. I find the documentation confusing, and in AWS's usual fashion, lacking in any kind of cohesive examples, that say, "do this, then this." If anyone has done this and can point me in the right direction, that would be a great help. At this point in time, I'm not sure whether it's a single step or multi step process.

So, here's a basic node package to build an app. I have uploaded a basic API app as a zip file, it doesn't much of anything. The idea is that once it's created, I can then update it using a grunt script - there are a couple of very good grunt modules that will do that, once it's created. But the initial creation was missing. Easy enough to add on more parameters to this now, too.
var applicationName = process.argv[2];
var environmentName = process.argv[3];
var regionName = process.argv[4];
var AWS = require('aws-sdk');
AWS.config.update({region: regionName});
var applicationParams = {
ApplicationName: applicationName
};
var environmentParams =
{
ApplicationName: applicationName, /* required */
EnvironmentName: environmentName, /* required */
VersionLabel: 'initial',
SolutionStackName: "64bit Amazon Linux 2015.03 v1.4.4 running Node.js",
CNAMEPrefix: applicationName,
Tier:
{
Version: " ",
Type: "Standard",
Name: "WebServer"
},
OptionSettings:
[
{
Namespace: 'aws:elasticbeanstalk:environment',
OptionName: 'EnvironmentType',
Value: 'SingleInstance'
},
{
Namespace: 'aws:autoscaling:launchconfiguration',
OptionName: 'EC2KeyName',
Value: 'MyPemFile'
},
{
Namespace: 'aws:autoscaling:launchconfiguration',
OptionName: 'IamInstanceProfile',
Value: 'aws-elasticbeanstalk-ec2-role'
},
{
Namespace: 'aws:autoscaling:launchconfiguration',
OptionName: 'InstanceType',
Value: 't1.micro'
}
],
};
var versionParams =
{
ApplicationName: applicationName, /* required */
VersionLabel: 'initial', /* required */
AutoCreateApplication: true,
SourceBundle:
{
S3Bucket: 'beanstalk-test-ff',
S3Key: 'test-app.zip'
}
};
var elasticbeanstalk = new AWS.ElasticBeanstalk();
elasticbeanstalk.createApplication(applicationParams, function(err, data)
{
console.log('Creating application');
console.log(data);
if (err)
{
if (err.message.indexOf("already exists") > -1)
{
console.log('Application already exists, continuing on');
}
else
{
console.log(err,err.stack); // an error occurred
}
}
else
{
elasticbeanstalk.createApplicationVersion(versionParams, function(err, data)
{
console.log('Creating application version....');
console.log(data);
if (err) console.log(err, err.stack); // an error occurred
else
{
elasticbeanstalk.createEnvironment(environmentParams, function(err, data)
{
console.log('Creating application environment....');
console.log(data);
if (err) console.log(err, err.stack); // an error occurred
});
}
});
}
});

Related

Cognito - Error: Invalid UserPoolId format

I am using AWS CDK to create a userpool and userpool client. I would like to be able to access the userpool id and userpool client id from a lambda once they have been created. I pass these two values to the lambda via environmental variables. Here is my code:
import { Construct } from 'constructs';
import {
IResource,
LambdaIntegration,
MockIntegration,
PassthroughBehavior,
RestApi,
} from 'aws-cdk-lib/aws-apigateway';
import {
NodejsFunction,
NodejsFunctionProps,
} from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import * as amplify from 'aws-cdk-lib/aws-amplify';
import {
aws_s3,
aws_ec2,
aws_rds,
aws_cognito,
aws_amplify,
Duration,
CfnOutput,
} from 'aws-cdk-lib';
export class FrontendService extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const userPool = new aws_cognito.UserPool(this, 'userpool', {
userPoolName: 'frontend-userpool',
selfSignUpEnabled: true,
signInAliases: {
email: true,
},
autoVerify: { email: true },
});
const userPoolClient = new aws_cognito.UserPoolClient(
this,
'frontend-app-client',
{
userPool,
generateSecret: false,
}
);
const bucket = new aws_s3.Bucket(this, 'FrontendStore');
const nodeJsFunctionProps: NodejsFunctionProps = {
environment: {
BUCKET: bucket.bucketName,
DB_NAME: 'hospoFEDB',
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
USER_POOL_ID: userPool.userPoolId,
USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
},
runtime: Runtime.NODEJS_14_X,
};
const registerLambda = new NodejsFunction(this, 'registerFunction', {
entry: 'dist/lambda/register.js',
memorySize: 1024,
...nodeJsFunctionProps,
});
const registerIntegration = new LambdaIntegration(registerLambda);
const api = new RestApi(this, 'frontend-api', {
restApiName: 'Frontend Service',
description: 'This service serves the frontend.',
});
const registerResource = api.root.addResource('register');
registerResource.addMethod('POST', registerIntegration);
}
}
Here is my lambda function and how I intend to use the USER_POOL_ID and USER_POOL_CLIENT_ID env variables:
import {
CognitoUserPool,
} from 'amazon-cognito-identity-js';
export const handler = async (event: any, context: any) => {
try {
console.log(process.env.USER_POOL_ID);
console.log(process.env.USER_POOL_CLIENT_ID);
const userPool = new CognitoUserPool({
UserPoolId: process.env.USER_POOL_ID as string,
ClientId: process.env.USER_POOL_CLIENT_ID as string,
});
return {
statusCode: 200,
};
} catch (error) {
if (error instanceof Error) {
const body = error.stack || (JSON.stringify(error, null, 2) as any);
return {
statusCode: 400,
headers: {},
body: JSON.stringify(body),
};
}
return {
statusCode: 400,
};
}
};
The idea with this setup is that I would create a cognito user pool and client then be able to pass those id's directly down. Currently if I run this locally via sam local start-api it generates the following USER_POOL_ID : Frontenduserpool87772999. If I try and use this id in the new CognitoUserPool({... part of my lambda function I get the following error:
Error: Invalid UserPoolId format.
If I deploy the app however and execute the lambda function from the deployed environment with the exact same code I get a USER_POOL_ID that looks more like: us-east-1_HAjkUj9hP. This works fine and I do not get the error above.
Should I assume that I can not create a user pool locally and will always have to point to the deployed user pool?
Should I assume that I can not create a user pool locally and will always have to point to the deployed user pool
Yes. See the docs: start-api creates an emulated local API endpoint and Lambda for local testing. It does not deploy or emulate other resources.
You can reference previously deployed AWS resources by passing a JSON file with the deployed physical values using the --env-vars flag.

How to assign serviceAccount using gcloud compute nodejs client?

I'm trying to create a new virtual machine using gcloud compute nodejs client:
const Compute = require('#google-cloud/compute');
const compute = new Compute();
async function createVM() {
try {
const zone = await compute.zone('us-central1-a');
const config = {
os: 'ubuntu',
http: true,
https: true,
metadata: {
items: [
{
key: 'startup-script-url',
value: 'gs://<path_to_startup_script>/startup_script.sh'
},
],
},
};
const data = await zone.createVM('vm-9', config);
const operation = data[1];
await operation.promise();
return console.log(' VM Created');
} catch (err) {
console.error(err);
return Promise.reject(err);
}
}
I have a serviceAccount with the needed roles for this VM to call other resources but I can't figure how to where to assign the serviceAccount when creating the new VM. Any pointers are greatly appreciated, I haven't been able to find any documentation and I'm stuck.
You can specify the service account to use in the new VM by adding a serviceAccounts field within the options for config passed into createVM. Here is an example snippet:
zone.createVM('name', {
serviceAccounts: [
{
email: '...',
scopes: [
'...'
]
}
]
})
Reference:
Service Account and Access Scopes or Method: instances.insert
createVM - The config object can take all the parameters of the instance resource.

How to enable apiKey required to true using the aws-sdk?

I have been stuck here for too long. How do I enable API Key for the particular REST method using the aws-sdk? I could enable it using the console, but not finding a method to achieve this using the nodejs sdk. So basically want to setup the secret key for specified API Endpoint + Resource + Method.
In the following snapshot, I enabled the api-key required to true from the console.
Docs referred: AWS Nodejs Doc
Here is what I have been able to do so far:
// CREATE API KEY
async function create() {
try {
const apiKey = await apigateway.createApiKeyAsync({
enabled: true,
generateDistinctId: true,
name: NAME_OF_KEY
});
/**
* #see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#createUsagePlan-property
*/
// CREATE USAGE PLAN AND LINK API
const usagePlan = await apigateway.createUsagePlanAsync({
name: NAME_OF_USAGE_PLAN,
apiStages: [
{
apiId: API_ID,
stage: STAGE
},
/* more items */
],
quota: QUOTA_INFO,
throttle: THROTTLE_INFO
});
/**
* Creates a usage plan key for adding an existing API key to a usage plan.
*/
// LINK API KEY AND USAGE PLAN
await apigateway.createUsagePlanKeyAsync({
keyId: apiKey.id,
keyType: 'API_KEY',
usagePlanId: usagePlan.id
});
return Promise.resolve(apiKey);
} catch (err) {
return Promise.reject(err);
}
}
You need to call the function updateMethod to update your method request:
Reference: Class: AWS.APIGateway
var params = {
httpMethod: 'STRING_VALUE', /* required */
resourceId: 'STRING_VALUE', /* required */
restApiId: 'STRING_VALUE', /* required */
patchOperations: [
{
from: 'STRING_VALUE',
op: add | remove | replace | move | copy | test,
path: 'STRING_VALUE',
value: 'STRING_VALUE'
},
/* more items */
]
};
apigateway.updateMethod(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
So you can do the following:
var apigateway = new AWS.APIGateway({apiVersion: '2015-07-09'});
var params = {
httpMethod: 'POST',
resourceId: 'resource id',
restApiId: API_ID,
patchOperations: [
{
op: 'replace',
path: '/apiKeyRequired',
value: 'true' || 'false'
},
]
};
apigateway.updateMethod(params, function(err, data) {
if (err) cb(err, err.stack); // an error occurred
else cb(null, data); // successful response
});
Hope it helps!

File creation is ignoring my parameters but creates an "Untitled" binary file

Using the Google API via the googleapis package and the file.create call simply does not work. I've experimented making the same call in Google's API Explorer and it does work. I'm at a bit of a loss.
The createSheet call is encapsulated as follows :
Google.prototype.createSheet = function(filename, callback) {
var services = google.drive('v3');
services.files.create({
"name" : filename,
"mimeType" : "application/vnd.google-apps.spreadsheet",
"description" : 'auto-generated by the cli',
"auth" : this.auth
}, function(err,response) {
if( err ) {
console.log('Error : unable to create file, ' + err);
return;
} else {
console.dir(response);
}
});
}
... the net result is,
{ kind: 'drive#file',
id: '0BwWAQdfAgbYzWk5XRFQyODQ0Zmc',
name: 'Untitled',
mimeType: 'application/octet-stream'
}
It's missing both the filename as well as the filetype.
The general framework here is working correctly as I can get a list of files and read from spreadsheets.

JayData with WebAPI OData V4 returns undefined

I'm writting AWS lambda function in NodeJS to read the data from WebAPI OData V4 service (http://localhost/SomeService/$metadata)
I generated metadata file using JaySvcUtil and referenced the resulting file in Lambda function
'use strict';
let $data = require('jaydata');
let meta = require('./apiData');
exports.handler = function (event, context) {
var con = new DataContext({
name: 'oData',
oDataServiceHost: 'http://localhost/SomeService'
});
con.onReady(function () {
$data.Users.forEach(function(data) {
console.log(data.Id);
});;
};
Unfortunately, when onReady method is invoked, Users object is undefined as well as any other type exosed by my API.
Any idea what i'm doing wrong?
Turned out that generated code created by jaysvcutil doesn't work with nodejs.
Instead of using .extend, .define should be used as explained in the doc.
$data.Class.define('Project.User', $data.Entity, null, {
'UserID': { 'key': true, 'type': 'Edm.Int32', 'nullable': false, 'computed': true },
});
$data.Class.define('$Project.Types.Context', $data.EntityContext, null, {
Users: { type: $data.EntitySet, elementType: Project.User }
});
And context initialization callback should be defined as follows:
jayContext = new $Project.Types.Context({
name: 'oData',
oDataServiceHost: 'http://localhost/SomeService'
});
jayContext.onReady(function (serviceContext) {
serviceContext.Users.forEach(function (user) {
console.log(user.UserID);
});
context.done(null, 'Hello World');
});

Resources