Skip to main content

Codecov

在本示例中,您将在Codecov 和 Port 之间创建一个 webhook 集成。该集成将有助于将覆盖实体导入 Port。

Port 配置​

创建以下蓝图定义:

Codecov coverage blueprint
{
"identifier": "codecov_coverage",
"title": "Codecov Coverage",
"icon": "Git",
"schema": {
"properties": {
"repository": {
"title": "Repository",
"type": "string",
"format": "url"
},
"coverage": {
"title": "Test Coverage",
"type": "string"
},
"service": {
"title": "Service",
"type": "string"
},
"author": {
"title": "Author",
"type": "string"
},
"createdAt": {
"title": "Created At",
"type": "string",
"format": "date-time"
},
"files": {
"title": "Tracked Files",
"type": "string",
"description": "Number of files tracked"
},
"lines": {
"title": "Tracked Lines",
"description": "Number of lines tracked",
"type": "string"
},
"branch": {
"title": "Branch",
"type": "string"
},
"report": {
"title": "Full Report Data",
"type": "object",
"description": "Detailed information about the codecov report"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {}
}

创建以下 webhook 配置using Port's UI :

Codecov webhook configuration
  1. 基本信息 选项卡 - 填写以下详细信息: 1.title: Codecov Mapper; 2.标识符 : codecov_mapper; 3.Description : 将 Codecov 覆盖范围映射到 Port 的 webhook 配置; 4.图标 : Git;
  2. 集成配置选项卡 - 填写以下 JQ 映射:
    [
    {
    "blueprint": "codecov_coverage",
    "filter": "true",
    "entity": {
    "identifier": ".body.repo.name | tostring",
    "title": ".body.repo.name | tostring",
    "properties": {
    "repository": ".body.repo.url",
    "coverage": ".body.head.totals.coverage",
    "service": ".body.owner.service",
    "author": ".body.head.author.name",
    "createdAt": ".body.head.timestamp | (strptime(\"%Y-%m-%dT%H:%M:%S\") | strftime(\"%Y-%m-%dT%H:%M:%SZ\"))",
    "files": ".body.head.totals.files",
    "lines": ".body.head.totals.lines",
    "branch": ".body.head.branch",
    "report": ".body.head.totals"
    }
    }
    }
    ]
    ::注意 Webhook URL 注意并复制此选项卡中提供的 Webhook URL ::: 3.点击页面底部的保存。

在 Codecov 中创建 webhook​

  1. 从您的 Codecov 账户,打开设置;

  2. 点击左侧边栏菜单中的Global YAML选项卡;

  3. 在 YAML 编辑器中添加以下 Codecov 配置,以便在代码库中发生事件时随时通知 Port:

    coverage:
    notify:
    webhook:
    default:
    only_pulls: false
    url: YOUR_PORT_WEBHOOK
    Webhook URL replacement

    Remember to replace YOUR_PORT_WEBOOK with the value of the URL you received after creating the webhook configuration in Port.

:::tip[notification service customization]
For more information on customizing the notification service, follow this [guide](https://docs.codecov.com/docs/notifications#standard-notification-fields)
:::

3.单击保存更改保存 webhook 配置。

有关自定义通知服务的更多信息,请参阅this documentation

一切就绪!当您的 Codecov 账户发生任何变化时,Port 提供的 URL 将触发一个 Webhook 事件。 Port 将根据映射解析事件,随后更新目录实体。

导入历史 Codecov 覆盖率​

在本示例中,您将使用 Provider 提供的 Python 脚本从 Codecov REST API 获取覆盖率数据并将其引用到 Port。

先决条件​

本示例使用的是上一节中的blueprint and webhook 定义。

此外,请 Provider 下列环境变量:

  • PORT_CLIENT_ID - 您的 Port 客户端 ID
  • PORT_CLIENT_SECRET - 您的 Port 客户端secret
  • CODECOV_TOKEN - Codecov API 访问令牌
  • CODECOV_SERVICE_PROVIDER - Git 托管服务提供商。可接受的 Values 包括 github、github_enterprise、bitbucket、bitbucket_server、gitlab 和 `gitlab_enterprise
  • CODECOV_SERVICE_PROVIDER_ACCOUNT_NAME - Git 服务提供商的用户名
凭据 使用此参数查找您的 Port 凭据guide

使用以下命令查找您的 Codecov API 令牌guide

使用以下 Python 脚本将 Codecov 历史覆盖率被用于到 Port 中:

Codecov Python script
## Import the needed libraries
import requests
from decouple import config
from loguru import logger
from typing import Any

# Get environment variables using the config object or os.environ["KEY"]
# These are the credentials passed by the variables of your pipeline to your tasks and in to your env

PORT_CLIENT_ID = config("PORT_CLIENT_ID")
PORT_CLIENT_SECRET = config("PORT_CLIENT_SECRET")
CODECOV_TOKEN = config("CODECOV_TOKEN")
CODECOV_SERVICE_PROVIDER = config("CODECOV_SERVICE_PROVIDER")
CODECOV_SERVICE_PROVIDER_ACCOUNT_NAME = config("CODECOV_SERVICE_PROVIDER_ACCOUNT_NAME")
CODECOV_API_URL = "https://api.codecov.io/api/v2"
PORT_API_URL = "https://api.getport.io/v1"

ALLOWED_SERVICE_PROVIDERS = {
"github",
"github_enterprise",
"bitbucket",
"bitbucket_server",
"gitlab",
"gitlab_enterprise",
}

if CODECOV_SERVICE_PROVIDER not in ALLOWED_SERVICE_PROVIDERS:
raise ValueError(
f"Invalid CODECOV_SERVICE_PROVIDER: {CODECOV_SERVICE_PROVIDER}. Allowed values are {', '.join(ALLOWED_SERVICE_PROVIDERS)}"
)

## Get Port Access Token
credentials = {"clientId": PORT_CLIENT_ID, "clientSecret": PORT_CLIENT_SECRET}
token_response = requests.post(f"{PORT_API_URL}/auth/access_token", json=credentials)
access_token = token_response.json()["accessToken"]

# You can now use the value in access_token when making further requests
port_headers = {"Authorization": f"Bearer {access_token}"}


def add_entity_to_port(blueprint_id: str, entity_object: dict[str, Any]):
response = requests.post(
f"{PORT_API_URL}/blueprints/{blueprint_id}/entities?upsert=true&merge=true",
json=entity_object,
headers=port_headers,
)
logger.info(response.json())


def get_paginated_resource(path: str, query_params: dict[str, Any] = {}):
logger.info(
f"Requesting paginated data for path: {path} and params: {query_params}"
)

url = f"{CODECOV_API_URL}/{path}"

while url:
try:
response = requests.get(url=url, params=query_params)
response.raise_for_status()
page_json = response.json()
batch_data = page_json["results"]
yield batch_data

url = page_json.get("next")

except requests.exceptions.HTTPError as e:
logger.error(f"HTTP error with info: {e}")
raise

logger.info(f"Successfully fetched paginated data for {path}")


def process_repository_entities(repository_data: list[dict[str, Any]]):
blueprint_id = "codecov_coverage"
for repo in repository_data:
report: dict[str, Any] = repo.get("totals", {})
entity = {
"identifier": repo["name"],
"title": repo["name"],
"properties": {
"repository": f"https://app.codecov.io/{repo['author']['service']}/{repo['author']['username']}/{repo['name']}",
"coverage": report.get("coverage") if report else None,
"service": repo["author"]["service"],
"author": repo["author"]["name"],
"createdAt": repo["updatestamp"],
"files": report.get("files") if report else None,
"lines": report.get("lines") if report else None,
"report": report,
"branch": repo["branch"],
},
"relations": {},
}
add_entity_to_port(blueprint_id=blueprint_id, entity_object=entity)


if __name__ == "__main__":
logger.debug("Starting Codecov app")
repository_path = (
f"{CODECOV_SERVICE_PROVIDER}/{CODECOV_SERVICE_PROVIDER_ACCOUNT_NAME}/repos"
)
for repositories_batch in get_paginated_resource(path=repository_path):
logger.debug(
f"Received Codecov repositories batch with size {len(repositories_batch)}"
)
process_repository_entities(repository_data=repositories_batch)

logger.debug("Finished Codecov app")

运行 python 脚本​

要从您的 Codecov 账户向 Port 输入覆盖数据,请运行以下命令:

export PORT_CLIENT_ID=<ENTER CLIENT ID>
export PORT_CLIENT_SECRET=<ENTER CLIENT SECRET>
export CODECOV_TOKEN=<ENTER CODECOV TOKEN>
export CODECOV_SERVICE_PROVIDER=<ENTER CODECOV SERVICE PROVIDER>
export CODECOV_SERVICE_PROVIDER_ACCOUNT_NAME=<ENTER CODECOV SERVICE PROVIDER ACCOUNT NAME>

git clone https://github.com/port-labs/example-codecov-test-coverage.git

cd example-codecov-test-coverage

pip install -r ./requirements.txt

python app.py
Python 脚本信息 查找有关 python 脚本的更多信息here

完成!现在您可以将历史覆盖范围从 Codecov 导入 Port。 Port 将根据映射解析对象,并相应地更新目录实体。