# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
import logging
from pathlib import Path
from typing import List, Optional
from azure.ai.ml.constants._common import DefaultOpenEncoding
from azure.ai.ml.constants._endpoint import LocalEndpointConstants
from .dockerfile_instructions import Cmd, Copy, From, Run, Workdir
module_logger = logging.getLogger(__name__)
class DockerfileResolver(object):
"""Represents the contents of a Dockerfile and handles writing the Dockerfile to User's system.
:param docker_base_image: name of local endpoint
:type docker_base_image: str
:param docker_conda_file_name: name of conda file to copy into docker image
:type docker_conda_file_name: str
:param docker_port: port to expose in docker image
:type docker_port: str
:param docker_azureml_app_path: path in docker image to user's azureml app
:type docker_azureml_app_path: (str, optional)
"""
def __init__(
self,
docker_base_image: str,
dockerfile: str,
docker_conda_file_name: Optional[str] = None,
docker_port: Optional[str] = None,
docker_azureml_app_path: Optional[str] = None,
install_debugpy: bool = False,
):
"""Constructor of a Dockerfile object.
:param docker_base_image: base image
:type docker_base_image: str
:param dockerfile: contents of dockerfile
:type dockerfile: str
:param docker_conda_file_name: name of local endpoint
:type docker_conda_file_name: str
:param docker_port: port to expose in docker image
:type docker_port: str
:param docker_azureml_app_path: name of local deployment
:type docker_azureml_app_path: (str, optional)
:return DockerfileResolver:
"""
self._instructions: List[object] = []
self._local_dockerfile_path: Optional[str] = None
self._dockerfile = dockerfile
self._docker_base_image = docker_base_image
self._docker_conda_file_name = docker_conda_file_name
self._docker_azureml_app_path = docker_azureml_app_path
self._docker_port = docker_port
self._construct(install_debugpy=install_debugpy)
@property
def local_path(self) -> Optional[str]:
"""Returns the local dockerfile path.
:return: str
"""
return self._local_dockerfile_path
def __str__(self) -> str:
"""Override DockerfileResolver str() built-in func to return the Dockerfile contents as a string.
:return: Dockerfile Contents
:rtype: str
"""
return "" if len(self._instructions) == 0 else "\n".join([str(instr) for instr in self._instructions])
def _construct(self, install_debugpy: bool = False) -> None:
"""Internal use only.
Constructs the Dockerfile instructions based on properties.
:param install_debugpy: Whether to install debugpy. Defaults to False.
:type install_debugpy: bool
"""
self._instructions = []
if self._docker_base_image:
self._instructions = [From(self._docker_base_image)]
else:
self._instructions = [self._dockerfile]
if self._docker_port:
self._instructions.extend(
[
Run(f"mkdir -p {self._docker_azureml_app_path}"),
Workdir(str(self._docker_azureml_app_path)),
]
)
if self._docker_conda_file_name and self._docker_azureml_app_path:
self._instructions.extend(
[
Copy(
[
f"{self._docker_conda_file_name}",
],
self._docker_azureml_app_path,
),
Run(
(
f"conda env create -n {LocalEndpointConstants.CONDA_ENV_NAME} "
f"--file {self._docker_conda_file_name}"
)
),
]
)
if install_debugpy:
self._instructions.extend(
[Run(f"conda run -n {LocalEndpointConstants.CONDA_ENV_NAME} pip install debugpy")]
)
self._instructions.extend(
[
Cmd(
[
"conda",
"run",
"--no-capture-output",
"-n",
LocalEndpointConstants.CONDA_ENV_NAME,
"runsvdir",
"/var/runit",
]
),
]
)
else:
if install_debugpy:
self._instructions.extend([Run("pip install debugpy")])
self._instructions.extend(
[
Cmd(["runsvdir", "/var/runit"]),
]
)
def write_file(self, directory_path: str, file_prefix: Optional[str] = None) -> None:
"""Writes this Dockerfile to a file in provided directory and file name prefix.
:param directory_path: absolute path of local directory to write Dockerfile.
:type directory_path: str
:param file_prefix: name of Dockerfile prefix
:type file_prefix: str
"""
file_name = f"{file_prefix}.Dockerfile" if file_prefix else "Dockerfile"
self._local_dockerfile_path = str(Path(directory_path, file_name).resolve())
with open(self._local_dockerfile_path, "w", encoding=DefaultOpenEncoding.WRITE) as f:
f.write(f"{str(self)}\n")