Skip to content
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

Update config.py #439

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
141 changes: 126 additions & 15 deletions podman/domain/config.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Read containers.conf file."""
"""Read containers.conf file and create Podman containers with network options."""

import sys
import urllib
from pathlib import Path
from typing import Dict, Optional
import json
import logging

from podman.api import cached_property
from podman.api.path_utils import get_xdg_config_home
from podman import PodmanClient
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is giving a circular import. Please, remove it. There are ways to call the client such as podman_client = self.client

from podman.errors import PodmanError

if sys.version_info >= (3, 11):
from tomllib import loads as toml_loads
Expand Down Expand Up @@ -65,7 +68,6 @@ class PodmanConfig:

def __init__(self, path: Optional[str] = None):
"""Read Podman configuration from users XDG_CONFIG_HOME."""

self.is_default = False
if path is None:
home = Path(get_xdg_config_home())
Expand Down Expand Up @@ -101,7 +103,7 @@ def __init__(self, path: Optional[str] = None):
) from e

# Read the old toml file configuration
if self.is_default and old_toml_file.exists():
if self.is_default and old_toml_file and old_toml_file.exists():
with old_toml_file.open(encoding='utf-8') as file:
buffer = file.read()
loaded_toml = toml_loads(buffer)
Expand All @@ -124,17 +126,17 @@ def id(self): # pylint: disable=invalid-name
def services(self):
"""Dict[str, ServiceConnection]: Returns list of service connections.

Examples:
podman_config = PodmanConfig()
address = podman_config.services["testing"]
print(f"Testing service address {address}")
"""
Examples:
podman_config = PodmanConfig()
address = podman_config.services["testing"]
print(f"Testing service address {address}")
"""
services: Dict[str, ServiceConnection] = {}

# read the keys of the toml file first
engine = self.attrs.get("engine")
if engine:
destinations = engine.get("service_destinations")
destinations = engine.get("service_destinations", {})
for key in destinations:
connection = ServiceConnection(key, attrs=destinations[key])
services[key] = connection
Expand All @@ -145,7 +147,7 @@ def services(self):
# json one
connection = self.attrs.get("Connection")
if connection:
destinations = connection.get("Connections")
destinations = connection.get("Connections", {})
for key in destinations:
connection = ServiceConnection(key, attrs=destinations[key])
services[key] = connection
Expand All @@ -155,19 +157,128 @@ def services(self):
@cached_property
def active_service(self):
"""Optional[ServiceConnection]: Returns active connection."""

# read the new json file format
connection = self.attrs.get("Connection")
if connection:
active = connection.get("Default")
destinations = connection.get("Connections")
return ServiceConnection(active, attrs=destinations[active])
destinations = connection.get("Connections", {})
if active in destinations:
return ServiceConnection(active, attrs=destinations[active])

# if we are here, that means there was no default in the new json file
engine = self.attrs.get("engine")
if engine:
active = engine.get("active_service")
destinations = engine.get("service_destinations")
return ServiceConnection(active, attrs=destinations[active])
destinations = engine.get("service_destinations", {})
if active in destinations:
return ServiceConnection(active, attrs=destinations[active])

return None

@cached_property
def network_options(self) -> Dict[str, list]:
"""
Retrieves network options for all configured networks.

Returns:
Dict[str, list]: A dictionary where keys are network names and values are lists of options.
"""
network_opts = {}
network_config = self.attrs.get("network", {})
for network_name, config in network_config.items():
# Assuming network options are stored as 'pasta_options', 'bridge_options', etc.
options_key = f"{network_name}_options"
if options_key in network_config:
network_opts[network_name] = network_config[options_key]
Comment on lines +187 to +192
Copy link
Member

Choose a reason for hiding this comment

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

besides pasta_options none of the other fields exists in containers.conf so it should not invent anything.

return network_opts


def create_container_with_pasta(network_name: str, port_mapping: str, image: str, container_name: str, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

There is nothing special about pasta API wise, all the networks settings should be able to be set already via the normal API.

"""
Creates and starts a Podman container with specified Pasta network options.

Args:
network_name (str): The name of the network (e.g., 'pasta').
port_mapping (str): The port mapping in 'host_port:container_port' format (e.g., '3128:3128').
image (str): The container image to use.
container_name (str): The name for the container.
**kwargs: Additional keyword arguments for container creation.

Returns:
podman.containers.Container: The created and started container instance.
"""
try:
# Initialize Podman client
podman_client = PodmanClient(base_url="unix://run/podman/io.podman")
Copy link
Contributor

Choose a reason for hiding this comment

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

This line relates to the fix above

logging.debug("Podman client initialized.")

# Read Podman configuration
podman_config = PodmanConfig()
logging.debug("Podman configuration loaded.")

# Extract network options for the specified network
network_opts = podman_config.network_options.get(network_name, [])
logging.debug(f"Original network options for '{network_name}': {network_opts}")

# Append the port mapping using '-T' flag
network_opts += ["-T", port_mapping]
logging.debug(f"Updated network options for '{network_name}': {network_opts}")
Comment on lines +223 to +225
Copy link
Member

Choose a reason for hiding this comment

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

This looks wrong, -T maps container ports to the host and you allow only one string. Overall I don't follow this at all.


# Create the container with network options
logging.info(f"Creating container '{container_name}' with image '{image}' on network '{network_name}' with options {network_opts}")

container = podman_client.containers.create(
image=image,
name=container_name,
networks={network_name: {}},
network_options={network_name: network_opts},
**kwargs # Include other parameters like environment variables, volumes, etc.
)
logging.info(f"Container '{container_name}' created successfully.")

# Start the container
container.start()
logging.info(f"Container '{container_name}' started successfully.")

return container

except PodmanError as pe:
logging.error(f"Podman error occurred: {pe}")
raise
except PermissionError as pe:
logging.error(f"Permission error: {pe}")
raise
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
raise


if __name__ == "__main__":
try:
# Define container parameters
network_name = "pasta" # Replace with your network name if different
port_mapping = "3128:3128" # Host port : Container port
image = "your-image" # Replace with your actual image
container_name = "your-container" # Replace with your desired container name

# Additional parameters (if any)
additional_kwargs = {
# Example: Environment variables
# "environment": {"ENV_VAR": "value"},
# Example: Volume mounts
# "volumes": {"/host/path": {"bind": "/container/path", "mode": "rw"}},
}

# Create and start the container
container = create_container_with_pasta(
network_name=network_name,
port_mapping=port_mapping,
image=image,
container_name=container_name,
**additional_kwargs
)

print(f"Container '{container.name}' is running with TCP namespace forwarding from host port {port_mapping.split(':')[0]} to container port {port_mapping.split(':')[1]}.")

except Exception as e:
print(f"Failed to create and start container: {e}")
Comment on lines +256 to +284
Copy link
Contributor

Choose a reason for hiding this comment

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

I am perplexed about the if __name__ == "__main__": block. What would be it's use case?

There are circular imports failures related to it, so I would remove it. The example looks nice though so actually it could be documented.