kopia lustrzana https://github.com/jupyterhub/repo2docker
Merge pull request #175 from mukundans91/validateImageName
Added regex pattern based validation for image name argumentpull/179/head
commit
5293080add
|
@ -30,7 +30,7 @@ from .buildpacks import (
|
|||
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
|
||||
CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage
|
||||
)
|
||||
from .utils import execute_cmd, ByteSpecification, maybe_cleanup
|
||||
from .utils import execute_cmd, ByteSpecification, maybe_cleanup, is_valid_docker_image_name
|
||||
from . import __version__
|
||||
|
||||
|
||||
|
@ -180,6 +180,25 @@ class Repo2Docker(Application):
|
|||
extra=dict(phase='failed'))
|
||||
sys.exit(1)
|
||||
|
||||
def validate_image_name(self, image_name):
|
||||
"""
|
||||
Validate image_name read by argparse contains only lowercase characters
|
||||
|
||||
Args:
|
||||
image_name (string): argument read by the argument parser
|
||||
|
||||
Returns:
|
||||
unmodified image_name
|
||||
|
||||
Raises:
|
||||
ArgumentTypeError: if image_name contains characters that are not lowercase
|
||||
"""
|
||||
|
||||
if not is_valid_docker_image_name(image_name):
|
||||
msg = "%r is not a valid docker image name. Image name can contain only lowercase characters." % image_name
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
return image_name
|
||||
|
||||
def get_argparser(self):
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument(
|
||||
|
@ -204,7 +223,8 @@ class Repo2Docker(Application):
|
|||
argparser.add_argument(
|
||||
'--image-name',
|
||||
help=('Name of image to be built. If unspecified will be '
|
||||
'autogenerated')
|
||||
'autogenerated'),
|
||||
type=self.validate_image_name
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
|
|
|
@ -2,6 +2,7 @@ from contextlib import contextmanager
|
|||
from functools import partial
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from traitlets import Integer
|
||||
|
||||
|
@ -56,6 +57,81 @@ def maybe_cleanup(path, cleanup=False):
|
|||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
|
||||
def is_valid_docker_image_name(image_name):
|
||||
"""
|
||||
Function that constructs a regex representing the docker image name and tests it against the given image_name
|
||||
Reference Regex definition in https://github.com/docker/distribution/blob/master/reference/regexp.go
|
||||
|
||||
Args:
|
||||
image_name: string representing a docker image name
|
||||
|
||||
Returns:
|
||||
True if image_name is valid else False
|
||||
|
||||
Example:
|
||||
|
||||
'test.Com/name:latest' is a valid tag
|
||||
|
||||
'Test/name:latest' is not a valid tag
|
||||
|
||||
Note:
|
||||
|
||||
This function has a stricter pattern than https://github.com/docker/distribution/blob/master/reference/regexp.go
|
||||
|
||||
This pattern will not allow cases like
|
||||
'TEST.com/name:latest' though docker considers it a valid tag
|
||||
"""
|
||||
reference_regex = re.compile(r"""^ # Anchored at start and end of string
|
||||
|
||||
( # Start capturing name
|
||||
|
||||
(?: # start grouping the optional registry domain name part
|
||||
|
||||
(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9]) # lowercase only '<domain-name-component>'
|
||||
|
||||
(?: # start optional group
|
||||
|
||||
(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+ # multiple repetitions of pattern '.<domain-name-component>'
|
||||
|
||||
)? # end optional grouping part of the '.' separated domain name
|
||||
|
||||
(?::[0-9]+)?/ # '<domain-name>' followed by an optional '<port>' component followed by '/' literal
|
||||
|
||||
)? # end grouping the optional registry domain part
|
||||
|
||||
# start <name-pattern>
|
||||
[a-z0-9]+ # must have a <name-component>
|
||||
(?:
|
||||
(?:(?:[\._]|__|[-]*)[a-z0-9]+)+ # repeat the pattern '<separator><name-component>'
|
||||
)? # optionally have multiple repetitions of the above line
|
||||
# end <name-pattern>
|
||||
|
||||
(?: # start optional name components
|
||||
|
||||
(?: # start multiple repetitions
|
||||
|
||||
/ # separate multiple name components by /
|
||||
# start <name-pattern>
|
||||
[a-z0-9]+ # must have a <name-component>
|
||||
(?:
|
||||
(?:(?:[\._]|__|[-]*)[a-z0-9]+)+ # repeat the pattern '<separator><name-component>'
|
||||
)? # optionally have multiple repetitions of the above line
|
||||
# end <name-pattern>
|
||||
|
||||
)+ # multiple repetitions of the pattern '/<name-component><separator><name-component>'
|
||||
|
||||
)? # optionally have the above group
|
||||
|
||||
) # end capturing name
|
||||
|
||||
(?::([\w][\w.-]{0,127}))? # optional capture <tag-pattern>=':<tag>'
|
||||
(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,})? # optionally capture <digest-pattern>='@<digest>'
|
||||
$""",
|
||||
re.VERBOSE)
|
||||
|
||||
return reference_regex.match(image_name) is not None
|
||||
|
||||
|
||||
class ByteSpecification(Integer):
|
||||
"""
|
||||
Allow easily specifying bytes in units of 1024 with suffixes
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
"""
|
||||
Tests that runs validity checks on arguments passed in from shell
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
def does_validate_image_name(builddir, image_name):
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
'repo2docker',
|
||||
'--no-run',
|
||||
'--no-build',
|
||||
'--image-name',
|
||||
str(image_name),
|
||||
builddir
|
||||
],
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode()
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
output = e.output.decode()
|
||||
if "error: argument --image-name: %r is not a valid docker image name. " \
|
||||
"Image name can contain only lowercase characters." % image_name in output:
|
||||
return False
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def test_image_name_fail():
|
||||
"""
|
||||
Test to check if repo2docker throws image_name validation error on --image-name argument containing
|
||||
uppercase characters and _ characters in incorrect positions.
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__)
|
||||
|
||||
assert not does_validate_image_name(builddir, 'Test/Invalid_name:1.0.0')
|
||||
|
||||
|
||||
def test_image_name_underscore_fail():
|
||||
"""
|
||||
Test to check if repo2docker throws image_name validation error on --image-name argument starts with _.
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__)
|
||||
|
||||
assert not does_validate_image_name(builddir, '_test/invalid_name:1.0.0')
|
||||
|
||||
|
||||
def test_image_name_double_dot_fail():
|
||||
"""
|
||||
Test to check if repo2docker throws image_name validation error on --image-name argument contains consecutive dots.
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__)
|
||||
|
||||
assert not does_validate_image_name(builddir, 'test..com/invalid_name:1.0.0')
|
||||
|
||||
|
||||
def test_image_name_valid_restircted_registry_domain_name_fail():
|
||||
"""
|
||||
Test to check if repo2docker throws image_name validation error on -image-name argument being invalid. Based on the
|
||||
regex definitions first part of registry domain cannot contain uppercase characters
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__)
|
||||
|
||||
assert not does_validate_image_name(builddir, 'Test.com/valid_name:1.0.0')
|
||||
|
||||
|
||||
def test_image_name_valid_registry_domain_name_success():
|
||||
"""
|
||||
Test to check if repo2docker runs with a valid --image-name argument.
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__) + '/dockerfile/simple/'
|
||||
|
||||
assert does_validate_image_name(builddir, 'test.COM/valid_name:1.0.0')
|
||||
|
||||
|
||||
def test_image_name_valid_name_success():
|
||||
"""
|
||||
Test to check if repo2docker runs with a valid --image-name argument.
|
||||
"""
|
||||
|
||||
builddir = os.path.dirname(__file__) + '/dockerfile/simple/'
|
||||
|
||||
assert does_validate_image_name(builddir, 'test.com/valid_name:1.0.0')
|
Ładowanie…
Reference in New Issue