moto/moto/codepipeline/models.py

217 lines
7.1 KiB
Python

import json
from datetime import datetime
from boto3 import Session
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.iam.exceptions import IAMNotFoundException
from moto.iam import iam_backends
from moto.codepipeline.exceptions import (
InvalidStructureException,
PipelineNotFoundException,
ResourceNotFoundException,
InvalidTagsException,
TooManyTagsException,
)
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
class CodePipeline(BaseModel):
def __init__(self, region, pipeline):
# the version number for a new pipeline is always 1
pipeline["version"] = 1
self.pipeline = self.add_default_values(pipeline)
self.tags = {}
self._arn = "arn:aws:codepipeline:{0}:{1}:{2}".format(
region, ACCOUNT_ID, pipeline["name"]
)
self._created = datetime.utcnow()
self._updated = datetime.utcnow()
@property
def metadata(self):
return {
"pipelineArn": self._arn,
"created": iso_8601_datetime_with_milliseconds(self._created),
"updated": iso_8601_datetime_with_milliseconds(self._updated),
}
def add_default_values(self, pipeline):
for stage in pipeline["stages"]:
for action in stage["actions"]:
if "runOrder" not in action:
action["runOrder"] = 1
if "configuration" not in action:
action["configuration"] = {}
if "outputArtifacts" not in action:
action["outputArtifacts"] = []
if "inputArtifacts" not in action:
action["inputArtifacts"] = []
return pipeline
def validate_tags(self, tags):
for tag in tags:
if tag["key"].startswith("aws:"):
raise InvalidTagsException(
"Not allowed to modify system tags. "
"System tags start with 'aws:'. "
"msg=[Caller is an end user and not allowed to mutate system tags]"
)
if (len(self.tags) + len(tags)) > 50:
raise TooManyTagsException(self._arn)
class CodePipelineBackend(BaseBackend):
def __init__(self):
self.pipelines = {}
@property
def iam_backend(self):
return iam_backends["global"]
def create_pipeline(self, region, pipeline, tags):
if pipeline["name"] in self.pipelines:
raise InvalidStructureException(
"A pipeline with the name '{0}' already exists in account '{1}'".format(
pipeline["name"], ACCOUNT_ID
)
)
try:
role = self.iam_backend.get_role_by_arn(pipeline["roleArn"])
service_principal = json.loads(role.assume_role_policy_document)[
"Statement"
][0]["Principal"]["Service"]
if "codepipeline.amazonaws.com" not in service_principal:
raise IAMNotFoundException("")
except IAMNotFoundException:
raise InvalidStructureException(
"CodePipeline is not authorized to perform AssumeRole on role {}".format(
pipeline["roleArn"]
)
)
if len(pipeline["stages"]) < 2:
raise InvalidStructureException(
"Pipeline has only 1 stage(s). There should be a minimum of 2 stages in a pipeline"
)
self.pipelines[pipeline["name"]] = CodePipeline(region, pipeline)
if tags:
self.pipelines[pipeline["name"]].validate_tags(tags)
new_tags = {tag["key"]: tag["value"] for tag in tags}
self.pipelines[pipeline["name"]].tags.update(new_tags)
return pipeline, sorted(tags, key=lambda i: i["key"])
def get_pipeline(self, name):
codepipeline = self.pipelines.get(name)
if not codepipeline:
raise PipelineNotFoundException(
"Account '{0}' does not have a pipeline with name '{1}'".format(
ACCOUNT_ID, name
)
)
return codepipeline.pipeline, codepipeline.metadata
def update_pipeline(self, pipeline):
codepipeline = self.pipelines.get(pipeline["name"])
if not codepipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, pipeline["name"]
)
)
# version number is auto incremented
pipeline["version"] = codepipeline.pipeline["version"] + 1
codepipeline._updated = datetime.utcnow()
codepipeline.pipeline = codepipeline.add_default_values(pipeline)
return codepipeline.pipeline
def list_pipelines(self):
pipelines = []
for name, codepipeline in self.pipelines.items():
pipelines.append(
{
"name": name,
"version": codepipeline.pipeline["version"],
"created": codepipeline.metadata["created"],
"updated": codepipeline.metadata["updated"],
}
)
return sorted(pipelines, key=lambda i: i["name"])
def delete_pipeline(self, name):
self.pipelines.pop(name, None)
def list_tags_for_resource(self, arn):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
tags = [{"key": key, "value": value} for key, value in pipeline.tags.items()]
return sorted(tags, key=lambda i: i["key"])
def tag_resource(self, arn, tags):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
pipeline.validate_tags(tags)
for tag in tags:
pipeline.tags.update({tag["key"]: tag["value"]})
def untag_resource(self, arn, tag_keys):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
for key in tag_keys:
pipeline.tags.pop(key, None)
codepipeline_backends = {}
for region in Session().get_available_regions("codepipeline"):
codepipeline_backends[region] = CodePipelineBackend()
for region in Session().get_available_regions(
"codepipeline", partition_name="aws-us-gov"
):
codepipeline_backends[region] = CodePipelineBackend()
for region in Session().get_available_regions("codepipeline", partition_name="aws-cn"):
codepipeline_backends[region] = CodePipelineBackend()