From 79af23aeb70fcbd37d56c73e5b026fff71688da0 Mon Sep 17 00:00:00 2001
From: b0bu <7663736+b0bu@users.noreply.github.com>
Date: Thu, 14 Jul 2022 16:57:04 +0100
Subject: [PATCH] Add support for codebuild (#5282)
---
IMPLEMENTATION_COVERAGE.md | 109 +++-
docs/docs/services/codebuild.rst | 75 +++
moto/__init__.py | 1 +
moto/backend_index.py | 1 +
moto/codebuild/__init__.py | 4 +
moto/codebuild/exceptions.py | 24 +
moto/codebuild/models.py | 263 ++++++++++
moto/codebuild/responses.py | 195 +++++++
moto/codebuild/urls.py | 5 +
tests/test_codebuild/test_codebuild.py | 699 +++++++++++++++++++++++++
10 files changed, 1365 insertions(+), 11 deletions(-)
create mode 100644 docs/docs/services/codebuild.rst
create mode 100644 moto/codebuild/__init__.py
create mode 100644 moto/codebuild/exceptions.py
create mode 100644 moto/codebuild/models.py
create mode 100644 moto/codebuild/responses.py
create mode 100644 moto/codebuild/urls.py
create mode 100644 tests/test_codebuild/test_codebuild.py
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index c921e2342..20f505d6c 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -338,7 +338,7 @@
## autoscaling
-49% implemented
+50% implemented
- [X] attach_instances
- [X] attach_load_balancer_target_groups
@@ -382,7 +382,7 @@
- [X] detach_load_balancer_target_groups
- [X] detach_load_balancers
- [ ] disable_metrics_collection
-- [ ] enable_metrics_collection
+- [X] enable_metrics_collection
- [ ] enter_standby
- [X] execute_policy
- [ ] exit_standby
@@ -462,6 +462,47 @@
- [ ] update_subscriber
+## ce
+
+11% implemented
+
+- [ ] create_anomaly_monitor
+- [ ] create_anomaly_subscription
+- [X] create_cost_category_definition
+- [ ] delete_anomaly_monitor
+- [ ] delete_anomaly_subscription
+- [X] delete_cost_category_definition
+- [X] describe_cost_category_definition
+- [ ] get_anomalies
+- [ ] get_anomaly_monitors
+- [ ] get_anomaly_subscriptions
+- [ ] get_cost_and_usage
+- [ ] get_cost_and_usage_with_resources
+- [ ] get_cost_categories
+- [ ] get_cost_forecast
+- [ ] get_dimension_values
+- [ ] get_reservation_coverage
+- [ ] get_reservation_purchase_recommendation
+- [ ] get_reservation_utilization
+- [ ] get_rightsizing_recommendation
+- [ ] get_savings_plans_coverage
+- [ ] get_savings_plans_purchase_recommendation
+- [ ] get_savings_plans_utilization
+- [ ] get_savings_plans_utilization_details
+- [ ] get_tags
+- [ ] get_usage_forecast
+- [ ] list_cost_allocation_tags
+- [ ] list_cost_category_definitions
+- [ ] list_tags_for_resource
+- [ ] provide_anomaly_feedback
+- [ ] tag_resource
+- [ ] untag_resource
+- [ ] update_anomaly_monitor
+- [ ] update_anomaly_subscription
+- [ ] update_cost_allocation_tags_status
+- [X] update_cost_category_definition
+
+
## cloudformation
30% implemented
@@ -709,6 +750,57 @@
- [X] untag_resource
+## codebuild
+
+17% implemented
+
+- [ ] batch_delete_builds
+- [ ] batch_get_build_batches
+- [X] batch_get_builds
+- [ ] batch_get_projects
+- [ ] batch_get_report_groups
+- [ ] batch_get_reports
+- [X] create_project
+- [ ] create_report_group
+- [ ] create_webhook
+- [ ] delete_build_batch
+- [X] delete_project
+- [ ] delete_report
+- [ ] delete_report_group
+- [ ] delete_resource_policy
+- [ ] delete_source_credentials
+- [ ] delete_webhook
+- [ ] describe_code_coverages
+- [ ] describe_test_cases
+- [ ] get_report_group_trend
+- [ ] get_resource_policy
+- [ ] import_source_credentials
+- [ ] invalidate_project_cache
+- [ ] list_build_batches
+- [ ] list_build_batches_for_project
+- [X] list_builds
+- [X] list_builds_for_project
+- [ ] list_curated_environment_images
+- [X] list_projects
+- [ ] list_report_groups
+- [ ] list_reports
+- [ ] list_reports_for_report_group
+- [ ] list_shared_projects
+- [ ] list_shared_report_groups
+- [ ] list_source_credentials
+- [ ] put_resource_policy
+- [ ] retry_build
+- [ ] retry_build_batch
+- [X] start_build
+- [ ] start_build_batch
+- [X] stop_build
+- [ ] stop_build_batch
+- [ ] update_project
+- [ ] update_project_visibility
+- [ ] update_report_group
+- [ ] update_webhook
+
+
## codecommit
3% implemented
@@ -1289,7 +1381,7 @@
## ds
-18% implemented
+19% implemented
- [ ] accept_shared_directory
- [ ] add_ip_routes
@@ -1320,7 +1412,6 @@
- [ ] describe_event_topics
- [ ] describe_ldaps_settings
- [ ] describe_regions
-- [ ] describe_settings
- [ ] describe_shared_directories
- [ ] describe_snapshots
- [ ] describe_trusts
@@ -1353,7 +1444,6 @@
- [ ] update_conditional_forwarder
- [ ] update_number_of_domain_controllers
- [ ] update_radius
-- [ ] update_settings
- [ ] update_trust
- [ ] verify_trust
@@ -2457,7 +2547,7 @@
## emr-serverless
-71% implemented
+50% implemented
- [ ] cancel_job_run
- [X] create_application
@@ -2468,7 +2558,7 @@
- [ ] list_job_runs
- [ ] list_tags_for_resource
- [X] start_application
-- [X] start_job_run
+- [ ] start_job_run
- [X] stop_application
- [ ] tag_resource
- [ ] untag_resource
@@ -2828,7 +2918,6 @@
- [ ] import_catalog_to_glue
- [ ] list_blueprints
- [X] list_crawlers
-- [ ] list_crawls
- [ ] list_custom_entity_types
- [ ] list_dev_endpoints
- [X] list_jobs
@@ -5998,7 +6087,6 @@
- backup-gateway
- billingconductor
- braket
-- ce
- chime
- chime-sdk-identity
- chime-sdk-media-pipelines
@@ -6012,7 +6100,6 @@
- cloudsearch
- cloudsearchdomain
- codeartifact
-- codebuild
- codedeploy
- codeguru-reviewer
- codeguruprofiler
@@ -6040,7 +6127,6 @@
- drs
- ecr-public
- elastic-inference
-- emr-serverless
- evidently
- finspace
- finspace-data
@@ -6138,6 +6224,7 @@
- qldb-session
- rbin
- rds-data
+- redshiftserverless
- resiliencehub
- robomaker
- route53-recovery-cluster
diff --git a/docs/docs/services/codebuild.rst b/docs/docs/services/codebuild.rst
new file mode 100644
index 000000000..79d8f104e
--- /dev/null
+++ b/docs/docs/services/codebuild.rst
@@ -0,0 +1,75 @@
+.. _implementedservice_codebuild:
+
+.. |start-h3| raw:: html
+
+
+
+.. |end-h3| raw:: html
+
+
+
+=========
+codebuild
+=========
+
+.. autoclass:: moto.codebuild.models.CodeBuildBackend
+
+|start-h3| Example usage |end-h3|
+
+.. sourcecode:: python
+
+ @mock_codebuild
+ def test_codebuild_behaviour:
+ boto3.client("codebuild")
+ ...
+
+
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [ ] batch_delete_builds
+- [ ] batch_get_build_batches
+- [X] batch_get_builds
+- [ ] batch_get_projects
+- [ ] batch_get_report_groups
+- [ ] batch_get_reports
+- [X] create_project
+- [ ] create_report_group
+- [ ] create_webhook
+- [ ] delete_build_batch
+- [X] delete_project
+- [ ] delete_report
+- [ ] delete_report_group
+- [ ] delete_resource_policy
+- [ ] delete_source_credentials
+- [ ] delete_webhook
+- [ ] describe_code_coverages
+- [ ] describe_test_cases
+- [ ] get_report_group_trend
+- [ ] get_resource_policy
+- [ ] import_source_credentials
+- [ ] invalidate_project_cache
+- [ ] list_build_batches
+- [ ] list_build_batches_for_project
+- [X] list_builds
+- [X] list_builds_for_project
+- [ ] list_curated_environment_images
+- [X] list_projects
+- [ ] list_report_groups
+- [ ] list_reports
+- [ ] list_reports_for_report_group
+- [ ] list_shared_projects
+- [ ] list_shared_report_groups
+- [ ] list_source_credentials
+- [ ] put_resource_policy
+- [ ] retry_build
+- [ ] retry_build_batch
+- [X] start_build
+- [ ] start_build_batch
+- [X] stop_build
+- [ ] stop_build_batch
+- [ ] update_project
+- [ ] update_project_visibility
+- [ ] update_report_group
+- [ ] update_webhook
+
diff --git a/moto/__init__.py b/moto/__init__.py
index de09ef8c6..f1ac3e2ff 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -63,6 +63,7 @@ mock_cloudfront = lazy_load(".cloudfront", "mock_cloudfront")
mock_cloudtrail = lazy_load(".cloudtrail", "mock_cloudtrail")
mock_cloudwatch = lazy_load(".cloudwatch", "mock_cloudwatch")
mock_codecommit = lazy_load(".codecommit", "mock_codecommit")
+mock_codebuild = lazy_load(".codebuild", "mock_codebuild")
mock_codepipeline = lazy_load(".codepipeline", "mock_codepipeline")
mock_cognitoidentity = lazy_load(
".cognitoidentity", "mock_cognitoidentity", boto3_name="cognito-identity"
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 464e15b9c..4ff8408f4 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -18,6 +18,7 @@ backend_url_patterns = [
("cloudfront", re.compile("https?://cloudfront\\.amazonaws\\.com")),
("cloudtrail", re.compile("https?://cloudtrail\\.(.+)\\.amazonaws\\.com")),
("cloudwatch", re.compile("https?://monitoring\\.(.+)\\.amazonaws.com")),
+ ("codebuild", re.compile("https?://codebuild\\.(.+)\\.amazonaws\\.com")),
("codecommit", re.compile("https?://codecommit\\.(.+)\\.amazonaws\\.com")),
("codepipeline", re.compile("https?://codepipeline\\.(.+)\\.amazonaws\\.com")),
(
diff --git a/moto/codebuild/__init__.py b/moto/codebuild/__init__.py
new file mode 100644
index 000000000..3b7d181d1
--- /dev/null
+++ b/moto/codebuild/__init__.py
@@ -0,0 +1,4 @@
+from .models import codebuild_backends
+from ..core.models import base_decorator
+
+mock_codebuild = base_decorator(codebuild_backends)
diff --git a/moto/codebuild/exceptions.py b/moto/codebuild/exceptions.py
new file mode 100644
index 000000000..84bfed944
--- /dev/null
+++ b/moto/codebuild/exceptions.py
@@ -0,0 +1,24 @@
+from moto.core.exceptions import JsonRESTError
+
+""" will need exceptions for each api endpoint hit """
+
+
+class InvalidInputException(JsonRESTError):
+ code = 400
+
+ def __init__(self, message):
+ super().__init__("InvalidInputException", message)
+
+
+class ResourceNotFoundException(JsonRESTError):
+ code = 400
+
+ def __init__(self, message):
+ super().__init__("ResourceNotFoundException", message)
+
+
+class ResourceAlreadyExistsException(JsonRESTError):
+ code = 400
+
+ def __init__(self, message):
+ super().__init__("ResourceAlreadyExistsException", message)
diff --git a/moto/codebuild/models.py b/moto/codebuild/models.py
new file mode 100644
index 000000000..ac96dd47e
--- /dev/null
+++ b/moto/codebuild/models.py
@@ -0,0 +1,263 @@
+from moto.core import BaseBackend, BaseModel
+from moto.core.utils import iso_8601_datetime_with_milliseconds, BackendDict
+from moto.core import get_account_id
+from collections import defaultdict
+from random import randint
+from dateutil import parser
+import datetime
+import uuid
+
+
+class CodeBuildProjectMetadata(BaseModel):
+ def __init__(self, project_name, source_version, artifacts, build_id, service_role):
+ current_date = iso_8601_datetime_with_milliseconds(datetime.datetime.utcnow())
+ self.build_metadata = dict()
+
+ self.build_metadata["id"] = build_id
+ self.build_metadata["arn"] = "arn:aws:codebuild:eu-west-2:{0}:build/{1}".format(
+ get_account_id(), build_id
+ )
+
+ self.build_metadata["buildNumber"] = 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": "arn:aws:logs:eu-west-2:{0}:log-group:null:log-stream:null".format(
+ get_account_id()
+ ),
+ "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"
+ ] = "arn:aws:kms:eu-west-2:{0}:alias/aws/s3".format(get_account_id())
+
+
+class CodeBuild(BaseModel):
+ def __init__(
+ self,
+ region,
+ project_name,
+ project_source,
+ artifacts,
+ environment,
+ serviceRole="some_role",
+ ):
+ current_date = iso_8601_datetime_with_milliseconds(datetime.datetime.utcnow())
+ self.project_metadata = dict()
+
+ self.project_metadata["name"] = project_name
+ self.project_metadata["arn"] = "arn:aws:codebuild:{0}:{1}:project/{2}".format(
+ region, get_account_id(), self.project_metadata["name"]
+ )
+ self.project_metadata[
+ "encryptionKey"
+ ] = "arn:aws:kms:{0}:{1}:alias/aws/s3".format(region, get_account_id())
+ self.project_metadata[
+ "serviceRole"
+ ] = "arn:aws:iam::{0}:role/service-role/{1}".format(
+ get_account_id(), 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, account_id):
+ super().__init__(region_name, account_id)
+ self.codebuild_projects = dict()
+ self.build_history = dict()
+ self.build_metadata = dict()
+ self.build_metadata_history = defaultdict(list)
+
+ def create_project(
+ self, project_name, project_source, artifacts, environment, service_role
+ ):
+ # required in other functions that don't
+ self.project_name = project_name
+ self.service_role = service_role
+
+ self.codebuild_projects[project_name] = CodeBuild(
+ 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):
+
+ projects = []
+
+ for project in self.codebuild_projects.keys():
+ projects.append(project)
+
+ return projects
+
+ def start_build(self, project_name, source_version=None, artifact_override=None):
+
+ build_id = "{0}:{1}".format(project_name, uuid.uuid4())
+
+ # construct a new build
+ self.build_metadata[project_name] = CodeBuildProjectMetadata(
+ 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):
+ 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()
+ phase["phaseType"] = status
+ phase["phaseStatus"] = "SUCCEEDED"
+ phase["startTime"] = current_date
+ phase["endTime"] = current_date
+ phase["durationInSeconds"] = randint(10, 100)
+ phases.append(phase)
+
+ return phases
+
+ def batch_get_builds(self, ids):
+ batch_build_metadata = []
+
+ 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=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):
+ try:
+ return self.build_history[project_name]
+ except KeyError:
+ return list()
+
+ def list_builds(self):
+ ids = []
+
+ for build_ids in self.build_history.values():
+ ids += build_ids
+ return ids
+
+ def delete_project(self, project_name):
+ self.build_metadata.pop(project_name, None)
+ self.codebuild_projects.pop(project_name, None)
+
+ def stop_build(self, build_id):
+
+ 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=randint(1, 5))
+ )
+ build["currentPhase"] = "COMPLETED"
+ build["buildStatus"] = "STOPPED"
+
+ return build
+
+
+codebuild_backends = BackendDict(CodeBuildBackend, "codebuild")
diff --git a/moto/codebuild/responses.py b/moto/codebuild/responses.py
new file mode 100644
index 000000000..c3ce1b6ff
--- /dev/null
+++ b/moto/codebuild/responses.py
@@ -0,0 +1,195 @@
+from moto.core.responses import BaseResponse
+from .models import codebuild_backends
+from .exceptions import (
+ InvalidInputException,
+ ResourceAlreadyExistsException,
+ ResourceNotFoundException,
+)
+from moto.core import get_account_id
+import json
+import re
+
+
+def _validate_required_params_source(source):
+ if source["type"] not in [
+ "BITBUCKET",
+ "CODECOMMIT",
+ "CODEPIPELINE",
+ "GITHUB",
+ "GITHUB_ENTERPRISE",
+ "NO_SOURCE",
+ "S3",
+ ]:
+ raise InvalidInputException("Invalid type provided: Project source type")
+
+ if "location" not in source:
+ raise InvalidInputException("Project source location is required")
+
+ if source["location"] == "":
+ raise InvalidInputException("Project source location is required")
+
+
+def _validate_required_params_service_role(service_role):
+ if (
+ "arn:aws:iam::{0}:role/service-role/".format(get_account_id())
+ not in service_role
+ ):
+ raise InvalidInputException(
+ "Invalid service role: Service role account ID does not match caller's account"
+ )
+
+
+def _validate_required_params_artifacts(artifacts):
+
+ if artifacts["type"] not in ["CODEPIPELINE", "S3", "NO_ARTIFACTS"]:
+ raise InvalidInputException("Invalid type provided: Artifact type")
+
+ if artifacts["type"] == "NO_ARTIFACTS":
+ if "location" in artifacts:
+ raise InvalidInputException(
+ "Invalid artifacts: artifact type NO_ARTIFACTS should have null location"
+ )
+ elif "location" not in artifacts or artifacts["location"] == "":
+ raise InvalidInputException("Project source location is required")
+
+
+def _validate_required_params_environment(environment):
+
+ if environment["type"] not in [
+ "WINDOWS_CONTAINER",
+ "LINUX_CONTAINER",
+ "LINUX_GPU_CONTAINER",
+ "ARM_CONTAINER",
+ ]:
+ raise InvalidInputException(
+ "Invalid type provided: {0}".format(environment["type"])
+ )
+
+ if environment["computeType"] not in [
+ "BUILD_GENERAL1_SMALL",
+ "BUILD_GENERAL1_MEDIUM",
+ "BUILD_GENERAL1_LARGE",
+ "BUILD_GENERAL1_2XLARGE",
+ ]:
+ raise InvalidInputException(
+ "Invalid compute type provided: {0}".format(environment["computeType"])
+ )
+
+
+def _validate_required_params_project_name(name):
+ if len(name) >= 150:
+ raise InvalidInputException(
+ "Only alphanumeric characters, dash, and underscore are supported"
+ )
+
+ if not re.match(r"^[A-Za-z]{1}.*[^!£$%^&*()+=|?`¬{}@~#:;<>\\/\[\]]$", name):
+ raise InvalidInputException(
+ "Only alphanumeric characters, dash, and underscore are supported"
+ )
+
+
+def _validate_required_params_id(build_id, build_ids):
+ if ":" not in build_id:
+ raise InvalidInputException("Invalid build ID provided")
+
+ if build_id not in build_ids:
+ raise ResourceNotFoundException("Build {0} does not exist".format(build_id))
+
+
+class CodeBuildResponse(BaseResponse):
+ @property
+ def codebuild_backend(self):
+ return codebuild_backends[self.region]
+
+ def list_builds_for_project(self):
+ _validate_required_params_project_name(self._get_param("projectName"))
+
+ if (
+ self._get_param("projectName")
+ not in self.codebuild_backend.codebuild_projects.keys()
+ ):
+ raise ResourceNotFoundException(
+ "The provided project arn:aws:codebuild:{0}:{1}:project/{2} does not exist".format(
+ self.region, get_account_id(), self._get_param("projectName")
+ )
+ )
+
+ ids = self.codebuild_backend.list_builds_for_project(
+ self._get_param("projectName")
+ )
+
+ return json.dumps({"ids": ids})
+
+ def create_project(self):
+ _validate_required_params_source(self._get_param("source"))
+ _validate_required_params_service_role(self._get_param("serviceRole"))
+ _validate_required_params_artifacts(self._get_param("artifacts"))
+ _validate_required_params_environment(self._get_param("environment"))
+ _validate_required_params_project_name(self._get_param("name"))
+
+ if self._get_param("name") in self.codebuild_backend.codebuild_projects.keys():
+ raise ResourceAlreadyExistsException(
+ "Project already exists: arn:aws:codebuild:{0}:{1}:project/{2}".format(
+ self.region, get_account_id(), self._get_param("name")
+ )
+ )
+
+ project_metadata = self.codebuild_backend.create_project(
+ self._get_param("name"),
+ self._get_param("source"),
+ self._get_param("artifacts"),
+ self._get_param("environment"),
+ self._get_param("serviceRole"),
+ )
+
+ return json.dumps({"project": project_metadata})
+
+ def list_projects(self):
+ project_metadata = self.codebuild_backend.list_projects()
+ return json.dumps({"projects": project_metadata})
+
+ def start_build(self):
+ _validate_required_params_project_name(self._get_param("projectName"))
+
+ if (
+ self._get_param("projectName")
+ not in self.codebuild_backend.codebuild_projects.keys()
+ ):
+ raise ResourceNotFoundException(
+ "Project cannot be found: arn:aws:codebuild:{0}:{1}:project/{2}".format(
+ self.region, get_account_id(), self._get_param("projectName")
+ )
+ )
+
+ metadata = self.codebuild_backend.start_build(
+ self._get_param("projectName"),
+ self._get_param("sourceVersion"),
+ self._get_param("artifactsOverride"),
+ )
+ return json.dumps({"build": metadata})
+
+ def batch_get_builds(self):
+ for build_id in self._get_param("ids"):
+ if ":" not in build_id:
+ raise InvalidInputException("Invalid build ID provided")
+
+ metadata = self.codebuild_backend.batch_get_builds(self._get_param("ids"))
+ return json.dumps({"builds": metadata})
+
+ def list_builds(self):
+ ids = self.codebuild_backend.list_builds()
+ return json.dumps({"ids": ids})
+
+ def delete_project(self):
+ _validate_required_params_project_name(self._get_param("name"))
+
+ self.codebuild_backend.delete_project(self._get_param("name"))
+ return
+
+ def stop_build(self):
+ _validate_required_params_id(
+ self._get_param("id"), self.codebuild_backend.list_builds()
+ )
+
+ metadata = self.codebuild_backend.stop_build(self._get_param("id"))
+ return json.dumps({"build": metadata})
diff --git a/moto/codebuild/urls.py b/moto/codebuild/urls.py
new file mode 100644
index 000000000..60990963f
--- /dev/null
+++ b/moto/codebuild/urls.py
@@ -0,0 +1,5 @@
+from .responses import CodeBuildResponse
+
+url_bases = [r"https?://codebuild\.(.+)\.amazonaws\.com"]
+
+url_paths = {"{0}/$": CodeBuildResponse.dispatch}
diff --git a/tests/test_codebuild/test_codebuild.py b/tests/test_codebuild/test_codebuild.py
new file mode 100644
index 000000000..c6ec1320f
--- /dev/null
+++ b/tests/test_codebuild/test_codebuild.py
@@ -0,0 +1,699 @@
+import boto3
+import sure # noqa # pylint: disable=unused-import
+from moto import mock_codebuild
+from moto.core import ACCOUNT_ID
+from botocore.exceptions import ClientError, ParamValidationError
+from uuid import uuid1
+import pytest
+
+
+@mock_codebuild
+def test_codebuild_create_project_s3_artifacts():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ # output artifacts
+ artifacts = dict()
+ artifacts["type"] = "S3"
+ artifacts["location"] = "bucketname"
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ response = client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ response.should_not.be.none
+ response["project"].should_not.be.none
+ response["project"]["serviceRole"].should_not.be.none
+ response["project"]["name"].should_not.be.none
+
+ response["project"]["environment"].should.equal(
+ {
+ "computeType": "BUILD_GENERAL1_SMALL",
+ "image": "contents_not_validated",
+ "type": "LINUX_CONTAINER",
+ }
+ )
+
+ response["project"]["source"].should.equal(
+ {"location": "bucketname/path/file.zip", "type": "S3"}
+ )
+
+ response["project"]["artifacts"].should.equal(
+ {"location": "bucketname", "type": "S3"}
+ )
+
+
+@mock_codebuild
+def test_codebuild_create_project_no_artifacts():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ # output artifacts
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ response = client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ response.should_not.be.none
+ response["project"].should_not.be.none
+ response["project"]["serviceRole"].should_not.be.none
+ response["project"]["name"].should_not.be.none
+
+ response["project"]["environment"].should.equal(
+ {
+ "computeType": "BUILD_GENERAL1_SMALL",
+ "image": "contents_not_validated",
+ "type": "LINUX_CONTAINER",
+ }
+ )
+
+ response["project"]["source"].should.equal(
+ {"location": "bucketname/path/file.zip", "type": "S3"}
+ )
+
+ response["project"]["artifacts"].should.equal({"type": "NO_ARTIFACTS"})
+
+
+@mock_codebuild
+def test_codebuild_create_project_with_invalid_name():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "!some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ # output artifacts
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ with pytest.raises(client.exceptions.from_code("InvalidInputException")) as err:
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ err.value.response["Error"]["Code"].should.equal("InvalidInputException")
+
+
+@mock_codebuild
+def test_codebuild_create_project_with_invalid_name_length():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project_" * 12
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ # output artifacts
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ with pytest.raises(client.exceptions.from_code("InvalidInputException")) as err:
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ err.value.response["Error"]["Code"].should.equal("InvalidInputException")
+
+
+@mock_codebuild
+def test_codebuild_create_project_when_exists():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ with pytest.raises(ClientError) as err:
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ err.value.response["Error"]["Code"].should.equal("ResourceAlreadyExistsException")
+
+
+@mock_codebuild
+def test_codebuild_list_projects():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ # output artifacts
+ artifacts = dict()
+ artifacts["type"] = "S3"
+ artifacts["location"] = "bucketname"
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name="project1",
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.create_project(
+ name="project2",
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ projects = client.list_projects()
+
+ projects["projects"].should_not.be.none
+ projects["projects"].should.equal(["project1", "project2"])
+
+
+@mock_codebuild
+def test_codebuild_list_builds_for_project_no_history():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ history = client.list_builds_for_project(projectName=name)
+
+ # no build history if it's never started
+ history["ids"].should.be.empty
+
+
+@mock_codebuild
+def test_codebuild_list_builds_for_project_with_history():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName=name)
+ response = client.list_builds_for_project(projectName=name)
+
+ response["ids"].should_not.be.empty
+
+
+# project never started
+@mock_codebuild
+def test_codebuild_get_batch_builds_for_project_no_history():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ response = client.list_builds_for_project(projectName=name)
+ response.should_not.be.none
+ response["ids"].should.be.empty
+
+ with pytest.raises(ParamValidationError) as err:
+ client.batch_get_builds(ids=response["ids"])
+ err.typename.should.equal("ParamValidationError")
+
+
+@mock_codebuild
+def test_codebuild_start_build_no_project():
+
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+
+ with pytest.raises(client.exceptions.from_code("ResourceNotFoundException")) as err:
+ client.start_build(projectName=name)
+ err.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
+
+
+@mock_codebuild
+def test_codebuild_start_build_no_overrides():
+
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ response = client.start_build(projectName=name)
+
+ response.should_not.be.none
+ response["build"].should_not.be.none
+ response["build"]["sourceVersion"].should.equal("refs/heads/main")
+
+
+@mock_codebuild
+def test_codebuild_start_build_multiple_times():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+
+ client.start_build(projectName=name)
+ client.start_build(projectName=name)
+ client.start_build(projectName=name)
+
+ len(client.list_builds()["ids"]).should.equal(3)
+
+
+@mock_codebuild
+def test_codebuild_start_build_with_overrides():
+
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ branch_override = "fix/testing"
+ artifacts_override = {"type": "NO_ARTIFACTS"}
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ response = client.start_build(
+ projectName=name,
+ sourceVersion=branch_override,
+ artifactsOverride=artifacts_override,
+ )
+
+ response.should_not.be.none
+ response["build"].should_not.be.none
+ response["build"]["sourceVersion"].should.equal("fix/testing")
+
+
+@mock_codebuild
+def test_codebuild_batch_get_builds_1_project():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName=name)
+
+ history = client.list_builds_for_project(projectName=name)
+ response = client.batch_get_builds(ids=history["ids"])
+
+ response.should_not.be.none
+ response["builds"].should_not.be.none
+ response["builds"][0]["currentPhase"].should.equal("COMPLETED")
+ response["builds"][0]["buildNumber"].should.be.a(int)
+ response["builds"][0]["phases"].should_not.be.none
+ len(response["builds"][0]["phases"]).should.equal(11)
+
+
+@mock_codebuild
+def test_codebuild_batch_get_builds_2_projects():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name="project-1",
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName="project-1")
+
+ client.create_project(
+ name="project-2",
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName="project-2")
+
+ response = client.list_builds()
+ response["ids"].should_not.be.empty
+
+ "project-1".should.be.within(response["ids"][0])
+ "project-2".should.be.within(response["ids"][1])
+
+ metadata = client.batch_get_builds(ids=response["ids"])["builds"]
+ metadata.should_not.be.none
+ "project-1".should.be.within(metadata[0]["id"])
+ "project-2".should.be.within(metadata[1]["id"])
+
+
+@mock_codebuild
+def test_codebuild_batch_get_builds_invalid_build_id():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ with pytest.raises(client.exceptions.InvalidInputException) as err:
+ client.batch_get_builds(ids=["some_project{}".format(uuid1())])
+ err.value.response["Error"]["Code"].should.equal("InvalidInputException")
+
+
+@mock_codebuild
+def test_codebuild_batch_get_builds_empty_build_id():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ with pytest.raises(ParamValidationError) as err:
+ client.batch_get_builds(ids=[])
+ err.typename.should.equal("ParamValidationError")
+
+
+@mock_codebuild
+def test_codebuild_delete_project():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName=name)
+
+ response = client.list_builds_for_project(projectName=name)
+ response["ids"].should_not.be.empty
+
+ client.delete_project(name=name)
+
+ with pytest.raises(ClientError) as err:
+ client.list_builds_for_project(projectName=name)
+ err.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
+
+
+@mock_codebuild
+def test_codebuild_stop_build():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ name = "some_project"
+ source = dict()
+ source["type"] = "S3"
+ # repository location for S3
+ source["location"] = "bucketname/path/file.zip"
+ artifacts = {"type": "NO_ARTIFACTS"}
+
+ environment = dict()
+ environment["type"] = "LINUX_CONTAINER"
+ environment["image"] = "contents_not_validated"
+ environment["computeType"] = "BUILD_GENERAL1_SMALL"
+ service_role = (
+ "arn:aws:iam::{0}:role/service-role/my-codebuild-service-role".format(
+ ACCOUNT_ID
+ )
+ )
+
+ client.create_project(
+ name=name,
+ source=source,
+ artifacts=artifacts,
+ environment=environment,
+ serviceRole=service_role,
+ )
+ client.start_build(projectName=name)
+
+ builds = client.list_builds()
+
+ response = client.stop_build(id=builds["ids"][0])
+ response["build"]["buildStatus"].should.equal("STOPPED")
+
+
+@mock_codebuild
+def test_codebuild_stop_build_no_build():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ with pytest.raises(client.exceptions.ResourceNotFoundException) as err:
+ client.stop_build(id="some_project:{0}".format(uuid1()))
+ err.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
+
+
+@mock_codebuild
+def test_codebuild_stop_build_bad_uid():
+ client = boto3.client("codebuild", region_name="eu-central-1")
+
+ with pytest.raises(client.exceptions.InvalidInputException) as err:
+ client.stop_build(id="some_project{0}".format(uuid1()))
+ err.value.response["Error"]["Code"].should.equal("InvalidInputException")