Skip to content

Commit cc47be7

Browse files
feat(cdk): port QA checks from airbyte-ci to airbyte-python-cdk
Co-Authored-By: Aaron <AJ> Steers <[email protected]>
1 parent ce2a7bb commit cc47be7

File tree

18 files changed

+2083
-0
lines changed

18 files changed

+2083
-0
lines changed

airbyte_cdk/cli/airbyte_cdk/_connector.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ def test(
174174
)
175175

176176

177+
from airbyte_cdk.cli.airbyte_cdk._qa import pre_release_check
178+
179+
connector_cli_group.add_command(pre_release_check)
180+
177181
__all__ = [
178182
"connector_cli_group",
179183
]

airbyte_cdk/cli/airbyte_cdk/_qa.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""CLI command for running QA checks on connectors."""
2+
3+
import asyncio
4+
from pathlib import Path
5+
from typing import List, Optional
6+
7+
import rich_click as click
8+
9+
from airbyte_cdk.cli.airbyte_cdk._util import resolve_connector_name_and_directory
10+
from airbyte_cdk.qa.checks import ENABLED_CHECKS
11+
from airbyte_cdk.qa.connector import Connector
12+
from airbyte_cdk.qa.models import CheckResult, CheckStatus, Report
13+
14+
15+
@click.command(name="pre-release-check")
16+
@click.option(
17+
"-c",
18+
"--check",
19+
"selected_checks",
20+
multiple=True,
21+
type=click.Choice([type(check).__name__ for check in ENABLED_CHECKS]),
22+
help="The name of the check to run. If not provided, all checks will be run.",
23+
)
24+
@click.option(
25+
"--connector-name",
26+
type=str,
27+
help="Name of the connector to check. Ignored if --connector-directory is provided.",
28+
)
29+
@click.option(
30+
"--connector-directory",
31+
type=click.Path(exists=True, file_okay=False, path_type=Path),
32+
help="Path to the connector directory.",
33+
)
34+
@click.option(
35+
"-r",
36+
"--report-path",
37+
"report_path",
38+
type=click.Path(file_okay=True, path_type=Path, writable=True, dir_okay=False),
39+
help="The path to the report file to write the results to as JSON.",
40+
)
41+
def pre_release_check(
42+
selected_checks: List[str],
43+
connector_name: Optional[str] = None,
44+
connector_directory: Optional[Path] = None,
45+
report_path: Optional[Path] = None,
46+
) -> None:
47+
"""Run pre-release checks on a connector.
48+
49+
This command runs quality assurance checks on a connector to ensure it meets
50+
Airbyte's standards for release. The checks include:
51+
52+
- Documentation checks
53+
- Metadata checks
54+
- Packaging checks
55+
- Security checks
56+
- Asset checks
57+
- Testing checks
58+
59+
If no connector name or directory is provided, we will look within the current working
60+
directory. If the current working directory is not a connector directory (e.g. starting
61+
with 'source-') and no connector name or path is provided, the process will fail.
62+
"""
63+
connector_name, connector_directory = resolve_connector_name_and_directory(
64+
connector_name=connector_name,
65+
connector_directory=connector_directory,
66+
)
67+
68+
connector = Connector(connector_name, connector_directory)
69+
70+
checks_to_run = [check for check in ENABLED_CHECKS if type(check).__name__ in selected_checks] if selected_checks else ENABLED_CHECKS
71+
72+
check_results = []
73+
for check in checks_to_run:
74+
result = check.run(connector)
75+
check_results.append(result)
76+
status_color = {
77+
CheckStatus.PASSED: "green",
78+
CheckStatus.FAILED: "red",
79+
CheckStatus.SKIPPED: "yellow",
80+
}[result.status]
81+
click.echo(f"[{status_color}]{result.status.value}[/{status_color}] {check.name}: {result.message}")
82+
83+
if report_path:
84+
Report(check_results=check_results).write(report_path)
85+
click.echo(f"Report written to {report_path}")
86+
87+
failed_checks = [check_result for check_result in check_results if check_result.status is CheckStatus.FAILED]
88+
if failed_checks:
89+
raise click.ClickException(f"{len(failed_checks)} checks failed")

airbyte_cdk/qa/__init__.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""The `airbyte_cdk.qa` module provides quality assurance checks for Airbyte connectors.
2+
3+
This module includes a framework for running pre-release checks on connectors to ensure
4+
they meet Airbyte's quality standards. The checks are organized into categories and can
5+
be run individually or as a group.
6+
7+
8+
The QA module includes the following check categories:
9+
10+
- **Packaging**: Checks related to connector packaging, including dependency management,
11+
versioning, and licensing.
12+
- **Metadata**: Checks related to connector metadata, including language tags, CDK tags,
13+
and breaking changes deadlines.
14+
- **Security**: Checks related to connector security, including HTTPS usage and base image
15+
requirements.
16+
- **Assets**: Checks related to connector assets, including icons and other visual elements.
17+
- **Documentation**: Checks related to connector documentation, ensuring it exists and is
18+
properly formatted.
19+
- **Testing**: Checks related to connector testing, ensuring acceptance tests are present.
20+
21+
22+
Checks can be configured based on various connector attributes:
23+
24+
- **Connector Language**: Checks can be configured to run only on connectors of specific
25+
languages (Python, Java, Low-Code, Manifest-Only).
26+
- **Connector Type**: Checks can be configured to run only on specific connector types
27+
(source, destination).
28+
- **Support Level**: Checks can be configured to run only on connectors with specific
29+
support levels (certified, community, etc.).
30+
- **Cloud Usage**: Checks can be configured to run only on connectors with specific
31+
cloud usage settings (enabled, disabled, etc.).
32+
- **Internal SL**: Checks can be configured to run only on connectors with specific
33+
internal service level requirements.
34+
35+
36+
Checks can be run using the `airbyte-cdk connector pre-release-check` command:
37+
38+
```bash
39+
airbyte-cdk connector pre-release-check --connector-name source-example
40+
41+
airbyte-cdk connector pre-release-check --connector-name source-example --check CheckConnectorUsesPoetry --check CheckVersionBump
42+
43+
airbyte-cdk connector pre-release-check --connector-directory /path/to/connector
44+
45+
airbyte-cdk connector pre-release-check --connector-name source-example --report-path report.json
46+
```
47+
48+
49+
The QA module is designed to be extensible. New checks can be added by creating a new
50+
class that inherits from the `Check` base class and implementing the required methods.
51+
52+
Example:
53+
54+
```python
55+
from airbyte_cdk.qa.models import Check, CheckCategory, CheckResult
56+
from airbyte_cdk.qa.connector import Connector
57+
58+
class MyCustomCheck(Check):
59+
name = "My custom check"
60+
description = "Description of what my check verifies"
61+
category = CheckCategory.TESTING
62+
63+
def _run(self, connector: Connector) -> CheckResult:
64+
if some_condition:
65+
return self.pass_(connector, "Check passed message")
66+
else:
67+
return self.fail(connector, "Check failed message")
68+
```
69+
"""

airbyte_cdk/qa/checks/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""QA checks for Airbyte connectors."""
2+
3+
from airbyte_cdk.qa.checks.assets import ENABLED_CHECKS as ASSETS_CHECKS
4+
from airbyte_cdk.qa.checks.documentation import ENABLED_CHECKS as DOCUMENTATION_CHECKS
5+
from airbyte_cdk.qa.checks.metadata import ENABLED_CHECKS as METADATA_CORRECTNESS_CHECKS
6+
from airbyte_cdk.qa.checks.packaging import ENABLED_CHECKS as PACKAGING_CHECKS
7+
from airbyte_cdk.qa.checks.security import ENABLED_CHECKS as SECURITY_CHECKS
8+
from airbyte_cdk.qa.checks.testing import ENABLED_CHECKS as TESTING_CHECKS
9+
10+
ENABLED_CHECKS = (
11+
DOCUMENTATION_CHECKS
12+
+ METADATA_CORRECTNESS_CHECKS
13+
+ PACKAGING_CHECKS
14+
+ ASSETS_CHECKS
15+
+ SECURITY_CHECKS
16+
+ TESTING_CHECKS
17+
)

airbyte_cdk/qa/checks/assets.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Asset checks for Airbyte connectors."""
2+
3+
from airbyte_cdk.qa import consts
4+
from airbyte_cdk.qa.connector import Connector
5+
from airbyte_cdk.qa.models import Check, CheckCategory, CheckResult
6+
7+
8+
class AssetCheck(Check):
9+
"""Base class for asset checks."""
10+
11+
category = CheckCategory.ASSETS
12+
13+
14+
class CheckConnectorHasIcon(AssetCheck):
15+
"""Check that connectors have an icon."""
16+
17+
name = "Connectors must have an icon"
18+
description = f"Connectors must have an icon file named `{consts.ICON_FILE_NAME}` in their code directory. This is to ensure that all connectors have a visual representation in the UI."
19+
20+
def _run(self, connector: Connector) -> CheckResult:
21+
"""Run the check.
22+
23+
Args:
24+
connector: The connector to check
25+
26+
Returns:
27+
CheckResult: The result of the check
28+
"""
29+
icon_path = connector.code_directory / consts.ICON_FILE_NAME
30+
if not icon_path.exists():
31+
return self.create_check_result(
32+
connector=connector,
33+
passed=False,
34+
message=f"Icon file {consts.ICON_FILE_NAME} does not exist",
35+
)
36+
return self.create_check_result(
37+
connector=connector,
38+
passed=True,
39+
message=f"Icon file {consts.ICON_FILE_NAME} exists",
40+
)
41+
42+
43+
ENABLED_CHECKS = [
44+
CheckConnectorHasIcon(),
45+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Documentation checks for Airbyte connectors."""
2+
3+
from airbyte_cdk.qa.checks.documentation.documentation import (
4+
CheckDocumentationExists,
5+
)
6+
7+
ENABLED_CHECKS = [
8+
CheckDocumentationExists(),
9+
]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Documentation checks for Airbyte connectors."""
2+
3+
from pathlib import Path
4+
5+
from airbyte_cdk.qa.connector import Connector
6+
from airbyte_cdk.qa.models import Check, CheckCategory, CheckResult
7+
8+
9+
class DocumentationCheck(Check):
10+
"""Base class for documentation checks."""
11+
12+
category = CheckCategory.DOCUMENTATION
13+
14+
15+
class CheckDocumentationExists(DocumentationCheck):
16+
"""Check that connectors have documentation."""
17+
18+
name = "Connectors must have documentation"
19+
description = "Connectors must have documentation to ensure that users can understand how to use them."
20+
21+
def _run(self, connector: Connector) -> CheckResult:
22+
"""Run the check.
23+
24+
Args:
25+
connector: The connector to check
26+
27+
Returns:
28+
CheckResult: The result of the check
29+
"""
30+
docs_dir = Path("/home/ubuntu/repos/airbyte/docs/integrations")
31+
connector_type_dir = docs_dir / connector.connector_type + "s"
32+
33+
doc_file = connector_type_dir / (connector.technical_name.replace("source-", "").replace("destination-", "") + ".md")
34+
35+
if not doc_file.exists():
36+
return self.create_check_result(
37+
connector=connector,
38+
passed=False,
39+
message=f"Documentation file {doc_file} does not exist",
40+
)
41+
return self.create_check_result(
42+
connector=connector,
43+
passed=True,
44+
message=f"Documentation file {doc_file} exists",
45+
)
46+
47+
48+
ENABLED_CHECKS = [
49+
CheckDocumentationExists(),
50+
]

0 commit comments

Comments
 (0)