Skip to content

Commit 660bfa2

Browse files
alafanecherejbfbell
authored andcommitted
connector base image: declare the base image package and implement (#30303)
Co-authored-by: alafanechere <[email protected]>
1 parent 05ad05a commit 660bfa2

33 files changed

+3905
-119
lines changed

.github/workflows/airbyte-ci-tests.yml

+12
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,15 @@ jobs:
3232
sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }}
3333
github_token: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }}
3434
subcommand: "test airbyte-ci/connectors/pipelines"
35+
- name: Run airbyte-ci/connectors/base_images tests
36+
id: run-airbyte-ci-connectors-base-images-tests
37+
uses: ./.github/actions/run-dagger-pipeline
38+
with:
39+
context: "pull_request"
40+
docker_hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }}
41+
docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
42+
gcp_gsm_credentials: ${{ secrets.GCP_GSM_CREDENTIALS }}
43+
gcs_credentials: ${{ secrets.METADATA_SERVICE_PROD_GCS_CREDENTIALS }}
44+
sentry_dsn: ${{ secrets.SENTRY_AIRBYTE_CI_DSN }}
45+
github_token: ${{ secrets.GH_PAT_MAINTENANCE_OCTAVIA }}
46+
subcommand: "test airbyte-ci/connectors/base_images"

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repos:
1414
- id: black
1515
args: ["--config", "pyproject.toml"]
1616
- repo: https://github.com/timothycrosley/isort
17-
rev: 5.10.1
17+
rev: 5.12.0
1818
hooks:
1919
- id: isort
2020
args:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# airbyte-connectors-base-images
2+
3+
This python package contains the base images used by Airbyte connectors.
4+
It is intended to be used as a python library.
5+
Our connector build pipeline ([`airbyte-ci`](https://github.com/airbytehq/airbyte/blob/master/airbyte-ci/connectors/pipelines/README.md#L1)) **will** use this library to build the connector images.
6+
Our base images are declared in code, using the [Dagger Python SDK](https://dagger-io.readthedocs.io/en/sdk-python-v0.6.4/).
7+
8+
9+
10+
## Where are the Dockerfiles?
11+
Our base images are not declared using Dockerfiles.
12+
They are declared in code using the [Dagger Python SDK](https://dagger-io.readthedocs.io/en/sdk-python-v0.6.4/).
13+
We prefer this approach because it allows us to interact with base images container as code: we can use python to declare the base images and use the full power of the language to build and test them.
14+
However, we do artificially generate Dockerfiles for debugging and documentation purposes.
15+
16+
17+
18+
### Example for `airbyte/python-connector-base`:
19+
```dockerfile
20+
FROM docker.io/python:3.9.18-slim-bookworm@sha256:44b7f161ed03f85e96d423b9916cdc8cb0509fb970fd643bdbc9896d49e1cad0
21+
RUN ln -snf /usr/share/zoneinfo/Etc/UTC /etc/localtime
22+
RUN pip install --upgrade pip==23.2.1
23+
ENV POETRY_VIRTUALENVS_CREATE=false
24+
ENV POETRY_VIRTUALENVS_IN_PROJECT=false
25+
ENV POETRY_NO_INTERACTION=1
26+
RUN pip install poetry==1.6.1
27+
```
28+
29+
30+
31+
## Base images
32+
33+
34+
### `airbyte/python-connector-base`
35+
36+
| Version | Published | Docker Image Address | Changelog |
37+
|---------|-----------|--------------|-----------|
38+
| 1.0.0 || docker.io/airbyte/python-connector-base:1.0.0@sha256:dd17e347fbda94f7c3abff539be298a65af2d7fc27a307d89297df1081a45c27 | Initial release: based on Python 3.9.18, on slim-bookworm system, with pip==23.2.1 and poetry==1.6.1 |
39+
40+
41+
## How to release a new base image version (example for Python)
42+
43+
### Requirements
44+
* [Docker](https://docs.docker.com/get-docker/)
45+
* [Poetry](https://python-poetry.org/docs/#installation)
46+
* Dockerhub logins
47+
48+
### Steps
49+
1. `poetry install`
50+
2. Open `base_images/python/bases.py`.
51+
3. Make changes to the `AirbytePythonConnectorBaseImage`, you're likely going to change the `get_container` method to change the base image.
52+
4. Implement the `container` property which must return a `dagger.Container` object.
53+
5. **Recommended**: Add new sanity checks to `run_sanity_check` to confirm that the new version is working as expected.
54+
6. Cut a new base image version by running `poetry run generate-release`. You'll need your DockerHub credentials.
55+
56+
It will:
57+
- Prompt you to pick which base image you'd like to publish.
58+
- Prompt you for a major/minor/patch/pre-release version bump.
59+
- Prompt you for a changelog message.
60+
- Run the sanity checks on the new version.
61+
- Optional: Publish the new version to DockerHub.
62+
- Regenerate the docs and the registry json file.
63+
7. Commit and push your changes.
64+
8. Create a PR and ask for a review from the Connector Operations team.
65+
66+
**Please note that if you don't publish your image while cutting the new version you can publish it later with `poetry run publish <repository> <version>`.**
67+
No connector will use the new base image version until its metadata is updated to use it.
68+
If you're not fully confident with the new base image version please:
69+
- please publish it as a pre-release version
70+
- try out the new version on a couple of connectors
71+
- cut a new version with a major/minor/patch bump and publish it
72+
- This steps can happen in different PRs.
73+
74+
75+
## Running tests locally
76+
```bash
77+
poetry run pytest
78+
# Static typing checks
79+
poetry run mypy base_images --check-untyped-defs
80+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#
2+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
from rich.console import Console
6+
7+
console = Console()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#
2+
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
"""This module declares common (abstract) classes and methods used by all base images."""
6+
from __future__ import annotations
7+
8+
from abc import ABC, abstractmethod
9+
from typing import final
10+
11+
import dagger
12+
import semver
13+
14+
from .published_image import PublishedImage
15+
16+
17+
class AirbyteConnectorBaseImage(ABC):
18+
"""An abstract class that represents an Airbyte base image.
19+
Please do not declare any Dagger with_exec instruction in this class as in the abstract class context we have no guarantee about the underlying system used in the base image.
20+
"""
21+
22+
@final
23+
def __init__(self, dagger_client: dagger.Client, version: semver.VersionInfo):
24+
"""Initializes the Airbyte base image.
25+
26+
Args:
27+
dagger_client (dagger.Client): The dagger client used to build the base image.
28+
version (semver.VersionInfo): The version of the base image.
29+
"""
30+
self.dagger_client = dagger_client
31+
self.version = version
32+
33+
# INSTANCE PROPERTIES:
34+
35+
@property
36+
def name_with_tag(self) -> str:
37+
"""Returns the full name of the Airbyte base image, with its tag.
38+
39+
Returns:
40+
str: The full name of the Airbyte base image, with its tag.
41+
"""
42+
return f"{self.repository}:{self.version}"
43+
44+
# MANDATORY SUBCLASSES ATTRIBUTES / PROPERTIES:
45+
46+
@property
47+
@abstractmethod
48+
def root_image(self) -> PublishedImage:
49+
"""Returns the base image used to build the Airbyte base image.
50+
51+
Raises:
52+
NotImplementedError: Raised if a subclass does not define a 'root_image' attribute.
53+
54+
Returns:
55+
PublishedImage: The base image used to build the Airbyte base image.
56+
"""
57+
raise NotImplementedError("Subclasses must define a 'root_image' attribute.")
58+
59+
@property
60+
@abstractmethod
61+
def repository(self) -> str:
62+
"""This is the name of the repository where the image will be hosted.
63+
e.g: airbyte/python-connector-base
64+
65+
Raises:
66+
NotImplementedError: Raised if a subclass does not define an 'repository' attribute.
67+
68+
Returns:
69+
str: The repository name where the image will be hosted.
70+
"""
71+
raise NotImplementedError("Subclasses must define an 'repository' attribute.")
72+
73+
# MANDATORY SUBCLASSES METHODS:
74+
75+
@abstractmethod
76+
def get_container(self, platform: dagger.Platform) -> dagger.Container:
77+
"""Returns the container of the Airbyte connector base image."""
78+
raise NotImplementedError("Subclasses must define a 'get_container' method.")
79+
80+
@abstractmethod
81+
async def run_sanity_checks(self, platform: dagger.Platform):
82+
"""Runs sanity checks on the base image container.
83+
This method is called before image publication.
84+
85+
Args:
86+
base_image_version (AirbyteConnectorBaseImage): The base image version on which the sanity checks should run.
87+
88+
Raises:
89+
SanityCheckError: Raised if a sanity check fails.
90+
"""
91+
raise NotImplementedError("Subclasses must define a 'run_sanity_checks' method.")
92+
93+
# INSTANCE METHODS:
94+
@final
95+
def get_base_container(self, platform: dagger.Platform) -> dagger.Container:
96+
"""Returns a container using the base image. This container is used to build the Airbyte base image.
97+
98+
Returns:
99+
dagger.Container: The container using the base python image.
100+
"""
101+
return self.dagger_client.pipeline(self.name_with_tag).container(platform=platform).from_(self.root_image.address)

0 commit comments

Comments
 (0)