Skip to content

cat/connectors-ci: replace docker runner with dagger runner in CAT #28000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cdfafe7
remove dependency of connectors to CAT
alafanechere Aug 10, 2023
8e656cb
make CAT use dagger
alafanechere Aug 10, 2023
1225a65
Merge branch 'master' into augustin/cat/dagger-runner-2
alafanechere Aug 10, 2023
3cfdf97
use poetry
alafanechere Aug 10, 2023
c88b86d
comment in dockerfile
alafanechere Aug 10, 2023
dec985e
improve tar path resolution
alafanechere Aug 10, 2023
c7041b0
improve CAT dockerfile
alafanechere Aug 10, 2023
d842ec7
use CAT in airbyte-ci with dagger-in-dagger
alafanechere Aug 10, 2023
4447206
update scaffolds
alafanechere Aug 10, 2023
65cb910
poetry in docker: simpler, heavier, but working
alafanechere Aug 10, 2023
e68d185
lock in dockerignore
alafanechere Aug 10, 2023
e25defe
poetry run in doc
alafanechere Aug 10, 2023
e498110
docstrings
alafanechere Aug 10, 2023
2ade1b8
python version not required now that we're on poetry
alafanechere Aug 10, 2023
2291531
docstring
alafanechere Aug 10, 2023
84ed430
remove async where not needed
alafanechere Aug 10, 2023
37995f3
add pytest-mock deps
alafanechere Aug 10, 2023
0555ca4
improve caching testing
alafanechere Aug 10, 2023
7c75f2f
improve Dockerfile
alafanechere Aug 10, 2023
45134ab
Merge branch 'master' into augustin/cat/dagger-runner-2
alafanechere Aug 10, 2023
83d3363
bump version
alafanechere Aug 10, 2023
1763641
revert timeout tweaks
alafanechere Aug 10, 2023
6b27957
add request mock as default test requirements
alafanechere Aug 11, 2023
a446715
fix timeout issue by preloading container
alafanechere Aug 11, 2023
e5c2309
handle huge command output in cat
alafanechere Aug 11, 2023
c4cbba9
fix CAT unit tests
alafanechere Aug 11, 2023
13d93bf
increase timeout for spec
alafanechere Aug 11, 2023
b0ac14a
fix CAT unit tests
alafanechere Aug 11, 2023
d28d91e
improve connector runner tests
alafanechere Aug 11, 2023
b4e4745
add missing markers
alafanechere Aug 11, 2023
801a2c3
update doc
alafanechere Aug 11, 2023
301f962
bump CAT dockerfile version
alafanechere Aug 11, 2023
765f611
add requirements.txt
alafanechere Aug 11, 2023
6a0cfda
disable gradle testing
alafanechere Aug 11, 2023
b3c902e
revert requirements
alafanechere Aug 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ def with_crane(

def mounted_connector_secrets(context: PipelineContext, secret_directory_path="secrets") -> Callable:
def mounted_connector_secrets_inner(container: Container):
container = container.with_exec(["mkdir", secret_directory_path], skip_entrypoint=True)
for secret_file_name, secret in context.connector_secrets.items():
container = container.with_mounted_secret(f"{secret_directory_path}/{secret_file_name}", secret)
return container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#

from dagger import QueryError
from pipelines.actions.environments import with_airbyte_python_connector
from pipelines.bases import StepResult, StepStatus
from pipelines.builds.common import BuildConnectorImageBase, BuildConnectorImageForAllPlatformsBase
from pipelines.contexts import ConnectorContext
from dagger import QueryError


class BuildConnectorImage(BuildConnectorImageBase):
Expand Down
2 changes: 1 addition & 1 deletion airbyte-ci/connectors/pipelines/pipelines/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ async def __aexit__(
class ConnectorContext(PipelineContext):
"""The connector context is used to store configuration for a specific connector pipeline run."""

DEFAULT_CONNECTOR_ACCEPTANCE_TEST_IMAGE = "airbyte/connector-acceptance-test:latest"
DEFAULT_CONNECTOR_ACCEPTANCE_TEST_IMAGE = "airbyte/connector-acceptance-test:dev"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this temporary or will we always run off a dev version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could always run off a dev version, the CAT image is built at runtime by dagger and it's cached so always using dev should not hurt perf. It's also convenient while developing on cat: you can easily test connector impact as airbyte ci will use the dev version. I'll leave it like this, but I'm open to pin CAT to a specific tag if needed (we can't pin it to a version < 1.0.0 though as the airbyte-ci cat orchestration is different).


def __init__(
self,
Expand Down
2 changes: 1 addition & 1 deletion airbyte-ci/connectors/pipelines/pipelines/hacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,6 @@ def never_fail_exec(command: List[str]) -> Callable:
"""

def never_fail_exec_inner(container: Container):
return container.with_exec(["sh", "-c", f"{' '.join(command)}; echo $? > /exit_code"])
return container.with_exec(["sh", "-c", f"{' '.join(command)}; echo $? > /exit_code"], skip_entrypoint=True)

return never_fail_exec_inner
79 changes: 51 additions & 28 deletions airbyte-ci/connectors/pipelines/pipelines/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""This module groups steps made to run tests agnostic to a connector language."""

import datetime
import os
from abc import ABC, abstractmethod
from functools import cached_property
from typing import ClassVar, List, Optional
Expand All @@ -13,7 +14,7 @@
import semver
import yaml
from connector_ops.utils import Connector
from dagger import Container, File
from dagger import Container, Directory, File
from pipelines import hacks
from pipelines.actions import environments
from pipelines.bases import CIContext, PytestStep, Step, StepResult, StepStatus
Expand Down Expand Up @@ -181,7 +182,7 @@ class AcceptanceTests(PytestStep):
CONTAINER_SECRETS_DIRECTORY = "/test_input/secrets"

@property
def cat_command(self) -> List[str]:
def base_cat_command(self) -> List[str]:
return [
"python",
"-m",
Expand All @@ -192,6 +193,18 @@ def cat_command(self) -> List[str]:
self.CONTAINER_TEST_INPUT_DIRECTORY,
]

async def get_cat_command(self, connector_dir: Directory) -> List[str]:
"""
Connectors can optionally setup or teardown resources before and after the acceptance tests are run.
This is done via the acceptance.py file in their integration_tests directory.
We append this module as a plugin the acceptance will use.
"""
cat_command = self.base_cat_command
if "integration_tests" in await connector_dir.entries():
if "acceptance.py" in await connector_dir.directory("integration_tests").entries():
cat_command += ["-p", "integration_tests.acceptance"]
return cat_command

async def _run(self, connector_under_test_image_tar: File) -> StepResult:
"""Run the acceptance test suite on a connector dev image. Build the connector acceptance test image if the tag is :dev.

Expand All @@ -203,10 +216,10 @@ async def _run(self, connector_under_test_image_tar: File) -> StepResult:
"""
if not self.context.connector.acceptance_test_config:
return StepResult(self, StepStatus.SKIPPED)

cat_container = await self._build_connector_acceptance_test(connector_under_test_image_tar)
cat_container = cat_container.with_(hacks.never_fail_exec(self.cat_command))

connector_dir = await self.context.get_connector_dir()
cat_container = await self._build_connector_acceptance_test(connector_under_test_image_tar, connector_dir)
cat_command = await self.get_cat_command(connector_dir)
cat_container = cat_container.with_(hacks.never_fail_exec(cat_command))
step_result = await self.get_step_result(cat_container)
secret_dir = cat_container.directory(self.CONTAINER_SECRETS_DIRECTORY)

Expand All @@ -217,44 +230,54 @@ async def _run(self, connector_under_test_image_tar: File) -> StepResult:
break
return step_result

def get_cache_buster(self) -> str:
async def get_cache_buster(self, connector_under_test_image_tar: File) -> str:
"""
This bursts the CAT cached results everyday.
This bursts the CAT cached results everyday and on new version or image size change.
It's cool because in case of a partially failing nightly build the connectors that already ran CAT won't re-run CAT.
We keep the guarantee that a CAT runs everyday.

Args:
connector_under_test_image_tar (File): The file holding the tar archive of the connector image.
Returns:
str: A string representing the current date.
str: A string representing the cachebuster value.
"""
return datetime.datetime.utcnow().strftime("%Y%m%d")
return (
datetime.datetime.utcnow().strftime("%Y%m%d")
+ self.context.connector.version
+ str(await connector_under_test_image_tar.size())
)

async def _build_connector_acceptance_test(self, connector_under_test_image_tar: File) -> Container:
"""Create a container to run connector acceptance tests, bound to a persistent docker host.
async def _build_connector_acceptance_test(self, connector_under_test_image_tar: File, test_input: Directory) -> Container:
"""Create a container to run connector acceptance tests.

Args:
connector_under_test_image_tar (File): The file containing the tar archive the image of the connector under test.
connector_under_test_image_tar (File): The file containing the tar archive of the image of the connector under test.
test_input (Directory): The connector under test directory.
Returns:
Container: A container with connector acceptance tests installed.
"""
test_input = await self.context.get_connector_dir()
cat_config = yaml.safe_load(await test_input.file("acceptance-test-config.yml").contents())

image_sha = await environments.load_image_to_docker_host(
self.context, connector_under_test_image_tar, cat_config["connector_image"]
)

if self.context.connector_acceptance_test_image.endswith(":dev"):
cat_container = self.context.connector_acceptance_test_source_dir.docker_build()
else:
cat_container = self.dagger_client.container().from_(self.context.connector_acceptance_test_image)

return (
environments.with_bound_docker_host(self.context, cat_container)
.with_entrypoint([])
.with_mounted_directory(self.CONTAINER_TEST_INPUT_DIRECTORY, test_input)
.with_env_variable("CONNECTOR_IMAGE_ID", image_sha)
.with_env_variable("CACHEBUSTER", self.get_cache_buster())
.with_workdir(self.CONTAINER_TEST_INPUT_DIRECTORY)
.with_exec(["mkdir", "-p", self.CONTAINER_SECRETS_DIRECTORY])
.with_(environments.mounted_connector_secrets(self.context, self.CONTAINER_SECRETS_DIRECTORY))
cat_container = (
cat_container.with_env_variable("RUN_IN_AIRBYTE_CI", "1")
.with_exec(["mkdir", "/dagger_share"], skip_entrypoint=True)
.with_env_variable("CACHEBUSTER", await self.get_cache_buster(connector_under_test_image_tar))
.with_mounted_file("/dagger_share/connector_under_test_image.tar", connector_under_test_image_tar)
.with_env_variable("CONNECTOR_UNDER_TEST_IMAGE_TAR_PATH", "/dagger_share/connector_under_test_image.tar")
.with_workdir("/test_input")
.with_mounted_directory("/test_input", test_input)
.with_(environments.mounted_connector_secrets(self.context, secret_directory_path="/test_input/secrets"))
)
if "_EXPERIMENTAL_DAGGER_RUNNER_HOST" in os.environ:
self.context.logger.info("Using experimental dagger runner host to run CAT with dagger-in-dagger")
cat_container = cat_container.with_env_variable(
"_EXPERIMENTAL_DAGGER_RUNNER_HOST", "unix:///var/run/buildkit/buildkitd.sock"
).with_unix_socket(
"/var/run/buildkit/buildkitd.sock", self.context.dagger_client.host().unix_socket("/var/run/buildkit/buildkitd.sock")
)

return cat_container.with_unix_socket("/var/run/docker.sock", self.context.dagger_client.host().unix_socket("/var/run/docker.sock"))
98 changes: 63 additions & 35 deletions airbyte-ci/connectors/pipelines/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions airbyte-ci/connectors/pipelines/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pytest-mock = "^3.10.0"


[tool.poetry.group.dev.dependencies]
freezegun = "^1.2.2"
pytest-cov = "^4.1.0"

[tool.poetry.scripts]
Expand Down
Loading