165 lines
5.6 KiB
Python
165 lines
5.6 KiB
Python
import os
|
|
import docker
|
|
import logging
|
|
|
|
from .version import BlenderVersion
|
|
from pathlib import Path
|
|
from docker.errors import BuildError, APIError, ContainerError, ImageNotFound
|
|
|
|
|
|
class BlenderDocker:
|
|
def __init__(self, blender: BlenderVersion, severity: int = logging.WARNING):
|
|
self._blender_version = blender
|
|
# Todo get a user config
|
|
self._docker_client = docker.DockerClient(base_url=f'unix:///var/run/docker.sock')
|
|
self.login()
|
|
self._docker_image = None
|
|
self._repository = 'stilobique/blender'
|
|
self._docker_image_name = self._blender_version.format_to_tag(self._repository)
|
|
|
|
self._docker_file = Path(os.getcwd(), 'Dockerfile')
|
|
if not self._docker_file.exists():
|
|
print(f'Dockerfile not found: {self._docker_file}')
|
|
exit(1)
|
|
|
|
self._args = {
|
|
'b3d_vs_major': f'{self._blender_version.major}',
|
|
'b3d_vs_minor': f'{self._blender_version.minor}.{self._blender_version.hotfix}',
|
|
}
|
|
|
|
self._severity = severity
|
|
|
|
def _get_logger(self, name: str) -> logging.Logger:
|
|
"""
|
|
Private Method to set a logger for a specific method set (by his name)
|
|
:param name: Make the logger file name.
|
|
:return:
|
|
"""
|
|
log_dir = Path('logs')
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
logger = logging.getLogger(f'blender_docker.{name}')
|
|
logger.setLevel(self._severity)
|
|
logger.propagate = False
|
|
|
|
if not logger.handlers:
|
|
handler = logging.FileHandler(log_dir / f'{name}.log')
|
|
handler.setFormatter(logging.Formatter(
|
|
'%(asctime)s | [%(levelname)s] %(message)s'
|
|
))
|
|
logger.addHandler(handler)
|
|
|
|
return logger
|
|
|
|
def login(self):
|
|
"""Login to the Docker Hub"""
|
|
try:
|
|
print('Login to Hub Docker.')
|
|
self._docker_client.login(
|
|
username=os.environ['DOCKER_HUB_USER'],
|
|
password=os.environ['DOCKER_HUB_PASSWORD']
|
|
)
|
|
|
|
except APIError as e:
|
|
print(f'Login failed with error: {e}')
|
|
exit(1)
|
|
|
|
def build(self):
|
|
"""Build the Blender Docker Image"""
|
|
logger = self._get_logger('build')
|
|
try:
|
|
print('Building Blender Docker Image')
|
|
logger.info('Building Blender Docker Image')
|
|
logger.info(f'\tBuilds withs args: {self._args}')
|
|
logger.info(f'\tTag generate: {self._docker_image_name}')
|
|
|
|
self._docker_image, build_logs = self._docker_client.images.build(
|
|
path=os.getcwd(),
|
|
# nocache=True,
|
|
buildargs=self._args,
|
|
tag=self._docker_image_name,
|
|
quiet=False,
|
|
rm=True,
|
|
forcerm=True
|
|
)
|
|
|
|
# Debug log:
|
|
for chunk in build_logs:
|
|
if 'stream' in chunk:
|
|
line = chunk['stream'].strip()
|
|
if line:
|
|
logger.debug(line)
|
|
|
|
elif 'error' in chunk:
|
|
logger.error(chunk['error'])
|
|
|
|
|
|
except BuildError as e:
|
|
logger.exception(f'Build docker image failed: {e}')
|
|
for line in e.build_log:
|
|
if 'stream' in line:
|
|
logger.debug(line['stream'].strip())
|
|
raise
|
|
|
|
@staticmethod
|
|
def tag(self):
|
|
print('\tSet Tags version and/or Latest')
|
|
pass
|
|
|
|
def test(self) -> bool:
|
|
"""
|
|
This method request an image (call the method build before) and try to execute the blender inside the container
|
|
to check if the program can be correctly executed.
|
|
Based on `docker run --rm stilobique/blender:5.1.2 ./blender --version`
|
|
:return: True if no issue are returned
|
|
"""
|
|
logger = self._get_logger('test')
|
|
try:
|
|
print(f'Testing your Blender Docker image {self._docker_image_name}')
|
|
logger.info(f'Testing your Blender Docker image {self._docker_image_name}.')
|
|
|
|
test = self._docker_client.containers.run(
|
|
name=f"Blender-Docker-Testing-{self._blender_version.tag_name_slugify()}",
|
|
image=self._docker_image_name,
|
|
command=['./blender', '--version'],
|
|
auto_remove=True,
|
|
remove=True,
|
|
detach=True,
|
|
stdout=True,
|
|
stderr=True,
|
|
stream=True,
|
|
)
|
|
result = test.wait()
|
|
exit_code = result.get('StatusCode')
|
|
|
|
logger.info(f'Exit code return: {exit_code}')
|
|
logger.info(test.logs(stdout=True, stderr=True).decode('utf-8', errors='replace').strip())
|
|
|
|
if exit_code == 0:
|
|
return True
|
|
|
|
print(f'Testing your Blender Docker image {self._docker_image_name} failed.')
|
|
return False
|
|
|
|
except ContainerError as e:
|
|
print(f'Container error: {e}')
|
|
exit(1)
|
|
|
|
except ImageNotFound as e:
|
|
print(f'Image not found: {e}')
|
|
exit(1)
|
|
|
|
except APIError as e:
|
|
print(f'API error: {e}')
|
|
for line in e.build_log:
|
|
if 'stream' in line:
|
|
print(line['stream'].strip())
|
|
exit(1)
|
|
|
|
def push(self):
|
|
self._docker_client.images.push(
|
|
repository=self._repository,
|
|
tag=self._blender_version.tag_name(),
|
|
stream=True,
|
|
)
|
|
|