Beispiel #1
0
    def __init__(self, scope: core.Stack, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # create a pipeline
        self.pipeline = codepipeline.Pipeline(self,
                                              "Pipeline",
                                              pipeline_name='API_Gateway')

        # add a source stage
        self.source_stage = self.pipeline.add_stage(stage_name="Source")
        self.source_artifact = codepipeline.Artifact()

        # codebuild projects
        self.codebuild_validate = CodeBuildProjects(
            self, "CodebuildValidate", buildspec='buildspec-diff.yml')
        self.codebuild_deploy = CodeBuildProjects(self,
                                                  "CodebuildDeploy",
                                                  buildspec='buildspec.yml')

        # add source action
        self.source_stage.add_action(
            codepipeline_actions.GitHubSourceAction(
                oauth_token=core.SecretValue.secrets_manager(
                    secret_id='prod/github_oauth_token',
                    json_field='github_oauth_token'),
                output=self.source_artifact,
                owner=config['CODEPIPELINE']['GITHUB_OWNER'],
                repo=config['CODEPIPELINE']['GITHUB_REPO'],
                branch=config['CODEPIPELINE']['GITHUB_BRANCH'],
                action_name='Pull_Source',
                run_order=1,
            ))

        # add validate stage
        self.validate_stage = self.pipeline.add_stage(stage_name='Validate')

        # add validate codebuild action
        self.validate_stage.add_action(
            codepipeline_actions.CodeBuildAction(
                input=self.source_artifact,
                project=self.codebuild_validate.project,
                action_name='Validate_Changes'))

        # add approval stage
        self.approval_stage = self.pipeline.add_stage(stage_name='Approval')

        # simple approval stage to continue build after manual validation complete
        self.approval_stage.add_action(
            codepipeline_actions.ManualApprovalAction(action_name='Approval'))

        # add deploy stage
        self.deploy_stage = self.pipeline.add_stage(stage_name='Deploy')

        # add deploy codebuild action
        self.deploy_stage.add_action(
            codepipeline_actions.CodeBuildAction(
                input=self.source_artifact,
                project=self.codebuild_deploy.project,
                action_name='Deploy_Changes'))
Beispiel #2
0
def create_approval_stage(self):
    approval_stage=_cp.StageProps(
        stage_name='Approval',
        actions=[
            _cpa.ManualApprovalAction(
                action_name='Approval',
                run_order=1
            )
        ]
    )
    return approval_stage
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        sourceArtifact = codepipeline.Artifact()
        cloudAssemblyArtifact = codepipeline.Artifact()

        pipeline = pipelines.CdkPipeline(
            self,
            'Pipeline',
            pipeline_name=self.node.try_get_context('repository_name') +
            "-{}-pipeline".format(STAGE),
            cloud_assembly_artifact=cloudAssemblyArtifact,
            source_action=actions.GitHubSourceAction(
                action_name='GitHub',
                output=sourceArtifact,
                oauth_token=core.SecretValue.secrets_manager('github-token'),
                owner=self.node.try_get_context('owner'),
                repo=self.node.try_get_context('repository_name'),
                branch=STAGE2),
            synth_action=pipelines.SimpleSynthAction(
                synth_command="cdk synth",
                install_commands=[
                    "pip install --upgrade pip", "npm i -g aws-cdk",
                    "pip install -r requirements.txt"
                ],
                source_artifact=sourceArtifact,
                cloud_assembly_artifact=cloudAssemblyArtifact,
                environment={'privileged': True}))

        stg = PipelineStage(self,
                            self.node.try_get_context('repository_name') +
                            "-{}".format(STAGE),
                            env={
                                'region': "ap-northeast-1",
                                'account': os.environ['STG_ACCOUNT_ID']
                            })

        stg_stage = pipeline.add_application_stage(stg)
        stg_stage.add_actions(
            actions.ManualApprovalAction(
                action_name="Approval",
                run_order=stg_stage.next_sequential_run_order()))
        prod = PipelineStage(self,
                             self.node.try_get_context('repository_name') +
                             "-{}".format(STAGE2),
                             env={
                                 'region': "ap-northeast-1",
                                 'account': os.environ['PROD_ACCOUNT_ID']
                             })
        pipeline.add_application_stage(app_stage=prod)
Beispiel #4
0
def approval_action(approval_name, sns_topic, notification_emails_list,
                    description):
    """
    approval_action configures a codepipeline manual approval

    :approval_name: name of the manual approval action
    :sns_topic: sns topic to use for notifications
    :notification_emails_list: a list of emails to notify to approve the action
    :description: description of the manual approval action
    :return: codepipeline action in a form of a CDK object that can be attached to a codepipeline stage
    """
    return codepipeline_actions.ManualApprovalAction(
        action_name=approval_name,
        notification_topic=sns_topic,
        notify_emails=notification_emails_list,
        additional_information=description,
        run_order=2,
    )
    def _create_manual_approve_step(self):
        # MLflow のダッシュボードを見るための情報
        mlflow_tracking_url = '#{trainStep.MLFLOW_SERVER_URI}'
        # EXPERIMENTS_ID = "4"
        experiment_name = '#{experimentInfo.experiment_name}'
        experiment_id = '#{experimentInfo.experiment_id}'
        # RUN_ID = "11ab290e535140609660b5d894ccdf17"
        run_id = '#{experimentInfo.run_id}'
        topic = self.sns_topic

        additional_info = f"Experiment name: {experiment_name} \nRun id: {run_id} "

        # mlflow_link = mlflow_tracking_url + "#/experiments/" + EXPERIMENTS_ID + "/runs/" + RUN_ID
        mlflow_link = mlflow_tracking_url + "#/experiments/" + experiment_id + "/runs/" + run_id

        stage = self.pipeline.add_stage(stage_name="Approve")
        action = codepipeline_actions.ManualApprovalAction(
            action_name=f"{self.name_prefix}-approval-action",
            notification_topic=topic,
            external_entity_link=mlflow_link,
            additional_information=additional_info)
        stage.add_action(action)
Beispiel #6
0
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        notification_email = ssm.StringParameter.value_from_lookup(
            self,
            parameter_name='/serverless-pipeline/sns/notifications/primary-email'
        )

        github_user = ssm.StringParameter.value_from_lookup(
            self,
            parameter_name='/serverless-pipeline/codepipeline/github/user'
        )

        github_repo = ssm.StringParameter.value_from_lookup(
            self,
            parameter_name='/serverless-pipeline/codepipeline/github/repo'
        )

        github_token = core.SecretValue.secrets_manager(
            '/serverless-pipeline/secrets/github/token',
            json_field='github-token',
        )

        artifact_bucket = s3.Bucket(
            self, 'BuildArtifactsBucket',
            removal_policy=core.RemovalPolicy.RETAIN,
            encryption=s3.BucketEncryption.KMS_MANAGED,
            versioned=True,
        )

        build_project = build.PipelineProject(
            self, 'BuildProject',
            project_name='serveless-pipeline',
            description='Build project for the serverless-pipeline',
            environment=build.LinuxBuildImage.STANDARD_2_0,
            environment_variables={
                'BUILD_ARTIFACT_BUCKET': build.BuildEnvironmentVariable(value=artifact_bucket.bucket_name),
            },
            cache=build.Cache.bucket(artifact_bucket, prefix='codebuild-cache'),
            build_spec=build.BuildSpec.from_object({
                'version': '0.2',
                'phases': {
                    'install': {
                        'runtime-versions': {
                            'nodejs': 10,
                        },
                        'commands': [
                            'echo "--------INSTALL PHASE--------"',
                            'pip3 install aws-sam-cli',
                        ]
                    },
                    'pre_build': {
                        'commands': [
                            'echo "--------PREBUILD PHASE--------"',
                            '# Example shows installation of NPM dependencies for shared deps (layers) in a SAM App',
                            '# cd functions/dependencies/shared_deps_one/nodejs',
                            '# npm install && cd',
                            '# cd functions/dependencies/shared_deps_two/nodejs',
                            '# npm install && cd',
                        ]
                    },
                    'build': {
                        'commands': [
                            'echo "--------BUILD PHASE--------"',
                            'echo "Starting SAM packaging `date` in `pwd`"',
                            'sam package --template-file template.yaml --s3-bucket $BUILD_ARTIFACT_BUCKET --output-template-file packaged.yaml',
                        ]
                    },
                    'post_build': {
                        'commands': [
                            'echo "--------POST-BUILD PHASE--------"',
                            'echo "SAM packaging completed on `date`"',
                        ]
                    }
                },
                'artifacts': {
                    'files': ['packaged.yaml'],
                    'discard-paths': 'yes',
                },
                'cache': {
                    'paths': ['/root/.cache/pip'],
                }
            })
        )

        serverless_pipeline = pipeline.Pipeline(
            self, 'ServerlessPipeline',
            artifact_bucket=artifact_bucket,
            pipeline_name='serverless-pipeline',
            restart_execution_on_update=True,
        )

        source_output = pipeline.Artifact()
        build_output = pipeline.Artifact()
        cfn_output = pipeline.Artifact()

        # NOTE: This Stage/Action requires a manual OAuth handshake in the browser be complete before automated deployment can occur
        # Create a new Pipeline in the console, manually authorize GitHub as a source, and then cancel the pipeline wizard.
        serverless_pipeline.add_stage(stage_name='Source', actions=[
            actions.GitHubSourceAction(
                action_name='SourceCodeRepo',
                owner=github_user,
                oauth_token=github_token,
                repo=github_repo,
                branch='master',
                output=source_output,
            )
        ])
        serverless_pipeline.add_stage(stage_name='Build', actions=[
            actions.CodeBuildAction(
                action_name='CodeBuildProject',
                input=source_output,
                outputs=[build_output],
                project=build_project,
                type=actions.CodeBuildActionType.BUILD,
            )
        ])
        serverless_pipeline.add_stage(stage_name='Staging', actions=[
            actions.CloudFormationCreateReplaceChangeSetAction(
                action_name='CreateChangeSet',
                admin_permissions=True,
                change_set_name='serverless-pipeline-changeset-Staging',
                stack_name='ServerlessPipelineStaging',
                template_path=pipeline.ArtifactPath(
                    build_output,
                    file_name='packaged.yaml'
                ),
                capabilities=[cfn.CloudFormationCapabilities.ANONYMOUS_IAM],
                run_order=1,
            ),
            actions.CloudFormationExecuteChangeSetAction(
                action_name='ExecuteChangeSet',
                change_set_name='serverless-pipeline-changeset-Staging',
                stack_name='ServerlessPipelineStaging',
                output=cfn_output,
                run_order=2,
            ),
        ])

        serverless_pipeline.add_stage(stage_name='Production', actions=[
            actions.CloudFormationCreateReplaceChangeSetAction(
                action_name='CreateChangeSet',
                admin_permissions=True,
                change_set_name='serverless-pipeline-changeset-Production',
                stack_name='ServerlessPipelineProduction',
                template_path=pipeline.ArtifactPath(
                    build_output,
                    file_name='packaged.yaml'
                ),
                capabilities=[cfn.CloudFormationCapabilities.ANONYMOUS_IAM],
                run_order=1,
            ),
            actions.ManualApprovalAction(
                action_name='DeploymentApproval',
                notify_emails=[notification_email],
                run_order=2,
            ),
            actions.CloudFormationExecuteChangeSetAction(
                action_name='ExecuteChangeSet',
                change_set_name='serverless-pipeline-changeset-Production',
                stack_name='ServerlessPipelineProduction',
                output=cfn_output,
                run_order=3,
            ),
        ])

        core.CfnOutput(
            self, 'BuildArtifactsBucketOutput',
            value=artifact_bucket.bucket_name,
            description='Amazon S3 Bucket for Pipeline and Build artifacts',
        )
        core.CfnOutput(
            self, 'CodeBuildProjectOutput',
            value=build_project.project_arn,
            description='CodeBuild Project name',
        )
        core.CfnOutput(
            self, 'CodePipelineOutput',
            value=serverless_pipeline.pipeline_arn,
            description='AWS CodePipeline pipeline name',
        )
Beispiel #7
0
    def __init__(self,
                 scope: core.Construct,
                 id: str,
                 *,
                 git_token_key="",
                 github_owner="",
                 github_repo="",
                 github_branch="",
                 **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        role = iam.Role(
            self,
            "Role",
            assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"))

        role.add_managed_policy(
            iam.ManagedPolicy.from_aws_managed_policy_name(
                "AdministratorAccess"))

        cdk_project = codebuild.PipelineProject(
            self,
            "Codebuild",
            build_spec=codebuild.BuildSpec.from_source_filename(
                "codebuild/buildspec.yaml"),
            cache=codebuild.Cache.bucket(s3.Bucket(self, "Bucket")),
            environment=codebuild.BuildEnvironment(
                build_image=codebuild.LinuxBuildImage.STANDARD_2_0,
                privileged=True),
            role=role)

        source_output = codepipeline.Artifact()
        staging_output = codepipeline.Artifact()
        production_output = codepipeline.Artifact()

        source_action = codepipeline_actions.GitHubSourceAction(
            action_name="GitHub_Source",
            owner=github_owner,
            repo=github_repo,
            branch=github_branch,
            oauth_token=core.SecretValue.secrets_manager(git_token_key),
            output=source_output)

        staging_action = codepipeline_actions.CodeBuildAction(
            action_name="Deliver",
            project=cdk_project,
            input=source_output,
            outputs=[staging_output],
            environment_variables={"ENV": {
                "value": "stg"
            }})

        manual_approval_action = codepipeline_actions.ManualApprovalAction(
            action_name="Approve")

        production_action = codepipeline_actions.CodeBuildAction(
            action_name="Deliver",
            project=cdk_project,
            input=source_output,
            outputs=[production_output],
            environment_variables={"ENV": {
                "value": "prd"
            }})

        key = kms.Key(self, "key")
        bucket = s3.Bucket(self, "bucket_artifacts", encryption_key=key)
        pipeline = codepipeline.Pipeline(self,
                                         "Pipeline",
                                         artifact_bucket=bucket)
        pipeline.add_stage(stage_name="Source", actions=[source_action])
        pipeline.add_stage(stage_name="Staging", actions=[staging_action])
        pipeline.add_stage(stage_name="Approval",
                           actions=[manual_approval_action])
        pipeline.add_stage(stage_name="Production",
                           actions=[production_action])
    def __init__(self, scope: cdk.Construct, construct_id: str,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here

        vpc = _ec2.Vpc(self,
                       "ecs-vpc",
                       cidr="10.0.0.0/16",
                       nat_gateways=1,
                       max_azs=3)

        clusterAdmin = _iam.Role(self,
                                 "AdminRole",
                                 assumed_by=_iam.AccountRootPrincipal())

        cluster = _ecs.Cluster(self, "ecs-cluster", vpc=vpc)

        logging = _ecs.AwsLogDriver(stream_prefix="ecs-logs")

        taskRole = _iam.Role(
            self,
            f"ecs-taskRole-{cdk.Stack.stack_name}",
            role_name=f"ecs-taskRole-{cdk.Stack.stack_name}",
            assumed_by=_iam.ServicePrincipal("ecs-tasks.amazonaws.com"))

        # ECS Contructs

        executionRolePolicy = _iam.PolicyStatement(
            effect=_iam.Effect.ALLOW,
            resources=['*'],
            actions=[
                "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage",
                "logs:CreateLogStream", "logs:PutLogEvents"
            ])

        taskDef = _ecs.FargateTaskDefinition(self,
                                             "ecs-taskdef",
                                             task_role=taskRole)

        taskDef.add_to_execution_role_policy(executionRolePolicy)

        container = taskDef.add_container(
            'flask-app',
            image=_ecs.ContainerImage.from_registry(
                "nikunjv/flask-image:blue"),
            memory_limit_mib=256,
            cpu=256,
            logging=logging)

        container.add_port_mappings(
            _ecs.PortMapping(container_port=5000, protocol=_ecs.Protocol.TCP))

        fargateService = ecs_patterns.ApplicationLoadBalancedFargateService(
            self,
            "ecs-service",
            cluster=cluster,
            task_definition=taskDef,
            public_load_balancer=True,
            desired_count=3,
            listener_port=80)

        scaling = fargateService.service.auto_scale_task_count(max_capacity=6)

        scaling.scale_on_cpu_utilization(
            "CpuScaling",
            target_utilization_percent=10,
            scale_in_cooldown=cdk.Duration.seconds(300),
            scale_out_cooldown=cdk.Duration.seconds(300))

        # PIPELINE CONSTRUCTS

        # ECR Repo

        ecrRepo = ecr.Repository(self, "EcrRepo")

        gitHubSource = codebuild.Source.git_hub(
            owner='samuelhailemariam',
            repo='aws-ecs-fargate-cicd-cdk',
            webhook=True,
            webhook_filters=[
                codebuild.FilterGroup.in_event_of(
                    codebuild.EventAction.PUSH).and_branch_is('main'),
            ])

        # CODEBUILD - project

        project = codebuild.Project(
            self,
            "ECSProject",
            project_name=cdk.Aws.STACK_NAME,
            source=gitHubSource,
            environment=codebuild.BuildEnvironment(
                build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_2,
                privileged=True),
            environment_variables={
                "CLUSTER_NAME": {
                    'value': cluster.cluster_name
                },
                "ECR_REPO_URI": {
                    'value': ecrRepo.repository_uri
                }
            },
            build_spec=codebuild.BuildSpec.from_object({
                'version': "0.2",
                'phases': {
                    'pre_build': {
                        'commands': [
                            'env',
                            'export TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}'
                        ]
                    },
                    'build': {
                        'commands': [
                            'cd docker-app',
                            'docker build -t $ECR_REPO_URI:$TAG .',
                            '$(aws ecr get-login --no-include-email)',
                            'docker push $ECR_REPO_URI:$TAG'
                        ]
                    },
                    'post_build': {
                        'commands': [
                            'echo "In Post-Build Stage"', 'cd ..',
                            "printf '[{\"name\":\"flask-app\",\"imageUri\":\"%s\"}]' $ECR_REPO_URI:$TAG > imagedefinitions.json",
                            "pwd; ls -al; cat imagedefinitions.json"
                        ]
                    }
                },
                'artifacts': {
                    'files': ['imagedefinitions.json']
                }
            }))

        # PIPELINE ACTIONS

        sourceOutput = codepipeline.Artifact()
        buildOutput = codepipeline.Artifact()

        sourceAction = codepipeline_actions.GitHubSourceAction(
            action_name='GitHub_Source',
            owner='samuelhailemariam',
            repo='aws-ecs-fargate-cicd-cdk',
            branch='master',
            oauth_token=cdk.SecretValue.secrets_manager("/my/github/token"),
            output=sourceOutput)

        buildAction = codepipeline_actions.CodeBuildAction(
            action_name='codeBuild',
            project=project,
            input=sourceOutput,
            outputs=[buildOutput])

        manualApprovalAction = codepipeline_actions.ManualApprovalAction(
            action_name='Approve')

        deployAction = codepipeline_actions.EcsDeployAction(
            action_name='DeployAction',
            service=fargateService.service,
            image_file=codepipeline.ArtifactPath(buildOutput,
                                                 'imagedefinitions.json'))

        pipeline = codepipeline.Pipeline(self, "ECSPipeline")

        source_stage = pipeline.add_stage(stage_name="Source",
                                          actions=[sourceAction])

        build_stage = pipeline.add_stage(stage_name="Build",
                                         actions=[buildAction])

        approve_stage = pipeline.add_stage(stage_name="Approve",
                                           actions=[manualApprovalAction])

        deploy_stage = pipeline.add_stage(stage_name="Deploy-to-ECS",
                                          actions=[deployAction])

        ecrRepo.grant_pull_push(project.role)

        project.add_to_role_policy(
            _iam.PolicyStatement(resources=['cluster.cluster_arn'],
                                 actions=[
                                     "ecs:DescribeCluster",
                                     "ecr:GetAuthorizationToken",
                                     "ecr:BatchCheckLayerAvailability",
                                     "ecr:BatchGetImage",
                                     "ecr:GetDownloadUrlForLayer"
                                 ]))

        # OUTPUT

        cdk.CfnOutput(
            self,
            "LoadBlancer-DNS",
            value=fargateService.load_balancer.load_balancer_dns_name)
Beispiel #9
0
def create_action(
    scope: core.Construct,
    id: str,
    action_def: Union[CodeCommitAction, CodeBuildAction,
                      CloudFormationCreateUpdateStackAction, ApprovalAction,
                      LambdaInvokeAction, S3SourceAction, ],
):
    action_name = action_def.pop("name")
    run_order = action_def.get("run_order", 1)
    variables_namespace = action_def.get("variables_namespace")
    role = (aws_iam.Role.from_role_arn(scope, f"{id}RoleRef",
                                       action_def["role_arn"])
            if "role_arn" in action_def else None)

    if action_def["type"] == "CODECOMMIT":
        action_def = cast(CodeCommitAction, action_def)
        repository = aws_codecommit.Repository.from_repository_name(
            scope, f"{id}Repo", action_def["repository"])
        output = aws_codepipeline.Artifact(action_def["output"])
        return aws_codepipeline_actions.CodeCommitSourceAction(
            action_name=action_name,
            output=output,
            repository=repository,
            branch=action_def.get("branch", "master"),
            run_order=run_order,
            role=role,
            variables_namespace=variables_namespace,
        )
    elif action_def["type"] == "S3_SOURCE":
        action_def = cast(S3SourceAction, action_def)
        output = aws_codepipeline.Artifact(action_def["output"])
        if "kms_key_arn" in action_def:
            role = aws_iam.Role(
                scope,
                f"{id}Role",
                assumed_by=aws_iam.AccountRootPrincipal(),
            )
            aws_kms.Key.from_key_arn(
                scope, f"{id}KeyRef",
                key_arn=action_def["kms_key_arn"]).grant_decrypt(role)
        if "bucket" in action_def:
            bucket = aws_s3.Bucket.from_bucket_name(scope,
                                                    f"{id}SourceBucketRef",
                                                    action_def["bucket"])
        else:
            bucket = aws_s3.Bucket(
                scope,
                f"{id}SourceBucket",
                block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL,
                removal_policy=core.RemovalPolicy.DESTROY,
            )
            core.CfnOutput(scope,
                           f"{id}SourceBucketName",
                           value=bucket.bucket_name)
        return aws_codepipeline_actions.S3SourceAction(
            action_name=action_name,
            output=output,
            run_order=run_order,
            role=role,
            bucket=bucket,
            bucket_key=action_def["key"],
        )
    elif action_def["type"] == "CODEBUILD":
        action_def = cast(CodeBuildAction, action_def)
        # Set up CodeBuild project
        project_params = {
            "build_spec":
            aws_codebuild.BuildSpec.from_source_filename(
                action_def.get("build_spec", "buildspec.yaml")),
            "timeout":
            core.Duration.minutes(int(action_def.get("timeout_minutes", 60))),
        }
        project_params["environment"] = {
            "build_image": aws_codebuild.LinuxBuildImage.AMAZON_LINUX_2_3
        }
        if "environment" in action_def:
            if "build_image" in action_def["environment"]:
                project_params["environment"]["build_image"] = getattr(
                    aws_codebuild.LinuxBuildImage,
                    action_def["environment"].pop("build_image"),
                )
            if "compute_type" in action_def["environment"]:
                project_params["environment"]["compute_type"] = getattr(
                    aws_codebuild.ComputeType,
                    action_def["environment"].pop("compute_type"),
                )
            project_params["environment"].update(**action_def["environment"])
        project_role = aws_iam.Role(
            scope,
            f"{id}CodeBuildRole",
            path="/codebuild/",
            assumed_by=aws_iam.ServicePrincipal(
                service="codebuild.amazonaws.com"),
        )
        project_role.add_to_policy(
            aws_iam.PolicyStatement(actions=["*"],
                                    resources=["*"],
                                    effect=aws_iam.Effect.ALLOW))
        project_environment_variables = ({
            var_key: aws_codebuild.BuildEnvironmentVariable(
                value=str(var_value),
                type=aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            )
            for var_key, var_value in
            action_def["environment_variables"].items()
            if "#" not in str(var_value)
        } if "environment_variables" in action_def else None)
        project = aws_codebuild.PipelineProject(
            scope,
            f"{id}Project",
            project_name=id,
            role=project_role,
            environment_variables=project_environment_variables,
            **project_params,
        )
        pipeline_environment_variables = ({
            var_key: aws_codebuild.BuildEnvironmentVariable(
                value=str(var_value),
                type=aws_codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            )
            for var_key, var_value in
            action_def["environment_variables"].items()
            if "#" in str(var_value)
        } if "environment_variables" in action_def else None)
        extra_inputs = ([
            aws_codepipeline.Artifact(input_)
            for input_ in action_def["extra_inputs"]
        ] if "extra_inputs" in action_def else None)
        outputs = ([
            aws_codepipeline.Artifact(output)
            for output in action_def["outputs"]
        ] if "outputs" in action_def else None)
        return aws_codepipeline_actions.CodeBuildAction(
            action_name=action_name,
            input=aws_codepipeline.Artifact(action_def["input"]),
            project=project,
            run_order=run_order,
            role=role,
            variables_namespace=variables_namespace,
            environment_variables=pipeline_environment_variables,
            extra_inputs=extra_inputs,
            outputs=outputs,
        )
    elif action_def["type"] == "CLOUDFORMATION":
        action_def = cast(CloudFormationCreateUpdateStackAction, action_def)
        return aws_codepipeline_actions.CloudFormationCreateUpdateStackAction(
            action_name=action_name,
            admin_permissions=False,
            stack_name=action_def["stack_name"],
            template_path=aws_codepipeline.ArtifactPath(
                aws_codepipeline.Artifact(action_def["input"]),
                action_def.get("template_path", "template.yaml"),
            ),
            capabilities=[
                # This lstrip does not support all possibilties, but is good enough for now
                aws_cloudformation.CloudFormationCapabilities[
                    capability.lstrip("CAPABILITY_")]
                for capability in action_def["capabilities"]
            ] if "capabilities" in action_def else None,
            deployment_role=role,
            role=role,
            parameter_overrides=action_def.get("parameter_overrides"),
            run_order=run_order,
            variables_namespace=variables_namespace,
        )
    elif action_def["type"] == "APPROVAL":
        action_def = cast(ApprovalAction, action_def)
        return aws_codepipeline_actions.ManualApprovalAction(
            action_name=action_name,
            run_order=run_order,
            role=role,
            additional_information=action_def.get("additional_information"),
            external_entity_link=action_def.get("external_entity_link"),
            notification_topic=action_def.get("notification_topic"),
            variables_namespace=variables_namespace,
        )
    elif action_def["type"] == "LAMBDA":
        action_def = cast(LambdaInvokeAction, action_def)
        user_parameters = action_def.get("user_parameters")
        return aws_codepipeline_actions.LambdaInvokeAction(
            action_name=action_name,
            run_order=run_order,
            lambda_=aws_lambda.Function.from_function_arn(
                scope, f"{id}Lambda", action_def["function_arn"]),
            user_parameters=user_parameters,
            role=role,
            variables_namespace=variables_namespace,
        )
Beispiel #10
0
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        CODECOMMIT_REPO_NAME = cdk.CfnParameter(
            self,
            "CodeCommitRepoName",
            type="String",
            default="serverless-api-pipeline-cdk",
            description="CodeCommit repository with the project code"
        ).value_as_string

        PIPELINE_NAME = "serverless-api-pipeline-cdk"

        source_artifact = codepipeline.Artifact()
        cloud_assembly_artifact = codepipeline.Artifact()

        pipeline = CdkPipeline(
            self,
            "Pipeline",
            pipeline_name=PIPELINE_NAME,
            cloud_assembly_artifact=cloud_assembly_artifact,
            source_action=codepipeline_actions.CodeCommitSourceAction(
                action_name="CodeCommit",
                output=source_artifact,
                branch='main',
                trigger=codepipeline_actions.CodeCommitTrigger.POLL,
                repository=codecommit.Repository(
                    self,
                    'ServerlessApiRepository',
                    repository_name=CODECOMMIT_REPO_NAME)),
            synth_action=SimpleSynthAction.standard_npm_synth(
                source_artifact=source_artifact,
                cloud_assembly_artifact=cloud_assembly_artifact,
                environment={'privileged': True},
                install_command=
                'cd ./serverless-rest-api/python-http-cdk; npm install -g aws-cdk; pip install -r requirements.txt; pip install -r ./src/api/requirements.txt ',
                synth_command='cdk synth --output $CODEBUILD_SRC_DIR/cdk.out'))

        # Add testing stage to the pipeline and testing activity with permissions necessary to run integration tests
        testing_stage = AppStage(self,
                                 'serverless-api-pipeline-cdk-Testing',
                                 cognito_stack_name='Cognito')
        pipeline_testing_stage = pipeline.add_application_stage(testing_stage)
        testing_action = ShellScriptAction(
            action_name='IntegrationTest',
            additional_artifacts=[source_artifact],
            commands=[
                'cd ./serverless-rest-api/python-http-cdk',
                'pip install -r ./tests/requirements.txt',
                'pip install -r ./src/api/requirements.txt',
                'python -m pytest tests/integration -v'
            ],
            use_outputs={
                'TEST_APPLICATION_STACK_NAME':
                pipeline.stack_output(testing_stage.api_stack_name)
            },
        )
        pipeline_testing_stage.add_actions(testing_action)
        testing_action.project.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=[
                    'cognito-idp:AdminDeleteUser',
                    'cognito-idp:AdminConfirmSignUp',
                    'cognito-idp:AdminAddUserToGroup'
                ],
                resources=[
                    f'arn:aws:cognito-idp:{cdk.Aws.REGION}:{cdk.Aws.ACCOUNT_ID}:userpool/*'
                ],
            ))
        testing_action.project.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['secretsmanager:GetRandomPassword'],
                resources=['*'],
            ))
        testing_action.project.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['dynamodb:*'],
                resources=[
                    f'arn:aws:dynamodb:{cdk.Aws.REGION}:{cdk.Aws.ACCOUNT_ID}:table/{testing_stage.stage_name}*'
                ],
            ))
        testing_action.project.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['cloudformation:DescribeStacks'],
                resources=[
                    f'arn:aws:cloudformation:{cdk.Aws.REGION}:{cdk.Aws.ACCOUNT_ID}:stack/{testing_stage.stage_name}*/*',
                    f'arn:aws:cloudformation:{cdk.Aws.REGION}:{cdk.Aws.ACCOUNT_ID}:stack/{testing_stage.cognito_stack_name}/*'
                ],
            ))

        # Create production deployment stage to the pipeline with manual approval action
        deployment_stage = AppStage(self,
                                    'serverless-api-pipeline-cdk-Deployment',
                                    cognito_stack_name='Cognito')
        pipeline_deployment_stage = pipeline.add_application_stage(
            deployment_stage)
        pipeline_deployment_stage.add_actions(
            codepipeline_actions.ManualApprovalAction(
                action_name='ApproveProductionDeployment', run_order=1))
Beispiel #11
0
def create_pipeline(
    scope: core.Construct,
    stack_name: str,
    ecr_repository: ecr.Repository,
    app_service: ecs.FargateService,
    config: StackConfig,
    worker_service: ecs.FargateService = None,
):

    project = codebuild.PipelineProject(
        scope,
        'build',
        project_name=stack_name,
        description=f'Build project for {stack_name}. Managed by AWS CDK.',
        environment=codebuild.BuildEnvironment(
            privileged=True,
            build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_2),
        environment_variables={
            'REPOSITORY_URI':
            codebuild.BuildEnvironmentVariable(
                value=ecr_repository.repository_uri),
        },
        cache=codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER,
                                    codebuild.LocalCacheMode.CUSTOM,
                                    codebuild.LocalCacheMode.SOURCE),
        build_spec=codebuild.BuildSpec.from_object({
            'version': '0.2',
            'phases': {
                'pre_build': {
                    'commands': [
                        '$(aws ecr get-login --no-include-email --region $AWS_REGION)',
                        'IMAGE_LATEST=${REPOSITORY_URI}:latest',
                        'IMAGE_VERSION=${REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION:0:7}'
                    ]
                },
                'build': {
                    'commands': [
                        f'docker login -u="{config.docker_user}" -p="{config.docker_password}"',
                        'docker build -f Dockerfile.prod -t ${IMAGE_LATEST} .',
                        'docker tag ${IMAGE_LATEST} ${IMAGE_VERSION}'
                    ]
                },
                'post_build': {
                    'commands': [
                        'docker push ${IMAGE_LATEST}',
                        'docker push ${IMAGE_VERSION}',
                        "printf '[{\"name\":\"container\",\"imageUri\":\"%s\"}]' ${IMAGE_VERSION} > imagedefinitions.json"
                    ]
                }
            },
            'artifacts': {
                'files': ['imagedefinitions.json']
            }
        }))
    ecr_repository.grant_pull_push(project)
    source_output = codepipeline.Artifact()
    source_action = actions.GitHubSourceAction(
        action_name='Source',
        owner=config.repo_owner,
        repo=config.repo_name,
        branch=config.repo_branch,
        oauth_token=core.SecretValue.plain_text(config.github_access_token),
        output=source_output,
    )

    build_output = codepipeline.Artifact()
    build_action = actions.CodeBuildAction(
        action_name='Build',
        project=project,
        input=source_output,
        outputs=[build_output],
        type=actions.CodeBuildActionType.BUILD,
    )

    artifact_bucket = s3.Bucket.from_bucket_name(scope, 'artifactBucket',
                                                 config.artifact_bucket)

    deploy_actions = [
        actions.EcsDeployAction(
            action_name='App',
            service=app_service,
            input=build_output,
        )
    ]
    if worker_service:
        deploy_actions.append(
            actions.EcsDeployAction(
                action_name='Worker',
                service=worker_service,
                input=build_output,
            ))

    pipeline = codepipeline.Pipeline(
        scope,
        'pipeline',
        pipeline_name=stack_name,
        restart_execution_on_update=True,
        artifact_bucket=artifact_bucket,
    )
    pipeline.add_stage(
        stage_name='Source',
        actions=[source_action],
    )
    pipeline.add_stage(stage_name='Build', actions=[build_action])
    if config.enable_deploy_approval:
        pipeline.add_stage(stage_name='Approval',
                           actions=[
                               actions.ManualApprovalAction(
                                   action_name='Approve', notify_emails=[])
                           ])
    pipeline.add_stage(
        stage_name='Deploy',
        actions=deploy_actions,
    )
Beispiel #12
0
    def __init__(self, scope: core.Construct, id: str, *, artifact_bucket_name: str, **kwargs) -> None:
        """Define the resources for the CodePipeline.
        
        :param scope: the parent construct
        :param id: the logical id
        :param artifact_bucket_name: the bucket name for artifacts passed between code pipeline 
        """
        super().__init__(scope, id, **kwargs)

        stack = core.Stack.of(self)
        account_id = stack.account
        region = stack.region

        repo_name = 'clin-msi'

        # build projects
        environment = cb.BuildEnvironment(build_image=cb.LinuxBuildImage.STANDARD_4_0)
        self._pytest_project = cb.PipelineProject(self, 'UnitTest', 
            project_name=f"{repo_name}-unittest",
            build_spec=cb.BuildSpec.from_object(buildspec_pytest),
            environment=environment)
        self._publish_project = cb.PipelineProject(self, 'Publish',
            project_name=f"{repo_name}-publish",
            build_spec=cb.BuildSpec.from_object(buildspec_publish),
            environment=environment)
        self._publish_project.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=["ssm:GetParameters"],
                resources=[
                    f"arn:aws:ssm:{region}:{account_id}:parameter/ClinMsi/PyPI/Credentials/Username",
                    f"arn:aws:ssm:{region}:{account_id}:parameter/ClinMsi/PyPI/Credentials/Password"
                ]
            ))

        # actions
        source_output = cp.Artifact()
        source_action = actions.BitBucketSourceAction(
            action_name='GitHub',
            connection_arn=f"arn:aws:codestar-connections:{region}:{account_id}:connection/a859d7f0-0bf3-48f5-bce1-492c9bc08bef",
            output=source_output,
            code_build_clone_output=True,
            owner="nch-igm",
            repo=repo_name,
            branch="master")

        unittest_action = actions.CodeBuildAction(action_name='UnittestAction', input=source_output, project=self._pytest_project)
        publish_action = actions.CodeBuildAction(action_name='PyPIPublishAction', input=source_output, project=self._publish_project)
        manual_approval = actions.ManualApprovalAction(action_name='ApproveToPublish')

        # initialize pipeline
        self._artifact_bucket = s3.Bucket.from_bucket_name(self, 'Bucket', artifact_bucket_name)
        self._pipeline = cp.Pipeline(
            self, 'CodePipeline',
            pipeline_name=repo_name,
            artifact_bucket=self._artifact_bucket)
        self._pipeline.add_to_role_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['codebuild:StartBuild', 'kms:PutKeyPolicy'],
                resources=[
                    self._pytest_project.project_arn,
                    self._publish_project.project_arn,
                    self._pipeline.artifact_bucket.bucket_arn
                ]))
        self._pipeline.add_stage(stage_name="Source", actions=[source_action])
        self._pipeline.add_stage(stage_name="UnitTest", actions=[unittest_action])
        self._pipeline.add_stage(stage_name="ManualApprovalForPublish", actions=[manual_approval])
        self._pipeline.add_stage(stage_name="Publish", actions=[publish_action])
    def __init__(self, scope: core.Construct, construct_id: str,
                 **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here

        repository = codecommit.Repository(
            self,
            "slackops-repository",
            repository_name="slackops-pipeline-repo",
            description="Repo for the SlackOps Pipeline Demo",
        )

        website_bucket = s3.Bucket(self,
                                   "website-bucket",
                                   removal_policy=core.RemovalPolicy.DESTROY,
                                   auto_delete_objects=True,
                                   public_read_access=True,
                                   website_index_document="index.html")

        manual_approval_topic = sns.Topic(
            self,
            "manual-approval-notification",
        )

        artifact_bucket = s3.Bucket(self,
                                    "artifact-bucket",
                                    removal_policy=core.RemovalPolicy.DESTROY)

        source_artifact = codepipeline.Artifact(artifact_name="Source")
        deployment_artifact = codepipeline.Artifact(artifact_name="Deployment")

        pipeline = codepipeline.Pipeline(
            self,
            "slackops-pipeline",
            artifact_bucket=artifact_bucket,
            stages=[
                codepipeline.StageOptions(
                    stage_name="Source",
                    actions=[
                        codepipeline_actions.CodeCommitSourceAction(
                            repository=repository,
                            branch="master",
                            output=source_artifact,
                            action_name="Source")
                    ]),
                codepipeline.StageOptions(
                    stage_name="Build",
                    actions=[
                        codepipeline_actions.CodeBuildAction(
                            input=source_artifact,
                            action_name="Build",
                            project=codebuild.PipelineProject(
                                self,
                                "build-project",
                                build_spec=codebuild.BuildSpec.
                                from_source_filename("buildspec.yml"),
                                environment=codebuild.BuildEnvironment(
                                    build_image=codebuild.LinuxBuildImage.
                                    STANDARD_5_0),
                            ),
                            outputs=[deployment_artifact])
                    ]),
                codepipeline.StageOptions(
                    stage_name=MANUAL_APPROVAL_STAGE_NAME,
                    actions=[
                        codepipeline_actions.ManualApprovalAction(
                            action_name=MANUAL_APPROVAL_ACTION_NAME,
                            additional_information=
                            "Please Approve the Deployment",
                            notification_topic=manual_approval_topic,
                        )
                    ]),
                codepipeline.StageOptions(
                    stage_name="Deploy",
                    actions=[
                        codepipeline_actions.S3DeployAction(
                            bucket=website_bucket,
                            input=deployment_artifact,
                            access_control=s3.BucketAccessControl.PUBLIC_READ,
                            action_name="deploy-to-s3")
                    ])
            ])

        # Build the API Gateway to record the approval or rejection

        rest_api = apigateway.RestApi(self,
                                      "slackops-apigw",
                                      deploy_options=apigateway.StageOptions(
                                          stage_name="prod", ))

        root_resource = rest_api.root.add_resource("v1")

        approval_resource = root_resource.add_resource("approval")

        api_gateway_role = iam.Role(self,
                                    "slackops-apigw-role",
                                    assumed_by=iam.ServicePrincipal(
                                        service="apigateway.amazonaws.com", ))
        api_gateway_role.add_to_policy(
            iam.PolicyStatement(actions=["codepipeline:PutApprovalResult"],
                                resources=[pipeline.pipeline_arn + "/*"]))

        # Double curlies to make str.format work
        mapping_template = """
#set($token = $input.params("token"))
#set($response = $input.params("response"))
{{
   "actionName": "{action_name}",
   "pipelineName": "{pipeline_name}",
   "result": {{ 
      "status": "$response",
      "summary": ""
   }},
   "stageName": "{stage_name}",
   "token": "$token"
}}
        """.format(
            action_name="approve-before-publication",
            pipeline_name=pipeline.pipeline_name,
            stage_name="Approval",
        )

        approval_integration = apigateway.AwsIntegration(
            service="codepipeline",
            action="PutApprovalResult",
            integration_http_method="POST",
            options=apigateway.IntegrationOptions(
                credentials_role=api_gateway_role,
                request_parameters={
                    "integration.request.header.x-amz-target":
                    "'CodePipeline_20150709.PutApprovalResult'",
                    "integration.request.header.Content-Type":
                    "'application/x-amz-json-1.1'",
                },
                passthrough_behavior=apigateway.PassthroughBehavior.NEVER,
                request_templates={"application/json": mapping_template},
                integration_responses=[
                    apigateway.IntegrationResponse(
                        status_code='400',
                        selection_pattern="4\d{2}",
                        response_parameters={
                            'method.response.header.error':
                            'integration.response.body'
                        }),
                    apigateway.IntegrationResponse(
                        status_code='500',
                        selection_pattern="5\d{2}",
                        response_parameters={
                            'method.response.header.error':
                            'integration.response.body'
                        }),
                    apigateway.IntegrationResponse(
                        status_code='200',
                        selection_pattern="2\d{2}",
                        response_parameters={
                            'method.response.header.response':
                            'integration.response.body'
                        }),
                ]))

        approval_method = approval_resource.add_method(
            http_method="GET",
            request_validator=apigateway.RequestValidator(
                self,
                "request-validator",
                rest_api=rest_api,
                request_validator_name="ParamValidator",
                validate_request_parameters=True),
            request_parameters={
                "method.request.querystring.token": True,
                "method.request.querystring.response":
                True,  # Approved / Rejected
            },
            method_responses=[
                apigateway.MethodResponse(
                    status_code='400',
                    response_parameters={'method.response.header.error':
                                         True}),
                apigateway.MethodResponse(
                    status_code='500',
                    response_parameters={'method.response.header.error':
                                         True}),
                apigateway.MethodResponse(
                    status_code='200',
                    response_parameters={
                        'method.response.header.response': True
                    }),
            ],
            integration=approval_integration,
        )

        # Notification mechanism

        ssm_parameter_webhook = ssm.StringParameter(
            self,
            "slackops-webhook-parameter",
            string_value="<replace-me>",
            parameter_name="/slackops/webhook-url")

        notification_lambda = _lambda.PythonFunction(
            self,
            "slackops-notification",
            entry=os.path.join(os.path.dirname(__file__), "..", "src"),
            index="index.py",
            handler="notification_handler",
            environment={
                "WEBHOOK_URL_PARAMETER": ssm_parameter_webhook.parameter_name,
                "API_ENDPOINT": rest_api.url_for_path("/v1/approval"),
            })

        notification_lambda.add_event_source(
            lambda_event_sources.SnsEventSource(topic=manual_approval_topic))

        ssm_parameter_webhook.grant_read(notification_lambda)

        # Outputs

        core.CfnOutput(self,
                       "repositoryHttps",
                       value=repository.repository_clone_url_http)

        core.CfnOutput(self,
                       "repositorySSH",
                       value=repository.repository_clone_url_ssh)

        core.CfnOutput(self,
                       "websiteUrl",
                       value=website_bucket.bucket_website_url)