Skip to content

Commit 2bc8848

Browse files
fix: Address PR comments - remove infer_connector_language, use build/docker for Dockerfile
Co-Authored-By: Aaron <AJ> Steers <[email protected]>
1 parent f3e1743 commit 2bc8848

File tree

4 files changed

+113
-107
lines changed

4 files changed

+113
-107
lines changed

airbyte_cdk/cli/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
11
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
2+
"""Airbyte CDK CLI.
3+
4+
The Airbyte CDK provides command-line tools for working with connectors and related resources.
5+
6+
7+
```bash
8+
pip install airbyte-cdk
9+
10+
pipx run airbyte-cdk [command]
11+
```
12+
13+
14+
15+
- `airbyte-cdk-build`: Build connector Docker images (legacy entry point)
16+
- `source-declarative-manifest`: Run a declarative YAML manifest connector
17+
- `airbyte-cdk`: Main CLI entry point with subcommands
18+
19+
20+
The `airbyte-cdk` command includes subcommands organized by category:
21+
22+
```bash
23+
airbyte-cdk image build [OPTIONS] CONNECTOR_DIR
24+
```
25+
26+
27+
For details on specific commands, see the documentation for each command module.
28+
"""

airbyte_cdk/cli/build/_run.py

Lines changed: 82 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from __future__ import annotations
88

9-
import argparse
109
import json
1110
import logging
1211
import os
@@ -15,11 +14,11 @@
1514
import sys
1615
import tempfile
1716
from pathlib import Path
18-
from typing import Any, Dict, List, Optional, Tuple
17+
from typing import Dict, List, Optional, Tuple
1918

2019
import yaml
2120

22-
from airbyte_cdk.cli.build.models import ConnectorLanguage, ConnectorMetadata, MetadataFile
21+
from airbyte_cdk.cli.build.models import ConnectorMetadata, MetadataFile
2322

2423
logger = logging.getLogger("airbyte-cdk.cli.build")
2524

@@ -34,23 +33,6 @@ def set_up_logging(verbose: bool = False) -> None:
3433
)
3534

3635

37-
def parse_args(args: List[str]) -> argparse.Namespace:
38-
"""Parse command line arguments for the build command."""
39-
parser = argparse.ArgumentParser(
40-
description="Build connector Docker images using the host Docker daemon"
41-
)
42-
parser.add_argument("connector_dir", type=str, help="Path to the connector directory")
43-
parser.add_argument(
44-
"--tag", type=str, default="dev", help="Tag to apply to the built image (default: dev)"
45-
)
46-
parser.add_argument(
47-
"--no-verify", action="store_true", help="Skip verification of the built image"
48-
)
49-
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose logging")
50-
51-
return parser.parse_args(args)
52-
53-
5436
def read_metadata(connector_dir: Path) -> ConnectorMetadata:
5537
"""Read and parse connector metadata from metadata.yaml.
5638
@@ -78,30 +60,7 @@ def read_metadata(connector_dir: Path) -> ConnectorMetadata:
7860
return metadata_file.data
7961

8062

81-
def infer_connector_language(metadata: ConnectorMetadata, connector_dir: Path) -> ConnectorLanguage:
82-
"""Infer the connector language from metadata and the file structure.
83-
84-
Args:
85-
metadata: The connector metadata.
86-
connector_dir: Path to the connector directory.
87-
88-
Returns:
89-
The inferred connector language.
90-
"""
91-
if metadata.language is not None:
92-
return metadata.language
93-
94-
if (connector_dir / "setup.py").exists() or (connector_dir / "pyproject.toml").exists():
95-
return ConnectorLanguage.PYTHON
96-
97-
if (connector_dir / "build.gradle").exists():
98-
return ConnectorLanguage.JAVA
99-
100-
if any((connector_dir / f).exists() for f in ["manifest.yaml", "spec.yaml", "spec.json"]):
101-
return ConnectorLanguage.LOW_CODE
10263

103-
logger.warning("Could not determine connector language, using UNKNOWN.")
104-
return ConnectorLanguage.UNKNOWN
10564

10665

10766
def run_docker_command(cmd: List[str], check: bool = True) -> Tuple[int, str, str]:
@@ -259,8 +218,17 @@ def build_from_base_image(
259218
f"Building Docker image from base image {base_image}: {full_image_name} for platforms {platforms}"
260219
)
261220

262-
with tempfile.TemporaryDirectory() as temp_dir:
263-
temp_dir_path = Path(temp_dir)
221+
docker_dir = connector_dir / "build" / "docker"
222+
docker_dir.mkdir(parents=True, exist_ok=True)
223+
224+
dockerfile_path = docker_dir / "Dockerfile"
225+
dockerignore_path = docker_dir / ".dockerignore"
226+
227+
os.environ["DOCKER_BUILDKIT"] = "1"
228+
229+
try:
230+
main_file = get_main_file_name(connector_dir)
231+
logger.info(f"Using main file: {main_file}")
264232

265233
dockerfile_content = f"""
266234
FROM {base_image}
@@ -271,18 +239,26 @@ def build_from_base_image(
271239
272240
RUN pip install .
273241
274-
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/{get_main_file_name(connector_dir)}"
275-
ENTRYPOINT ["python", "/airbyte/integration_code/{get_main_file_name(connector_dir)}"]
242+
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/{main_file}"
243+
ENTRYPOINT ["python", "/airbyte/integration_code/{main_file}"]
276244
"""
277245

278-
dockerfile_path = temp_dir_path / "Dockerfile"
279-
dockerfile_path.write_text(dockerfile_content)
246+
dockerignore_content = """
247+
**/__pycache__
248+
**/.pytest_cache
249+
**/.venv
250+
**/.coverage
251+
**/venv
252+
**/.idea
253+
**/.vscode
254+
**/.DS_Store
255+
**/node_modules
256+
**/.git
257+
build/docker
258+
"""
280259

281-
for item in connector_dir.iterdir():
282-
if item.is_dir():
283-
shutil.copytree(item, temp_dir_path / item.name)
284-
else:
285-
shutil.copy2(item, temp_dir_path / item.name)
260+
dockerfile_path.write_text(dockerfile_content)
261+
dockerignore_path.write_text(dockerignore_content)
286262

287263
build_cmd = [
288264
"docker",
@@ -296,8 +272,12 @@ def build_from_base_image(
296272
f"io.airbyte.version={metadata.dockerImageTag}",
297273
"--label",
298274
f"io.airbyte.name={metadata.dockerRepository}",
275+
"--file",
276+
str(dockerfile_path),
277+
"--ignorefile",
278+
str(dockerignore_path),
299279
"--load", # Load the image into the local Docker daemon
300-
str(temp_dir_path),
280+
str(connector_dir),
301281
]
302282

303283
try:
@@ -307,6 +287,26 @@ def build_from_base_image(
307287
except subprocess.CalledProcessError as e:
308288
logger.error(f"Failed to build image: {e}")
309289
raise
290+
finally:
291+
if dockerfile_path.exists():
292+
try:
293+
dockerfile_path.unlink()
294+
logger.debug(f"Cleaned up temporary file: {dockerfile_path}")
295+
except Exception as e:
296+
logger.warning(f"Failed to clean up temporary file {dockerfile_path}: {e}")
297+
298+
if dockerignore_path.exists():
299+
try:
300+
dockerignore_path.unlink()
301+
logger.debug(f"Cleaned up temporary file: {dockerignore_path}")
302+
except Exception as e:
303+
logger.warning(f"Failed to clean up temporary file {dockerignore_path}: {e}")
304+
305+
try:
306+
docker_dir.rmdir()
307+
logger.debug(f"Removed empty directory: {docker_dir}")
308+
except Exception:
309+
pass
310310

311311

312312
def verify_image(image_name: str) -> bool:
@@ -341,26 +341,28 @@ def verify_image(image_name: str) -> bool:
341341
return True
342342

343343

344-
def run_command(args: List[str]) -> int:
344+
def run_command(connector_dir: Path, tag: str, no_verify: bool, verbose: bool) -> int:
345345
"""Run the build command with the given arguments.
346346
347347
Args:
348-
args: Command line arguments.
348+
connector_dir: Path to the connector directory.
349+
tag: Tag to apply to the built image.
350+
no_verify: Whether to skip verification of the built image.
351+
verbose: Whether to enable verbose logging.
349352
350353
Returns:
351354
Exit code (0 for success, non-zero for failure).
352355
"""
353356
try:
354-
parsed_args = parse_args(args)
355-
set_up_logging(parsed_args.verbose)
357+
set_up_logging(verbose)
356358

357359
if not verify_docker_installation():
358360
logger.error(
359361
"Docker is not installed or not running. Please install Docker and try again."
360362
)
361363
return 1
362364

363-
connector_dir = Path(parsed_args.connector_dir).absolute()
365+
connector_dir = connector_dir.absolute()
364366

365367
if not connector_dir.exists():
366368
logger.error(f"Connector directory not found: {connector_dir}")
@@ -374,23 +376,20 @@ def run_command(args: List[str]) -> int:
374376
logger.error(f"Error reading connector metadata: {e}")
375377
return 1
376378

377-
language = infer_connector_language(metadata, connector_dir)
378-
logger.info(f"Detected connector language: {language}")
379-
380379
try:
381380
platforms = "linux/amd64,linux/arm64"
382381
logger.info(f"Building for platforms: {platforms}")
383382

384383
if metadata.connectorBuildOptions and metadata.connectorBuildOptions.baseImage:
385384
image_name = build_from_base_image(
386-
connector_dir, metadata, parsed_args.tag, platforms
385+
connector_dir, metadata, tag, platforms
387386
)
388387
else:
389388
image_name = build_from_dockerfile(
390-
connector_dir, metadata, parsed_args.tag, platforms
389+
connector_dir, metadata, tag, platforms
391390
)
392391

393-
if not parsed_args.no_verify:
392+
if not no_verify:
394393
if verify_image(image_name):
395394
logger.info(f"Build completed successfully: {image_name}")
396395
return 0
@@ -407,7 +406,7 @@ def run_command(args: List[str]) -> int:
407406

408407
except Exception as e:
409408
logger.error(f"Unexpected error: {e}")
410-
if parsed_args and parsed_args.verbose:
409+
if verbose:
411410
import traceback
412411

413412
logger.error(traceback.format_exc())
@@ -416,7 +415,22 @@ def run_command(args: List[str]) -> int:
416415

417416
def run() -> None:
418417
"""Entry point for the airbyte-cdk build command."""
419-
sys.exit(run_command(sys.argv[1:]))
418+
import argparse
419+
420+
parser = argparse.ArgumentParser(description="Build connector Docker images")
421+
parser.add_argument("connector_dir", type=str, help="Path to the connector directory")
422+
parser.add_argument("--tag", type=str, default="dev", help="Tag to apply to the built image (default: dev)")
423+
parser.add_argument("--no-verify", action="store_true", help="Skip verification of the built image")
424+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose logging")
425+
426+
args = parser.parse_args(sys.argv[1:])
427+
428+
sys.exit(run_command(
429+
connector_dir=Path(args.connector_dir),
430+
tag=args.tag,
431+
no_verify=args.no_verify,
432+
verbose=args.verbose
433+
))
420434

421435

422436
if __name__ == "__main__":

airbyte_cdk/cli/commands/image.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from airbyte_cdk.cli.build._run import (
99
build_from_base_image,
1010
build_from_dockerfile,
11-
infer_connector_language,
1211
read_metadata,
1312
set_up_logging,
1413
verify_docker_installation,
@@ -49,8 +48,10 @@ def build(connector_dir: Path, tag: str, no_verify: bool, verbose: bool) -> None
4948
click.echo(f"Connector: {metadata.dockerRepository}")
5049
click.echo(f"Version: {metadata.dockerImageTag}")
5150

52-
language = infer_connector_language(metadata, connector_dir)
53-
click.echo(f"Detected connector language: {language}")
51+
if metadata.language:
52+
click.echo(f"Connector language from metadata: {metadata.language}")
53+
else:
54+
click.echo("Connector language not specified in metadata")
5455

5556
platforms = "linux/amd64,linux/arm64"
5657
click.echo(f"Building for platforms: {platforms}")

docs/cli/index.md

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)