diff --git a/airbyte_cdk/cli/airbyte_cdk/_connector.py b/airbyte_cdk/cli/airbyte_cdk/_connector.py index 6889b4fb8..378a052d5 100644 --- a/airbyte_cdk/cli/airbyte_cdk/_connector.py +++ b/airbyte_cdk/cli/airbyte_cdk/_connector.py @@ -93,7 +93,7 @@ @click.group( name="connector", - help=__doc__.replace("\n", "\n\n"), # Render docstring as help text (markdown) + help=__doc__.replace("\n", "\n\n"), # Render docstring as help text (markdown) # type: ignore ) def connector_cli_group() -> None: """Connector related commands.""" @@ -113,10 +113,21 @@ def connector_cli_group() -> None: default=False, help="Only collect tests, do not run them.", ) +@click.option( + "--use-docker-image", + # is_flag=True, + default=False, + type=str, + help="Run tests via docker.", + callback=lambda ctx, param, value: ( + "true" if value is True else (value if isinstance(value, str) else False) + ), +) def test( connector: str | Path | None = None, *, collect_only: bool = False, + use_docker_image: str | bool = False, ) -> None: """Run connector tests. @@ -127,12 +138,20 @@ def test( If no connector name or directory is provided, we will look within the current working directory. If the current working directory is not a connector directory (e.g. starting - with 'source-') and no connector name or path is provided, the process will fail. + with 'source-' or 'destination-') and no connector name or path is provided, the process + will fail. """ + if isinstance( + use_docker_image, + str, + ) and use_docker_image.lower() in {"true", "false"}: + use_docker_image = bool(use_docker_image) + if pytest is None: raise ImportError( "pytest is not installed. Please install pytest to run the connector tests." ) + click.echo("Connector test command executed.") connector_name, connector_directory = resolve_connector_name_and_directory(connector) @@ -157,9 +176,19 @@ def test( test_file_path.parent.mkdir(parents=True, exist_ok=True) test_file_path.write_text(file_text) + pytest_args.append("-p airbyte_cdk.test.standard_tests.pytest_hooks") + if collect_only: pytest_args.append("--collect-only") + if use_docker_image: + click.echo(f"Using Docker-based test runner...") + pytest_args.append("--use-docker-image") + if isinstance(use_docker_image, str): + pytest_args.append( + use_docker_image, + ) + pytest_args.append(str(test_file_path)) click.echo(f"Running tests from connector directory: {connector_directory}...") click.echo(f"Test file: {test_file_path}") diff --git a/airbyte_cdk/cli/airbyte_cdk/_image.py b/airbyte_cdk/cli/airbyte_cdk/_image.py index cd0ca5cc9..9e49b6d2f 100644 --- a/airbyte_cdk/cli/airbyte_cdk/_image.py +++ b/airbyte_cdk/cli/airbyte_cdk/_image.py @@ -50,6 +50,10 @@ def build( ) -> None: """Build a connector Docker image. + This command builds a Docker image for a connector, using either + the connector's Dockerfile or a base image specified in the metadata. + The image is built for both AMD64 and ARM64 architectures. + [CONNECTOR] can be a connector name (e.g. 'source-pokeapi'), a path to a connector directory, or omitted to use the current working directory. If a string containing '/' is provided, it is treated as a path. Otherwise, it is treated as a connector name. """ diff --git a/airbyte_cdk/test/standard_tests/_job_runner.py b/airbyte_cdk/test/standard_tests/_job_runner.py index ad8316d78..476d93fdd 100644 --- a/airbyte_cdk/test/standard_tests/_job_runner.py +++ b/airbyte_cdk/test/standard_tests/_job_runner.py @@ -4,25 +4,28 @@ import logging import tempfile import uuid +from abc import abstractmethod +from collections.abc import Callable from dataclasses import asdict from pathlib import Path -from typing import Any, Callable, Literal +from typing import Any, Literal import orjson from typing_extensions import Protocol, runtime_checkable from airbyte_cdk.models import ( ConfiguredAirbyteCatalog, + ConnectorSpecification, Status, ) -from airbyte_cdk.test import entrypoint_wrapper +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput, _run_command from airbyte_cdk.test.standard_tests.models import ( ConnectorTestScenario, ) def _errors_to_str( - entrypoint_output: entrypoint_wrapper.EntrypointOutput, + entrypoint_output: EntrypointOutput, ) -> str: """Convert errors from entrypoint output to a string.""" if not entrypoint_output.errors: @@ -50,8 +53,10 @@ class IConnector(Protocol): directly on the connector (which doesn't yet exist). """ - def spec(self, logger: logging.Logger) -> Any: + @abstractmethod + def spec(self, logger: logging.Logger) -> ConnectorSpecification: """Connectors should have a `spec` method.""" + ... def run_test_job( @@ -60,7 +65,7 @@ def run_test_job( *, test_scenario: ConnectorTestScenario | None = None, catalog: ConfiguredAirbyteCatalog | dict[str, Any] | None = None, -) -> entrypoint_wrapper.EntrypointOutput: +) -> EntrypointOutput: """Run a test scenario from provided CLI args and return the result.""" # Use default (empty) scenario if not provided: test_scenario = test_scenario or ConnectorTestScenario() @@ -115,7 +120,7 @@ def run_test_job( # This is a bit of a hack because the source needs the catalog early. # Because it *also* can fail, we have to redundantly wrap it in a try/except block. - result: entrypoint_wrapper.EntrypointOutput = entrypoint_wrapper._run_command( # noqa: SLF001 # Non-public API + result: EntrypointOutput = _run_command( # noqa: SLF001 # Non-public API source=connector_obj, # type: ignore [arg-type] args=args, expecting_exception=test_scenario.expect_exception, diff --git a/airbyte_cdk/test/standard_tests/connector_base.py b/airbyte_cdk/test/standard_tests/connector_base.py index 394028247..2897c20de 100644 --- a/airbyte_cdk/test/standard_tests/connector_base.py +++ b/airbyte_cdk/test/standard_tests/connector_base.py @@ -8,10 +8,10 @@ import inspect import os import sys -from collections.abc import Callable from pathlib import Path -from typing import cast +from typing import Literal, cast +import pytest import yaml from boltons.typeutils import classproperty @@ -21,6 +21,7 @@ ) from airbyte_cdk.test import entrypoint_wrapper from airbyte_cdk.test.standard_tests._job_runner import IConnector, run_test_job +from airbyte_cdk.test.standard_tests.docker_connectors import DockerConnector from airbyte_cdk.test.standard_tests.models import ( ConnectorTestScenario, ) @@ -30,19 +31,43 @@ ) +@pytest.fixture +def use_docker_image(request: pytest.FixtureRequest) -> str | bool: + """Fixture to determine if a Docker image should be used for the test.""" + return request.config.getoption("use_docker_image") + + class ConnectorTestSuiteBase(abc.ABC): """Base class for connector test suites.""" - connector: type[IConnector] | Callable[[], IConnector] | None # type: ignore [reportRedeclaration] + connector_class: type[IConnector] | None = None """The connector class or a factory function that returns an scenario of IConnector.""" - @classproperty # type: ignore [no-redef] - def connector(cls) -> type[IConnector] | Callable[[], IConnector] | None: + @classmethod + def get_test_class_dir(cls) -> Path: + """Get the file path that contains the class.""" + module = sys.modules[cls.__module__] + # Get the directory containing the test file + return Path(inspect.getfile(module)).parent + + @classmethod + def create_connector( + cls, + scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, + ) -> IConnector: + """Instantiate the connector class.""" """Get the connector class for the test suite. This assumes a python connector and should be overridden by subclasses to provide the specific connector class to be tested. """ + if use_docker_image: + return cls.create_docker_connector( + docker_image=use_docker_image, + ) + connector_root = cls.get_connector_root_dir() connector_name = connector_root.absolute().name @@ -65,7 +90,10 @@ def connector(cls) -> type[IConnector] | Callable[[], IConnector] | None: # Dynamically get the class from the module try: - return cast(type[IConnector], getattr(module, expected_class_name)) + return cast( + type[IConnector], + getattr(module, expected_class_name), + )() except AttributeError as e: # We did not find it based on our expectations, so let's check if we can find it # with a case-insensitive match. @@ -77,43 +105,84 @@ def connector(cls) -> type[IConnector] | Callable[[], IConnector] | None: raise ImportError( f"Module '{expected_module_name}' does not have a class named '{expected_class_name}'." ) from e - return cast(type[IConnector], getattr(module, matching_class_name)) - - @classmethod - def get_test_class_dir(cls) -> Path: - """Get the file path that contains the class.""" - module = sys.modules[cls.__module__] - # Get the directory containing the test file - return Path(inspect.getfile(module)).parent + return cast( + type[IConnector], + getattr(module, matching_class_name), + )() @classmethod - def create_connector( + def create_docker_connector( cls, - scenario: ConnectorTestScenario | None, + docker_image: str | Literal[True], ) -> IConnector: - """Instantiate the connector class.""" - connector = cls.connector # type: ignore - if connector: - if callable(connector) or isinstance(connector, type): - # If the connector is a class or factory function, instantiate it: - return cast(IConnector, connector()) # type: ignore [redundant-cast] - - # Otherwise, we can't instantiate the connector. Fail with a clear error message. - raise NotImplementedError( - "No connector class or connector factory function provided. " - "Please provide a class or factory function in `cls.connector`, or " - "override `cls.create_connector()` to define a custom initialization process." + """Create a connector instance using Docker.""" + if not docker_image: + raise ValueError("Docker image is required to create a Docker connector.") + + # Create the connector object by building the connector + if docker_image is True: + return DockerConnector.from_connector_directory( + connector_directory=cls.get_connector_root_dir(), + ) + + if not isinstance(docker_image, str): + raise ValueError( + "Expected `docker_image` to be 'True' or of type `str`. " + f"Type found: {type(docker_image).__name__}" + ) + + # Create the connector object using the provided Docker image + return DockerConnector( + connector_name=cls.get_connector_root_dir().name, + docker_image=docker_image, ) - # Test Definitions + # Test Definitions (Generic for all connectors) + + def test_spec( + self, + *, + use_docker_image: str | bool, + ) -> None: + """Standard test for `spec`. + + This test does not require a `scenario` input, since `spec` + does not require any inputs. + + We assume `spec` should always succeed and it should always generate + a valid `SPEC` message. + + Note: the parsing of messages by type also implicitly validates that + the generated `SPEC` message is valid JSON. + """ + scenario = ConnectorTestScenario() # Empty scenario, empty config + result = run_test_job( + verb="spec", + test_scenario=scenario, + connector=self.create_connector( + scenario=scenario, + use_docker_image=use_docker_image, + ), + ) + # If an error occurs, it will be raised above. + + assert len(result.spec_messages) == 1, ( + "Expected exactly 1 spec message but got {len(result.spec_messages)}", + result.errors, + ) def test_check( self, scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> None: """Run `connection` acceptance tests.""" result: entrypoint_wrapper.EntrypointOutput = run_test_job( - self.create_connector(scenario), + self.create_connector( + scenario, + use_docker_image=use_docker_image, + ), "check", test_scenario=scenario, ) diff --git a/airbyte_cdk/test/standard_tests/declarative_sources.py b/airbyte_cdk/test/standard_tests/declarative_sources.py index f454a267f..d00e4016f 100644 --- a/airbyte_cdk/test/standard_tests/declarative_sources.py +++ b/airbyte_cdk/test/standard_tests/declarative_sources.py @@ -64,7 +64,9 @@ def components_py_path(cls) -> Path | None: @classmethod def create_connector( cls, - scenario: ConnectorTestScenario | None, + scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> IConnector: """Create a connector scenario for the test suite. @@ -74,6 +76,13 @@ def create_connector( Subclasses should not need to override this method. """ scenario = scenario or ConnectorTestScenario() # Use default (empty) scenario if None + if use_docker_image: + return cls.create_docker_connector( + docker_image=use_docker_image, + ) + + config: dict[str, Any] = scenario.get_config_dict(empty_if_missing=True) + manifest_dict = yaml.safe_load(cls.manifest_yaml_path.read_text()) config = { "__injected_manifest": manifest_dict, diff --git a/airbyte_cdk/test/standard_tests/docker_connectors.py b/airbyte_cdk/test/standard_tests/docker_connectors.py new file mode 100644 index 000000000..884bbf35d --- /dev/null +++ b/airbyte_cdk/test/standard_tests/docker_connectors.py @@ -0,0 +1,173 @@ +"""Test containers.""" + +from __future__ import annotations + +import json +import logging +import subprocess +from collections.abc import Mapping +from pathlib import Path +from typing import Any, TextIO + +from airbyte_cdk.connector import BaseConnector +from airbyte_cdk.models import ConnectorSpecification +from airbyte_cdk.test.standard_tests._job_runner import IConnector +from airbyte_cdk.utils.docker import build_connector_image, run_docker_command + + +class CliConnector(IConnector): + """CLI connector class.""" + + def __init__( + self, + *, + connector_name: str, + logger: logging.Logger, + ) -> None: + self.connector_name = connector_name + self.default_logger = logger or logging.getLogger(__name__) + + @staticmethod + def read_config(config_path: str) -> Mapping[str, Any]: + config = BaseConnector._read_json_file(config_path) + if isinstance(config, Mapping): + return config + else: + raise ValueError( + f"The content of {config_path} is not an object and therefore is not a valid config. Please ensure the file represent a config." + ) + + def spec_( + self, + logger: logging.Logger, + ) -> Any: + """Run `spec` command.""" + self.launch( + ["spec"], + logger=logger or self.default_logger, + ) + + def check( + self, + logger: logging.Logger, + config: dict[str, Any] | Path, + ) -> None: + """Run the `check` command.""" + self.launch( + ["check"], + logger=logger, + ) + + def _run_cli( + self, + args: list[str], + *, + logger: logging.Logger, + stdout: TextIO | None = None, + stdin: TextIO | None = None, + ) -> subprocess.CompletedProcess[str]: + """Run the CLI command.""" + logger.info(f"Running CLI connector: {self.connector_name} with args: {args}") + base_cmd: list[str] = [ + self.connector_name, + *args, + ] + return subprocess.run( + args=base_cmd, + text=True, + stdout=stdout, + stdin=stdin, + ) + + def launch( + self, + args: list[str], + *, + logger: logging.Logger, + ) -> None: + """Run the connector.""" + logger = logger or self.default_logger + self._run_cli( + [self.connector_name, *args], + logger=logger, + ) + + +class DockerConnector(CliConnector): + """Docker connector class.""" + + def __init__( + self, + *, + connector_name: str, + docker_image: str, + logger: logging.Logger | None = None, + ) -> None: + self.docker_image = docker_image + self._config_file_path: Path | None = None + super().__init__( + connector_name=connector_name, + logger=logger, + ) + + def spec(self, logger: logging.Logger) -> ConnectorSpecification: + """ + Returns the spec for this integration. The spec is a JSON-Schema object describing the required configurations (e.g: username and password) + required to run this integration. By default, this will be loaded from a "spec.yaml" or a "spec.json" in the package root. + """ + self.launch( + ["spec"], + logger=logger, + ) + + def configure(self, config: Mapping[str, Any], temp_dir: str) -> Mapping[str, Any]: + """ + Persist config in temporary directory to run the Source job + """ + self._config_file_path = Path(temp_dir) / "config.json" + self._config_file_path.write_text(json.dumps(config)) + + return config + + def launch( + self, + args: list[str], + *, + logger: logging.Logger | None = None, + ) -> subprocess.CompletedProcess[str]: + """Run the connector.""" + _ = logger + print(f"Running docker connector: {self.connector_name} with args: {args}") + docker_base_cmd: list[str] = [ + "docker", + "run", + "--rm", + "--network=host", + ] + return run_docker_command( + cmd=[ + *docker_base_cmd, + self.docker_image, + *args, + ], + ) + + @classmethod + def from_connector_directory( + cls, + connector_directory: Path, + *, + logger: logging.Logger | None = None, + ) -> DockerConnector: + """Create a new Docker connector.""" + connector_name = connector_directory.name + docker_image = build_connector_image( + connector_name=connector_name, + connector_directory=connector_directory, + tag="dev", + ) + return cls( + connector_name=connector_name, + docker_image=docker_image, + logger=logger, + ) diff --git a/airbyte_cdk/test/standard_tests/pytest_hooks.py b/airbyte_cdk/test/standard_tests/pytest_hooks.py index b6197a0c3..3b6969764 100644 --- a/airbyte_cdk/test/standard_tests/pytest_hooks.py +++ b/airbyte_cdk/test/standard_tests/pytest_hooks.py @@ -16,6 +16,23 @@ import pytest +def pytest_addoption(parser: pytest.Parser) -> None: + parser.addoption( + "--use-docker-image", + action="store", + dest="use_docker_image", + metavar="IMAGE", + default=None, + help="test connector containerized", + ) + + +@pytest.fixture +def use_docker_image(request): + """True if pytest was invoked with --use-docker-image.""" + return request.config.getoption("use_docker_image") + + def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: """ A helper for pytest_generate_tests hook. @@ -42,12 +59,18 @@ class TestMyConnector(ConnectorTestSuiteBase): ``` """ # Check if the test function requires an 'scenario' argument - if "scenario" in metafunc.fixturenames: - # Retrieve the test class - test_class = metafunc.cls - if test_class is None: - return + if "scenario" not in metafunc.fixturenames and "use_docker_image" not in metafunc.fixturenames: + return None + # Retrieve the test class + test_class = metafunc.cls + if test_class is None: + return + + argnames: list[str] = [] + argvalues: list[object] = [] + + if "scenario" in metafunc.fixturenames: # Get the 'scenarios' attribute from the class scenarios_attr = getattr(test_class, "get_scenarios", None) if scenarios_attr is None: @@ -58,4 +81,17 @@ class TestMyConnector(ConnectorTestSuiteBase): scenarios = test_class.get_scenarios() ids = [str(scenario) for scenario in scenarios] + + # if "use_docker_image" not in metafunc.fixturenames: + # raise ValueError( + # "The 'use_docker_image' argument should be used in conjunction with 'scenario'. " + # f"Metafunc: {metafunc.function}, Test class: {test_class!s}" + # ) + metafunc.parametrize("scenario", scenarios, ids=ids) + + # if "use_docker_image" in metafunc.fixturenames: + # metafunc.parametrize( + # "use_docker_image", + # [metafunc.config.getoption("use_docker_image")], + # ) diff --git a/airbyte_cdk/test/standard_tests/source_base.py b/airbyte_cdk/test/standard_tests/source_base.py index a256fa04c..4ee9e1fbc 100644 --- a/airbyte_cdk/test/standard_tests/source_base.py +++ b/airbyte_cdk/test/standard_tests/source_base.py @@ -12,7 +12,7 @@ SyncMode, Type, ) -from airbyte_cdk.test import entrypoint_wrapper +from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput from airbyte_cdk.test.standard_tests._job_runner import run_test_job from airbyte_cdk.test.standard_tests.connector_base import ( ConnectorTestSuiteBase, @@ -32,6 +32,8 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase): def test_check( self, scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> None: """Run standard `check` tests on the connector. @@ -39,9 +41,12 @@ def test_check( This test is designed to validate the connector's ability to establish a connection and return its status with the expected message type. """ - result: entrypoint_wrapper.EntrypointOutput = run_test_job( - self.create_connector(scenario), - "check", + result: EntrypointOutput = run_test_job( + connector=self.create_connector( + scenario=scenario, + use_docker_image=use_docker_image, + ), + verb="check", test_scenario=scenario, ) conn_status_messages: list[AirbyteMessage] = [ @@ -56,41 +61,24 @@ def test_check( def test_discover( self, scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> None: """Standard test for `discover`.""" run_test_job( - self.create_connector(scenario), + self.create_connector( + scenario, + use_docker_image=use_docker_image, + ), "discover", test_scenario=scenario, ) - def test_spec(self) -> None: - """Standard test for `spec`. - - This test does not require a `scenario` input, since `spec` - does not require any inputs. - - We assume `spec` should always succeed and it should always generate - a valid `SPEC` message. - - Note: the parsing of messages by type also implicitly validates that - the generated `SPEC` message is valid JSON. - """ - result = run_test_job( - verb="spec", - test_scenario=None, - connector=self.create_connector(scenario=None), - ) - # If an error occurs, it will be raised above. - - assert len(result.spec_messages) == 1, ( - "Expected exactly 1 spec message but got {len(result.spec_messages)}", - result.errors, - ) - def test_basic_read( self, scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> None: """Run standard `read` test on the connector. @@ -99,8 +87,11 @@ def test_basic_read( obtain the catalog of streams, and then it runs a `read` job to fetch records from those streams. """ - discover_result = run_test_job( - self.create_connector(scenario), + discover_result: EntrypointOutput = run_test_job( + self.create_connector( + scenario, + use_docker_image=use_docker_image, + ), "discover", test_scenario=scenario, ) @@ -118,8 +109,11 @@ def test_basic_read( for stream in discover_result.catalog.catalog.streams # type: ignore [reportOptionalMemberAccess, union-attr] ] ) - result = run_test_job( - self.create_connector(scenario), + result: EntrypointOutput = run_test_job( + self.create_connector( + scenario, + use_docker_image=use_docker_image, + ), "read", test_scenario=scenario, catalog=configured_catalog, @@ -131,6 +125,8 @@ def test_basic_read( def test_fail_read_with_bad_catalog( self, scenario: ConnectorTestScenario, + *, + use_docker_image: str | bool, ) -> None: """Standard test for `read` when passed a bad catalog file.""" invalid_configured_catalog = ConfiguredAirbyteCatalog( @@ -154,8 +150,11 @@ def test_fail_read_with_bad_catalog( ) # Set expected status to "failed" to ensure the test fails if the connector. scenario.status = "failed" - result: entrypoint_wrapper.EntrypointOutput = run_test_job( - self.create_connector(scenario), + result: EntrypointOutput = run_test_job( + self.create_connector( + scenario, + use_docker_image=use_docker_image, + ), "read", test_scenario=scenario, catalog=asdict(invalid_configured_catalog), diff --git a/airbyte_cdk/test/standard_tests/util.py b/airbyte_cdk/test/standard_tests/util.py index 58ae19d85..e57a09bfe 100644 --- a/airbyte_cdk/test/standard_tests/util.py +++ b/airbyte_cdk/test/standard_tests/util.py @@ -18,11 +18,11 @@ ) TEST_CLASS_MAPPING: dict[ - Literal["python", "manifest-only", "declarative"], type[ConnectorTestSuiteBase] + Literal["python", "manifest-only", "java"], type[ConnectorTestSuiteBase] ] = { "python": SourceTestSuiteBase, "manifest-only": DeclarativeSourceTestSuite, - # "declarative": DeclarativeSourceTestSuite, + # "java": DestinationTestSuiteBase, } diff --git a/airbyte_cdk/utils/docker.py b/airbyte_cdk/utils/docker.py index db6355eac..0baa0eae5 100644 --- a/airbyte_cdk/utils/docker.py +++ b/airbyte_cdk/utils/docker.py @@ -137,6 +137,7 @@ def _tag_image( def build_connector_image( connector_name: str, connector_directory: Path, + *, metadata: MetadataFile, tag: str, primary_arch: ArchEnum = ArchEnum.ARM64, # Assume MacBook M series by default @@ -163,13 +164,12 @@ def build_connector_image( ValueError: If the connector build options are not defined in metadata.yaml. ConnectorImageBuildError: If the image build or tag operation fails. """ - connector_kebab_name = connector_name - if dockerfile_override: dockerfile_path = dockerfile_override else: - dockerfile_path = connector_directory / "build" / "docker" / "Dockerfile" - dockerignore_path = connector_directory / "build" / "docker" / "Dockerfile.dockerignore" + dockerfile_dir = connector_directory / "build" / "docker" + dockerfile_path = dockerfile_dir / "Dockerfile" + dockerignore_path = dockerfile_dir / "Dockerfile.dockerignore" try: dockerfile_text, dockerignore_text = get_dockerfile_templates( metadata=metadata, @@ -192,6 +192,7 @@ def build_connector_image( ), ) from e + dockerfile_dir.mkdir(parents=True, exist_ok=True) dockerfile_path.write_text(dockerfile_text) dockerignore_path.write_text(dockerignore_text) @@ -210,7 +211,7 @@ def build_connector_image( base_image = metadata.data.connectorBuildOptions.baseImage build_args: dict[str, str | None] = { "BASE_IMAGE": base_image, - "CONNECTOR_NAME": connector_kebab_name, + "CONNECTOR_NAME": connector_name, "EXTRA_BUILD_SCRIPT": extra_build_script, } @@ -340,24 +341,24 @@ def get_dockerfile_templates( from_dir=connector_directory, ) # airbyte_repo_root successfully resolved - dockerfile_path = ( + source_dockerfile_path = ( airbyte_repo_root / "docker-images" / f"Dockerfile.{metadata.data.language.value}-connector" ) - dockerignore_path = ( + source_dockerignore_path = ( airbyte_repo_root / "docker-images" / f"Dockerfile.{metadata.data.language.value}-connector.dockerignore" ) - if not dockerfile_path.exists(): + if not source_dockerfile_path.exists(): raise FileNotFoundError( - f"Dockerfile for {metadata.data.language.value} connector not found at {dockerfile_path}" + f"Dockerfile for {metadata.data.language.value} connector not found at {source_dockerfile_path}" ) - if not dockerignore_path.exists(): + if not source_dockerignore_path.exists(): raise FileNotFoundError( - f".dockerignore for {metadata.data.language.value} connector not found at {dockerignore_path}" + f".dockerignore for {metadata.data.language.value} connector not found at {source_dockerignore_path}" ) - return dockerfile_path.read_text(), dockerignore_path.read_text() + return source_dockerfile_path.read_text(), source_dockerignore_path.read_text() def run_docker_command(