jcberquist / aws-cfml

Lucee/ColdFusion library for interacting with AWS API's
MIT License
72 stars 53 forks source link

aws-cfml

aws-cfml is a CFML library for interacting with AWS APIs.

It requires Lucee 4.5+ or ColdFusion 11+.

It currently supports the following APIs:

Note: It has full support for the AWS S3 and AWS DynamoDB REST APIs. Other services are supported to varying degrees - if you are using a service and add a method you need, please consider contributing it back to this project. My thanks to @davidsf and @sjdaniels who contributed the translate and rekognition service components respectively.

aws-cfml also supports making signed requests to arbitrary AWS endpoints. It currently supports only AWS Signature v4 for authentication.

Installation

This wrapper can be installed as standalone library or as a ColdBox Module. Either approach requires a simple CommandBox command:

$ box install aws-cfml

Alternatively the git repository can be cloned into the desired directory.

Standalone Usage

Once the library has been installed, the core aws component can be instantiated directly:

aws = new path.to.awscfml.aws(
    awsKey = 'YOUR_PUBLIC_KEY',
    awsSecretKey = 'YOUR_PRIVATE_KEY',
    defaultRegion = 'us-east-1'
);

Note: An optional httpProxy argument is available when initializing the core aws component (a struct with server and port keys). When set this will proxy all AWS requests.

ColdBox Module

To use the library as a ColdBox Module, add the init arguments to the moduleSettings struct in config/Coldbox.cfc:

moduleSettings = {
    awscfml: {
        awsKey: '',
        awsSecretKey: '',
        defaultRegion: ''
    }
}

You can then leverage the library via the injection DSL: aws@awscfml:

property name="aws" inject="aws@awscfml";

Note: You can bypass the init arguments altogether and use environment variables, a credentials file, or an IAM role with aws-cfml. See Credentials below.

Getting Started

// aws.cfc contains components for interacting with AWS services
aws = new aws(awsKey = 'YOUR_PUBLIC_KEY', awsSecretKey = 'YOUR_PRIVATE_KEY', defaultRegion = 'us-east-1');

buckets = aws.s3.listBuckets();
tables = aws.dynamodb.listTables();

// you can make signed requests to any AWS endpoint using the following:
response = aws.api.call(service, host, region, httpMethod, path, queryParams, headers, body, awsCredentials);

// using named arguments you can supply a `region` to any method call
// this overrides the default region set at init
response = aws.s3.listBucket(region = 'us-west-1', bucket = 'mybucket');

All responses are returned as a struct with the following format:

response = {
    responseHeaders: { } // struct containing the headers returned from the HTTP request
    responseTime: 123 // time in milliseconds of the HTTP request
    statusCode: 200 // status code returned
    rawData: bodyOfHTTPResponse // whatever was in the body of the HTTP request response
    data: parsedRawData // the rawData response parsed into CFML (from XML or JSON)
};

Note: The data key might not be present if the request is one where it does not make sense to parse the body of the response. (For instance when getting an object from S3.)

Credentials

You can supply an aws key and secret key at initialization:

aws = new aws( awsKey = 'YOUR_PUBLIC_KEY', awsSecretKey = 'YOUR_PRIVATE_KEY' )

If you do not then aws-cfml will follow the configuration resolution followed by the AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html - see Configuration Settings and Precedence. In order, it will check for the presence of environment variables, a credentials file, and IAM role credentials (which are only valid on EC2).

You can also pass credentials as a named argument to any service method:

aws = new aws();
awsCredentials = {awsKey: 'ANOTHER_PUBLIC_KEY', awsSecretKey: 'ANOTHER_PRIVATE_KEY'};
response = aws.s3.listBucket(awsCredentials = awsCredentials, region = 'us-west-1', bucket = 'mybucket');

DynamoDB

DynamoDB data is typed. The types supported are Number, String, Binary, Boolean, Null, String Set, Number Set, Binary Set, List and Map. http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html#DataModel.DataTypes

This library's DynamoDB implementation is set up to work with CFML types by default, so that you can supply data in a struct containing string, number, boolean, binary, null, array and struct values. Structs and arrays can be nested. Everything is then type encoded automatically.

Note: This has worked really well for me on Lucee, however, it might not work as well with ColdFusion due to its less accurate variable typing. In both cases, keep in mind that data will typed as a Number if an isNumeric() check returns true, which it may well do in situations you do not expect. In addition, when using ColdFusion the serializeJSON() function seems to want to encode anything that can be cast as a number to a number in the JSON string, so the JSON string has to be edited by dynamodb.cfc before posting it to DynamoDB. It seems to work in my (limited) testing, but it is quite possible I have missed some of the encoding mistakes, which would lead to DynamoDB returning errors for invalidly encoded JSON strings.

Similarly when you retrieve data from a DynamoDB table, it will be automatically decoded for you, so that you get back a struct of data for each row.

Here is an example:

// putting an item with a HASH key of `id = 1`
// note that table HASH and RANGE key values are included in the item struct
item = {
    'id': 1,
    'thisisnull': javaCast( 'null', '' ),
    'number': 3.45,
    'nested': {
        'list': [ 'foo', 2 ]
    }
};
putItemResult = aws.dynamodb.putItem( 'myTableName', item );

// getting that item
itemFromDynamoDB = aws.dynamodb.getItem( 'myTableName', { 'id': 1 } );

If you do not want your data to be type encoded automatically you have two options. The first is to pass the argument typeDefinitions = typeDefStruct into a method where typeDefStruct is a struct whose keys match keys in your item, and whose values are the types of the key values in your item. Where a key match is found, dynamodb.cfc will use the specified type for encoding rather than attempting to determine the type.

The other option is to pass dataTypeEncoding = false to any method, and data will be not be encoded at all. (Thus you will need to encode items yourself.)

Note: If you want to use non-native CFML types such as the various set types, you will need to use one of these latter two options when putting items.

Polly

The following operations have been implemented: DescribeVoices, SynthesizeSpeech.

You can configure the default language and default engine by using a constructorArgs struct in your module settings (or by passing this struct in at init if you are using this as a standalone library):

{
    translate: {
        defaultLanguageCode: 'es-ES',
        defaultEngine: 'neural'
    }
}

Example of synthesizing text using "Kendra" voice for default language (en-US).

response = aws.polly.synthesizeSpeech( 'Hello World', 'Kendra' );
// The stream data is in: response.rawData

Rekognition

The following basic image processing operations have been implemented: DetectText, DetectFaces, RecognizeCelebrities, DetectLabels, DetectModerationLabels.

Here is an example for detecting unsafe content in an image:

imageBinary = fileReadBinary(expandPath('/path/to/image.jpg'));
imageBase64 = binaryEncode(imageBinary, 'base64');

response = aws.rekognition.detectModerationLabels({'Bytes': imageBase64});
moderationlabels = response.data.ModerationLabels;
// moderationlabels is an array of moderation label structs
// see https://docs.aws.amazon.com/rekognition/latest/dg/moderation.html

S3

Most basic operations are supported for S3. However, there is currently no support for updating bucket settings. Support for encrypted buckets and objects is also missing.

TODO: provide an example for using the getFormPostParams() method.

Secrets Manager

Only the GetSecretValue operation has been implemented. Here's an example of using it:

response = aws.secretsmanager.getSecretValue( 'YourSecretId' );
secretValue = response.data.SecretString;

SSM

Only the getParameter and getParameters operations have been implemented. Here's an example of using it:

response = aws.ssm.getParameter( 'YourParameterName', true );
decryptedValue = response.data.Parameter.Value;

Translate

You can configure the default source and target languages by using a constructorArgs struct in your module settings (or by passing this struct in at init if you are using this as a standalone library):

{
    translate: {
        defaultSourceLanguageCode: 'es',
        defaultTargetLanguageCode: 'en'
    }
}

Also you can override in the translate call:

response = aws.translate.translateText( Text = 'house', SourceLanguageCode = 'en', TargetLanguageCode = 'de' );
// The translated text is in: response.data.TranslatedText;

You can see the supported language codes by the service in the api docs: https://docs.aws.amazon.com/en_en/translate/latest/dg/API_TranslateText.html