Skip to content
Draft
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
25 changes: 25 additions & 0 deletions .github/workflows/pr-verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ jobs:
uv sync --locked --directory src/
uv run --directory src/ pytest -m "integration" -v

integration-tests-podman:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Install Podman
run: |
sudo apt-get update
sudo apt-get -y install podman podman-compose podman-docker

- name: Configure Podman socket
run: |
systemctl --user enable --now podman.socket
sudo rm -rf /var/run/docker.sock
sudo ln -s /run/user/$(id -u)/podman/podman.sock /var/run/docker.sock

- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0

- name: Run integration tests with Podman
run: |
uv sync --locked --directory src/
uv run --directory src/ pytest -m "integration" -v

code-quality:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions docker-compose.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
ALLOW_STOP: 1
CONTAINERS: 1
EXEC: 1
NETWORKS: 1
POST: 1
VERSION: 1
read_only: true
Expand Down
2 changes: 1 addition & 1 deletion src/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM ghcr.io/astral-sh/uv:0.9.10 AS uv-builder

FROM restic/restic:0.18.1
FROM docker.io/restic/restic:0.18.1

RUN apk update && apk add dcron

Expand Down
11 changes: 8 additions & 3 deletions src/restic_compose_backup/backup_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,28 @@ def run(
volumes: dict = None,
environment: dict = None,
labels: dict = None,
source_container_id: str = None,
network_names: set[str] = set(),
):
logger.info("Starting backup container")
client = utils.docker_client()

container = client.containers.run(
container = client.containers.create(
image,
command,
labels=labels,
detach=True,
environment=environment + ["BACKUP_PROCESS_CONTAINER=true"],
volumes=volumes,
network_mode=f"container:{source_container_id}", # reuse original container network for optional access to docker proxy
working_dir=os.getcwd(),
tty=True,
)

for network_name in network_names:
network = client.networks.get(network_name)
network.connect(container)

container.start()

logger.info("Backup process container: %s", container.name)
log_generator = container.logs(stdout=True, stderr=True, stream=True, follow=True)

Expand Down
2 changes: 1 addition & 1 deletion src/restic_compose_backup/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def backup(config, containers: RunningContainers):
command="rcb start-backup-process",
volumes=volumes,
environment=containers.this_container.environment,
source_container_id=containers.this_container.id,
network_names=containers.this_container.network_names,
labels={
containers.backup_process_label: "True",
"com.docker.compose.project": containers.project_name,
Expand Down
10 changes: 10 additions & 0 deletions src/restic_compose_backup/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ def __init__(self, data: dict):
self._include = self._parse_pattern(self.get_label(enums.LABEL_VOLUMES_INCLUDE))
self._exclude = self._parse_pattern(self.get_label(enums.LABEL_VOLUMES_EXCLUDE))

# Parse network information
network_settings: dict = data.get("NetworkSettings", {})
networks: dict = network_settings.get("Networks", {})
self._networks = networks

@property
def instance(self) -> "Container":
"""Container: Get a service specific subclass instance"""
Expand Down Expand Up @@ -73,6 +78,11 @@ def service_name(self) -> str:
"com.docker.compose.service", default=""
) or self.get_label("com.docker.swarm.service.name", default="")

@property
def network_names(self) -> set[str]:
"""set[str]: Set of network names the container is connected to"""
return set(self._networks.keys())

@property
def backup_process_label(self) -> str:
"""str: The unique backup process label for this project"""
Expand Down
31 changes: 27 additions & 4 deletions src/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,18 +487,41 @@ def backup_container_with_multi_project(
# Remove the old container
backup_cont.remove()

# Parse volumes from Binds format to volumes dict format
# Binds format: ["/host/path:/container/path:ro"]
# Volumes format: {"/host/path": {"bind": "/container/path", "mode": "ro"}}
volumes_dict = {}
for bind in host_config.get("Binds", []):
parts = bind.split(":")
if len(parts) >= 2:
host_path = parts[0]
container_path = parts[1]
mode = parts[2] if len(parts) > 2 else "rw"
volumes_dict[host_path] = {"bind": container_path, "mode": mode}

# Get network names
networks = list(container_info["NetworkSettings"]["Networks"].keys())

# Create a new container with the updated environment
# which is needed for the backup container to identify itself
# Note: We don't set hostname - Docker/Podman will set it to the container ID
new_container = docker_client.containers.create(
config["Image"],
command=config.get("Cmd"),
environment=env_list,
volumes=host_config.get("Binds", []),
network=list(container_info["NetworkSettings"]["Networks"].keys())[0]
if container_info["NetworkSettings"]["Networks"]
else None,
volumes=volumes_dict,
name=container_info["Name"].strip("/"),
labels=config.get("Labels", {}),
detach=True,
working_dir=config.get("WorkingDir"),
entrypoint=config.get("Entrypoint"),
)

# Connect to networks after creation (more compatible with Podman)
for network_name in networks:
network = docker_client.networks.get(network_name)
network.connect(new_container)

# Start the new container
new_container.start()

Expand Down
8 changes: 8 additions & 0 deletions src/tests/unit/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ def wrapper(*args, **kwargs):
"Status": "running",
"Running": True,
},
"NetworkSettings": {
"Networks": {
f"{project}_default": {
"NetworkID": "network-id",
"IPAddress": "10.0.0.1",
}
}
},
}
for container in containers
]
Expand Down
Loading