2018-12-19 02:08:13 +01:00
|
|
|
"""
|
2023-08-19 13:47:43 +02:00
|
|
|
Platydock is a helper script that allows you to automatically create a
|
|
|
|
Dockerfile for Platypush starting from a configuration file.
|
2018-12-19 02:08:13 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
import argparse
|
2023-08-19 13:47:43 +02:00
|
|
|
import inspect
|
2018-12-19 02:08:13 +01:00
|
|
|
import os
|
2021-09-16 17:53:40 +02:00
|
|
|
import pathlib
|
2023-08-20 13:31:13 +02:00
|
|
|
import re
|
2018-12-19 02:08:13 +01:00
|
|
|
import sys
|
2023-08-20 13:31:13 +02:00
|
|
|
import textwrap
|
2023-08-22 02:49:05 +02:00
|
|
|
from typing import Iterable, Sequence
|
2018-12-19 02:08:13 +01:00
|
|
|
|
|
|
|
from platypush.config import Config
|
2023-08-20 02:35:25 +02:00
|
|
|
from platypush.utils.manifest import (
|
|
|
|
BaseImage,
|
|
|
|
Dependencies,
|
|
|
|
InstallContext,
|
|
|
|
PackageManagers,
|
|
|
|
)
|
2023-08-19 13:47:43 +02:00
|
|
|
|
|
|
|
|
2023-08-20 01:54:55 +02:00
|
|
|
# pylint: disable=too-few-public-methods
|
2023-08-19 22:46:37 +02:00
|
|
|
class DockerfileGenerator:
|
|
|
|
"""
|
|
|
|
Generate a Dockerfile from on a configuration file.
|
2023-08-19 13:47:43 +02:00
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
:param cfgfile: Path to the configuration file.
|
|
|
|
:param image: The base image to use.
|
|
|
|
"""
|
2023-08-19 13:47:43 +02:00
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
_pkg_manager_by_base_image = {
|
|
|
|
BaseImage.ALPINE: PackageManagers.APK,
|
2023-08-20 01:54:55 +02:00
|
|
|
BaseImage.DEBIAN: PackageManagers.APT,
|
2023-08-19 22:46:37 +02:00
|
|
|
BaseImage.UBUNTU: PackageManagers.APT,
|
|
|
|
}
|
2023-08-19 13:47:43 +02:00
|
|
|
|
2023-08-20 14:05:22 +02:00
|
|
|
_header = textwrap.dedent(
|
|
|
|
"""
|
|
|
|
# This Dockerfile was automatically generated by Platydock.
|
|
|
|
#
|
|
|
|
# You can build a Platypush image from it by running
|
|
|
|
# `docker build -t platypush .` in the same folder as this file,
|
|
|
|
# or copy it to the root a Platypush source folder to install the
|
|
|
|
# checked out version instead of downloading it first.
|
|
|
|
#
|
|
|
|
# You can then run your new image through:
|
|
|
|
# docker run --rm --name platypush \\
|
|
|
|
# -v /path/to/your/config/dir:/etc/platypush \\
|
|
|
|
# -v /path/to/your/workdir:/var/lib/platypush \\
|
|
|
|
# -p 8080:8080 \\
|
|
|
|
# platypush\n
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
_footer = textwrap.dedent(
|
|
|
|
"""
|
|
|
|
# You can customize the name of your installation by passing
|
|
|
|
# --device-id=... to the launched command.
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
2023-08-20 13:31:13 +02:00
|
|
|
def __init__(self, cfgfile: str, image: BaseImage, gitref: str) -> None:
|
2023-08-19 22:46:37 +02:00
|
|
|
self.cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
|
|
|
|
self.image = image
|
2023-08-20 13:31:13 +02:00
|
|
|
self.gitref = gitref
|
2023-08-19 22:46:37 +02:00
|
|
|
|
|
|
|
def generate(self) -> str:
|
|
|
|
"""
|
|
|
|
Generate a Dockerfile based on a configuration file.
|
|
|
|
|
|
|
|
:return: The content of the generated Dockerfile.
|
|
|
|
"""
|
2023-08-20 01:54:55 +02:00
|
|
|
import platypush
|
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
Config.init(self.cfgfile)
|
|
|
|
new_file_lines = []
|
|
|
|
ports = self._get_exposed_ports()
|
|
|
|
pkg_manager = self._pkg_manager_by_base_image[self.image]
|
|
|
|
deps = Dependencies.from_config(
|
|
|
|
self.cfgfile,
|
|
|
|
pkg_manager=pkg_manager,
|
|
|
|
install_context=InstallContext.DOCKER,
|
2023-08-20 02:35:25 +02:00
|
|
|
base_image=self.image,
|
2023-08-19 22:46:37 +02:00
|
|
|
)
|
2023-08-19 13:47:43 +02:00
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
is_after_expose_cmd = False
|
|
|
|
base_file = os.path.join(
|
2023-08-20 01:54:55 +02:00
|
|
|
str(pathlib.Path(inspect.getfile(platypush)).parent),
|
|
|
|
'install',
|
2023-08-19 22:46:37 +02:00
|
|
|
'docker',
|
|
|
|
f'{self.image}.Dockerfile',
|
2023-05-07 12:08:28 +02:00
|
|
|
)
|
2023-08-19 22:46:37 +02:00
|
|
|
|
|
|
|
with open(base_file, 'r') as f:
|
|
|
|
file_lines = [line.rstrip() for line in f.readlines()]
|
|
|
|
|
2023-08-20 14:05:22 +02:00
|
|
|
new_file_lines.extend(self._header.split('\n'))
|
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
for line in file_lines:
|
2023-08-20 13:31:13 +02:00
|
|
|
if re.match(
|
|
|
|
r'RUN /install/platypush/install/scripts/[A-Za-z0-9_-]+/install.sh',
|
|
|
|
line.strip(),
|
|
|
|
):
|
|
|
|
new_file_lines.append(self._generate_git_clone_command())
|
|
|
|
elif line.startswith('RUN cd /install '):
|
2023-08-19 22:46:37 +02:00
|
|
|
for new_line in deps.before:
|
|
|
|
new_file_lines.append('RUN ' + new_line)
|
|
|
|
|
|
|
|
for new_line in deps.to_pkg_install_commands():
|
|
|
|
new_file_lines.append('RUN ' + new_line)
|
|
|
|
elif line == 'RUN rm -rf /install':
|
|
|
|
for new_line in deps.to_pip_install_commands():
|
|
|
|
new_file_lines.append('RUN ' + new_line)
|
|
|
|
|
|
|
|
for new_line in deps.after:
|
|
|
|
new_file_lines.append('RUN' + new_line)
|
|
|
|
elif line.startswith('EXPOSE ') and ports:
|
|
|
|
if not is_after_expose_cmd:
|
|
|
|
new_file_lines.extend([f'EXPOSE {port}' for port in ports])
|
|
|
|
is_after_expose_cmd = True
|
|
|
|
|
|
|
|
continue
|
2023-08-20 14:05:22 +02:00
|
|
|
elif line.startswith('CMD'):
|
|
|
|
new_file_lines.extend(self._footer.split('\n'))
|
2023-08-19 22:46:37 +02:00
|
|
|
|
|
|
|
new_file_lines.append(line)
|
|
|
|
|
|
|
|
return '\n'.join(new_file_lines)
|
|
|
|
|
2023-08-20 13:31:13 +02:00
|
|
|
def _generate_git_clone_command(self) -> str:
|
2023-08-22 02:49:05 +02:00
|
|
|
"""
|
|
|
|
Generates a git clone command in Dockerfile that checks out the repo
|
|
|
|
and the right git reference, if the application sources aren't already
|
|
|
|
available under /install.
|
|
|
|
"""
|
2023-08-20 13:31:13 +02:00
|
|
|
pkg_manager = self._pkg_manager_by_base_image[self.image]
|
|
|
|
install_cmd = ' '.join(pkg_manager.value.install)
|
|
|
|
uninstall_cmd = ' '.join(pkg_manager.value.uninstall)
|
|
|
|
return textwrap.dedent(
|
|
|
|
f"""
|
|
|
|
RUN if [ ! -f "/install/setup.py" ]; then \\
|
|
|
|
echo "Platypush source not found under the current directory, downloading it" && \\
|
|
|
|
{install_cmd} git && \\
|
|
|
|
rm -rf /install && \\
|
|
|
|
git clone https://github.com/BlackLight/platypush.git /install && \\
|
|
|
|
cd /install && \\
|
|
|
|
git checkout {self.gitref} && \\
|
|
|
|
{uninstall_cmd} git; \\
|
|
|
|
fi
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
2023-08-22 02:49:05 +02:00
|
|
|
@classmethod
|
|
|
|
def from_cmdline(cls, args: Sequence[str]) -> 'DockerfileGenerator':
|
|
|
|
"""
|
|
|
|
Create a DockerfileGenerator instance from command line arguments.
|
|
|
|
|
|
|
|
:param args: Command line arguments.
|
|
|
|
:return: A DockerfileGenerator instance.
|
|
|
|
"""
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
prog='platydock',
|
|
|
|
add_help=False,
|
|
|
|
description='Create a Platypush Dockerfile from a config.yaml.',
|
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
'-h', '--help', dest='show_usage', action='store_true', help='Show usage'
|
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
'cfgfile',
|
|
|
|
type=str,
|
|
|
|
nargs='?',
|
|
|
|
help='The path to the configuration file. If not specified a minimal '
|
|
|
|
'Dockerfile with no extra dependencies will be generated.',
|
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
'--image',
|
|
|
|
'-i',
|
|
|
|
dest='image',
|
|
|
|
required=False,
|
|
|
|
type=BaseImage,
|
|
|
|
choices=list(BaseImage),
|
|
|
|
default=BaseImage.ALPINE,
|
|
|
|
help='Base image to use for the Dockerfile.',
|
|
|
|
)
|
|
|
|
|
|
|
|
parser.add_argument(
|
|
|
|
'--ref',
|
|
|
|
'-r',
|
|
|
|
dest='gitref',
|
|
|
|
required=False,
|
|
|
|
type=str,
|
|
|
|
default='master',
|
|
|
|
help='If platydock is not run from a Platypush installation directory, '
|
|
|
|
'it will clone the source via git. You can specify through this '
|
|
|
|
'option which branch, tag or commit hash to use. Defaults to master.',
|
|
|
|
)
|
|
|
|
|
|
|
|
opts, _ = parser.parse_known_args(args)
|
|
|
|
if opts.show_usage:
|
|
|
|
parser.print_help()
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
if not opts.cfgfile:
|
|
|
|
opts.cfgfile = os.path.join(
|
|
|
|
str(pathlib.Path(inspect.getfile(Config)).parent),
|
|
|
|
'config.auto.yaml',
|
|
|
|
)
|
|
|
|
|
|
|
|
print(
|
|
|
|
f'No configuration file specified. Using {opts.cfgfile}.',
|
|
|
|
file=sys.stderr,
|
|
|
|
)
|
|
|
|
|
|
|
|
return cls(opts.cfgfile, image=opts.image, gitref=opts.gitref)
|
|
|
|
|
2023-08-19 22:46:37 +02:00
|
|
|
@staticmethod
|
|
|
|
def _get_exposed_ports() -> Iterable[int]:
|
|
|
|
"""
|
|
|
|
:return: The listen ports used by the backends enabled in the configuration
|
|
|
|
file.
|
|
|
|
"""
|
|
|
|
backends_config = Config.get_backends()
|
|
|
|
return {
|
|
|
|
int(port)
|
|
|
|
for port in (
|
|
|
|
backends_config.get('http', {}).get('port'),
|
|
|
|
backends_config.get('tcp', {}).get('port'),
|
|
|
|
)
|
|
|
|
if port
|
|
|
|
}
|
2018-12-19 02:08:13 +01:00
|
|
|
|
2019-12-01 22:27:54 +01:00
|
|
|
|
2018-12-19 02:08:13 +01:00
|
|
|
def main():
|
2023-08-19 13:47:43 +02:00
|
|
|
"""
|
|
|
|
Generates a Dockerfile based on the configuration file.
|
|
|
|
"""
|
2023-08-22 02:49:05 +02:00
|
|
|
print(DockerfileGenerator.from_cmdline(sys.argv[1:]).generate())
|
2023-08-19 22:46:37 +02:00
|
|
|
return 0
|
2018-12-19 02:08:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2023-08-19 22:46:37 +02:00
|
|
|
sys.exit(main())
|
2018-12-19 02:08:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
# vim:sw=4:ts=4:et:
|