moto/moto/codebuild/models.py
2022-11-10 08:43:20 -01:00

286 lines
10 KiB
Python

from moto.core import BaseBackend, BackendDict, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.moto_api._internal import mock_random
from collections import defaultdict
from dateutil import parser
from typing import Any, Dict, List, Optional
import datetime
class CodeBuildProjectMetadata(BaseModel):
def __init__(
self,
account_id: str,
region_name: str,
project_name: str,
source_version: Optional[str],
artifacts: Optional[Dict[str, Any]],
build_id: str,
service_role: str,
):
current_date = iso_8601_datetime_with_milliseconds(datetime.datetime.utcnow())
self.build_metadata: Dict[str, Any] = dict()
self.build_metadata["id"] = build_id
self.build_metadata[
"arn"
] = f"arn:aws:codebuild:{region_name}:{account_id}:build/{build_id}"
self.build_metadata["buildNumber"] = mock_random.randint(1, 100)
self.build_metadata["startTime"] = current_date
self.build_metadata["currentPhase"] = "QUEUED"
self.build_metadata["buildStatus"] = "IN_PROGRESS"
self.build_metadata["sourceVersion"] = (
source_version if source_version else "refs/heads/main"
)
self.build_metadata["projectName"] = project_name
self.build_metadata["phases"] = [
{
"phaseType": "SUBMITTED",
"phaseStatus": "SUCCEEDED",
"startTime": current_date,
"endTime": current_date,
"durationInSeconds": 0,
},
{"phaseType": "QUEUED", "startTime": current_date},
]
self.build_metadata["source"] = {
"type": "CODECOMMIT", # should be different based on what you pass in
"location": "https://git-codecommit.eu-west-2.amazonaws.com/v1/repos/testing",
"gitCloneDepth": 1,
"gitSubmodulesConfig": {"fetchSubmodules": False},
"buildspec": "buildspec/stuff.yaml", # should present in the codebuild project somewhere
"insecureSsl": False,
}
self.build_metadata["secondarySources"] = []
self.build_metadata["secondarySourceVersions"] = []
self.build_metadata["artifacts"] = artifacts
self.build_metadata["secondaryArtifacts"] = []
self.build_metadata["cache"] = {"type": "NO_CACHE"}
self.build_metadata["environment"] = {
"type": "LINUX_CONTAINER",
"image": "aws/codebuild/amazonlinux2-x86_64-standard:3.0",
"computeType": "BUILD_GENERAL1_SMALL",
"environmentVariables": [],
"privilegedMode": False,
"imagePullCredentialsType": "CODEBUILD",
}
self.build_metadata["serviceRole"] = service_role
self.build_metadata["logs"] = {
"deepLink": "https://console.aws.amazon.com/cloudwatch/home?region=eu-west-2#logEvent:group=null;stream=null",
"cloudWatchLogsArn": f"arn:aws:logs:{region_name}:{account_id}:log-group:null:log-stream:null",
"cloudWatchLogs": {"status": "ENABLED"},
"s3Logs": {"status": "DISABLED", "encryptionDisabled": False},
}
self.build_metadata["timeoutInMinutes"] = 45
self.build_metadata["queuedTimeoutInMinutes"] = 480
self.build_metadata["buildComplete"] = False
self.build_metadata["initiator"] = "rootme"
self.build_metadata[
"encryptionKey"
] = f"arn:aws:kms:{region_name}:{account_id}:alias/aws/s3"
class CodeBuild(BaseModel):
def __init__(
self,
account_id: str,
region: str,
project_name: str,
project_source: Dict[str, Any],
artifacts: Dict[str, Any],
environment: Dict[str, Any],
serviceRole: str = "some_role",
):
current_date = iso_8601_datetime_with_milliseconds(datetime.datetime.utcnow())
self.project_metadata: Dict[str, Any] = dict()
self.project_metadata["name"] = project_name
self.project_metadata["arn"] = "arn:aws:codebuild:{0}:{1}:project/{2}".format(
region, account_id, self.project_metadata["name"]
)
self.project_metadata[
"encryptionKey"
] = f"arn:aws:kms:{region}:{account_id}:alias/aws/s3"
self.project_metadata[
"serviceRole"
] = f"arn:aws:iam::{account_id}:role/service-role/{serviceRole}"
self.project_metadata["lastModifiedDate"] = current_date
self.project_metadata["created"] = current_date
self.project_metadata["badge"] = dict()
self.project_metadata["badge"][
"badgeEnabled"
] = False # this false needs to be a json false not a python false
self.project_metadata["environment"] = environment
self.project_metadata["artifacts"] = artifacts
self.project_metadata["source"] = project_source
self.project_metadata["cache"] = dict()
self.project_metadata["cache"]["type"] = "NO_CACHE"
self.project_metadata["timeoutInMinutes"] = ""
self.project_metadata["queuedTimeoutInMinutes"] = ""
class CodeBuildBackend(BaseBackend):
def __init__(self, region_name: str, account_id: str):
super().__init__(region_name, account_id)
self.codebuild_projects: Dict[str, CodeBuild] = dict()
self.build_history: Dict[str, List[str]] = dict()
self.build_metadata: Dict[str, CodeBuildProjectMetadata] = dict()
self.build_metadata_history: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
def create_project(
self,
project_name: str,
project_source: Dict[str, Any],
artifacts: Dict[str, Any],
environment: Dict[str, Any],
service_role: str,
) -> Dict[str, Any]:
# required in other functions that don't
self.project_name = project_name
self.service_role = service_role
self.codebuild_projects[project_name] = CodeBuild(
self.account_id,
self.region_name,
project_name,
project_source,
artifacts,
environment,
service_role,
)
# empty build history
self.build_history[project_name] = list()
return self.codebuild_projects[project_name].project_metadata
def list_projects(self) -> List[str]:
projects = []
for project in self.codebuild_projects.keys():
projects.append(project)
return projects
def start_build(
self,
project_name: str,
source_version: Optional[str] = None,
artifact_override: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
build_id = "{0}:{1}".format(project_name, mock_random.uuid4())
# construct a new build
self.build_metadata[project_name] = CodeBuildProjectMetadata(
self.account_id,
self.region_name,
project_name,
source_version,
artifact_override,
build_id,
self.service_role,
)
self.build_history[project_name].append(build_id)
# update build histroy with metadata for build id
self.build_metadata_history[project_name].append(
self.build_metadata[project_name].build_metadata
)
return self.build_metadata[project_name].build_metadata
def _set_phases(self, phases: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
current_date = iso_8601_datetime_with_milliseconds(datetime.datetime.utcnow())
# No phaseStatus for QUEUED on first start
for existing_phase in phases:
if existing_phase["phaseType"] == "QUEUED":
existing_phase["phaseStatus"] = "SUCCEEDED"
statuses = [
"PROVISIONING",
"DOWNLOAD_SOURCE",
"INSTALL",
"PRE_BUILD",
"BUILD",
"POST_BUILD",
"UPLOAD_ARTIFACTS",
"FINALIZING",
"COMPLETED",
]
for status in statuses:
phase: Dict[str, Any] = dict()
phase["phaseType"] = status
phase["phaseStatus"] = "SUCCEEDED"
phase["startTime"] = current_date
phase["endTime"] = current_date
phase["durationInSeconds"] = mock_random.randint(10, 100)
phases.append(phase)
return phases
def batch_get_builds(self, ids: List[str]) -> List[Dict[str, Any]]:
batch_build_metadata: List[Dict[str, Any]] = []
for metadata in self.build_metadata_history.values():
for build in metadata:
if build["id"] in ids:
build["phases"] = self._set_phases(build["phases"])
build["endTime"] = iso_8601_datetime_with_milliseconds(
parser.parse(build["startTime"])
+ datetime.timedelta(minutes=mock_random.randint(1, 5))
)
build["currentPhase"] = "COMPLETED"
build["buildStatus"] = "SUCCEEDED"
batch_build_metadata.append(build)
return batch_build_metadata
def list_builds_for_project(self, project_name: str) -> List[str]:
try:
return self.build_history[project_name]
except KeyError:
return list()
def list_builds(self) -> List[str]:
ids = []
for build_ids in self.build_history.values():
ids += build_ids
return ids
def delete_project(self, project_name: str) -> None:
self.build_metadata.pop(project_name, None)
self.codebuild_projects.pop(project_name, None)
def stop_build(self, build_id: str) -> Optional[Dict[str, Any]]: # type: ignore[return]
for metadata in self.build_metadata_history.values():
for build in metadata:
if build["id"] == build_id:
# set completion properties with variable completion time
build["phases"] = self._set_phases(build["phases"])
build["endTime"] = iso_8601_datetime_with_milliseconds(
parser.parse(build["startTime"])
+ datetime.timedelta(minutes=mock_random.randint(1, 5))
)
build["currentPhase"] = "COMPLETED"
build["buildStatus"] = "STOPPED"
return build
codebuild_backends = BackendDict(CodeBuildBackend, "codebuild")