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'))
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)
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)
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', )
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)
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, )
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))
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, )
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)