Home Reference Source Test

functions/transfer/transfer/uploader.js

import typedError from 'error/typed';

import Artifact from './artifact';
import Destination from './destination';
import validate from './validate';

/**
 * A validation error was encountered
 *
 * @type {Error}
 */
const inputArtifactsError = typedError({
  message: 'No InputArtifacts defined.',
  type: 'pipeline.start',
});

export default class Uploader {
  /**
   * Uploads artifacts to a remote S3 bucket defined inside an array of file
   * globs, of locations inside pipeline input artifacts.
   *
   * @example
   * ```
   *  {
   *     "CodePipeline.job": {
   *         "id": "11111111-abcd-1111-abcd-111111abcdef",
   *         "accountId": "111111111111",
   *         "data": {
   *             "actionConfiguration": {
   *                 "configuration": {
   *                     "FunctionName": "MyLambdaFunctionForAWSCodePipeline",
   *                     "UserParameters": "...."
   *                 }
   *             },
   *             "inputArtifacts": [
   *                 {
   *                     "location": {
   *                         "s3Location": {
   *                             "bucketName": "codepipeline-us-east-2-1234567890",
   *                             "objectKey": "rj90jda90ja90j09tj09jaf.zip"
   *                         },
   *                         "type": "S3"
   *                     },
   *                     "revision": null,
   *                     "name": "ArtifactName"
   *                 }
   *             ],
   *             "outputArtifacts": [],
   *             "artifactCredentials": {
   *                 "secretAccessKey": "....",
   *                 "sessionToken": "....",
   *                 "accessKeyId": "...."
   *             },
   *             "continuationToken": "A continuation token if continuing job"
   *         }
   *     }
   *  }
   * ```
   *
   * @param {Object} data - A CodePipeline data event object.
   */
  constructor({ data }) {
    /**
     * The data object as received from AWS CodePipeline.
     *
     * @type {Object}
     */
    this.data = data;
    /**
     * The parsed contents of the `UserParameters` string as defined inside the
     * action in the CodePipeline resource. This should be a valid array of objects,
     * similar to the example below:
     *
     * ```
     * [
     * {
     *   "roleArn": {
     *     "Fn::GetParam": ["DeployOutput", "Outputs.json", "AssetS3BucketTransferRole"]
     *   },
     *   "bucket": {
     *     "Fn::GetParam": ["DeployOutput", "Outputs.json", "AssetS3Bucket"]
     *   },
     *   "prefix": "s3/key/prefix/",
     *   "src": [
     *     "BuildOutput::*.js"
     *   ]
     * }
     * ]
     * ```
     *
     * @type {Object}
     */
    this.parameters = JSON.parse(this.data.actionConfiguration.configuration.UserParameters);
    const inputs = data.inputArtifacts;
    if (!inputs || inputs.length === 0) {
      throw inputArtifactsError({});
    }
    const mapper = a => Artifact.toArtifactMapEntry(a, data.artifactCredentials);
    /**
     * An array of {@link Artifact} objects which have been made available to
     * the CodePipeline action definition.
     *
     * @type {String}
     */
    this.artifacts = inputs.map(mapper).reduce((memo, [key, val]) => {
      const obj = memo;
      obj[key] = val;
      return obj;
    }, {});
  }

  /**
   * Retrieve validated (and defaulted) parameters which have been defined inside
   * the action in the CodePipeline resource.
   *
   * @return {Object} valid parameters to be used to construct the state machine input.
   */
  async userParameters() {
    return validate(this.parameters);
  }

  /**
   * Maps all defined src/dest combinations (from {@link Uploader#userParameters}) to
   * destination objects ready for upload.
   *
   * @return {Array[Destination]} an array of Destination objects
   */
  async destinations() {
    const { artifacts } = this;
    const params = await this.userParameters();
    return params.map(p => new Destination(p, artifacts));
  }

  /**
   * Performs the intended operation and uploads source files from pipeine
   * artifacts to remote S3 locations by assuming the provided roles to
   * provide the correct permission sets.
   *
   * The promise resolves successfully after all source files have been uploaded.
   *
   * @return {Boolean} true if successful, throws otherwise.
   */
  async perform() {
    const destinations = await this.destinations();
    return Promise.all(destinations.map(d => d.upload()));
  }
}