diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 83d8cc37f798..57503e8a35d4 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -21,4 +21,4 @@ Add: `test_spec` additionally checks if Dockerfile has `ENV AIRBYTE_ENTRYPOINT` Add test whether PKs present and not None if `source_defined_primary_key` defined: https://github.com/airbytehq/airbyte/pull/4140 ## 0.1.5 -Add configurable timeout for the acceptance tests: https://github.com/airbytehq/airbyte/pull/4296 +Add configurable timeout for the acceptance tests: https://github.com/airbytehq/airbyte/pull/4296 \ No newline at end of file diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py index 6725a04fe133..528f22eb886b 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py @@ -46,6 +46,11 @@ def test_match_expected(self, connector_spec: ConnectorSpecification, connector_ if connector_spec: assert spec_messages[0].spec == connector_spec, "Spec should be equal to the one in spec.json file" + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT"), "AIRBYTE_ENTRYPOINT must be set in dockerfile" + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT") == " ".join( + docker_runner.entry_point + ), "env should be equal to space-joined entrypoint" + # Getting rid of technical variables that start with an underscore config = {key: value for key, value in connector_config.data.items() if not key.startswith("_")} diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py index 4748ed733a7b..766558df2ee7 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py @@ -130,3 +130,12 @@ def run(self, cmd, config=None, state=None, catalog=None, **kwargs) -> Iterable[ yield AirbyteMessage.parse_raw(line) except ValidationError as exc: logging.warning("Unable to parse connector's output %s", exc) + + @property + def env_variables(self): + env_vars = self._image.attrs["Config"]["Env"] + return {env.split("=", 1)[0]: env.split("=", 1)[1] for env in env_vars} + + @property + def entry_point(self): + return self._image.attrs["Config"]["Entrypoint"] diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_spec_unit.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_spec_unit.py new file mode 100644 index 000000000000..cada61cadf82 --- /dev/null +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_spec_unit.py @@ -0,0 +1,101 @@ +# +# MIT License +# +# Copyright (c) 2020 Airbyte +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import io + +import docker +import pytest +from source_acceptance_test.utils import ConnectorRunner + + +def build_docker_image(text: str, tag: str) -> docker.models.images.Image: + """ + Really for this test we dont need to remove the image since we access it by a string name + and remove it also by a string name. But maybe we wanna use it somewhere + """ + client = docker.from_env() + fileobj = io.BytesIO(bytes(text, "utf-8")) + image, iterools_tee = client.images.build(fileobj=fileobj, tag=tag, forcerm=True, rm=True) + return image + + +@pytest.fixture +def correct_connector_image() -> str: + dockerfile_text = """ + FROM scratch + ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" + ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + """ + tag = "my-valid-one" + build_docker_image(dockerfile_text, tag) + yield tag + client = docker.from_env() + client.images.remove(image=tag, force=True) + + +@pytest.fixture +def connector_image_without_env(): + dockerfile_text = """ + FROM scratch + ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + """ + tag = "my-no-env" + build_docker_image(dockerfile_text, tag) + yield tag + client = docker.from_env() + client.images.remove(image=tag, force=True) + + +@pytest.fixture +def connector_image_with_ne_properties(): + dockerfile_text = """ + FROM scratch + ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" + ENTRYPOINT ["python3", "/airbyte/integration_code/main.py"] + """ + tag = "my-ne-properties" + build_docker_image(dockerfile_text, tag) + yield tag + client = docker.from_env() + client.images.remove(image=tag, force=True) + + +class TestEnvAttributes: + def test_correct_connector_image(self, correct_connector_image, tmp_path): + docker_runner = ConnectorRunner(image_name=correct_connector_image, volume=tmp_path) + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT"), "AIRBYTE_ENTRYPOINT must be set in dockerfile" + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT") == " ".join( + docker_runner.entry_point + ), "env should be equal to space-joined entrypoint" + + def test_connector_image_without_env(self, connector_image_without_env, tmp_path): + docker_runner = ConnectorRunner(image_name=connector_image_without_env, volume=tmp_path) + assert not docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT"), "this test should fail if AIRBYTE_ENTRYPOINT defined" + + def test_docker_image_env_ne_entrypoint(self, connector_image_with_ne_properties, tmp_path): + docker_runner = ConnectorRunner(image_name=connector_image_with_ne_properties, volume=tmp_path) + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT"), "AIRBYTE_ENTRYPOINT must be set in dockerfile" + assert docker_runner.env_variables.get("AIRBYTE_ENTRYPOINT") != " ".join(docker_runner.entry_point), ( + "This test should fail if we have " ".join(ENTRYPOINT)==ENV" + )