Skip to content

Commit

Permalink
feat: add Google Cloud Build CI support (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
escarls committed May 14, 2024
1 parent d3f982f commit 7432bad
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 0 deletions.
2 changes: 2 additions & 0 deletions codecov_cli/helpers/ci_adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from codecov_cli.helpers.ci_adapters.buildkite import BuildkiteAdapter
from codecov_cli.helpers.ci_adapters.circleci import CircleCICIAdapter
from codecov_cli.helpers.ci_adapters.cirrus_ci import CirrusCIAdapter
from codecov_cli.helpers.ci_adapters.cloudbuild import GoogleCloudBuildAdapter
from codecov_cli.helpers.ci_adapters.codebuild import AWSCodeBuildCIAdapter
from codecov_cli.helpers.ci_adapters.droneci import DroneCIAdapter
from codecov_cli.helpers.ci_adapters.github_actions import GithubActionsCIAdapter
Expand Down Expand Up @@ -54,6 +55,7 @@ def get_ci_providers_list():
TeamcityAdapter(),
TravisCIAdapter(),
AWSCodeBuildCIAdapter(),
GoogleCloudBuildAdapter(),
# local adapter should always be the last one
LocalAdapter(),
]
70 changes: 70 additions & 0 deletions codecov_cli/helpers/ci_adapters/cloudbuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os

from codecov_cli.helpers.ci_adapters.base import CIAdapterBase


class GoogleCloudBuildAdapter(CIAdapterBase):
"""
Google Cloud Build uses variable substitutions in the builds
https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values
For these to be available as environment variables, so this adapter
can read the values, you have to manually map the substitution variables to
environment variables on the build step, like this
env:
- '_PR_NUMBER=$_PR_NUMBER'
- 'BRANCH_NAME=$BRANCH_NAME'
- 'BUILD_ID=$BUILD_ID'
- 'COMMIT_SHA=$COMMIT_SHA'
- 'LOCATION=$LOCATION'
- 'PROJECT_ID=$PROJECT_ID'
- 'PROJECT_NUMBER=$PROJECT_NUMBER'
- 'REF_NAME=$REF_NAME'
- 'REPO_FULL_NAME=$REPO_FULL_NAME'
- 'TRIGGER_NAME=$TRIGGER_NAME'
Read more about manual substitution mapping here:
https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values#map_substitutions_manually
"""

def detect(self) -> bool:
return all(
list(
map(os.getenv, ["LOCATION", "PROJECT_NUMBER", "PROJECT_ID", "BUILD_ID"])
)
)

def _get_branch(self):
return os.getenv("BRANCH_NAME")

def _get_build_code(self):
return os.getenv("BUILD_ID")

def _get_commit_sha(self):
return os.getenv("COMMIT_SHA")

def _get_slug(self):
return os.getenv("REPO_FULL_NAME")

def _get_build_url(self):
# to build the url, the environment variables LOCATION, PROJECT_ID and BUILD_ID are needed
if not all(list(map(os.getenv, ["LOCATION", "PROJECT_ID", "BUILD_ID"]))):
return None

location = os.getenv("LOCATION")
project_id = os.getenv("PROJECT_ID")
build_id = os.getenv("BUILD_ID")

return f"https://console.cloud.google.com/cloud-build/builds;region={location}/{build_id}?project={project_id}"

def _get_pull_request_number(self):
pr_num = os.getenv("_PR_NUMBER")
return pr_num if pr_num != "" else None

def _get_job_code(self):
job_code = os.getenv("TRIGGER_NAME")
return job_code if job_code != "" else None

def _get_service(self):
return "google_cloud_build"

def get_service_name(self):
return "GoogleCloudBuild"
213 changes: 213 additions & 0 deletions tests/ci_adapters/test_cloudbuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import os
from enum import Enum

import pytest

from codecov_cli.fallbacks import FallbackFieldEnum
from codecov_cli.helpers.ci_adapters.cloudbuild import GoogleCloudBuildAdapter


class CloudBuildEnvEnum(str, Enum):
BRANCH_NAME = "BRANCH_NAME"
BUILD_ID = "BUILD_ID"
COMMIT_SHA = "COMMIT_SHA"
LOCATION = "LOCATION"
PROJECT_ID = "PROJECT_ID"
PROJECT_NUMBER = "PROJECT_NUMBER"
REPO_FULL_NAME = "REPO_FULL_NAME"
_PR_NUMBER = "_PR_NUMBER"
TRIGGER_NAME = "TRIGGER_NAME"


class TestCloudBuild(object):
@pytest.mark.parametrize(
"env_dict,expected",
[
({}, False),
(
{
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
CloudBuildEnvEnum.PROJECT_NUMBER: "123",
},
False,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
CloudBuildEnvEnum.PROJECT_NUMBER: "123",
},
False,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_NUMBER: "123",
},
False,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
},
False,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
CloudBuildEnvEnum.PROJECT_NUMBER: "123",
},
True,
),
],
)
def test_detect(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().detect()
assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({CloudBuildEnvEnum.BRANCH_NAME: "abc"}, "abc"),
],
)
def test_branch(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(FallbackFieldEnum.branch)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
(
{CloudBuildEnvEnum.BUILD_ID: "52cbb633-aca0-4289-90bd-76e4e60baf82"},
"52cbb633-aca0-4289-90bd-76e4e60baf82",
),
],
)
def test_build_code(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(
FallbackFieldEnum.build_code
)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
(
{
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
},
None,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
},
None,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.LOCATION: "global",
},
None,
),
(
{
CloudBuildEnvEnum.BUILD_ID: "fd02b20f-72a3-41b5-862d-2c15e5f289de",
CloudBuildEnvEnum.LOCATION: "global",
CloudBuildEnvEnum.PROJECT_ID: "my_project",
},
"https://console.cloud.google.com/cloud-build/builds;region=global/fd02b20f-72a3-41b5-862d-2c15e5f289de?project=my_project",
),
],
)
def test_build_url(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(
FallbackFieldEnum.build_url
)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({CloudBuildEnvEnum.COMMIT_SHA: "123456789000111"}, "123456789000111"),
],
)
def test_commit_sha(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(
FallbackFieldEnum.commit_sha
)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({CloudBuildEnvEnum.TRIGGER_NAME: ""}, None),
({CloudBuildEnvEnum.TRIGGER_NAME: "build-job-name"}, "build-job-name"),
],
)
def test_job_code(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(
FallbackFieldEnum.job_code
)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({CloudBuildEnvEnum._PR_NUMBER: ""}, None),
({CloudBuildEnvEnum._PR_NUMBER: "123"}, "123"),
],
)
def test_pull_request_number(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(
FallbackFieldEnum.pull_request_number
)

assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({CloudBuildEnvEnum.REPO_FULL_NAME: "owner/repo"}, "owner/repo"),
],
)
def test_slug(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = GoogleCloudBuildAdapter().get_fallback_value(FallbackFieldEnum.slug)

assert actual == expected

def test_service(self):
assert (
GoogleCloudBuildAdapter().get_fallback_value(FallbackFieldEnum.service)
== "google_cloud_build"
)

0 comments on commit 7432bad

Please sign in to comment.