From 9d9e01e0908f253b88f49da909f69834d7e8ce11 Mon Sep 17 00:00:00 2001 From: Tamir Date: Mon, 24 Mar 2025 15:09:26 +0200 Subject: [PATCH 1/3] removed the option to name a container on creation --- datacrunch/containers/containers.py | 94 ++++++++++++++++--- .../container_deployments_example.py | 2 - .../containers/sglang_deployment_example.py | 1 - .../unit_tests/containers/test_containers.py | 11 ++- 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/datacrunch/containers/containers.py b/datacrunch/containers/containers.py index d005946..227fd0e 100644 --- a/datacrunch/containers/containers.py +++ b/datacrunch/containers/containers.py @@ -97,9 +97,31 @@ class VolumeMount: @dataclass_json @dataclass class Container: + """Container configuration for deployment creation and updates. + This class omits the name field which is managed by the system. + + :param image: Container image to use + :param exposed_port: Port to expose from the container + :param healthcheck: Optional health check configuration + :param entrypoint_overrides: Optional entrypoint override settings + :param env: Optional list of environment variables + :param volume_mounts: Optional list of volume mounts + """ + image: str + exposed_port: int + healthcheck: Optional[HealthcheckSettings] = None + entrypoint_overrides: Optional[EntrypointOverridesSettings] = None + env: Optional[List[EnvVar]] = None + volume_mounts: Optional[List[VolumeMount]] = None + + +@dataclass_json +@dataclass +class ContainerInfo: """Container configuration for deployments. + This class is read-only and includes the system-managed name field. - :param name: Name of the container + :param name: Name of the container (system-managed) :param image: Container image to use :param exposed_port: Port to expose from the container :param healthcheck: Optional health check configuration @@ -115,6 +137,24 @@ class Container: env: Optional[List[EnvVar]] = None volume_mounts: Optional[List[VolumeMount]] = None + @classmethod + def from_container(cls, name: str, container: Container) -> 'ContainerInfo': + """Create a ContainerInfo from a Container and a name. + + :param name: Name of the container + :param container: Container specification + :return: ContainerInfo instance + """ + return cls( + name=name, + image=container.image, + exposed_port=container.exposed_port, + healthcheck=container.healthcheck, + entrypoint_overrides=container.entrypoint_overrides, + env=container.env, + volume_mounts=container.volume_mounts + ) + @dataclass_json @dataclass @@ -224,7 +264,31 @@ class ScalingOptions: @dataclass_json(undefined=Undefined.EXCLUDE) @dataclass class Deployment: + """Configuration for creating or updating a container deployment. + This class uses Container instead of ContainerInfo to prevent name setting. + + :param name: Name of the deployment + :param container_registry_settings: Settings for accessing container registry + :param containers: List of container specifications in the deployment + :param compute: Compute resource configuration + :param is_spot: Whether is spot deployment + :param endpoint_base_url: Optional base URL for the deployment endpoint + :param scaling: Optional scaling configuration + """ + name: str + container_registry_settings: ContainerRegistrySettings + containers: List[Container] + compute: ComputeResource + is_spot: bool = False + endpoint_base_url: Optional[str] = None + scaling: Optional[ScalingOptions] = None + + +@dataclass_json(undefined=Undefined.EXCLUDE) +@dataclass +class DeploymentInfo: """Configuration for a container deployment. + This class is read-only and includes system-managed fields. :param name: Name of the deployment :param container_registry_settings: Settings for accessing container registry @@ -237,7 +301,7 @@ class Deployment: """ name: str container_registry_settings: ContainerRegistrySettings - containers: List[Container] + containers: List[ContainerInfo] compute: ComputeResource is_spot: bool = False endpoint_base_url: Optional[str] = None @@ -359,59 +423,59 @@ def __init__(self, http_client) -> None: """ self.client = http_client - def get_deployments(self) -> List[Deployment]: + def get_deployments(self) -> List[DeploymentInfo]: """Get all deployments :return: list of deployments - :rtype: List[Deployment] + :rtype: List[DeploymentInfo] """ response = self.client.get(CONTAINER_DEPLOYMENTS_ENDPOINT) - return [Deployment.from_dict(deployment, infer_missing=True) for deployment in response.json()] + return [DeploymentInfo.from_dict(deployment, infer_missing=True) for deployment in response.json()] - def get_deployment_by_name(self, deployment_name: str) -> Deployment: + def get_deployment_by_name(self, deployment_name: str) -> DeploymentInfo: """Get a deployment by name :param deployment_name: name of the deployment :type deployment_name: str :return: deployment - :rtype: Deployment + :rtype: DeploymentInfo """ response = self.client.get( f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}") - return Deployment.from_dict(response.json(), infer_missing=True) + return DeploymentInfo.from_dict(response.json(), infer_missing=True) def create_deployment( self, deployment: Deployment - ) -> Deployment: + ) -> DeploymentInfo: """Create a new deployment :param deployment: deployment configuration :type deployment: Deployment :return: created deployment - :rtype: Deployment + :rtype: DeploymentInfo """ response = self.client.post( CONTAINER_DEPLOYMENTS_ENDPOINT, deployment.to_dict() ) - return Deployment.from_dict(response.json(), infer_missing=True) + return DeploymentInfo.from_dict(response.json(), infer_missing=True) - def update_deployment(self, deployment_name: str, deployment: Deployment) -> Deployment: + def update_deployment(self, deployment_name: str, deployment: DeploymentInfo) -> DeploymentInfo: """Update an existing deployment :param deployment_name: name of the deployment to update :type deployment_name: str :param deployment: updated deployment - :type deployment: Deployment + :type deployment: DeploymentInfo :return: updated deployment - :rtype: Deployment + :rtype: DeploymentInfo """ response = self.client.patch( f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}", deployment.to_dict() ) - return Deployment.from_dict(response.json(), infer_missing=True) + return DeploymentInfo.from_dict(response.json(), infer_missing=True) def delete_deployment(self, deployment_name: str) -> None: """Delete a deployment diff --git a/examples/containers/container_deployments_example.py b/examples/containers/container_deployments_example.py index f0cd992..4ecb524 100644 --- a/examples/containers/container_deployments_example.py +++ b/examples/containers/container_deployments_example.py @@ -27,7 +27,6 @@ # Configuration constants DEPLOYMENT_NAME = "my-deployment" -CONTAINER_NAME = "my-app" IMAGE_NAME = "your-image-name:version" # Environment variables @@ -93,7 +92,6 @@ def main() -> None: # Create container configuration container = Container( - name=CONTAINER_NAME, image=IMAGE_NAME, exposed_port=80, healthcheck=HealthcheckSettings( diff --git a/examples/containers/sglang_deployment_example.py b/examples/containers/sglang_deployment_example.py index 43e7195..e6d5c23 100644 --- a/examples/containers/sglang_deployment_example.py +++ b/examples/containers/sglang_deployment_example.py @@ -206,7 +206,6 @@ def main() -> None: # Create container configuration container = Container( - name=CONTAINER_NAME, image=IMAGE_URL, exposed_port=30000, healthcheck=HealthcheckSettings( diff --git a/tests/unit_tests/containers/test_containers.py b/tests/unit_tests/containers/test_containers.py index 6067808..c329613 100644 --- a/tests/unit_tests/containers/test_containers.py +++ b/tests/unit_tests/containers/test_containers.py @@ -8,10 +8,12 @@ SECRETS_ENDPOINT, SERVERLESS_COMPUTE_RESOURCES_ENDPOINT, Container, + ContainerInfo, ContainerDeploymentStatus, ContainerRegistrySettings, ContainersService, Deployment, + DeploymentInfo, EnvVar, EnvVarType, EntrypointOverridesSettings, @@ -213,10 +215,10 @@ def test_get_deployments(self, containers_service, deployments_endpoint): # assert assert type(deployments) == list assert len(deployments) == 1 - assert type(deployment) == Deployment + assert type(deployment) == DeploymentInfo assert deployment.name == DEPLOYMENT_NAME assert len(deployment.containers) == 1 - assert type(deployment.containers[0]) == Container + assert type(deployment.containers[0]) == ContainerInfo assert type(deployment.compute) == ComputeResource assert deployment.compute.name == COMPUTE_RESOURCE_NAME assert responses.assert_call_count(deployments_endpoint, 1) is True @@ -236,7 +238,7 @@ def test_get_deployment_by_name(self, containers_service, deployments_endpoint): deployment = containers_service.get_deployment_by_name(DEPLOYMENT_NAME) # assert - assert type(deployment) == Deployment + assert type(deployment) == DeploymentInfo assert deployment.name == DEPLOYMENT_NAME assert len(deployment.containers) == 1 assert deployment.containers[0].name == CONTAINER_NAME @@ -275,7 +277,6 @@ def test_create_deployment(self, containers_service, deployments_endpoint): # create deployment object container = Container( - name=CONTAINER_NAME, image="nginx:latest", exposed_port=80, healthcheck=HealthcheckSettings( @@ -304,7 +305,7 @@ def test_create_deployment(self, containers_service, deployments_endpoint): created_deployment = containers_service.create_deployment(deployment) # assert - assert type(created_deployment) == Deployment + assert type(created_deployment) == DeploymentInfo assert created_deployment.name == DEPLOYMENT_NAME assert len(created_deployment.containers) == 1 assert created_deployment.containers[0].name == CONTAINER_NAME From a5edb6609cbaf900565b78096113dbb026f6b493 Mon Sep 17 00:00:00 2001 From: Tamir Date: Mon, 24 Mar 2025 15:17:06 +0200 Subject: [PATCH 2/3] fix test --- tests/unit_tests/containers/test_containers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/containers/test_containers.py b/tests/unit_tests/containers/test_containers.py index c329613..030ccbe 100644 --- a/tests/unit_tests/containers/test_containers.py +++ b/tests/unit_tests/containers/test_containers.py @@ -275,7 +275,6 @@ def test_create_deployment(self, containers_service, deployments_endpoint): status=200 ) - # create deployment object container = Container( image="nginx:latest", exposed_port=80, @@ -293,6 +292,7 @@ def test_create_deployment(self, containers_service, deployments_endpoint): container_registry_settings = ContainerRegistrySettings( is_private=False) + # create deployment object deployment = Deployment( name=DEPLOYMENT_NAME, container_registry_settings=container_registry_settings, @@ -324,7 +324,7 @@ def test_update_deployment(self, containers_service, deployments_endpoint): ) # create deployment object - container = Container( + container = ContainerInfo( name=CONTAINER_NAME, image="nginx:latest", exposed_port=80 @@ -335,7 +335,7 @@ def test_update_deployment(self, containers_service, deployments_endpoint): compute = ComputeResource(name=COMPUTE_RESOURCE_NAME, size=1) - deployment = Deployment( + deployment = DeploymentInfo( name=DEPLOYMENT_NAME, container_registry_settings=container_registry_settings, containers=[container], @@ -347,7 +347,7 @@ def test_update_deployment(self, containers_service, deployments_endpoint): DEPLOYMENT_NAME, deployment) # assert - assert type(updated_deployment) == Deployment + assert type(updated_deployment) == DeploymentInfo assert updated_deployment.name == DEPLOYMENT_NAME assert len(updated_deployment.containers) == 1 assert updated_deployment.containers[0].name == CONTAINER_NAME From fe92b9974fdda9a071658618f445162ffa4bbbac Mon Sep 17 00:00:00 2001 From: Tamir Date: Mon, 24 Mar 2025 15:27:08 +0200 Subject: [PATCH 3/3] removed unused method --- datacrunch/containers/containers.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/datacrunch/containers/containers.py b/datacrunch/containers/containers.py index 227fd0e..ef7f890 100644 --- a/datacrunch/containers/containers.py +++ b/datacrunch/containers/containers.py @@ -137,24 +137,6 @@ class ContainerInfo: env: Optional[List[EnvVar]] = None volume_mounts: Optional[List[VolumeMount]] = None - @classmethod - def from_container(cls, name: str, container: Container) -> 'ContainerInfo': - """Create a ContainerInfo from a Container and a name. - - :param name: Name of the container - :param container: Container specification - :return: ContainerInfo instance - """ - return cls( - name=name, - image=container.image, - exposed_port=container.exposed_port, - healthcheck=container.healthcheck, - entrypoint_overrides=container.entrypoint_overrides, - env=container.env, - volume_mounts=container.volume_mounts - ) - @dataclass_json @dataclass