Skip to content

Commit

Permalink
Add basetag property and helper functions
Browse files Browse the repository at this point in the history
Signed-off-by: Tim van Katwijk <[email protected]>
  • Loading branch information
tim-vk committed Oct 1, 2022
1 parent 48f8b95 commit 017fc3b
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 3 deletions.
54 changes: 52 additions & 2 deletions dockerfile_parse/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def parent_images(self, parents):
lines[instr['startline']:instr['endline']+1] = [instr['content']]

self.lines = lines

@property
def is_multistage(self):
return len(self.parent_images) > 1
Expand All @@ -428,6 +428,21 @@ def baseimage(self, new_image):
raise RuntimeError('No stage defined to set base image on')
images[-1] = new_image
self.parent_images = images

@property
def basetag(self):
"""
:return: tag of base image, i.e. tag of base image
"""
_, tag = tag_from(self.baseimage)
return tag

@basetag.setter
def basetag(self, new_tag):
"""
only change the tag of the final stage FROM instruction
"""
self.baseimage = tag_to(self.baseimage, new_tag)

@property
def cmd(self):
Expand Down Expand Up @@ -882,7 +897,42 @@ def image_from(from_value):
match = re.match(regex, from_value)
return match.group('image', 'name') if match else (None, None)


def tag_from(from_value):
"""
:param from_value: string like "registry:port/image:tag AS name"
:return: tuple of the image and tag e.g. ("image", "tag")
"""

image, _ = image_from(from_value)
bare, _, tag = image.rpartition(":") if image and ":" in image else (None, None, None)

# check if a tag was actually present
if not valid_tag(tag) or not bare:
return (image, None)

return (bare, tag)

def valid_tag(tag):
"""
:param tag to be checked for validity
:return: true or false
"""
regex = re.compile(r"""(?x) # readable, case-insensitive regex
^(?P<tag>[a-zA-Z0-9\_][a-zA-Z0-9\.\_\-]*)$ # valid tag format (alphanumeric characters, numbers . _ and - (. and - not leading))
""")
match = re.match(regex, tag) if tag else None
return True if match and match.group('tag') and len(match.group('tag')) < 128 else False

def tag_to(image, new_tag):
"""
:param image: string like "image:tag" or "image"
:param tag: string like "latest"
:return: string like "image:new_tag" or "image" if no tag was given
"""

bare, _ = tag_from(image)
return ":".join(filter(None, [bare.strip() if bare else None, new_tag.strip() if new_tag else None]))

def _endline(line):
"""
Make sure the line ends with a single newline.
Expand Down
139 changes: 138 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

from dockerfile_parse import DockerfileParser
from dockerfile_parse.parser import image_from
from dockerfile_parse.parser import tag_from
from dockerfile_parse.parser import tag_to
from dockerfile_parse.parser import valid_tag
from dockerfile_parse.constants import COMMENT_INSTRUCTION
from dockerfile_parse.util import b2u, u2b, Context
from tests.fixtures import dfparser, instruction
Expand Down Expand Up @@ -312,11 +315,22 @@ def test_get_baseimg_from_df(self, dfparser):
"LABEL a b\n"]
assert dfparser.baseimage == 'fedora:latest'

def test_get_basetag_from_df(self,dfparser):
dfparser.lines = ["From fedora:latest\n",
"LABEL a b\n"]
assert dfparser.basetag == 'latest'

def test_get_baseimg_from_arg(self, dfparser):
dfparser.lines = ["ARG BASE=fedora:latest\n",
"FROM $BASE\n",
"LABEL a b\n"]
assert dfparser.baseimage == 'fedora:latest'

def test_get_basetag_from_arg(self, dfparser):
dfparser.lines = ["ARG BASE=fedora:latest\n",
"FROM $BASE\n",
"LABEL a b\n"]
assert dfparser.basetag == 'latest'

def test_get_baseimg_from_build_arg(self, tmpdir):
tmpdir_path = str(tmpdir.realpath())
Expand All @@ -328,6 +342,16 @@ def test_get_baseimg_from_build_arg(self, tmpdir):
assert dfp.baseimage == 'fedora:latest'
assert not dfp.args

def test_get_basetag_from_build_arg(self, tmpdir):
tmpdir_path = str(tmpdir.realpath())
b_args = {"BASE": "fedora:latest"}
dfp = DockerfileParser(tmpdir_path, env_replace=True, build_args=b_args)
dfp.lines = ["ARG BASE=centos:latest\n",
"FROM $BASE\n",
"LABEL a b\n"]
assert dfp.basetag == 'latest'
assert not dfp.args

def test_set_no_baseimage(self, dfparser):
dfparser.lines = []
with pytest.raises(RuntimeError):
Expand Down Expand Up @@ -468,6 +492,114 @@ def test_image_from(self, from_value, expect):
result = image_from(from_value)
assert result == expect

@pytest.mark.parametrize(('from_value', 'expect'), [
(
"",
(None, None),
),
(
" ",
(None, None),
), (
" foo",
('foo', None),
), (
"foo:bar as baz ",
('foo', 'bar'),
), (
"foo as baz",
('foo', None),
), (
"foo and some other junk", # we won't judge
('foo', None),
), (
"registry.example.com:5000/foo/bar",
('registry.example.com:5000/foo/bar', None),
), (
"registry.example.com:5000/foo/bar:baz",
('registry.example.com:5000/foo/bar', "baz"),
), (
"localhost:5000/foo/bar:baz",
('localhost:5000/foo/bar', "baz"),
)
])
def test_tag_from(self, from_value, expect):
result = tag_from(from_value)
assert result == expect

@pytest.mark.parametrize(('from_image', 'from_tag', 'expect'), [
(
" ",
" ",
"",
),(
"foo",
None,
'foo',
), (
"foo",
"bar",
'foo:bar',
), (
"foo",
"",
'foo',
), (
"foo:bar",
"baz",
'foo:baz',
), (
"registry.example.com:5000/foo/bar",
"baz",
'registry.example.com:5000/foo/bar:baz',
),
(
"localhost:5000/foo/bar",
"baz",
'localhost:5000/foo/bar:baz',
),
(
"nonvalid1@%registry.example.com:5000/foo/bar",
"baz",
'nonvalid1@%registry.example.com:5000/foo/bar:baz',
),
(
"registry.example.com:5000/foo/bar",
"baz",
'registry.example.com:5000/foo/bar:baz',
),(
"registry.example.com:5000/foo/bar:baz",
"bap",
'registry.example.com:5000/foo/bar:bap',
)
])
def test_tag_to(self, from_image, from_tag, expect):
result = tag_to(from_image, from_tag)
assert result == expect


@pytest.mark.parametrize(('tag', 'expect'), [
(
"Tag",
True
),(
"tAg.",
True
), (
"tag-tag",
True
), (
".notTag",
False
), (
"not/tag",
False
)
])
def test_valid_tag(self, tag, expect):
result = valid_tag(tag)
assert result == expect

def test_parent_images(self, dfparser):
FROM = ('my-builder:latest', 'rhel7:7.5')
template = dedent("""\
Expand Down Expand Up @@ -507,8 +639,9 @@ def test_parent_images_missing_from(self, dfparser):
assert dfparser.content.count('FROM') == 4

def test_modify_instruction(self, dfparser):
FROM = ('ubuntu', 'fedora:')
FROM = ('ubuntu', 'fedora:theBest')
CMD = ('old❤cmd', 'new❤command')
TAG = ('theBest', 'newtag')
df_content = dedent("""\
FROM {0}
CMD {1}""").format(FROM[0], CMD[0])
Expand All @@ -518,6 +651,10 @@ def test_modify_instruction(self, dfparser):
assert dfparser.baseimage == FROM[0]
dfparser.baseimage = FROM[1]
assert dfparser.baseimage == FROM[1]

assert dfparser.basetag == TAG[0]
dfparser.basetag = TAG[1]
assert dfparser.basetag == TAG[1]

assert dfparser.cmd == CMD[0]
dfparser.cmd = CMD[1]
Expand Down

0 comments on commit 017fc3b

Please sign in to comment.