Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/distro_image_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
- name: Run fboss-image build
run: ./fboss-image/distro_cli/fboss-image build fboss-image/from_source.json

- name: Run distro_cli tests
run: |
docker exec ${USER}-fboss-image-builder ctest -R distro_cli -V

- name: Cleanup
if: always()
run: |
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1154,3 +1154,6 @@ if (GITHUB_ACTIONS_BUILD)
fsdb_all_services
)
endif()

# FBOSS Image Builder distro_cli tests
include(cmake/FbossImageDistroCliTests.cmake)
36 changes: 36 additions & 0 deletions cmake/FbossImageDistroCliTests.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CMake to build and test fboss-image/distro_cli

# In general, libraries and binaries in fboss/foo/bar are built by
# cmake/FooBar.cmake

include(FBPythonBinary)

file(GLOB DISTRO_CLI_TEST_SOURCES
"fboss-image/distro_cli/tests/*_test.py"
)

file(GLOB DISTRO_CLI_TEST_HELPERS
"fboss-image/distro_cli/tests/test_helpers.py"
)

file(GLOB_RECURSE DISTRO_CLI_LIB_SOURCES
"fboss-image/distro_cli/builder/*.py"
"fboss-image/distro_cli/cmds/*.py"
"fboss-image/distro_cli/lib/*.py"
"fboss-image/distro_cli/tools/*.py"
)

file(COPY "fboss-image/distro_cli/tests/data" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/fboss-image/distro_cli/tests")

add_fb_python_unittest(
distro_cli_tests
BASE_DIR "fboss-image"
SOURCES
${DISTRO_CLI_TEST_SOURCES}
${DISTRO_CLI_TEST_HELPERS}
${DISTRO_CLI_LIB_SOURCES}
ENV
"PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/fboss-image/distro_cli"
)

install_fb_python_executable(distro_cli_tests)
10 changes: 10 additions & 0 deletions fboss-image/distro_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ python3 -m unittest discover -s tests -p '*_test.py'
python3 -m unittest tests.cli_test
```

#### With CMake

```bash
# Build and run all tests
cmake --build . --target distro_cli_tests

# Run via CTest
ctest -R distro_cli -V
```

### Linting

```bash
Expand Down
11 changes: 11 additions & 0 deletions fboss-image/distro_cli/lib/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) 2004-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.

"""Constants for FBOSS image builder."""

# Docker image names
FBOSS_BUILDER_IMAGE = "fboss_builder"
101 changes: 101 additions & 0 deletions fboss-image/distro_cli/lib/docker/container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright (c) 2004-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.

"""Docker container lifecycle management."""

import logging
import subprocess
from pathlib import Path

logger = logging.getLogger(__name__)


def run_container(
image: str,
command: list[str],
volumes: dict[Path, Path] | None = None,
env: dict[str, str] | None = None,
privileged: bool = False,
interactive: bool = False,
ephemeral: bool = True,
working_dir: str | None = None,
name: str | None = None,
stdout=None,
stderr=None,
) -> int:
"""Run a command in a Docker container.

Args:
image: Docker image name to run
command: Command to execute in container (as list)
volumes: Dictionary mapping host paths to container paths
env: Dictionary of environment variables
privileged: Run container in privileged mode
interactive: Run container in interactive mode (-it)
ephemeral: Remove container after it exits (--rm, default: True)
working_dir: Working directory inside container
name: Name for the container
stdout: File object for stdout (default: inherit)
stderr: File object for stderr (default: inherit)

Returns:
Exit code from the container

Raises:
RuntimeError: If docker command fails to start
"""
logger.info(f"Running container from image: {image}")
logger.info(f"Executing: {command}")

# Build docker run command
cmd = ["docker", "run"]

# Start with default flags
cmd.append("--network=host")

# Add flags
if ephemeral:
cmd.append("--rm")

if interactive:
cmd.extend(["-i", "-t"])

if privileged:
cmd.append("--privileged")

if name:
cmd.extend(["--name", name])

if working_dir:
cmd.extend(["-w", working_dir])

if volumes:
for host_path, container_path in volumes.items():
cmd.extend(["-v", f"{host_path}:{container_path}"])

if env:
for key, value in env.items():
cmd.extend(["-e", f"{key}={value}"])

cmd.append(image)

# Finally, add the actual command to run
cmd.extend(command)

logger.debug(f"Running: {' '.join(str(c) for c in cmd)}")

try:
result = subprocess.run(
cmd,
stdout=stdout,
stderr=stderr,
check=False # Don't raise on non-zero exit
)
logger.info(f"Container exited with code: {result.returncode}")
return result.returncode
except FileNotFoundError:
raise RuntimeError("Docker command not found. Is Docker installed and in PATH?")
Loading