[solved] AWS S3 - File uploads

--------- AWS Lambda code ---------

Here’s the utils part of my sdk library

'use strict';
const AWS = require('aws-sdk');
// ----------------------------------------------------------------------------------------------------------------

const gleanFromParameters_missing = (parameters) => {
    console.info(`NOTE: you may pass a function 'gleanFromParameters(parameters) 
    into 'configForAWS({ environment, event, context, gleanFromParameters }) 
    to extract info from the object: { environment, event, context }`);
}

// create a configuration for our task 
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#update-property
const configForAWS = ({
    environment = { deepFail: false, deepResults: false, deepDebug: false },
    event = {},
    context = {},
    gleanFromParameters = gleanFromParameters_missing
}) => {
    try {
        let parameters = { environment, event, context };
        parameters = gleanFromParameters(parameters);
        sanityCheckEnvironment(environment);
        const awsConfig = { region: environment.region };
        console.debug(`configForAWS, call: AWS.config.update`, awsConfig);
        AWS.config.update(awsConfig);
        return Promise.resolve({
            ...parameters,
            awsConfig
        });
    } catch (err) {
        throw new Error(`configForAWS threw: ${err.message || err}`);
    }
}

// sanity check environment -- nothing can be empty
const sanityCheckEnvironment = (env) => {
    for (const fldName in env) {
        if (env[fldName] === null || env[fldName] === undefined) {
            throw new Error(`configForAWS, ${fldName} is empty in environment`);
        }
    }
}

// ----------------------------------------------------------------------------------------------------------------

// log our results, with the return value from S3
const reportResults = (parameters) => {
    const { environment } = { ...parameters };
    const { deepResults } = { ...environment };
    try {
        if (deepResults) {
            console.debug(`reportResults parameters:`, JSON.stringify(parameters, null, 2));
        }
        console.log(`completed with success`);
        return parameters;
    } catch (err) {
        throw new Error(`reportResults threw: ${err.message || err}`);
    }
}

// ----------------------------------------------------------------------------------------------------------------

const CallbackSuccessWithText = function (callback, message) {
    const response = {
        statusCode: 200,
        body: message
    };
    return callback(null, response);
};

// ----------------------------------------------------------------------------------------------------------------

const CallbackErrorResponse = function (_a) {
    const err = _a.err, fnName = _a.fnName, callback = _a.callback;
    console.error(fnName + " threw: ", err);
    if (err instanceof HTMLError) {
        return callback(null, err.htmlResponse());
    }
    const unspecifiedErr = new InternalError(function () { return err.message || err; });
    return callback(null, unspecifiedErr.htmlResponse());
};

// ----------------------------------------------------------------------------------------------------------------

module.exports = {
    configForAWS,
    reportResults,
    CallbackSuccessWithText,
    CallbackErrorResponse,
};

Here’s the S3 part of my sdk library

'use strict';
const S3 = require('aws-sdk/clients/s3');
// how long to wait between tests 
const SECONDS_BETWEEN_S3_CHECKS = 5;
// how long to wait before failure
const SECONDS_BEFORE_S3_FAIL = 20;
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
const doGetSignedUrl = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoGetSignedUrl(parameters);
                logParamsForDoGetSignedUrl(params, parameters);
                s3.getSignedUrl(params.Operation, params.Params, (err, getSignedUrlResult) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doGetSignedUrlResult: getSignedUrlResult
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doGetSignedUrl threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoGetSignedUrl = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doGetSignedUrl, call: s3.getSignedUrl`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doGetSignedUrl, call: s3.headBucket, paramBlock:`, loggableParamBlock);
    }
}

const buildS3GetSignedUrlOperation_missing = () => {
    throw new Error(`ERROR: a function 'buildS3GetSignedUrlOperation(parameters)' is missing from the parameters to build the S3 operation`);
}

const buildS3GetSignedUrlParams_missing = () => {
    throw new Error(`NOTE: a function 'buildS3GetSignedUrlParams(parameters)' is missing from the parameters to build the S3 params`);
}

// create S3 params for writing the variable
const paramsForDoGetSignedUrl = (parameters) => {
    const {
        buildS3GetSignedUrlOperation = buildS3GetSignedUrlOperation_missing,
        buildS3GetSignedUrlParams = buildS3GetSignedUrlParams_missing,
    } = { ...parameters };
    return {
        Operation: buildS3GetSignedUrlOperation(parameters),
        Params: buildS3GetSignedUrlParams(parameters)
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and check to see if a bucket exists
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#headBucket-property
const doHeadBucket = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoHeadBucket(parameters);
                logParamsForDoHeadBucket(params, parameters);
                s3.headBucket(params, (err, s3Result) => {
                    if (err) {
                        if (/NotFound/.test(err)) {
                            resolve(parameters); // resolve with no result, this is ok -- anything else is bust
                        }
                        if (/Forbidden/.test(err)) {
                            resolve(parameters); // resolve with no result, this is ok -- anything else is bust
                        }
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doHeadBucketResult: {
                            ...s3Result,
                            bucketName: params.Bucket
                        }
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doHeadBucket threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoHeadBucket = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doHeadBucket, call: s3.headBucket`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doHeadBucket, call: s3.headBucket, paramBlock:`, loggableParamBlock);
    }
}

const buildS3HeadBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3HeadBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to head`);
}

// create S3 params for writing the variable
const paramsForDoHeadBucket = (parameters) => {
    const {
        buildS3HeadBucketName = buildS3HeadBucketName_missing,
    } = { ...parameters };
    return {
        Bucket: buildS3HeadBucketName(parameters),
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and create a bucket
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#createBucket-property
const doCreateBucket = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoCreateBucket(parameters);
                logParamsForDoCreateBucket(params, parameters);
                s3.createBucket(params, (err, s3Result) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doCreateBucketResult: s3Result
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doCreateBucket threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoCreateBucket = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doCreateBucket, call: s3.createBucket`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doCreateBucket, call: s3.createBucket, paramBlock:`, loggableParamBlock);
    }
}

const buildS3CreateBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3CreateBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

// create S3 params for writing the variable, us-east-1 was aws' first and needs special recognition
const paramsForDoCreateBucket = (parameters) => {
    const {
        environment, buildS3CreateBucketName = buildS3CreateBucketName_missing,
    } = { ...parameters };
    return (environment.region === 'us-east-1') ? {
        Bucket: buildS3CreateBucketName(parameters),
    } : {
            Bucket: buildS3CreateBucketName(parameters),
            CreateBucketConfiguration: {
                LocationConstraint: environment.region
            }
        };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and create a bucket
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putBucketVersioning-property
const doPutBucketVersioning = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoPutBucketVersioning(parameters);
                logParamsForDoPutBucketVersioning(params, parameters);
                s3.putBucketVersioning(params, (err, s3Result) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doPutBucketVersioningResult: s3Result
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doPutBucketVersioning threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoPutBucketVersioning = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doPutBucketVersioning, call: s3.putBucketVersioning`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doPutBucketVersioning, call: s3.putBucketVersioning, paramBlock:`, loggableParamBlock);
    }
}

const buildS3VersioningBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3VersioningBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

// create S3 params for writing the variable
const paramsForDoPutBucketVersioning = (parameters) => {
    const {
        buildS3VersioningBucketName = buildS3VersioningBucketName_missing,
    } = { ...parameters };
    return {
        Bucket: buildS3VersioningBucketName(parameters),
        VersioningConfiguration: {
            MFADelete: "Disabled",
            Status: "Enabled"
        }
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putBucketCors-property
const doPutBucketCors = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoPutBucketCors(parameters);
                logParamsForDoPutBucketCors(params, parameters);
                s3.putBucketCors(params, (err, s3Result) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doPutBucketCorsResult: s3Result
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doPutBucketCors threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoPutBucketCors = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doPutBucketCors, call: s3.putBucketCors`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doPutBucketCors, call: s3.putBucketCors, paramBlock:`, loggableParamBlock);
    }
}

const buildS3CorsBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3CorsBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

const buildS3CorsBucketConfig_missing = () => {
    throw new Error(`ERROR: a function 'buildS3CorsBucketConfig(parameters)' is missing from the parameters to build the S3 bucket cors config`);
}

// create S3 params for writing the variable
const paramsForDoPutBucketCors = (parameters) => {
    const {
        buildS3CorsBucketName = buildS3CorsBucketName_missing,
        buildS3CorsBucketConfig = buildS3CorsBucketConfig_missing,
    } = { ...parameters };
    return {
        Bucket: buildS3CorsBucketName(parameters),
        CORSConfiguration: buildS3CorsBucketConfig(parameters)
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and create a bucket
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putPublicAccessBlock-property
const doPutPublicAccessBlock = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoPutPublicAccessBlock(parameters);
                logParamsForDoPutPublicAccessBlock(params, parameters);
                s3.putPublicAccessBlock(params, (err, s3Result) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doPutPublicAccessBlockResult: s3Result
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doPutPublicAccessBlock threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoPutPublicAccessBlock = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doPutPublicAccessBlock, call: s3.putPublicAccessBlock`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        console.debug(`doPutPublicAccessBlock, call: s3.putPublicAccessBlock, paramBlock:`, loggableParamBlock);
    }
}

const buildS3PublicAccessBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3PublicAccessBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

// create S3 params for writing the variable
const paramsForDoPutPublicAccessBlock = (parameters) => {
    const {
        buildS3PublicAccessBucketName = buildS3PublicAccessBucketName_missing,
    } = { ...parameters };
    return {
        Bucket: buildS3PublicAccessBucketName(parameters),
        PublicAccessBlockConfiguration: { 
            BlockPublicAcls: true,
            BlockPublicPolicy: true,
            IgnorePublicAcls: true,
            RestrictPublicBuckets: true
        }
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// ensure the bucket exists and is ready
const doEnsureBucket = (parameters) => {
    try {
        return new Promise((resolve, reject) => {
            doHeadBucket(parameters) // if bucket exists
                .then(parameters => {
                    const { doHeadBucketResult } = { ...parameters };
                    const { bucketName } = { ...doHeadBucketResult };
                    if (bucketName) {
                        resolve(parameters); // then the bucket is there
                    } else {
                        doCreateBucket(parameters) // otherwise create it, which we have to wait for
                            .then(parameters => {
                                const bucketCreateFailedAfter = new Date().getTime() + (1000 * SECONDS_BEFORE_S3_FAIL); // we won't wait too long...
                                const myWatcher = setInterval(() => {
                                    doHeadBucket(parameters) // is is there yet?
                                        .then(parameters => {
                                            const { doHeadBucketResult } = { ...parameters };
                                            const { bucketName } = { ...doHeadBucketResult };
                                            if (bucketName) {
                                                clearInterval(myWatcher);
                                                resolve(parameters); // then we succeded
                                            }
                                        })
                                        .catch(err => {
                                            if (new Date().getTime() > bucketCreateFailedAfter) {
                                                clearInterval(myWatcher);
                                                reject(`doEnsureBucket call rejected: ${err.message || err}`); // we waited, and it never showed up
                                            }
                                        })
                                }, 1000 * SECONDS_BETWEEN_S3_CHECKS);
                            })
                            .catch(err => {
                                reject(`doCreateBucket call threw: ${err.message || err}`); // we could not create it
                            });
                    }
                })
                .catch(err => {
                    reject(`doHeadBucket call threw: ${err.message || err}`); // we could not find it
                });
        });
    } catch (err) {
        throw new Error(`doEnsureBucket threw: ${err.message || err}`);
    }
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and write these objects [Value] into a file as JSON
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
const doPutObject = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForDoPutObject(parameters);
                logParamsForDoPutObject(params, parameters);
                s3.putObject(params, (err, s3Result) => {
                    if (err) {
                        reject({ message: err });
                    }
                    resolve({
                        ...parameters,
                        doPutObjectResult: s3Result
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doPutObject threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForDoPutObject = (paramBlock, parameters) => {
    const { environment } = { ...parameters };
    const { deepDebug } = { ...environment };
    if (deepDebug) {
        console.debug(`---------> doPutObject, call: s3.putObject`, JSON.stringify(paramBlock, null, 2));
    } else {
        const loggableParamBlock = JSON.parse(JSON.stringify(paramBlock));
        loggableParamBlock.Body = deepDebug ? loggableParamBlock.Body : `<redacted>`;
        console.debug(`doPutObject, call: s3.putObject, paramBlock:`, loggableParamBlock);
    }
}

const buildS3PutObjectBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3PutObjectBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

const buildS3PutObjectS3Key_missing = () => {
    throw new Error(`ERROR: a function 'buildS3PutObjectS3Key(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

const buildS3PutObjectS3ReadyData_missing = () => {
    throw new Error(`ERROR: a function 'buildS3PutObjectS3ReadyData(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

// create S3 params for writing the variable
const paramsForDoPutObject = (parameters) => {
    const {
        buildS3PutObjectS3ReadyData = buildS3PutObjectS3ReadyData_missing,
        buildS3PutObjectBucketName = buildS3PutObjectBucketName_missing,
        buildS3PutObjectS3Key = buildS3PutObjectS3Key_missing,
    } = { ...parameters };
    const s3ReadyData = buildS3PutObjectS3ReadyData(parameters)
    return {
        Body: Buffer.from(s3ReadyData, 'utf8'),
        Bucket: buildS3PutObjectBucketName(parameters),
        Key: buildS3PutObjectS3Key(parameters),
    };
}

// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

// connect to s3 and read the object 
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property
const doGetObject = (parameters) => {
    try {
        const { awsConfig } = { ...parameters };
        const s3 = new S3(awsConfig);
        return new Promise((resolve, reject) => {
            try {
                const params = paramsForGetObject(parameters);
                logParamsForGetObject(params, parameters);
                s3.getObject(params, (err, s3Response) => {
                    if (err) {
                        console.error(`AWS.clients.s3.getObject errored:`, err.stack);
                        reject({
                            ...parameters,
                            error: err.message,
                            stack: err.stack
                        });
                    }
                    resolve({
                        ...parameters,
                        s3Response
                    });
                });
            }
            catch (err) {
                reject(err.message || err);
            }
        });
    } catch (err) {
        throw new Error(`doGetObject threw: ${err.message || err}`);
    }
}

// log safely
const logParamsForGetObject = (paramBlock, parameters) => {
    console.debug(`doGetObject, call: s3.getObject`, paramBlock);
}

const buildS3GetObjectBucketName_missing = () => {
    throw new Error(`ERROR: a function 'buildS3GetObjectBucketName(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

const buildS3GetObjectS3Key_missing = () => {
    throw new Error(`ERROR: a function 'buildS3GetObjectS3Key(parameters)' is missing from the parameters to build the S3 bucket you wish to create`);
}

// create S3 params for reading the params file
const paramsForGetObject = (parameters) => {
    const {
        buildS3GetObjectBucketName = buildS3GetObjectBucketName_missing,
        buildS3GetObjectS3Key = buildS3GetObjectS3Key_missing,
    } = { ...parameters };
    return {
        Bucket: buildS3GetObjectBucketName(parameters),
        Key: buildS3GetObjectS3Key(parameters),
    };
}


// ----------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------------

module.exports = {
    doCreateBucket,
    doEnsureBucket,
    doGetObject,
    doGetSignedUrl,
    doHeadBucket,
    doPutBucketCors,
    doPutObject,
    doPutPublicAccessBlock,
    doPutBucketVersioning,
};

Cheers, you made it this far !!

Hope it all helps !!

The End.

1 Like