"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cloudwatch = require("@aws-cdk/aws-cloudwatch");
const events = require("@aws-cdk/aws-events");
const iam = require("@aws-cdk/aws-iam");
const cdk = require("@aws-cdk/cdk");
const artifacts_1 = require("./artifacts");
const codebuild_generated_1 = require("./codebuild.generated");
const CODEPIPELINE_TYPE = 'CODEPIPELINE';
/**
 * Represents a reference to a CodeBuild Project.
 *
 * If you're managing the Project alongside the rest of your CDK resources,
 * use the {@link Project} class.
 *
 * If you want to reference an already existing Project
 * (or one defined in a different CDK Stack),
 * use the {@link import} method.
 */
class ProjectRef extends cdk.Construct {
    /**
     * Import a Project defined either outside the CDK,
     * or in a different CDK Stack
     * (and exported using the {@link export} method).
     *
     * @note if you're importing a CodeBuild Project for use
     *   in a CodePipeline, make sure the existing Project
     *   has permissions to access the S3 Bucket of that Pipeline -
     *   otherwise, builds in that Pipeline will always fail.
     *
     * @param parent the parent Construct for this Construct
     * @param name the logical name of this Construct
     * @param props the properties of the referenced Project
     * @returns a reference to the existing Project
     */
    static import(parent, name, props) {
        return new ImportedProjectRef(parent, name, props);
    }
    /**
     * Export this Project. Allows referencing this Project in a different CDK Stack.
     */
    export() {
        return {
            projectName: new cdk.Output(this, 'ProjectName', { value: this.projectName }).makeImportValue(),
        };
    }
    /**
     * Defines a CloudWatch event rule triggered when the build project state
     * changes. You can filter specific build status events using an event
     * pattern filter on the `build-status` detail field:
     *
     *      const rule = project.onStateChange('OnBuildStarted', target);
     *      rule.addEventPattern({
     *          detail: {
     *              'build-status': [
     *                  "IN_PROGRESS",
     *                  "SUCCEEDED",
     *                  "FAILED",
     *                  "STOPPED"
     *              ]
     *          }
     *      });
     *
     * You can also use the methods `onBuildFailed` and `onBuildSucceeded` to define rules for
     * these specific state changes.
     *
     * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
     */
    onStateChange(name, target, options) {
        const rule = new events.EventRule(this, name, options);
        rule.addTarget(target);
        rule.addEventPattern({
            source: ['aws.codebuild'],
            detailType: ['CodeBuild Build State Change'],
            detail: {
                'project-name': [
                    this.projectName
                ]
            }
        });
        return rule;
    }
    /**
     * Defines a CloudWatch event rule that triggers upon phase change of this
     * build project.
     *
     * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html
     */
    onPhaseChange(name, target, options) {
        const rule = new events.EventRule(this, name, options);
        rule.addTarget(target);
        rule.addEventPattern({
            source: ['aws.codebuild'],
            detailType: ['CodeBuild Build Phase Change'],
            detail: {
                'project-name': [
                    this.projectName
                ]
            }
        });
        return rule;
    }
    /**
     * Defines an event rule which triggers when a build starts.
     */
    onBuildStarted(name, target, options) {
        const rule = this.onStateChange(name, target, options);
        rule.addEventPattern({
            detail: {
                'build-status': ['IN_PROGRESS']
            }
        });
        return rule;
    }
    /**
     * Defines an event rule which triggers when a build fails.
     */
    onBuildFailed(name, target, options) {
        const rule = this.onStateChange(name, target, options);
        rule.addEventPattern({
            detail: {
                'build-status': ['FAILED']
            }
        });
        return rule;
    }
    /**
     * Defines an event rule which triggers when a build completes successfully.
     */
    onBuildSucceeded(name, target, options) {
        const rule = this.onStateChange(name, target, options);
        rule.addEventPattern({
            detail: {
                'build-status': ['SUCCEEDED']
            }
        });
        return rule;
    }
    /**
     * @returns a CloudWatch metric associated with this build project.
     * @param metricName The name of the metric
     * @param props Customization properties
     */
    metric(metricName, props) {
        return new cloudwatch.Metric(Object.assign({ namespace: 'AWS/CodeBuild', metricName, dimensions: { ProjectName: this.projectName } }, props));
    }
    /**
     * Measures the number of builds triggered.
     *
     * Units: Count
     *
     * Valid CloudWatch statistics: Sum
     *
     * @default sum over 5 minutes
     */
    metricBuilds(props) {
        return this.metric('Builds', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Measures the duration of all builds over time.
     *
     * Units: Seconds
     *
     * Valid CloudWatch statistics: Average (recommended), Maximum, Minimum
     *
     * @default average over 5 minutes
     */
    metricDuration(props) {
        return this.metric('Duration', Object.assign({ statistic: 'avg' }, props));
    }
    /**
     * Measures the number of successful builds.
     *
     * Units: Count
     *
     * Valid CloudWatch statistics: Sum
     *
     * @default sum over 5 minutes
     */
    metricSucceededBuilds(props) {
        return this.metric('SucceededBuilds', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Measures the number of builds that failed because of client error or
     * because of a timeout.
     *
     * Units: Count
     *
     * Valid CloudWatch statistics: Sum
     *
     * @default sum over 5 minutes
     */
    metricFailedBuilds(props) {
        return this.metric('FailedBuilds', Object.assign({ statistic: 'sum' }, props));
    }
    /**
     * Allows using build projects as event rule targets.
     */
    asEventRuleTarget(_ruleArn, _ruleId) {
        if (!this.eventsRole) {
            this.eventsRole = new iam.Role(this, 'EventsRole', {
                assumedBy: new cdk.ServicePrincipal('events.amazonaws.com')
            });
            this.eventsRole.addToPolicy(new cdk.PolicyStatement()
                .addAction('codebuild:StartBuild')
                .addResource(this.projectArn));
        }
        return {
            id: this.id,
            arn: this.projectArn,
            roleArn: this.eventsRole.roleArn,
        };
    }
}
exports.ProjectRef = ProjectRef;
class ImportedProjectRef extends ProjectRef {
    constructor(parent, name, props) {
        super(parent, name);
        this.role = undefined;
        this.projectArn = cdk.Arn.fromComponents({
            service: 'codebuild',
            resource: 'project',
            resourceName: props.projectName,
        });
        this.projectName = props.projectName;
    }
}
/**
 * A representation of a CodeBuild Project.
 */
class Project extends ProjectRef {
    constructor(parent, name, props) {
        super(parent, name);
        this.role = props.role || new iam.Role(this, 'Role', {
            assumedBy: new cdk.ServicePrincipal('codebuild.amazonaws.com')
        });
        const environment = this.renderEnvironment(props.environment, props.environmentVariables);
        let cache;
        if (props.cacheBucket) {
            const cacheDir = props.cacheDir != null ? props.cacheDir : '';
            cache = {
                type: 'S3',
                location: props.cacheBucket.arnForObjects(cacheDir)
            };
            props.cacheBucket.grantReadWrite(this.role);
        }
        // let source "bind" to the project. this usually involves granting permissions
        // for the code build role to interact with the source.
        const source = props.source;
        source.bind(this);
        const artifacts = this.parseArtifacts(props);
        artifacts.bind(this);
        const sourceJson = source.toSourceJSON();
        if (typeof props.buildSpec === 'string') {
            sourceJson.buildSpec = props.buildSpec;
        }
        else {
            sourceJson.buildSpec = JSON.stringify(props.buildSpec);
        }
        this.validateCodePipelineSettings(source, artifacts);
        const resource = new codebuild_generated_1.cloudformation.ProjectResource(this, 'Resource', {
            description: props.description,
            source: sourceJson,
            artifacts: artifacts.toArtifactsJSON(),
            serviceRole: this.role.roleArn,
            environment,
            encryptionKey: props.encryptionKey && props.encryptionKey.keyArn,
            badgeEnabled: props.badge,
            cache,
            projectName: props.projectName,
        });
        this.projectArn = resource.projectArn;
        this.projectName = resource.ref;
        this.addToRolePolicy(this.createLoggingPermission());
    }
    /**
     * Add a permission only if there's a policy attached.
     * @param statement The permissions statement to add
     */
    addToRolePolicy(statement) {
        if (this.role) {
            this.role.addToPolicy(statement);
        }
    }
    createLoggingPermission() {
        const logGroupArn = cdk.Arn.fromComponents({
            service: 'logs',
            resource: 'log-group',
            sep: ':',
            resourceName: new cdk.FnConcat('/aws/codebuild/', this.projectName),
        });
        const logGroupStarArn = new cdk.FnConcat(logGroupArn, ':*');
        const p = new cdk.PolicyStatement();
        p.allow();
        p.addResource(logGroupArn);
        p.addResource(logGroupStarArn);
        p.addAction('logs:CreateLogGroup');
        p.addAction('logs:CreateLogStream');
        p.addAction('logs:PutLogEvents');
        return p;
    }
    renderEnvironment(env = {}, projectVars = {}) {
        const vars = {};
        const containerVars = env.environmentVariables || {};
        // first apply environment variables from the container definition
        for (const name of Object.keys(containerVars)) {
            vars[name] = containerVars[name];
        }
        // now apply project-level vars
        for (const name of Object.keys(projectVars)) {
            vars[name] = projectVars[name];
        }
        const hasEnvironmentVars = Object.keys(vars).length > 0;
        const buildImage = env.buildImage || LinuxBuildImage.UBUNTU_14_04_BASE;
        const errors = buildImage.validate(env);
        if (errors.length > 0) {
            throw new Error("Invalid CodeBuild environment: " + errors.join('\n'));
        }
        return {
            type: buildImage.type,
            image: buildImage.imageId,
            privilegedMode: env.priviledged || false,
            computeType: env.computeType || buildImage.defaultComputeType,
            environmentVariables: !hasEnvironmentVars ? undefined : Object.keys(vars).map(name => ({
                name,
                type: vars[name].type || BuildEnvironmentVariableType.PlainText,
                value: vars[name].value
            }))
        };
    }
    parseArtifacts(props) {
        if (props.artifacts) {
            return props.artifacts;
        }
        if (props.source.toSourceJSON().type === CODEPIPELINE_TYPE) {
            return new artifacts_1.CodePipelineBuildArtifacts();
        }
        else {
            return new artifacts_1.NoBuildArtifacts();
        }
    }
    validateCodePipelineSettings(source, artifacts) {
        const sourceType = source.toSourceJSON().type;
        const artifactsType = artifacts.toArtifactsJSON().type;
        if ((sourceType === CODEPIPELINE_TYPE || artifactsType === CODEPIPELINE_TYPE) &&
            (sourceType !== artifactsType)) {
            throw new Error('Both source and artifacts must be set to CodePipeline');
        }
    }
}
exports.Project = Project;
/**
 * Build machine compute type.
 */
var ComputeType;
(function (ComputeType) {
    ComputeType["Small"] = "BUILD_GENERAL1_SMALL";
    ComputeType["Medium"] = "BUILD_GENERAL1_MEDIUM";
    ComputeType["Large"] = "BUILD_GENERAL1_LARGE";
})(ComputeType = exports.ComputeType || (exports.ComputeType = {}));
/**
 * A CodeBuild image running Linux.
 * This class has a bunch of public constants that represent the most popular images.
 * If you need to use with an image that isn't in the named constants,
 * you can always instantiate it directly.
 *
 * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
 */
class LinuxBuildImage {
    constructor(imageId) {
        this.imageId = imageId;
        this.type = 'LINUX_CONTAINER';
        this.defaultComputeType = ComputeType.Small;
    }
    validate(_) {
        return [];
    }
}
LinuxBuildImage.UBUNTU_14_04_BASE = new LinuxBuildImage('aws/codebuild/ubuntu-base:14.04');
LinuxBuildImage.UBUNTU_14_04_ANDROID_JAVA8_24_4_1 = new LinuxBuildImage('aws/codebuild/android-java-8:24.4.1');
LinuxBuildImage.UBUNTU_14_04_ANDROID_JAVA8_26_1_1 = new LinuxBuildImage('aws/codebuild/android-java-8:26.1.1');
LinuxBuildImage.UBUNTU_14_04_DOCKER_17_09_0 = new LinuxBuildImage('aws/codebuild/docker:17.09.0');
LinuxBuildImage.UBUNTU_14_04_GOLANG_1_10 = new LinuxBuildImage('aws/codebuild/golang:1.10');
LinuxBuildImage.UBUNTU_14_04_OPEN_JDK_8 = new LinuxBuildImage('aws/codebuild/java:openjdk-8');
LinuxBuildImage.UBUNTU_14_04_OPEN_JDK_9 = new LinuxBuildImage('aws/codebuild/java:openjdk-9');
LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0 = new LinuxBuildImage('aws/codebuild/nodejs:10.1.0');
LinuxBuildImage.UBUNTU_14_04_NODEJS_8_11_0 = new LinuxBuildImage('aws/codebuild/nodejs:8.11.0');
LinuxBuildImage.UBUNTU_14_04_NODEJS_6_3_1 = new LinuxBuildImage('aws/codebuild/nodejs:6.3.1');
LinuxBuildImage.UBUNTU_14_04_PHP_5_6 = new LinuxBuildImage('aws/codebuild/php:5.6');
LinuxBuildImage.UBUNTU_14_04_PHP_7_0 = new LinuxBuildImage('aws/codebuild/php:7.0');
LinuxBuildImage.UBUNTU_14_04_PYTHON_3_6_5 = new LinuxBuildImage('aws/codebuild/python:3.6.5');
LinuxBuildImage.UBUNTU_14_04_PYTHON_3_5_2 = new LinuxBuildImage('aws/codebuild/python:3.5.2');
LinuxBuildImage.UBUNTU_14_04_PYTHON_3_4_5 = new LinuxBuildImage('aws/codebuild/python:3.4.5');
LinuxBuildImage.UBUNTU_14_04_PYTHON_3_3_6 = new LinuxBuildImage('aws/codebuild/python:3.3.6');
LinuxBuildImage.UBUNTU_14_04_PYTHON_2_7_12 = new LinuxBuildImage('aws/codebuild/python:2.7.12');
LinuxBuildImage.UBUNTU_14_04_RUBY_2_5_1 = new LinuxBuildImage('aws/codebuild/ruby:2.5.1');
LinuxBuildImage.UBUNTU_14_04_RUBY_2_3_1 = new LinuxBuildImage('aws/codebuild/ruby:2.3.1');
LinuxBuildImage.UBUNTU_14_04_RUBY_2_2_5 = new LinuxBuildImage('aws/codebuild/ruby:2.2.5');
LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_1_1 = new LinuxBuildImage('aws/codebuild/dot-net:core-1');
LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_2_0 = new LinuxBuildImage('aws/codebuild/dot-net:core-2.0');
LinuxBuildImage.UBUNTU_14_04_DOTNET_CORE_2_1 = new LinuxBuildImage('aws/codebuild/dot-net:core-2.1');
exports.LinuxBuildImage = LinuxBuildImage;
/**
 * A CodeBuild image running Windows.
 * This class has a bunch of public constants that represent the most popular images.
 * If you need to use with an image that isn't in the named constants,
 * you can always instantiate it directly.
 *
 * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
 */
class WindowsBuildImage {
    constructor(imageId) {
        this.imageId = imageId;
        this.type = 'WINDOWS_CONTAINER';
        this.defaultComputeType = ComputeType.Medium;
    }
    validate(buildEnvironment) {
        const ret = [];
        if (buildEnvironment.computeType === ComputeType.Small) {
            ret.push("Windows images do not support the Small ComputeType");
        }
        return ret;
    }
}
WindowsBuildImage.WIN_SERVER_CORE_2016_BASE = new WindowsBuildImage('aws/codebuild/windows-base:1.0');
exports.WindowsBuildImage = WindowsBuildImage;
var BuildEnvironmentVariableType;
(function (BuildEnvironmentVariableType) {
    /**
     * An environment variable in plaintext format.
     */
    BuildEnvironmentVariableType["PlainText"] = "PLAINTEXT";
    /**
     * An environment variable stored in Systems Manager Parameter Store.
     */
    BuildEnvironmentVariableType["ParameterStore"] = "PARAMETER_STORE";
})(BuildEnvironmentVariableType = exports.BuildEnvironmentVariableType || (exports.BuildEnvironmentVariableType = {}));
class ProjectName extends cdk.Token {
}
exports.ProjectName = ProjectName;
//# sourceMappingURL=data:application/json;base64, 