Call aws-cli from AWS Lambda - amazon-web-services

is there ANY way to execute aws-cli inside AWS Lambda?
It doesn't seem to be pre-installed.
(I've checked with "which aws" via Node.js child-process, and it didn't exist.)

Now we can use Layers inside Lambda. Bash layer with aws-cli is available at https://github.com/gkrizek/bash-lambda-layer
handler () {
set -e
# Event Data is sent as the first parameter
EVENT_DATA=$1
# This is the Event Data
echo $EVENT_DATA
# Example of command usage
EVENT_JSON=$(echo $EVENT_DATA | jq .)
# Example of AWS command that's output will show up in CloudWatch Logs
aws s3 ls
# This is the return value because it's being sent to stderr (>&2)
echo "{\"success\": true}" >&2
}

Not unless you include it (and all of its dependencies) as part of your deployment package. Even then you would have to call it from within python since Lambda doesn't allow you to execute shell commands. Even if you get there, I would not recommend trying to do a sync in a Lambda function since you're limited to a maximum of 5 minutes of execution time. On top of that, the additional spin-up time just isn't worth it in many cases since you're paying for every 100ms chunk.
So you can, but you probably shouldn't.
EDIT: Lambda does allow you to execute shell commands

aws-cli is a python package. To make it available on a AWS Lambda function you need to pack it with your function zip file.
1) Start an EC2 instance with 64-bit Amazon Linux;
2) Create a python virtualenv:
mkdir ~/awscli_virtualenv
virtualenv ~/awscli_virtualenv
3) Activate virtualenv:
cd ~/awscli_virtualenv/bin
source activate
4) Install aws-cli and pyyaml:
pip install awscli
python -m easy_install pyyaml
5) Change the first line of the aws python script:
sed -i '1 s/^.*$/\#\!\/usr\/bin\/python/' aws
6) Deactivate virtualenv:
deactivate
7) Make a dir with all the files you need to run aws-cli on lambda:
cd ~
mkdir awscli_lambda
cd awscli_lambda
cp ~/awscli_virtualenv/bin/aws .
cp -r ~/awscli_virtualenv/lib/python2.7/dist-packages .
cp -r ~/awscli_virtualenv/lib64/python2.7/dist-packages .
8) Create a function (python or nodejs) that will call aws-cli:
For example (nodejs):
var Q = require('q');
var path = require('path');
var spawn = require('child-process-promise').spawn;
exports.handler = function(event, context) {
var folderpath = '/folder/to/sync';
var s3uel = 's3://name-of-your-bucket/path/to/folder';
var libpath = path.join(__dirname, 'lib');
var env = Object.create(process.env);
env.LD_LIBRARY_PATH = libpath;
var command = path.join(__dirname, 'aws');
var params = ['s3', 'sync', '.', s3url];
var options = { cwd: folderpath };
var spawnp = spawn(command, params, options);
spawnp.childProcess.stdout.on('data', function (data) {
console.log('[spawn] stdout: ', data.toString());
});
spawnp.childProcess.stderr.on('data', function (data) {
console.log('[spawn] stderr: ', data.toString());
});
return spawnp
.then(function(result) {
if (result['code'] != 0) throw new Error(["aws s3 sync exited with code", result['code']].join(''));
return result;
});
}
Create the index.js file (with the code above or your code) on ~/awscli_lambda/index.js
9) Zip everything (aws-cli files and dependencies and your function):
cd ~
zip -r awscli_lambda.zip awscli_lambda

Now you can simply run it as Docker container within lambda along with AWS CLI.

You can use the AWS node.js SDK which should be available in Lambda without installing it.
var AWS = require('aws-sdk');
var lambda = new AWS.Lambda();
lambda.invoke({
FunctionName: 'arn:aws:lambda:us-west-2:xxxx:function:FN_NAME',
Payload: {},
},
function(err, result) {
...
});
As far as I can tell you get most, if not all the cli functionality. See the full documentation here: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html

you can try this. I got this working for me.
1- add the AWS CLI layer
https://harishkm.in/2020/06/16/run-aws-cli-in-a-lambda-function/
2- add a lambda and run the following commands to run any AWS CLI command line.
https://harishkm.in/2020/06/16/run-bash-scripts-in-aws-lambda-functions/
function handler () {
EVENT_DATA=$1
DATA=`/opt/awscli/aws s3 ls `
RESPONSE="{\"statusCode\": 200, \"body\": \"$DATA\"}"
echo $RESPONSE
}

If you are provisioning your Lambda using code, then this is the most easiest way
lambda_function.add_layers(AwsCliLayer(scope, "AwsCliLayer"))
Ref: https://pypi.org/project/aws-cdk.lambda-layer-awscli/

I think that you should separate your trigger logic from the action.
Put a container with aws cli on another ec2 And use aws lambda to trigger that into an action.

Related

How to run lambda function on a schedule in my localhost?

I have a task that need to be scheduled on aws lambda function. I wrote a SAM template as below and I see it works when deploying on aws environment (my function get triggered intervally).
But we want to do testing on dev environment first before deploying. I use sam local start-api [OPTIONS] to deploy our functions to dev environment. But the problem is that, every functions configured as rest API work, but the schedule task not. I'm not sure is it possible on local/dev environment or not. If not, please suggest a solution (is it possible?). Thank you
This is template:
aRestApi:
...
...
sendMonthlyReport:
Type: AWS::Serverless::Function
Properties:
Handler: src.monthlyReport
Runtime: nodejs16.x
Events:
ScheduledEvent:
Type: Schedule
Properties:
Schedule: "cron(* * * * *)"
If you search for local testing before deployment of lambda functions you will probably be fed this resource by the quote-unquote "google": https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html
However, there are other ways to do it.
I personally use the public docker image from aws.
Here is an example of the docker image created for a specific use case of mine.
FROM public.ecr.aws/lambda/python:3.8
RUN yum -y install tar gzip zlib freetype-devel \
gcc \
ghostscript \
lcms2-devel \
libffi-devel \
libimagequant-devel \
.
enter code here
enter code here
and some more dependencies ....
&& yum clean all
COPY requirements.txt ./
RUN python3.8 -m pip install -r requirements.txt
# Replace Pillow with Pillow-SIMD to take advantage of AVX2
RUN pip uninstall -y pillow && CC="cc -mavx2" pip install -U --force-reinstall pillow-simd
COPY <handler>.py ./<handler>.py
# Set the CMD to your handler
ENTRYPOINT [ "python3.8","app5.py" ]
In your case, follow instructions for node and run the docker image locally. If it works, you can then continue with aws lambda creation/update.
I see that you also have a cron job, why not use the cron job to invoke this lambda function separately and not define it in you SAML?
There are a number of ways you can invoke a lambda function based on event.
For example to invoke using cli: (For aws-cliv2)
make sure you configure awscli
# !/bin/bash
export AWS_PROFILE=<your aws profile>
export AWS_REGION=<aws region>
aws lambda invoke --function-name <function name> \
--cli-binary-format raw-in-base64-out \
--log-type Tail \
--payload <your json payload > \
<output filename>
Makes it loosely coupled.
Then you can use carlo's node cronjob suggestion to invoke is as many times you like, free of charge.
I used localstack for the demonstrate. LocalStack is a cloud service emulator that runs in a single container on your laptop or in your CI environment. You can see more detail in this link https://github.com/localstack/localstack
I would use node-cron to set a scheduler on a node file to run.
npm install --save node-cron
var cron = require('node-cron');
cron.schedule('* * * * *', () => {
console.log('running a task every minute');
});
https://www.npmjs.com/package/node-cron
You can also check for this DigitalOcean tutorial!

How to add aws-cli v2 in production?

I have developed an application in nodejs/vuejs and I want to dockerize the whole project before push it in production.
Knowing that my API is executing an aws command at a specific time, I need to install and configure AWS-CLIv2 in production.
crontab.scheduleJob('30 8,12 * * *', () => {
shelljs.exec("rm -rf src/data/*.csv && aws s3 cp s3://${bucketName}/`aws s3 ls s3://${bucketName} | tail -n 1 | awk '{print $4}'` src/data");
});
For development, I installed (from line command) and configured AWS locally from https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html
Would it be possible to install aws-cliv2 via the API's Dockerfile? What structure should I adopt?
Otherwise offer me your solutions please ?
Thank you
Maybe you can try use the SDK of AWS for interact with the AWS API (in this case the S3 Bucket), but if you prefer use the aws-cli binary you can install the binary like this docker image is made it or make your docker image based in this image.

AWS CDK: run external build command in CDK sequence?

Is it possible to run an external build command as part of a CDK stack sequence? Intention: 1) create a rest API, 2) write rest URL to config file, 3) build and deploy a React app:
import apigateway = require('#aws-cdk/aws-apigateway');
import cdk = require('#aws-cdk/core');
import fs = require('fs')
import s3deployment = require('#aws-cdk/aws-s3-deployment');
export class MyStack extends cdk.Stack {
const restApi = new apigateway.RestApi(this, ..);
fs.writeFile('src/app-config.json',
JSON.stringify({ "api": restApi.deploymentStage.urlForPath('/myResource') }))
// TODO locally run 'npm run build', create 'build' folder incl rest api config
const websiteBucket = new s3.Bucket(this, ..)
new s3deployment.BucketDeployment(this, .. {
sources: [s3deployment.Source.asset('build')],
destinationBucket: websiteBucket
})
}
Unfortunately, it is not possible, as the necessary references are only available after deploy and therefore after you try to write the file (the file will contain cdk tokens).
I personally have solved this problem by telling cdk to output the apigateway URLs to a file and then parse it after the deploy to upload it so a S3 bucket, to do it you need:
deploy with the output file options, for example:
cdk deploy -O ./cdk.out/deploy-output.json
In ./cdk.out/deploy-output.json you will find a JSON object with a key for each stack that produced an output (e.g. your stack that contains an API gateway)
manually parse that JSON to get your apigateway url
create your configuration file and upload it to S3 (you can do it via aws-sdk)
Of course, you have the last steps in a custom script, which means that you have to wrap your cdk deploy. I suggest to do so with a nodejs script, so that you can leverage aws-sdk to upload your file to S3 easily.
Accepting that cdk doesn't support this, I split logic into two cdk scripts, accessed API gateway URL as cdk output via the cli, then wrapped everything in a bash script.
AWS CDK:
// API gateway
const api = new apigateway.RestApi(this, 'my-api', ..)
// output url
const myResourceURL = api.deploymentStage.urlForPath('/myResource');
new cdk.CfnOutput(this, 'MyRestURL', { value: myResourceURL });
Bash:
# deploy api gw
cdk deploy --app (..)
# read url via cli with --query
export rest_url=`aws cloudformation describe-stacks --stack-name (..) --query "Stacks[0].Outputs[?OutputKey=='MyRestURL'].OutputValue" --output text`
# configure React app
echo "{ \"api\" : { \"invokeUrl\" : \"$rest_url\" } }" > src/app-config.json
# build React app with url
npm run build
# run second cdk app to deploy React built output folder
cdk deploy --app (..)
Is there a better way?
I solved a similar issue:
Needed to build and upload react-app as well
Supported dynamic configuration reading from react-app - look here
Released my react-app with specific version (in a separate flow)
Then, during CDK deployment of my app, it took a specific version of my react-app (version retrieved from local configuration) and uploaded its zip file to S3 bucket using CDK BucketDeployment
Then, using AwsCustomResource I generated a configuration file with references to Cognito and API-GW and uploaded this file to S3 as well:
// create s3 bucket for react-app
const uiBucket = new Bucket(this, "ui", {
bucketName: this.stackName + "-s3-react-app",
blockPublicAccess: BlockPublicAccess.BLOCK_ALL
});
let confObj = {
"myjsonobj" : {
"region": `${this.region}`,
"identity_pool_id": `${props.CognitoIdentityPool.ref}`,
"myBackend": `${apiGw.deploymentStage.urlForPath("/")}`
}
};
const dataString = JSON.stringify(confObj, null, 4);
const bucketDeployment = new BucketDeployment(this, this.stackName + "-app", {
destinationBucket: uiBucket,
sources: [Source.asset(`reactapp-v1.zip`)]
});
bucketDeployment.node.addDependency(uiBucket)
const s3Upload = new custom.AwsCustomResource(this, 'config-json', {
policy: custom.AwsCustomResourcePolicy.fromSdkCalls({resources: custom.AwsCustomResourcePolicy.ANY_RESOURCE}),
onCreate: {
service: "S3",
action: "putObject",
parameters: {
Body: dataString,
Bucket: `${uiBucket.bucketName}`,
Key: "app-config.json",
},
physicalResourceId: PhysicalResourceId.of(`${uiBucket.bucketName}`)
}
});
s3Upload.node.addDependency(bucketDeployment);
As others have mentioned, this isn't supported within CDK. So this how we solved it in SST: https://github.com/serverless-stack/serverless-stack
On the CDK side, allow defining React environment variables using the outputs of other constructs.
// Create a React.js app
const site = new sst.ReactStaticSite(this, "Site", {
path: "frontend",
environment: {
// Pass in the API endpoint to our app
REACT_APP_API_URL: api.url,
},
});
Spit out a config file while starting the local environment for the backend.
Then start React using sst-env -- react-scripts start, where we have a simple CLI that reads from the config file and loads them as build-time environment variables in React.
While deploying, replace these environment variables inside a custom resource based on the outputs.
We wrote about it here: https://serverless-stack.com/chapters/setting-serverless-environments-variables-in-a-react-app.html
And here's the source for the ReactStaticSite and StaticSite constructs for reference.
In my case, I'm using the Python language for CDK. I have a Makefile which I invoke directly from my app.py like this:
os.system("make"). I use the make to build up a layer zip file per AWS Docs. Technically you can invoke whatever you'd like. You must import the os package of course. Hope this helps.

How do I unzip a .zip file in google cloud storage?

How do I unzip a .zip file in Goolge Cloud Storage Bucket? (If we have some other tool like 'CloudBerry Explorer' for AWS, that will be great.)
You can use Python, e.g. from a Cloud Function:
from google.cloud import storage
from zipfile import ZipFile
from zipfile import is_zipfile
import io
def zipextract(bucketname, zipfilename_with_path):
storage_client = storage.Client()
bucket = storage_client.get_bucket(bucketname)
destination_blob_pathname = zipfilename_with_path
blob = bucket.blob(destination_blob_pathname)
zipbytes = io.BytesIO(blob.download_as_string())
if is_zipfile(zipbytes):
with ZipFile(zipbytes, 'r') as myzip:
for contentfilename in myzip.namelist():
contentfile = myzip.read(contentfilename)
blob = bucket.blob(zipfilename_with_path + "/" + contentfilename)
blob.upload_from_string(contentfile)
zipextract("mybucket", "path/file.zip") # if the file is gs://mybucket/path/file.zip
If you ended up having a zip file on your Google Cloud Storage bucket because you had to move large files from another server with the gsutil cp command, you could instead gzip when copying and it will be transferred in compressed format and unzippet when arriving to the bucket.
It is built in gsutil cp by using the -Z argument.
E.g.
gsutil cp -Z largefile.txt gs://bucket/largefile.txt
Here is some code I created to run as a Firebase Cloud Function. It is designed to listen to files loaded into a bucket with the content-type 'application/zip' and extract them in place.
const functions = require('firebase-functions');
const admin = require("firebase-admin");
const path = require('path');
const fs = require('fs');
const os = require('os');
const unzip = require('unzipper')
admin.initializeApp();
const storage = admin.storage();
const runtimeOpts = {
timeoutSeconds: 540,
memory: '2GB'
}
exports.unzip = functions.runWith(runtimeOpts).storage.object().onFinalize((object) => {
return new Promise((resolve, reject) => {
//console.log(object)
if (object.contentType !== 'application/zip') {
reject();
} else {
const bucket = firebase.storage.bucket(object.bucket)
const remoteFile = bucket.file(object.name)
const remoteDir = object.name.replace('.zip', '')
console.log(`Downloading ${remoteFile}`)
remoteFile.createReadStream()
.on('error', err => {
console.error(err)
reject(err);
})
.on('response', response => {
// Server connected and responded with the specified status and headers.
//console.log(response)
})
.on('end', () => {
// The file is fully downloaded.
console.log("Finished downloading.")
resolve();
})
.pipe(unzip.Parse())
.on('entry', entry => {
const file = bucket.file(`${remoteDir}/${entry.path}`)
entry.pipe(file.createWriteStream())
.on('error', err => {
console.log(err)
reject(err);
})
.on('finish', () => {
console.log(`Finsihed extracting ${remoteDir}/${entry.path}`)
});
entry.autodrain();
});
}
})
});
In shell, you can use the below command to unzip a compressed file
gsutil cat gs://bucket/obj.csv.gz | zcat | gsutil cp - gs://bucket/obj.csv
There is no mechanism in the GCS to unzip the files. A feature request regarding the same has already been forwarded to the Google development team.
As an alternative, you can upload the ZIP files to the GCS bucket and then download them to a persistent disk attached to a VM instance, unzip them there, and upload the unzipped files using the gsutil tool.
There are Data flow templates in google Cloud data flow which helps to Zip/unzip the files in cloud storage.Refer below screenshots.
This template stages a batch pipeline that decompresses files on Cloud Storage to a specified location. This functionality is useful when you want to use compressed data to minimize network bandwidth costs.
The pipeline automatically handles multiple compression modes during a single execution and determines the decompression mode to use based on the file extension (.bzip2, .deflate, .gz, .zip).
Pipeline requirements
The files to decompress must be in one of the following formats: Bzip2, Deflate, Gzip, Zip.
The output directory must exist prior to pipeline execution.
I'm afraid that by default in Google Cloud no program could do this..., but you can have this functionality, for example, using Python.
Universal method available on any machine where Python is installed (so also on Google Cloud):
You need to enter the following commands:
python
or if you need administrator rights:
sudo python
and then in the Python Interpreter:
>>> from zipfile import ZipFile
>>> zip_file = ZipFile('path_to_file/t.zip', 'r')
>>> zip_file.extractall('path_to_extract_folder')
and finally, press Ctrl+D to exit the Python Interpreter.
The unpacked files will be located in the location you specify (of course, if you had the appropriate permissions for these locations).
The above method works identically for Python 2 and Python 3.
Enjoy it to the fullest! :)
Enable Dataflow API in your gcloud console
Create a temp dir in your bucket (cant use root).
Replace YOUR_REGION (e.g. europe-west6) and YOUR_BUCKET in the below command and run it with gcloud cli (presumption is gz file is at root - change if not):
gcloud dataflow jobs run unzip \
--gcs-location gs://dataflow-templates-YOUR_REGION/latest/Bulk_Decompress_GCS_Files \
--region YOUR_REGION \
--num-workers 1 \
--staging-location gs://YOUR_BUCKET/temp \
--parameters inputFilePattern=gs://YOUR_BUCKET/*.gz,outputDirectory=gs://YOUR_BUCKET/,outputFailureFile=gs://YOUR_BUCKET/decomperror.txt
Another fast way to do it using Python in version 3.2 or higher:
import shutil
shutil.unpack_archive('filename')
The method also allows you to indicate the destination folder:
shutil.unpack_archive('filename', 'extract_dir')
The above method works not only for zip archives, but also for tar, gztar, bztar, or xztar archives.
If you need more options look into documentation of shutil module: shutil.unpack_archive

How to execute .jar file from AWS Lambda Serverless?

I have tried with following code.
var exec = require('child_process').execFile;
var runCmd = 'java -jar ' + process.env.LAMBDA_TASK_ROOT + '/src/' + 'myjar.jar'
exec(runCmd,
function (err, resp) {
if (err) {
cb(null, { err: err})
} else {
cb(null, { resp: resp})
}
)
Here, I have put my jar file in the root folder and src folder also.
but it is giving my following error. I have already added the.jar file with the code.but i got following error.
"err": {
"code": "ENOENT",
"errno": "ENOENT",
"syscall": "spawn java -jar /var/task/src/myjar.jar",
"path": "java -jar /var/task/src/myjar.jar",
"spawnargs": [],
"cmd": "java -jar /var/task/src/myjar.jar"
}
So How, Can I execute this .jar file in AWS Lambda environment?
Please help me.
With Lambda Layers you can now bring in multiple runtimes.
https://github.com/lambci/yumda and https://github.com/mthenw/awesome-layers both have a lot of prebuilt packages that you can use to create a layer so you have a second runtime available in your environment.
For instance, I'm currently working on a project that uses the Ruby 2.5 runtime on top of a custom layer built from lambci/yumbda to provide Java.
mkdir dependencies
docker run --rm -v "$PWD"/dependencies:/lambda/opt lambci/yumda:1 yum install -y java-1.8.0-openjdk-devel.x86_64
cd dependencies
zip -yr ../javaLayer .
upload javaLayer.zip to aws lambda as a layer
add layer to your function
within your function, java will be located at /opt/lib/jvm/{YOUR_SPECIFIC_JAVA_VERSION}/jre/bin/java
AWS Lambda lets you select a runtime at the time of creation of that lambda function, or later you can change it again.
So, as you are running the Lambda function with NodeJs runtime, the container will not have Java runtime available to it.
You can only have one type of runtime in one container in case of AWS Lambda.
So, Create a separate Lambda with the Jar file that you want to run having Java as the runtime and then you can trigger that lambda function from your current NodeJS lambda function if that's what you ultimately want.
Following is an example of how you can call another Lambda function using NodeJS
var aws = require('aws-sdk');
var lambda = new aws.Lambda({
region: 'put_your_region_here'
});
lambda.invoke({
FunctionName: 'lambda_function_name',
Payload: JSON.stringify(event, null, 2)
}, function(error, data) {
if (error) {
context.done('error', error);
}
if(data.Payload){
context.succeed(data.Payload)
}
});
You can refer to the official documentation for more details.
In addition to the other answers: Since 2020 December, Lambda supports container images: https://aws.amazon.com/blogs/aws/new-for-aws-lambda-container-image-support/
Ex.: I created a container image using AWS's open-source base image for python, adding a line to install java. One thing my python code did was execute a .jar file using a sys call.