Removed class and added function to validate docker image name
MODIFY
repo2docker/app.py
repo2docker/app.py
@@ -29,7 +29,7 @@ from .buildpacks import (
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage
)
-from .utils import execute_cmd, ByteSpecification, maybe_cleanup, ImageNameValidator
+from .utils import execute_cmd, ByteSpecification, maybe_cleanup, is_valid_docker_image_name
from . import __version__
@@ -127,7 +127,7 @@ class Repo2Docker(Application):
Validate image_name read by argparse contains only lowercase characters
Args:
- image_name: string argument read by the argument parser
+ image_name (string): argument read by the argument parser
Returns:
unmodified image_name
@@ -136,7 +136,7 @@ class Repo2Docker(Application):
ArgumentTypeError: if image_name contains characters that are not lowercase
"""
- if not ImageNameValidator.is_valid_image_name(image_name):
+ 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

MODIFY
repo2docker/utils.py
repo2docker/utils.py
@@ -57,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
@@ -100,166 +175,3 @@ class ByteSpecification(Integer):
raise TraitError('{val} is not a valid memory specification. Must be an int or a string with suffix K, M, G, T'.format(val=value))
else:
return int(float(num) * self.UNIT_SUFFIXES[suffix])
-
-
-class ImageNameValidator:
- """
- Given a docker image_name, check if the image_name conforms to the restrictions placed by docker.
-
- Class defines the regex patterns based off of the definitions in
- https://github.com/docker/distribution/blob/master/reference/regexp.go. There are some modifications as noted below.
- """
-
- def __init__(self):
- alpha_numeric_regex = r'[a-z0-9]+'
- """str: raw pattern denoting only lowercase character and numbers part of name"""
-
- separator_regex = r'(?:[\._]|__|[-]*)'
- """str: raw pattern denoting separators allowed to be embedded in component names"""
-
- domain_component_regex_lowercase = r'(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])'
- """str: raw pattern restricts the domain component of the tag to have at least 3 lowercase alphabets or numbers
- Different from the https://github.com/docker/distribution/blob/master/reference/regexp.go in the sense only allow
- lowercase characters
- """
-
- domain_component_regex = r'(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])'
- """str: raw pattern restricts the domain component of the tag to have at least 3 alphabets or numbers"""
-
- numbers = r'[0-9]+'
- """str: raw pattern restricts to only one or more numbers"""
-
- tag_regex = r'[\w][\w.-]{0,127}'
- """str: raw pattern matching valid tag names that can at most contain 128 characters"""
-
- digest_regex = r'[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}'
- """str: raw patten representing an image digest"""
-
- name_component_regex = self.expression(alpha_numeric_regex,
- self.optional(self.repeated(separator_regex,
- alpha_numeric_regex)
- )
- )
- """str: restricts registry path component to start with alpha_numeric_regex followed by optional parts that can
- have separators"""
-
- domain_regex = self.expression(domain_component_regex_lowercase,
- self.optional(self.repeated(r'\.', domain_component_regex)),
- self.optional(r':', numbers))
- """str: representing a registry domain starting with domain_component_regex followed by option period separated
- domain_component_regex followed by optional : separated port
-
- Example:
-
- 'test.Com/name:latest' is still a valid tag
- but
- 'Test/name:latest' is not a valid tag
-
- Note:
-
- This give a stricter pattern as in the first part in a '.' separated registry domain must always be lowercase
-
- This pattern will not allow cases like
- 'TEST.com/name:latest' though docker considers it a valid tag
- """
-
- name_regex = self.expression(self.optional(domain_regex, r'/'),
- name_component_regex,
- self.optional(self.repeated(r'/', name_component_regex)))
- """str: defines a pattern representing an optional registry domain followed by one or more component names
- separated by /"""
-
- self.reference_regex = self.anchored(self.capture(name_regex),
- self.optional(r':', self.capture(tag_regex)),
- self.optional(r'@', digest_regex))
- """str: defines a pattern representing a reference. The pattern is anchored and has capturing groups for
- name, tag and digest"""
-
- @staticmethod
- def is_valid_image_name(image_name):
- """
- Static method that tests whether image_name conforms to a reference pattern
-
- Args:
- image_name: string representing the image name
-
- Returns:
- True if it a valid docker image name
- """
-
- validator = ImageNameValidator()
- result = re.match(validator.reference_regex, image_name)
-
- return result is not None
-
- def expression(self, *args):
- """
- Defines a full expression where each regex must follow the other
- Args:
- *args: Argument list representing regex
-
- Returns:
- an expression which is a concatenation of the regexes in the *args
- """
- s = r''.join(list(args))
- return s
-
- def optional(self, *args):
- """
- Wraps the expression in a non-capturing group and makes it optional
-
- Args:
- *args: Argument list representing regex
-
- Returns:
- a string representing the regex wrapped in non-capturing group with optional production
- """
- return self.group(self.expression(*args)) + r'?'
-
- def repeated(self, *args):
- """
- Wraps the expression in a non-capturing group to get one or more matches
-
- Args:
- *args: Argument list representing regex
-
- Returns:
- a string representing the regex wrapped in non-capturing group with one or more matches
- """
- return self.group(self.expression(*args)) + r'+'
-
- def group(self, *args):
- """
- Wraps the expression in a non-capturing group
-
- Args:
- *args: Argument list representing regex
-
- Returns:
- wraps the expression represented by args in non-capturing group
- """
- return r'(?:' + self.expression(*args) + r')'
-
- def capture(self, *args):
- """
- Wraps the expression in a capturing group
-
- Args:
- *args: Argument list representing regex
-
- Returns:
- wraps the expression represented by args in capturing group
- """
- return r'(' + self.expression(*args) + r')'
-
- def anchored(self, *args):
- """
- Anchors the regular expression by adding start and end delimiters
-
- Args:
- *args: Argument list representing regex
-
- Returns:
- anchored regex
- """
- return r'^' + self.expression(*args) + r'$'
\ No newline at end of file

Removed class and added function to validate docker image name