airbyte-internal-ops 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {airbyte_internal_ops-0.2.0.dist-info → airbyte_internal_ops-0.2.2.dist-info}/METADATA +19 -3
- {airbyte_internal_ops-0.2.0.dist-info → airbyte_internal_ops-0.2.2.dist-info}/RECORD +41 -41
- airbyte_ops_mcp/__init__.py +2 -2
- airbyte_ops_mcp/cli/cloud.py +207 -306
- airbyte_ops_mcp/cloud_admin/api_client.py +51 -26
- airbyte_ops_mcp/cloud_admin/connection_config.py +2 -2
- airbyte_ops_mcp/constants.py +61 -1
- airbyte_ops_mcp/github_actions.py +69 -1
- airbyte_ops_mcp/mcp/_http_headers.py +56 -0
- airbyte_ops_mcp/mcp/_mcp_utils.py +2 -2
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +57 -43
- airbyte_ops_mcp/mcp/github.py +34 -1
- airbyte_ops_mcp/mcp/prerelease.py +3 -3
- airbyte_ops_mcp/mcp/prod_db_queries.py +293 -50
- airbyte_ops_mcp/mcp/{live_tests.py → regression_tests.py} +158 -176
- airbyte_ops_mcp/mcp/server.py +3 -3
- airbyte_ops_mcp/prod_db_access/db_engine.py +7 -11
- airbyte_ops_mcp/prod_db_access/queries.py +79 -0
- airbyte_ops_mcp/prod_db_access/sql.py +86 -0
- airbyte_ops_mcp/{live_tests → regression_tests}/__init__.py +3 -3
- airbyte_ops_mcp/{live_tests → regression_tests}/cdk_secrets.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/connection_secret_retriever.py +3 -3
- airbyte_ops_mcp/{live_tests → regression_tests}/connector_runner.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/message_cache/__init__.py +3 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/regression/__init__.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/schema_generation.py +3 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/validation/__init__.py +2 -2
- airbyte_ops_mcp/{live_tests → regression_tests}/validation/record_validators.py +4 -2
- {airbyte_internal_ops-0.2.0.dist-info → airbyte_internal_ops-0.2.2.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.2.0.dist-info → airbyte_internal_ops-0.2.2.dist-info}/entry_points.txt +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/ci_output.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/commons/__init__.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/config.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/connection_fetcher.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/evaluation_modes.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/http_metrics.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/message_cache/duckdb_cache.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/models.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/obfuscation.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/regression/comparators.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/validation/catalog_validators.py +0 -0
airbyte_ops_mcp/cli/cloud.py
CHANGED
|
@@ -5,8 +5,7 @@ Commands:
|
|
|
5
5
|
airbyte-ops cloud connector get-version-info - Get connector version info
|
|
6
6
|
airbyte-ops cloud connector set-version-override - Set connector version override
|
|
7
7
|
airbyte-ops cloud connector clear-version-override - Clear connector version override
|
|
8
|
-
airbyte-ops cloud connector
|
|
9
|
-
airbyte-ops cloud connector regression-test - Run regression tests comparing connector versions
|
|
8
|
+
airbyte-ops cloud connector regression-test - Run regression tests (single-version or comparison)
|
|
10
9
|
airbyte-ops cloud connector fetch-connection-config - Fetch connection config to local file
|
|
11
10
|
"""
|
|
12
11
|
|
|
@@ -45,8 +44,12 @@ from airbyte_ops_mcp.constants import (
|
|
|
45
44
|
DEFAULT_CLOUD_SQL_PROXY_PORT,
|
|
46
45
|
ENV_GCP_PROD_DB_ACCESS_CREDENTIALS,
|
|
47
46
|
)
|
|
48
|
-
from airbyte_ops_mcp.
|
|
49
|
-
|
|
47
|
+
from airbyte_ops_mcp.mcp.cloud_connector_versions import (
|
|
48
|
+
get_cloud_connector_version,
|
|
49
|
+
set_cloud_connector_version_override,
|
|
50
|
+
)
|
|
51
|
+
from airbyte_ops_mcp.regression_tests.cdk_secrets import get_first_config_from_secrets
|
|
52
|
+
from airbyte_ops_mcp.regression_tests.ci_output import (
|
|
50
53
|
generate_regression_report,
|
|
51
54
|
get_report_summary,
|
|
52
55
|
write_github_output,
|
|
@@ -55,32 +58,28 @@ from airbyte_ops_mcp.live_tests.ci_output import (
|
|
|
55
58
|
write_json_output,
|
|
56
59
|
write_test_summary,
|
|
57
60
|
)
|
|
58
|
-
from airbyte_ops_mcp.
|
|
61
|
+
from airbyte_ops_mcp.regression_tests.connection_fetcher import (
|
|
59
62
|
fetch_connection_data,
|
|
60
63
|
save_connection_data_to_files,
|
|
61
64
|
)
|
|
62
|
-
from airbyte_ops_mcp.
|
|
65
|
+
from airbyte_ops_mcp.regression_tests.connection_secret_retriever import (
|
|
63
66
|
enrich_config_with_secrets,
|
|
64
67
|
should_use_secret_retriever,
|
|
65
68
|
)
|
|
66
|
-
from airbyte_ops_mcp.
|
|
69
|
+
from airbyte_ops_mcp.regression_tests.connector_runner import (
|
|
67
70
|
ConnectorRunner,
|
|
68
71
|
ensure_image_available,
|
|
69
72
|
)
|
|
70
|
-
from airbyte_ops_mcp.
|
|
73
|
+
from airbyte_ops_mcp.regression_tests.http_metrics import (
|
|
71
74
|
MitmproxyManager,
|
|
72
75
|
parse_http_dump,
|
|
73
76
|
)
|
|
74
|
-
from airbyte_ops_mcp.
|
|
77
|
+
from airbyte_ops_mcp.regression_tests.models import (
|
|
75
78
|
Command,
|
|
76
79
|
ConnectorUnderTest,
|
|
77
80
|
ExecutionInputs,
|
|
78
81
|
TargetOrControl,
|
|
79
82
|
)
|
|
80
|
-
from airbyte_ops_mcp.mcp.cloud_connector_versions import (
|
|
81
|
-
get_cloud_connector_version,
|
|
82
|
-
set_cloud_connector_version_override,
|
|
83
|
-
)
|
|
84
83
|
|
|
85
84
|
# Path to connectors directory within the airbyte repo
|
|
86
85
|
CONNECTORS_SUBDIR = Path("airbyte-integrations") / "connectors"
|
|
@@ -596,187 +595,6 @@ def _fetch_control_image_from_metadata(connector_name: str) -> str | None:
|
|
|
596
595
|
return f"{docker_repository}:{docker_image_tag}"
|
|
597
596
|
|
|
598
597
|
|
|
599
|
-
@connector_app.command(name="live-test")
|
|
600
|
-
def live_test(
|
|
601
|
-
connector_image: Annotated[
|
|
602
|
-
str | None,
|
|
603
|
-
Parameter(
|
|
604
|
-
help="Full connector image name with tag (e.g., airbyte/source-github:1.0.0). "
|
|
605
|
-
"Optional if connector_name or connection_id is provided."
|
|
606
|
-
),
|
|
607
|
-
] = None,
|
|
608
|
-
connector_name: Annotated[
|
|
609
|
-
str | None,
|
|
610
|
-
Parameter(
|
|
611
|
-
help="Connector name to build from source (e.g., 'source-pokeapi'). "
|
|
612
|
-
"If provided, builds the image locally with tag 'dev'."
|
|
613
|
-
),
|
|
614
|
-
] = None,
|
|
615
|
-
repo_root: Annotated[
|
|
616
|
-
str | None,
|
|
617
|
-
Parameter(
|
|
618
|
-
help="Path to the airbyte repo root. Required if connector_name is provided "
|
|
619
|
-
"and the repo cannot be auto-detected."
|
|
620
|
-
),
|
|
621
|
-
] = None,
|
|
622
|
-
command: Annotated[
|
|
623
|
-
Literal["spec", "check", "discover", "read"],
|
|
624
|
-
Parameter(help="The Airbyte command to run."),
|
|
625
|
-
] = "check",
|
|
626
|
-
connection_id: Annotated[
|
|
627
|
-
str | None,
|
|
628
|
-
Parameter(
|
|
629
|
-
help="Airbyte Cloud connection ID to fetch config/catalog from. "
|
|
630
|
-
"Mutually exclusive with config-path/catalog-path. "
|
|
631
|
-
"If provided, connector_image can be auto-detected."
|
|
632
|
-
),
|
|
633
|
-
] = None,
|
|
634
|
-
config_path: Annotated[
|
|
635
|
-
str | None,
|
|
636
|
-
Parameter(help="Path to the connector config JSON file."),
|
|
637
|
-
] = None,
|
|
638
|
-
catalog_path: Annotated[
|
|
639
|
-
str | None,
|
|
640
|
-
Parameter(help="Path to the configured catalog JSON file (required for read)."),
|
|
641
|
-
] = None,
|
|
642
|
-
state_path: Annotated[
|
|
643
|
-
str | None,
|
|
644
|
-
Parameter(help="Path to the state JSON file (optional for read)."),
|
|
645
|
-
] = None,
|
|
646
|
-
output_dir: Annotated[
|
|
647
|
-
str,
|
|
648
|
-
Parameter(help="Directory to store test artifacts."),
|
|
649
|
-
] = "/tmp/live_test_artifacts",
|
|
650
|
-
) -> None:
|
|
651
|
-
"""Run live validation tests on a connector.
|
|
652
|
-
|
|
653
|
-
This command runs the specified Airbyte protocol command against a connector
|
|
654
|
-
and validates the output. Results are written to the output directory and
|
|
655
|
-
to GitHub Actions outputs if running in CI.
|
|
656
|
-
|
|
657
|
-
You can provide the connector image in three ways:
|
|
658
|
-
1. --connector-image: Use a pre-built image from Docker registry
|
|
659
|
-
2. --connector-name: Build the image locally from source code
|
|
660
|
-
3. --connection-id: Auto-detect from an Airbyte Cloud connection
|
|
661
|
-
|
|
662
|
-
You can provide config/catalog either via file paths OR via a connection_id
|
|
663
|
-
that fetches them from Airbyte Cloud.
|
|
664
|
-
"""
|
|
665
|
-
output_path = Path(output_dir)
|
|
666
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
667
|
-
|
|
668
|
-
cmd = Command(command)
|
|
669
|
-
|
|
670
|
-
config_file: Path | None = None
|
|
671
|
-
catalog_file: Path | None = None
|
|
672
|
-
state_file = Path(state_path) if state_path else None
|
|
673
|
-
resolved_connector_image: str | None = connector_image
|
|
674
|
-
|
|
675
|
-
# If connector_name is provided, build the image from source
|
|
676
|
-
if connector_name:
|
|
677
|
-
if connector_image:
|
|
678
|
-
write_github_output("success", False)
|
|
679
|
-
write_github_output(
|
|
680
|
-
"error", "Cannot specify both connector_image and connector_name"
|
|
681
|
-
)
|
|
682
|
-
exit_with_error("Cannot specify both connector_image and connector_name")
|
|
683
|
-
|
|
684
|
-
repo_root_path = Path(repo_root) if repo_root else None
|
|
685
|
-
built_image = _build_connector_image_from_source(
|
|
686
|
-
connector_name=connector_name,
|
|
687
|
-
repo_root=repo_root_path,
|
|
688
|
-
tag="dev",
|
|
689
|
-
)
|
|
690
|
-
if not built_image:
|
|
691
|
-
write_github_output("success", False)
|
|
692
|
-
write_github_output("error", f"Failed to build image for {connector_name}")
|
|
693
|
-
exit_with_error(f"Failed to build image for {connector_name}")
|
|
694
|
-
resolved_connector_image = built_image
|
|
695
|
-
|
|
696
|
-
if connection_id:
|
|
697
|
-
if config_path or catalog_path:
|
|
698
|
-
write_github_output("success", False)
|
|
699
|
-
write_github_output(
|
|
700
|
-
"error", "Cannot specify both connection_id and file paths"
|
|
701
|
-
)
|
|
702
|
-
exit_with_error(
|
|
703
|
-
"Cannot specify both connection_id and config_path/catalog_path"
|
|
704
|
-
)
|
|
705
|
-
|
|
706
|
-
print_success(f"Fetching config/catalog from connection: {connection_id}")
|
|
707
|
-
connection_data = fetch_connection_data(connection_id)
|
|
708
|
-
config_file, catalog_file = save_connection_data_to_files(
|
|
709
|
-
connection_data, output_path / "connection_data"
|
|
710
|
-
)
|
|
711
|
-
print_success(
|
|
712
|
-
f"Fetched config for source: {connection_data.source_name} "
|
|
713
|
-
f"with {len(connection_data.stream_names)} streams"
|
|
714
|
-
)
|
|
715
|
-
|
|
716
|
-
if not resolved_connector_image and connection_data.connector_image:
|
|
717
|
-
resolved_connector_image = connection_data.connector_image
|
|
718
|
-
print_success(f"Auto-detected connector image: {resolved_connector_image}")
|
|
719
|
-
else:
|
|
720
|
-
config_file = Path(config_path) if config_path else None
|
|
721
|
-
catalog_file = Path(catalog_path) if catalog_path else None
|
|
722
|
-
|
|
723
|
-
if not resolved_connector_image:
|
|
724
|
-
write_github_output("success", False)
|
|
725
|
-
write_github_output("error", "Missing connector image")
|
|
726
|
-
exit_with_error(
|
|
727
|
-
"You must provide one of the following: a connector_image, a connector_name, "
|
|
728
|
-
"or a connection_id for a connection that has an associated connector image. "
|
|
729
|
-
"If using connection_id, ensure the connection has a connector image configured."
|
|
730
|
-
)
|
|
731
|
-
|
|
732
|
-
# If connector_name was provided, we just built the image locally and it is already
|
|
733
|
-
# available in Docker, so we skip the image availability check/pull. Only try to pull
|
|
734
|
-
# if we didn't just build it (i.e., using a pre-built image from registry).
|
|
735
|
-
if not connector_name and not ensure_image_available(resolved_connector_image):
|
|
736
|
-
write_github_output("success", False)
|
|
737
|
-
write_github_output(
|
|
738
|
-
"error", f"Failed to pull image: {resolved_connector_image}"
|
|
739
|
-
)
|
|
740
|
-
exit_with_error(f"Failed to pull connector image: {resolved_connector_image}")
|
|
741
|
-
|
|
742
|
-
result = _run_connector_command(
|
|
743
|
-
connector_image=resolved_connector_image,
|
|
744
|
-
command=cmd,
|
|
745
|
-
output_dir=output_path,
|
|
746
|
-
target_or_control=TargetOrControl.TARGET,
|
|
747
|
-
config_path=config_file,
|
|
748
|
-
catalog_path=catalog_file,
|
|
749
|
-
state_path=state_file,
|
|
750
|
-
)
|
|
751
|
-
|
|
752
|
-
print_json(result)
|
|
753
|
-
|
|
754
|
-
write_github_outputs(
|
|
755
|
-
{
|
|
756
|
-
"success": result["success"],
|
|
757
|
-
"connector": resolved_connector_image,
|
|
758
|
-
"command": command,
|
|
759
|
-
"exit_code": result["exit_code"],
|
|
760
|
-
}
|
|
761
|
-
)
|
|
762
|
-
|
|
763
|
-
write_test_summary(
|
|
764
|
-
connector_image=resolved_connector_image,
|
|
765
|
-
test_type="live-test",
|
|
766
|
-
success=result["success"],
|
|
767
|
-
results={
|
|
768
|
-
"command": command,
|
|
769
|
-
"exit_code": result["exit_code"],
|
|
770
|
-
"output_dir": output_dir,
|
|
771
|
-
},
|
|
772
|
-
)
|
|
773
|
-
|
|
774
|
-
if result["success"]:
|
|
775
|
-
print_success(f"Live test passed for {resolved_connector_image}")
|
|
776
|
-
else:
|
|
777
|
-
exit_with_error(f"Live test failed for {resolved_connector_image}")
|
|
778
|
-
|
|
779
|
-
|
|
780
598
|
def _run_with_optional_http_metrics(
|
|
781
599
|
connector_image: str,
|
|
782
600
|
command: Command,
|
|
@@ -855,25 +673,34 @@ def _run_with_optional_http_metrics(
|
|
|
855
673
|
|
|
856
674
|
@connector_app.command(name="regression-test")
|
|
857
675
|
def regression_test(
|
|
858
|
-
|
|
676
|
+
skip_compare: Annotated[
|
|
677
|
+
bool,
|
|
678
|
+
Parameter(
|
|
679
|
+
help="If True, skip comparison and run single-version tests only. "
|
|
680
|
+
"If False (default), run comparison tests (target vs control)."
|
|
681
|
+
),
|
|
682
|
+
] = False,
|
|
683
|
+
test_image: Annotated[
|
|
859
684
|
str | None,
|
|
860
685
|
Parameter(
|
|
861
|
-
help="
|
|
862
|
-
"
|
|
686
|
+
help="Test connector image with tag (e.g., airbyte/source-github:1.0.0). "
|
|
687
|
+
"This is the image under test - in comparison mode, it's compared against control_image."
|
|
863
688
|
),
|
|
864
689
|
] = None,
|
|
865
690
|
control_image: Annotated[
|
|
866
691
|
str | None,
|
|
867
692
|
Parameter(
|
|
868
693
|
help="Control connector image (baseline version) with tag (e.g., airbyte/source-github:1.0.0). "
|
|
869
|
-
"
|
|
694
|
+
"Ignored if `skip_compare=True`."
|
|
870
695
|
),
|
|
871
696
|
] = None,
|
|
872
697
|
connector_name: Annotated[
|
|
873
698
|
str | None,
|
|
874
699
|
Parameter(
|
|
875
|
-
help="Connector name to build
|
|
876
|
-
"If provided, builds the
|
|
700
|
+
help="Connector name to build image from source (e.g., 'source-pokeapi'). "
|
|
701
|
+
"If provided, builds the image locally with tag 'dev'. "
|
|
702
|
+
"For comparison tests (default), this builds the target image. "
|
|
703
|
+
"For single-version tests (skip_compare=True), this builds the test image."
|
|
877
704
|
),
|
|
878
705
|
] = None,
|
|
879
706
|
repo_root: Annotated[
|
|
@@ -892,7 +719,7 @@ def regression_test(
|
|
|
892
719
|
Parameter(
|
|
893
720
|
help="Airbyte Cloud connection ID to fetch config/catalog from. "
|
|
894
721
|
"Mutually exclusive with config-path/catalog-path. "
|
|
895
|
-
"If provided, control_image can be auto-detected."
|
|
722
|
+
"If provided, test_image/control_image can be auto-detected."
|
|
896
723
|
),
|
|
897
724
|
] = None,
|
|
898
725
|
config_path: Annotated[
|
|
@@ -915,26 +742,30 @@ def regression_test(
|
|
|
915
742
|
bool,
|
|
916
743
|
Parameter(
|
|
917
744
|
help="Capture HTTP traffic metrics via mitmproxy (experimental). "
|
|
918
|
-
"Requires mitmdump to be installed."
|
|
745
|
+
"Requires mitmdump to be installed. Only used in comparison mode."
|
|
919
746
|
),
|
|
920
747
|
] = False,
|
|
921
748
|
) -> None:
|
|
922
|
-
"""Run regression tests
|
|
749
|
+
"""Run regression tests on connectors.
|
|
750
|
+
|
|
751
|
+
This command supports two modes:
|
|
923
752
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
753
|
+
Comparison mode (skip_compare=False, default):
|
|
754
|
+
Runs the specified Airbyte protocol command against both the target (new)
|
|
755
|
+
and control (baseline) connector versions, then compares the results.
|
|
756
|
+
This helps identify regressions between versions.
|
|
757
|
+
|
|
758
|
+
Single-version mode (skip_compare=True):
|
|
759
|
+
Runs the specified Airbyte protocol command against a single connector
|
|
760
|
+
and validates the output. No comparison is performed.
|
|
927
761
|
|
|
928
762
|
Results are written to the output directory and to GitHub Actions outputs
|
|
929
763
|
if running in CI.
|
|
930
764
|
|
|
931
|
-
You can provide the
|
|
932
|
-
1. --
|
|
933
|
-
2. --connector-name: Build the
|
|
934
|
-
|
|
935
|
-
You can provide the control image in two ways:
|
|
936
|
-
1. --control-image: Use a pre-built image from Docker registry
|
|
937
|
-
2. --connection-id: Auto-detect from an Airbyte Cloud connection
|
|
765
|
+
You can provide the test image in three ways:
|
|
766
|
+
1. --test-image: Use a pre-built image from Docker registry
|
|
767
|
+
2. --connector-name: Build the image locally from source code
|
|
768
|
+
3. --connection-id: Auto-detect from an Airbyte Cloud connection
|
|
938
769
|
|
|
939
770
|
You can provide config/catalog either via file paths OR via a connection_id
|
|
940
771
|
that fetches them from Airbyte Cloud.
|
|
@@ -947,17 +778,31 @@ def regression_test(
|
|
|
947
778
|
config_file: Path | None = None
|
|
948
779
|
catalog_file: Path | None = None
|
|
949
780
|
state_file = Path(state_path) if state_path else None
|
|
950
|
-
|
|
781
|
+
|
|
782
|
+
# Resolve the test image (used in both single-version and comparison modes)
|
|
783
|
+
resolved_test_image: str | None = test_image
|
|
951
784
|
resolved_control_image: str | None = control_image
|
|
952
785
|
|
|
953
|
-
#
|
|
786
|
+
# Validate conflicting parameters
|
|
787
|
+
# Single-version mode: reject comparison-specific parameters
|
|
788
|
+
if skip_compare and control_image:
|
|
789
|
+
write_github_output("success", False)
|
|
790
|
+
write_github_output(
|
|
791
|
+
"error", "Cannot specify control_image with skip_compare=True"
|
|
792
|
+
)
|
|
793
|
+
exit_with_error(
|
|
794
|
+
"Cannot specify --control-image with --skip-compare. "
|
|
795
|
+
"Control image is only used in comparison mode."
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# If connector_name is provided, build the image from source
|
|
954
799
|
if connector_name:
|
|
955
|
-
if
|
|
800
|
+
if resolved_test_image:
|
|
956
801
|
write_github_output("success", False)
|
|
957
802
|
write_github_output(
|
|
958
|
-
"error", "Cannot specify both
|
|
803
|
+
"error", "Cannot specify both test_image and connector_name"
|
|
959
804
|
)
|
|
960
|
-
exit_with_error("Cannot specify both
|
|
805
|
+
exit_with_error("Cannot specify both --test-image and --connector-name")
|
|
961
806
|
|
|
962
807
|
repo_root_path = Path(repo_root) if repo_root else None
|
|
963
808
|
built_image = _build_connector_image_from_source(
|
|
@@ -969,7 +814,7 @@ def regression_test(
|
|
|
969
814
|
write_github_output("success", False)
|
|
970
815
|
write_github_output("error", f"Failed to build image for {connector_name}")
|
|
971
816
|
exit_with_error(f"Failed to build image for {connector_name}")
|
|
972
|
-
|
|
817
|
+
resolved_test_image = built_image
|
|
973
818
|
|
|
974
819
|
if connection_id:
|
|
975
820
|
if config_path or catalog_path:
|
|
@@ -997,10 +842,11 @@ def regression_test(
|
|
|
997
842
|
print_success("Successfully retrieved unmasked secrets from database")
|
|
998
843
|
except Exception as e:
|
|
999
844
|
print_error(f"Failed to retrieve unmasked secrets: {e}")
|
|
1000
|
-
|
|
1001
|
-
"
|
|
1002
|
-
"
|
|
1003
|
-
f"
|
|
845
|
+
exit_with_error(
|
|
846
|
+
f"Failed to retrieve unmasked secrets: {e}\n"
|
|
847
|
+
f"Unset USE_CONNECTION_SECRET_RETRIEVER or verify that the "
|
|
848
|
+
f"{ENV_GCP_PROD_DB_ACCESS_CREDENTIALS} environment variable is set "
|
|
849
|
+
f"with valid database credentials and that the Cloud SQL Proxy is running."
|
|
1004
850
|
)
|
|
1005
851
|
|
|
1006
852
|
config_file, catalog_file = save_connection_data_to_files(
|
|
@@ -1011,8 +857,16 @@ def regression_test(
|
|
|
1011
857
|
f"with {len(connection_data.stream_names)} streams"
|
|
1012
858
|
)
|
|
1013
859
|
|
|
1014
|
-
# Auto-detect
|
|
1015
|
-
if not
|
|
860
|
+
# Auto-detect test/control image from connection if not provided
|
|
861
|
+
if not resolved_test_image and connection_data.connector_image:
|
|
862
|
+
resolved_test_image = connection_data.connector_image
|
|
863
|
+
print_success(f"Auto-detected test image: {resolved_test_image}")
|
|
864
|
+
|
|
865
|
+
if (
|
|
866
|
+
not skip_compare
|
|
867
|
+
and not resolved_control_image
|
|
868
|
+
and connection_data.connector_image
|
|
869
|
+
):
|
|
1016
870
|
resolved_control_image = connection_data.connector_image
|
|
1017
871
|
print_success(f"Auto-detected control image: {resolved_control_image}")
|
|
1018
872
|
elif config_path:
|
|
@@ -1049,24 +903,24 @@ def regression_test(
|
|
|
1049
903
|
config_file = None
|
|
1050
904
|
catalog_file = Path(catalog_path) if catalog_path else None
|
|
1051
905
|
|
|
1052
|
-
# Auto-detect control_image from metadata.yaml if connector_name is provided
|
|
1053
|
-
if not resolved_control_image and connector_name:
|
|
906
|
+
# Auto-detect control_image from metadata.yaml if connector_name is provided (comparison mode only)
|
|
907
|
+
if not skip_compare and not resolved_control_image and connector_name:
|
|
1054
908
|
resolved_control_image = _fetch_control_image_from_metadata(connector_name)
|
|
1055
909
|
if resolved_control_image:
|
|
1056
910
|
print_success(
|
|
1057
911
|
f"Auto-detected control image from metadata.yaml: {resolved_control_image}"
|
|
1058
912
|
)
|
|
1059
913
|
|
|
1060
|
-
# Validate that we have
|
|
1061
|
-
if not
|
|
914
|
+
# Validate that we have the required images
|
|
915
|
+
if not resolved_test_image:
|
|
1062
916
|
write_github_output("success", False)
|
|
1063
|
-
write_github_output("error", "No
|
|
917
|
+
write_github_output("error", "No test image specified")
|
|
1064
918
|
exit_with_error(
|
|
1065
|
-
"You must provide one of the following: a
|
|
1066
|
-
"to build the
|
|
919
|
+
"You must provide one of the following: a test_image, a connector_name "
|
|
920
|
+
"to build the image from source, or a connection_id to auto-detect the image."
|
|
1067
921
|
)
|
|
1068
922
|
|
|
1069
|
-
if not resolved_control_image:
|
|
923
|
+
if not skip_compare and not resolved_control_image:
|
|
1070
924
|
write_github_output("success", False)
|
|
1071
925
|
write_github_output("error", "No control image specified")
|
|
1072
926
|
exit_with_error(
|
|
@@ -1076,97 +930,144 @@ def regression_test(
|
|
|
1076
930
|
)
|
|
1077
931
|
|
|
1078
932
|
# Pull images if they weren't just built locally
|
|
1079
|
-
# If connector_name was provided, we just built the
|
|
1080
|
-
if not connector_name and not ensure_image_available(
|
|
933
|
+
# If connector_name was provided, we just built the test image locally
|
|
934
|
+
if not connector_name and not ensure_image_available(resolved_test_image):
|
|
1081
935
|
write_github_output("success", False)
|
|
1082
|
-
write_github_output("error", f"Failed to pull image: {
|
|
1083
|
-
exit_with_error(
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
936
|
+
write_github_output("error", f"Failed to pull image: {resolved_test_image}")
|
|
937
|
+
exit_with_error(f"Failed to pull test image: {resolved_test_image}")
|
|
938
|
+
|
|
939
|
+
if (
|
|
940
|
+
not skip_compare
|
|
941
|
+
and resolved_control_image
|
|
942
|
+
and not ensure_image_available(resolved_control_image)
|
|
943
|
+
):
|
|
1088
944
|
write_github_output("success", False)
|
|
1089
945
|
write_github_output("error", f"Failed to pull image: {resolved_control_image}")
|
|
1090
946
|
exit_with_error(
|
|
1091
947
|
f"Failed to pull control connector image: {resolved_control_image}"
|
|
1092
948
|
)
|
|
1093
949
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
)
|
|
950
|
+
# Execute the appropriate mode
|
|
951
|
+
if skip_compare:
|
|
952
|
+
# Single-version mode: run only the connector image
|
|
953
|
+
result = _run_connector_command(
|
|
954
|
+
connector_image=resolved_test_image,
|
|
955
|
+
command=cmd,
|
|
956
|
+
output_dir=output_path,
|
|
957
|
+
target_or_control=TargetOrControl.TARGET,
|
|
958
|
+
config_path=config_file,
|
|
959
|
+
catalog_path=catalog_file,
|
|
960
|
+
state_path=state_file,
|
|
961
|
+
)
|
|
1107
962
|
|
|
1108
|
-
|
|
1109
|
-
connector_image=resolved_control_image,
|
|
1110
|
-
command=cmd,
|
|
1111
|
-
output_dir=control_output,
|
|
1112
|
-
target_or_control=TargetOrControl.CONTROL,
|
|
1113
|
-
enable_http_metrics=enable_http_metrics,
|
|
1114
|
-
config_path=config_file,
|
|
1115
|
-
catalog_path=catalog_file,
|
|
1116
|
-
state_path=state_file,
|
|
1117
|
-
)
|
|
963
|
+
print_json(result)
|
|
1118
964
|
|
|
1119
|
-
|
|
1120
|
-
|
|
965
|
+
write_github_outputs(
|
|
966
|
+
{
|
|
967
|
+
"success": result["success"],
|
|
968
|
+
"connector": resolved_test_image,
|
|
969
|
+
"command": command,
|
|
970
|
+
"exit_code": result["exit_code"],
|
|
971
|
+
}
|
|
972
|
+
)
|
|
1121
973
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
974
|
+
write_test_summary(
|
|
975
|
+
connector_image=resolved_test_image,
|
|
976
|
+
test_type="regression-test",
|
|
977
|
+
success=result["success"],
|
|
978
|
+
results={
|
|
979
|
+
"command": command,
|
|
980
|
+
"exit_code": result["exit_code"],
|
|
981
|
+
"output_dir": output_dir,
|
|
982
|
+
},
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
if result["success"]:
|
|
986
|
+
print_success(
|
|
987
|
+
f"Single-version regression test passed for {resolved_test_image}"
|
|
988
|
+
)
|
|
989
|
+
else:
|
|
990
|
+
exit_with_error(
|
|
991
|
+
f"Single-version regression test failed for {resolved_test_image}"
|
|
992
|
+
)
|
|
993
|
+
else:
|
|
994
|
+
# Comparison mode: run both target and control images
|
|
995
|
+
target_output = output_path / "target"
|
|
996
|
+
control_output = output_path / "control"
|
|
997
|
+
|
|
998
|
+
target_result = _run_with_optional_http_metrics(
|
|
999
|
+
connector_image=resolved_test_image,
|
|
1000
|
+
command=cmd,
|
|
1001
|
+
output_dir=target_output,
|
|
1002
|
+
target_or_control=TargetOrControl.TARGET,
|
|
1003
|
+
enable_http_metrics=enable_http_metrics,
|
|
1004
|
+
config_path=config_file,
|
|
1005
|
+
catalog_path=catalog_file,
|
|
1006
|
+
state_path=state_file,
|
|
1007
|
+
)
|
|
1128
1008
|
|
|
1129
|
-
|
|
1009
|
+
control_result = _run_with_optional_http_metrics(
|
|
1010
|
+
connector_image=resolved_control_image, # type: ignore[arg-type]
|
|
1011
|
+
command=cmd,
|
|
1012
|
+
output_dir=control_output,
|
|
1013
|
+
target_or_control=TargetOrControl.CONTROL,
|
|
1014
|
+
enable_http_metrics=enable_http_metrics,
|
|
1015
|
+
config_path=config_file,
|
|
1016
|
+
catalog_path=catalog_file,
|
|
1017
|
+
state_path=state_file,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
both_succeeded = target_result["success"] and control_result["success"]
|
|
1021
|
+
regression_detected = target_result["success"] != control_result["success"]
|
|
1130
1022
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
"
|
|
1134
|
-
"
|
|
1135
|
-
"control_image": resolved_control_image,
|
|
1136
|
-
"command": command,
|
|
1137
|
-
"target_exit_code": target_result["exit_code"],
|
|
1138
|
-
"control_exit_code": control_result["exit_code"],
|
|
1023
|
+
combined_result = {
|
|
1024
|
+
"target": target_result,
|
|
1025
|
+
"control": control_result,
|
|
1026
|
+
"both_succeeded": both_succeeded,
|
|
1139
1027
|
"regression_detected": regression_detected,
|
|
1140
1028
|
}
|
|
1141
|
-
)
|
|
1142
1029
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1030
|
+
print_json(combined_result)
|
|
1031
|
+
|
|
1032
|
+
write_github_outputs(
|
|
1033
|
+
{
|
|
1034
|
+
"success": both_succeeded and not regression_detected,
|
|
1035
|
+
"target_image": resolved_test_image,
|
|
1036
|
+
"control_image": resolved_control_image,
|
|
1037
|
+
"command": command,
|
|
1038
|
+
"target_exit_code": target_result["exit_code"],
|
|
1039
|
+
"control_exit_code": control_result["exit_code"],
|
|
1040
|
+
"regression_detected": regression_detected,
|
|
1041
|
+
}
|
|
1042
|
+
)
|
|
1154
1043
|
|
|
1155
|
-
|
|
1156
|
-
write_github_summary(summary)
|
|
1044
|
+
write_json_output("regression_report", combined_result)
|
|
1157
1045
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
)
|
|
1166
|
-
else:
|
|
1167
|
-
exit_with_error(
|
|
1168
|
-
f"Both versions failed for {resolved_target_image} vs {resolved_control_image}"
|
|
1046
|
+
report_path = generate_regression_report(
|
|
1047
|
+
target_image=resolved_test_image,
|
|
1048
|
+
control_image=resolved_control_image, # type: ignore[arg-type]
|
|
1049
|
+
command=command,
|
|
1050
|
+
target_result=target_result,
|
|
1051
|
+
control_result=control_result,
|
|
1052
|
+
output_dir=output_path,
|
|
1169
1053
|
)
|
|
1054
|
+
print_success(f"Generated regression report: {report_path}")
|
|
1055
|
+
|
|
1056
|
+
summary = get_report_summary(report_path)
|
|
1057
|
+
write_github_summary(summary)
|
|
1058
|
+
|
|
1059
|
+
if regression_detected:
|
|
1060
|
+
exit_with_error(
|
|
1061
|
+
f"Regression detected between {resolved_test_image} and {resolved_control_image}"
|
|
1062
|
+
)
|
|
1063
|
+
elif both_succeeded:
|
|
1064
|
+
print_success(
|
|
1065
|
+
f"Regression test passed for {resolved_test_image} vs {resolved_control_image}"
|
|
1066
|
+
)
|
|
1067
|
+
else:
|
|
1068
|
+
exit_with_error(
|
|
1069
|
+
f"Both versions failed for {resolved_test_image} vs {resolved_control_image}"
|
|
1070
|
+
)
|
|
1170
1071
|
|
|
1171
1072
|
|
|
1172
1073
|
@connector_app.command(name="fetch-connection-config")
|