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 包括 githubgithub_enterprisebitbucketbitbucket_servergitlab 和 `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 将根据映射解析对象,并相应地更新目录实体。