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,
|
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
|
||||||
CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage
|
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__
|
from . import __version__
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,6 +180,25 @@ class Repo2Docker(Application):
|
||||||
extra=dict(phase='failed'))
|
extra=dict(phase='failed'))
|
||||||
sys.exit(1)
|
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):
|
def get_argparser(self):
|
||||||
argparser = argparse.ArgumentParser()
|
argparser = argparse.ArgumentParser()
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
|
@ -204,7 +223,8 @@ class Repo2Docker(Application):
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
'--image-name',
|
'--image-name',
|
||||||
help=('Name of image to be built. If unspecified will be '
|
help=('Name of image to be built. If unspecified will be '
|
||||||
'autogenerated')
|
'autogenerated'),
|
||||||
|
type=self.validate_image_name
|
||||||
)
|
)
|
||||||
|
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
|
|
|
@ -2,6 +2,7 @@ from contextlib import contextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
from traitlets import Integer
|
from traitlets import Integer
|
||||||
|
|
||||||
|
@ -56,6 +57,81 @@ def maybe_cleanup(path, cleanup=False):
|
||||||
shutil.rmtree(path, ignore_errors=True)
|
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):
|
class ByteSpecification(Integer):
|
||||||
"""
|
"""
|
||||||
Allow easily specifying bytes in units of 1024 with suffixes
|
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