File: tar_utils.py

package info (click to toggle)
python-podman 5.4.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,140 kB
  • sloc: python: 7,532; makefile: 82; sh: 75
file content (134 lines) | stat: -rw-r--r-- 3,840 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""Utility functions for working with tarballs."""

import pathlib
import random
import shutil
import tarfile
import tempfile
from fnmatch import fnmatch
from typing import BinaryIO, Optional

import sys


def prepare_containerignore(anchor: str) -> list[str]:
    """Return the list of patterns for filenames to exclude.

    .containerignore takes precedence over .dockerignore.
    """
    for filename in (".containerignore", ".dockerignore"):
        ignore = pathlib.Path(anchor) / filename
        if not ignore.exists():
            continue

        with ignore.open(encoding='utf-8') as file:
            return list(
                filter(
                    lambda L: L and not L.startswith("#"),
                    (line.strip() for line in file.readlines()),
                )
            )
    return []


def prepare_containerfile(anchor: str, dockerfile: str) -> str:
    """Ensure that Containerfile, or a proxy Containerfile is in context_dir.

    Args:
        anchor: Build context directory
        dockerfile: Path to Dockerfile/Containerfile

    Returns:
        path to Dockerfile/Containerfile in root of context directory
    """
    anchor_path = pathlib.Path(anchor)
    dockerfile_path = pathlib.Path(dockerfile)

    if dockerfile_path.parent.samefile(anchor_path):
        return dockerfile_path.name

    proxy_path = anchor_path / f".containerfile.{random.getrandbits(160):x}"
    shutil.copy2(dockerfile_path, proxy_path, follow_symlinks=False)
    return proxy_path.name


def create_tar(
    anchor: str, name: str = None, exclude: list[str] = None, gzip: bool = False
) -> BinaryIO:
    """Create a tarfile from context_dir to send to Podman service.

    Args:
        anchor: Directory to use as root of tar file.
        name: Name of tar file.
        exclude: List of patterns for files to exclude from tar file.
        gzip: When True, gzip compress tar file.
    """

    def add_filter(info: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
        """Filter files targeted to be added to tarfile.

        Args:
            info: Information on the file targeted to be added

        Returns:
            None: if file is not to be added
            TarInfo: when file is to be added. Modified as needed.

        Notes:
            exclude is captured from parent
        """

        if not (info.isfile() or info.isdir() or info.issym()):
            return None

        if _exclude_matcher(info.name, exclude):
            return None

        # Workaround https://bugs.python.org/issue32713. Fixed in Python 3.7
        if info.mtime < 0 or info.mtime > 8**11 - 1:
            info.mtime = int(info.mtime)

        # do not leak client information to service
        info.uid = 0
        info.uname = info.gname = "root"

        if sys.platform == "win32":
            info.mode = info.mode & 0o755 | 0o111

        return info

    if name is None:
        # pylint: disable=consider-using-with
        name = tempfile.NamedTemporaryFile(prefix="podman_context", suffix=".tar")
    else:
        name = pathlib.Path(name)

    if exclude is None:
        exclude = []
    else:
        exclude = exclude.copy()

    # FIXME caller needs to add this...
    # exclude.append(".dockerignore")
    exclude.append(name.name)

    mode = "w:gz" if gzip else "w"
    with tarfile.open(name.name, mode) as tar:
        tar.add(anchor, arcname="", recursive=True, filter=add_filter)

    return open(name.name, "rb")  # pylint: disable=consider-using-with


def _exclude_matcher(path: str, exclude: list[str]) -> bool:
    """Returns True if path matches an entry in exclude.

    Note:
        FIXME Not compatible, support !, **, etc
    """
    if not exclude:
        return False

    for pattern in exclude:
        if fnmatch(path, pattern):
            return True
    return False