Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codecov-cli/codecov_cli/helpers/ci_adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from codecov_cli.helpers.ci_adapters.droneci import DroneCIAdapter
from codecov_cli.helpers.ci_adapters.github_actions import GithubActionsCIAdapter
from codecov_cli.helpers.ci_adapters.gitlab_ci import GitlabCIAdapter
from codecov_cli.helpers.ci_adapters.harness import HarnessAdapter
from codecov_cli.helpers.ci_adapters.heroku import HerokuCIAdapter
from codecov_cli.helpers.ci_adapters.jenkins import JenkinsAdapter
from codecov_cli.helpers.ci_adapters.local import LocalAdapter
Expand Down Expand Up @@ -48,6 +49,7 @@ def get_ci_providers_list():
BitriseCIAdapter(),
AppveyorCIAdapter(),
WoodpeckerCIAdapter(),
HarnessAdapter(),
HerokuCIAdapter(),
DroneCIAdapter(),
BuildkiteAdapter(),
Expand Down
63 changes: 63 additions & 0 deletions codecov-cli/codecov_cli/helpers/ci_adapters/harness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os

from codecov_cli.helpers.ci_adapters.base import CIAdapterBase
from codecov_cli.helpers.git import parse_slug

# https://developer.harness.io/docs/continuous-integration/troubleshoot-ci/ci-env-var/

# Uses CI_ prefixed environment variables variant if available and DRONE_ prefixed environment variables variant if not
# The DRONE variables overlap with the Drone CI Adapter as Harness CI is built on top
class HarnessAdapter(CIAdapterBase):
def detect(self) -> bool:
return bool(os.getenv("HARNESS_BUILD_ID"))

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

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

def _get_pull_request_number(self):
return os.getenv("DRONE_PULL_REQUEST")

def _get_job_code(self):
return None

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

def _get_build_url(self):
return os.getenv("CI_BUILD_LINK")

def _get_slug(self):
# DRONE_REPO is the canonical org/repo slug on Harness CI (Drone-compatible).
# CI_REPO is often equivalent but some setups only set the repo name; then use
# DRONE_REPO_NAMESPACE + DRONE_REPO_NAME (or short CI_REPO as the name part).
if drone_repo := os.getenv("DRONE_REPO"):
return drone_repo
ci_repo = os.getenv("CI_REPO")
if ci_repo and "/" in ci_repo:
return ci_repo
namespace = os.getenv("DRONE_REPO_NAMESPACE") or os.getenv("CI_REPO_NAMESPACE")
name = os.getenv("DRONE_REPO_NAME") or (
ci_repo if ci_repo and "/" not in ci_repo else None
)
if namespace and name:
return f"{namespace}/{name}"
# Many Harness builds omit CI_REPO / *_NAMESPACE but still set clone/remotes:
# https://developer.harness.io/docs/continuous-integration/troubleshoot-ci/ci-env-var/
for env_var in (
"DRONE_GIT_HTTP_URL",
"CI_REPO_REMOTE",
"DRONE_REMOTE_URL",
):
if url := os.getenv(env_var):
if slug := parse_slug(url):
return slug
return None

def _get_service(self):
return "harness"

def get_service_name(self):
return "Harness"
2 changes: 1 addition & 1 deletion codecov-cli/codecovcli_commands
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Usage: codecovcli [OPTIONS] COMMAND [ARGS]...

Options:
--auto-load-params-from [CircleCI|GithubActions|GitlabCI|Bitbucket|Bitrise|AppVeyor|Woodpecker|Heroku|DroneCI|BuildKite|AzurePipelines|Jenkins|CirrusCI|Teamcity|Travis|AWSCodeBuild|GoogleCloudBuild|Local]
--auto-load-params-from [CircleCI|GithubActions|GitlabCI|Bitbucket|Bitrise|AppVeyor|Woodpecker|Harness|Heroku|DroneCI|BuildKite|AzurePipelines|Jenkins|CirrusCI|Teamcity|Travis|AWSCodeBuild|GoogleCloudBuild|Local]
--codecov-yml-path PATH
-u, --enterprise-url, --url TEXT
Change the upload host (Enterprise use)
Expand Down
4 changes: 2 additions & 2 deletions codecov-cli/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ Homepage = "https://about.codecov.io"
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
packages = ["codecov_cli"]
[tool.setuptools.packages.find]
include = ["codecov_cli*"]

[tool.pytest.ini_options]
env = ["CODECOV_ENV=test"]
Expand Down
156 changes: 156 additions & 0 deletions codecov-cli/tests/ci_adapters/test_harnessci.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import os
from enum import Enum

import pytest

from codecov_cli.fallbacks import FallbackFieldEnum
from codecov_cli.helpers.ci_adapters import HarnessAdapter


class HarnessEnvEnum(str, Enum):
HARNESS_BUILD_ID = "HARNESS_BUILD_ID"
CI_BUILD_LINK = "CI_BUILD_LINK"
CI_BUILD_NUMBER = "CI_BUILD_NUMBER"
CI_REPO = "CI_REPO"
CI_REPO_NAMESPACE = "CI_REPO_NAMESPACE"
DRONE = "DRONE"
DRONE_COMMIT_BRANCH = "DRONE_COMMIT_BRANCH"
DRONE_COMMIT_SHA = "DRONE_COMMIT_SHA"
DRONE_PULL_REQUEST = "DRONE_PULL_REQUEST"
DRONE_GIT_HTTP_URL = "DRONE_GIT_HTTP_URL"
DRONE_REMOTE_URL = "DRONE_REMOTE_URL"
DRONE_REPO = "DRONE_REPO"
DRONE_REPO_NAME = "DRONE_REPO_NAME"
DRONE_REPO_NAMESPACE = "DRONE_REPO_NAMESPACE"
CI_REPO_REMOTE = "CI_REPO_REMOTE"

class TestHarnessCI(object):
@pytest.mark.parametrize(
"env_dict,expected",
[
({}, False),
({HarnessEnvEnum.HARNESS_BUILD_ID: "123"}, True),
],
)
def test_detect(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().detect()
assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({HarnessEnvEnum.DRONE_COMMIT_BRANCH: "branch"}, "branch"),
],
)
def test_branch(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().get_fallback_value(FallbackFieldEnum.branch)
assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({HarnessEnvEnum.DRONE_COMMIT_SHA: "sha"}, "sha"),
],
)
def test_commit_sha(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().get_fallback_value(FallbackFieldEnum.commit_sha)
assert actual == expected

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

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({HarnessEnvEnum.CI_BUILD_NUMBER: "123"}, "123"),
],
)
def test_build_code(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().get_fallback_value(FallbackFieldEnum.build_code)
assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({HarnessEnvEnum.CI_BUILD_LINK: "https://example.com"}, "https://example.com"),
],
)
def test_build_url(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().get_fallback_value(FallbackFieldEnum.build_url)
assert actual == expected

@pytest.mark.parametrize(
"env_dict,expected",
[
({}, None),
({HarnessEnvEnum.DRONE_REPO: "owner/repo"}, "owner/repo"),
({HarnessEnvEnum.CI_REPO: "owner/repo"}, "owner/repo"),
(
{
HarnessEnvEnum.DRONE_REPO_NAMESPACE: "owner",
HarnessEnvEnum.DRONE_REPO_NAME: "repo",
},
"owner/repo",
),
(
{
HarnessEnvEnum.DRONE_REPO_NAMESPACE: "owner",
HarnessEnvEnum.CI_REPO: "repo",
},
"owner/repo",
),
(
{
HarnessEnvEnum.CI_REPO_NAMESPACE: "owner",
HarnessEnvEnum.CI_REPO: "repo",
},
"owner/repo",
),
({HarnessEnvEnum.CI_REPO: "repo"}, None),
(
{
HarnessEnvEnum.DRONE_GIT_HTTP_URL: "https://github.com/myorg/myrepo.git",
},
"myorg/myrepo",
),
(
{
HarnessEnvEnum.CI_REPO_REMOTE: "https://gitlab.com/mygroup/myrepo.git",
},
"mygroup/myrepo",
),
(
{
HarnessEnvEnum.DRONE_REMOTE_URL: "git@github.com:acme/coverage.git",
},
"acme/coverage",
),
],
)
def test_slug(self, env_dict, expected, mocker):
mocker.patch.dict(os.environ, env_dict)
actual = HarnessAdapter().get_fallback_value(FallbackFieldEnum.slug)
assert actual == expected

def test_service(self):
assert (
HarnessAdapter().get_fallback_value(FallbackFieldEnum.service) == "harness"
)
4 changes: 4 additions & 0 deletions codecov-cli/tests/helpers/test_ci_adapter_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
DroneCIAdapter,
GithubActionsCIAdapter,
GitlabCIAdapter,
HarnessAdapter,
HerokuCIAdapter,
JenkinsAdapter,
LocalAdapter,
Expand Down Expand Up @@ -53,6 +54,9 @@ def test_returns_woodpecker(self):
def test_returns_teamcity(self):
assert isinstance(get_ci_adapter("teamcity"), TeamcityAdapter)

def test_returns_harness(self):
assert isinstance(get_ci_adapter("harness"), HarnessAdapter)

def test_returns_herokuci(self):
assert isinstance(get_ci_adapter("heroku"), HerokuCIAdapter)

Expand Down
2 changes: 1 addition & 1 deletion prevent-cli/preventcli_commands
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Usage: sentry-prevent-cli [OPTIONS] COMMAND [ARGS]...

Options:
--auto-load-params-from [CircleCI|GithubActions|GitlabCI|Bitbucket|Bitrise|AppVeyor|Woodpecker|Heroku|DroneCI|BuildKite|AzurePipelines|Jenkins|CirrusCI|Teamcity|Travis|AWSCodeBuild|GoogleCloudBuild|Local]
--auto-load-params-from [CircleCI|GithubActions|GitlabCI|Bitbucket|Bitrise|AppVeyor|Woodpecker|Harness|Heroku|DroneCI|BuildKite|AzurePipelines|Jenkins|CirrusCI|Teamcity|Travis|AWSCodeBuild|GoogleCloudBuild|Local]
--yml-path PATH
-u, --enterprise-url, --url TEXT
Change the upload host (Enterprise use)
Expand Down
Loading