Source code for docker_pkg.dockerfile
"""
Dockerfile.template processing
The files are Jinja2 templates, the class provides built-in templates to ease
writing Dockerfiles.
"""
import re
from typing import Any, Dict, Set
import debian.debian_support
from jinja2 import Environment, FileSystemLoader, Template, StrictUndefined
from docker_pkg import log, ImageLabel
[docs]class TemplateEngine:
known_images: Set[str] = set()
config: Dict[str, Any] = {}
env: Environment = Environment(extensions=["jinja2.ext.do"], undefined=StrictUndefined)
[docs] @classmethod
def setup(cls, config: Dict[str, Any], known_images: Set[str]):
cls.config = config
cls.known_images = known_images
cls.setup_filters()
[docs] @classmethod
def setup_filters(cls):
cls.setup_apt_install()
cls.setup_apt_remove()
def find_image_tag(image_name):
label = ImageLabel(cls.config, image_name, "")
image_name = label.label()
for img_with_tag in cls.known_images:
try:
name, tag = img_with_tag.split(":")
except ValueError:
continue
if image_name == name:
return img_with_tag
raise ValueError(
"Image {name} not found, or it has no tag (known={known})".format(
name=image_name, known=cls.known_images
)
)
cls.env.filters["image_tag"] = find_image_tag
def upstream_version(image_tag):
try:
image_name, tag = image_tag.split(":")
except ValueError:
raise ValueError(f"upstream_version({image_tag}) -> no image name found in input")
if "-" not in tag:
return tag
return debian.debian_support.BaseVersion(tag).upstream_version
cls.env.filters["upstream_version"] = upstream_version
def get_uid(user: str):
mappings = cls.config["known_uid_mappings"]
if user not in mappings:
# If there is no available mapping, we just return the username.
# If strict use of numeric uids is required by toggling the force_numeric_user
# configuration option on, the dockerfile we generate will be rejected by
# the check in has_numeric_user() at build time, thus the build
# will fail.
log.warn("UID mapping for user %s not found", user)
return user
else:
return str(mappings[user])
cls.env.filters["uid"] = get_uid
def add_user(usr):
id = get_uid(usr)
if id == usr:
raise ValueError("No mapping found for user '{u}'".format(u=usr))
groupadd = "groupadd -o -g {id} -r {usr}".format(id=id, usr=usr)
useradd = "useradd -l -o -r -m -d /var/lib/{usr} -g {usr} -u {id} {usr}".format(
id=id, usr=usr
)
return "{g} && {u}".format(g=groupadd, u=useradd)
cls.env.filters["add_user"] = add_user
[docs] @classmethod
def setup_apt_install(cls):
t = Template(
"""
{%- if apt_only_proxy -%}
echo 'Acquire::http::Proxy \"{{ apt_only_proxy }}\";' > /etc/apt/apt.conf.d/80_proxy \\
&& apt-get update {{ apt_options }} \\
{%- else -%}
apt-get update {{ apt_options }} \\
{%- endif %}
&& DEBIAN_FRONTEND=noninteractive \\
apt-get install {{ apt_options }} --yes {{ packages }} --no-install-recommends \\
{%- if apt_only_proxy %}
&& rm -f /etc/apt/apt.conf.d/80_proxy \\
{%- endif %}
&& apt-get clean && rm -rf /var/lib/apt/lists/* """
)
def apt_install(pkgs):
# Allow people to write easier to read newline separated package
# lists by turning them into space separated ones for apt
pkgs = pkgs.replace("\n", " ")
return t.render(packages=pkgs, **cls.config)
cls.env.filters["apt_install"] = apt_install
[docs] @classmethod
def setup_apt_remove(cls):
t = Template(
"""
{%- if apt_only_proxy -%}
echo 'Acquire::http::Proxy \"{{ apt_only_proxy }}\";' > /etc/apt/apt.conf.d/80_proxy && \\
{%- endif -%}
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get remove --yes --purge {{ packages }} \\
{%- if apt_only_proxy %}
&& rm -f /etc/apt/apt.conf.d/80_proxy \\
{%- endif %}
&& apt-get clean && rm -rf /var/lib/apt/lists/* """
)
def apt_remove(pkgs):
return t.render(packages=pkgs, **cls.config)
cls.env.filters["apt_remove"] = apt_remove
def __init__(self, path: str):
self.env.loader = FileSystemLoader(path)
[docs]def from_template(path: str, name: str) -> Template:
return TemplateEngine(path).env.get_template(name)
[docs]def has_numeric_user(dockerfile: str) -> bool:
# Return true in case dockerfile does not contain a USER instruction
numeric_user = True
regex = re.compile(r"^USER\s+\d+(?:\:\d+)?$")
for line in dockerfile.split("\n"):
if line.startswith("USER "):
numeric_user = True if regex.match(line) else False
return numeric_user