"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const actions = require("@aws-cdk/aws-codepipeline-api");
const events = require("@aws-cdk/aws-events");
const iam = require("@aws-cdk/aws-iam");
const s3 = require("@aws-cdk/aws-s3");
const cdk = require("@aws-cdk/cdk");
const util = require("@aws-cdk/util");
const codepipeline_generated_1 = require("./codepipeline.generated");
const stage_1 = require("./stage");
/**
 * The ARN of a pipeline
 */
class PipelineArn extends cdk.Arn {
}
exports.PipelineArn = PipelineArn;
/**
 * The name of the pipeline.
 */
class PipelineName extends cdk.Token {
}
exports.PipelineName = PipelineName;
/**
 * The pipeline version.
 */
class PipelineVersion extends cdk.Token {
}
exports.PipelineVersion = PipelineVersion;
/**
 * An AWS CodePipeline pipeline with its associated IAM role and S3 bucket.
 *
 * @example
 * // create a pipeline
 * const pipeline = new Pipeline(this, 'Pipeline');
 *
 * // add a stage
 * const sourceStage = new Stage(pipeline, 'Source');
 *
 * // add a source action to the stage
 * new codecommit.PipelineSource(sourceStage, 'Source', {
 *     artifactName: 'SourceArtifact',
 *     repository: repo,
 * });
 *
 * // ... add more stages
 */
class Pipeline extends cdk.Construct {
    constructor(parent, name, props) {
        super(parent, name);
        this.stages = new Array();
        props = props || {};
        actions.validateName('Pipeline', props.pipelineName);
        // If a bucket has been provided, use it - otherwise, create a bucket.
        let propsBucket = props.artifactBucket;
        if (!propsBucket) {
            propsBucket = new s3.Bucket(this, 'ArtifactsBucket', {
                removalPolicy: cdk.RemovalPolicy.Orphan
            });
        }
        this.artifactBucket = propsBucket;
        this.role = new iam.Role(this, 'Role', {
            assumedBy: new cdk.ServicePrincipal('codepipeline.amazonaws.com')
        });
        const codePipeline = new codepipeline_generated_1.cloudformation.PipelineResource(this, 'Resource', {
            artifactStore: new cdk.Token(() => this.renderArtifactStore()),
            stages: new cdk.Token(() => this.renderStages()),
            roleArn: this.role.roleArn,
            restartExecutionOnUpdate: props && props.restartExecutionOnUpdate,
            pipelineName: props && props.pipelineName,
        });
        // this will produce a DependsOn for both the role and the policy resources.
        codePipeline.addDependency(this.role);
        this.artifactBucket.grantReadWrite(this.role);
        this.pipelineName = codePipeline.ref;
        this.pipelineVersion = codePipeline.getAtt('Version');
        // Does not expose a Fn::GetAtt for the ARN so we'll have to make it ourselves
        this.pipelineArn = new PipelineArn(cdk.Arn.fromComponents({
            service: 'codepipeline',
            resource: this.pipelineName
        }));
    }
    /**
     * Adds a statement to the pipeline role.
     */
    addToRolePolicy(statement) {
        this.role.addToPolicy(statement);
    }
    /**
     * Allows the pipeline to be used as a CloudWatch event rule target.
     *
     * Usage:
     *
     *      const pipeline = new Pipeline(this, 'MyPipeline');
     *      const rule = new EventRule(this, 'MyRule', { schedule: 'rate(1 minute)' });
     *      rule.addTarget(pipeline);
     *
     */
    asEventRuleTarget(_ruleArn, _ruleId) {
        // the first time the event rule target is retrieved, we define an IAM
        // role assumable by the CloudWatch events service which is allowed to
        // start the execution of this pipeline. no need to define more than one
        // role per pipeline.
        if (!this.eventsRole) {
            this.eventsRole = new iam.Role(this, 'EventsRole', {
                assumedBy: new cdk.ServicePrincipal('events.amazonaws.com')
            });
            this.eventsRole.addToPolicy(new cdk.PolicyStatement()
                .addResource(this.pipelineArn)
                .addAction('codepipeline:StartPipelineExecution'));
        }
        return {
            id: this.id,
            arn: this.pipelineArn,
            roleArn: this.eventsRole.roleArn,
        };
    }
    /**
     * Defines an event rule triggered by the "CodePipeline Pipeline Execution
     * State Change" event emitted from this pipeline.
     *
     * @param target Initial target to add to the event rule. You can also add
     * targets and customize target inputs by calling `rule.addTarget(target[,
     * options])` after the rule was created.
     *
     * @param options Additional options to pass to the event rule
     *
     * @param name The name of the event rule construct. If you wish to define
     * more than a single onStateChange event, you will need to explicitly
     * specify a name.
     */
    onStateChange(name, target, options) {
        const rule = new events.EventRule(this, name, options);
        rule.addTarget(target);
        rule.addEventPattern({
            detailType: ['CodePipeline Pipeline Execution State Change'],
            source: ['aws.codepipeline'],
            resources: [this.pipelineArn],
        });
        return rule;
    }
    /**
     * Validate the pipeline structure
     *
     * Validation happens according to the rules documented at
     *
     * https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#pipeline-requirements
     * @override
     */
    validate() {
        return util.flatten([
            this.validateHasStages(),
            this.validateSourceActionLocations()
        ]);
    }
    /**
     * If a stage is added as a child, add it to the list of stages.
     * TODO: This is a hack that should be removed once the CDK has an
     *       onChildAdded type hook.
     * @override
     */
    addChild(child, name) {
        super.addChild(child, name);
        if (child instanceof stage_1.Stage) {
            this.appendStage(child);
        }
    }
    appendStage(stage) {
        if (this.stages.find(x => x.id === stage.id)) {
            throw new Error(`A stage with name '${stage.id}' already exists`);
        }
        this.stages.push(stage);
    }
    validateSourceActionLocations() {
        return util.flatMap(this.stages, (stage, i) => {
            const onlySourceActionsPermitted = i === 0;
            return util.flatMap(stage.actions, (action, _) => actions.validateSourceAction(onlySourceActionsPermitted, action.category, action.id, stage.id));
        });
    }
    validateHasStages() {
        if (this.stages.length < 2) {
            return ['Pipeline must have at least two stages'];
        }
        return [];
    }
    renderArtifactStore() {
        let encryptionKey;
        const bucketKey = this.artifactBucket.encryptionKey;
        if (bucketKey) {
            encryptionKey = {
                type: 'KMS',
                id: bucketKey.keyArn,
            };
        }
        const bucketName = this.artifactBucket.bucketName;
        if (!bucketName) {
            throw new Error('Artifacts bucket must have a bucket name');
        }
        return {
            type: 'S3',
            location: bucketName,
            encryptionKey
        };
    }
    renderStages() {
        return this.stages.map(stage => stage.render());
    }
}
exports.Pipeline = Pipeline;
//# sourceMappingURL=data:application/json;base64, 