Add support for codebuild (#5282)

This commit is contained in:
b0bu 2022-07-14 16:57:04 +01:00 committed by GitHub
parent c1bdd764c7
commit 79af23aeb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1365 additions and 11 deletions

View File

@ -338,7 +338,7 @@
## autoscaling
<details>
<summary>49% implemented</summary>
<summary>50% implemented</summary>
- [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
</details>
## ce
<details>
<summary>11% implemented</summary>
- [ ] 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
</details>
## cloudformation
<details>
<summary>30% implemented</summary>
@ -709,6 +750,57 @@
- [X] untag_resource
</details>
## codebuild
<details>
<summary>17% implemented</summary>
- [ ] 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
</details>
## codecommit
<details>
<summary>3% implemented</summary>
@ -1289,7 +1381,7 @@
## ds
<details>
<summary>18% implemented</summary>
<summary>19% implemented</summary>
- [ ] 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
</details>
@ -2457,7 +2547,7 @@
## emr-serverless
<details>
<summary>71% implemented</summary>
<summary>50% implemented</summary>
- [ ] 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

View File

@ -0,0 +1,75 @@
.. _implementedservice_codebuild:
.. |start-h3| raw:: html
<h3>
.. |end-h3| raw:: html
</h3>
=========
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

View File

@ -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"

View File

@ -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")),
(

View File

@ -0,0 +1,4 @@
from .models import codebuild_backends
from ..core.models import base_decorator
mock_codebuild = base_decorator(codebuild_backends)

View File

@ -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)

263
moto/codebuild/models.py Normal file
View File

@ -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")

195
moto/codebuild/responses.py Normal file
View File

@ -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})

5
moto/codebuild/urls.py Normal file
View File

@ -0,0 +1,5 @@
from .responses import CodeBuildResponse
url_bases = [r"https?://codebuild\.(.+)\.amazonaws\.com"]
url_paths = {"{0}/$": CodeBuildResponse.dispatch}

View File

@ -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")