kopia lustrzana https://github.com/jupyterhub/repo2docker
add docstrings and format regex
rodzic
e85918b024
commit
c7bb2056d6
|
@ -1,12 +1,13 @@
|
|||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
|
||||
from traitlets import Integer
|
||||
|
||||
|
||||
def execute_cmd(cmd, capture=False, **kwargs):
|
||||
"""
|
||||
Call given command, yielding output line by line if capture=True
|
||||
|
@ -18,8 +19,7 @@ def execute_cmd(cmd, capture=False, **kwargs):
|
|||
proc = subprocess.Popen(cmd, **kwargs)
|
||||
|
||||
if not capture:
|
||||
# not capturing output, let the subprocesses talk directly
|
||||
# to the terminal
|
||||
# not capturing output, let subprocesses talk directly to terminal
|
||||
ret = proc.wait()
|
||||
if ret != 0:
|
||||
raise subprocess.CalledProcessError(ret, cmd)
|
||||
|
@ -32,6 +32,7 @@ def execute_cmd(cmd, capture=False, **kwargs):
|
|||
buf = []
|
||||
|
||||
def flush():
|
||||
"""Flush next line of the buffer"""
|
||||
line = b''.join(buf).decode('utf8', 'replace')
|
||||
buf[:] = []
|
||||
return line
|
||||
|
@ -53,6 +54,7 @@ def execute_cmd(cmd, capture=False, **kwargs):
|
|||
|
||||
@contextmanager
|
||||
def maybe_cleanup(path, cleanup=False):
|
||||
"""Execute cleanup of the passed path when cleanup flag is True."""
|
||||
yield
|
||||
if cleanup:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
@ -60,48 +62,57 @@ def maybe_cleanup(path, cleanup=False):
|
|||
|
||||
def validate_and_generate_port_mapping(port_mapping):
|
||||
"""
|
||||
Validate the port mapping list provided as argument and split into as dictionary of key being continer port and the
|
||||
values being None, or 'host_port' or ['interface_ip','host_port']
|
||||
Validate the port mapping list and return a list of validated tuples.
|
||||
|
||||
Each entry in the passed port mapping list will be converted to a
|
||||
tuple with a containing a string with the format 'key:value' with the
|
||||
`key` being the container's port and the
|
||||
`value` being `None`, `host_port` or `['interface_ip','host_port']`
|
||||
|
||||
|
||||
Args:
|
||||
port_mapping (list): List of strings of format 'host_port:container_port'
|
||||
with optional tcp udp values and host network interface
|
||||
port_mapping (list): List of strings of format
|
||||
`'host_port:container_port'` with optional tcp udp values and host
|
||||
network interface
|
||||
|
||||
Returns:
|
||||
List of validated tuples of form ('host_port:container_port') with optional tcp udp values and host network interface
|
||||
List of validated tuples of form ('host_port:container_port') with
|
||||
optional tcp udp values and host network interface
|
||||
|
||||
Raises:
|
||||
Exception on invalid port mapping
|
||||
|
||||
Note:
|
||||
One limitation cannot bind single container_port to multiple host_ports (docker-py supports this but repo2docker
|
||||
does not)
|
||||
One limitation of repo2docker is it cannot bind a
|
||||
single container_port to multiple host_ports
|
||||
(docker-py supports this but repo2docker does not)
|
||||
|
||||
Examples:
|
||||
Valid port mappings are
|
||||
127.0.0.1:90:900
|
||||
:999 - To match to any host port
|
||||
999:999/tcp - bind 999 host port to 999 tcp container port
|
||||
Valid port mappings are:
|
||||
- `127.0.0.1:90:900`
|
||||
- `:999` (match to any host port)
|
||||
- `999:999/tcp` (bind 999 host port to 999 tcp container port)
|
||||
|
||||
Invalid port mapping
|
||||
127.0.0.1::999 --- even though docker accepts it
|
||||
other invalid ip address combinations
|
||||
Invalid port mapping:
|
||||
- `127.0.0.1::999` (even though docker accepts it)
|
||||
- other invalid ip address combinations
|
||||
"""
|
||||
reg_regex = re.compile('''^(
|
||||
( # or capturing group
|
||||
(?: # start capturing ip address of network interface
|
||||
(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} # first three parts
|
||||
(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # last part of the ip address
|
||||
:(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){1,4})
|
||||
)?
|
||||
| # host ip with port or only port
|
||||
(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
|
||||
)
|
||||
:
|
||||
(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
|
||||
(?:/udp|/tcp)?
|
||||
)$''', re.VERBOSE)
|
||||
reg_regex = re.compile(r"""
|
||||
^(
|
||||
( # or capturing group
|
||||
(?: # start capturing ip address of network interface
|
||||
(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3} # first three parts
|
||||
(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # last part of the ip address
|
||||
:(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){1,4})
|
||||
)?
|
||||
| # host ip with port or only port
|
||||
(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
|
||||
)
|
||||
:
|
||||
(?:6553[0-5]|655[0-2][0-9]|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|(\d){0,4})
|
||||
(?:/udp|/tcp)?
|
||||
)$
|
||||
""", re.VERBOSE)
|
||||
ports = {}
|
||||
if not port_mapping:
|
||||
return None
|
||||
|
@ -120,84 +131,90 @@ def validate_and_generate_port_mapping(port_mapping):
|
|||
host_port = port_host[0] if len(port_host[0]) > 0 else None
|
||||
container_port = port_host[1]
|
||||
|
||||
|
||||
if host is None:
|
||||
ports[str(container_port)] = host_port
|
||||
else:
|
||||
ports[str(container_port)] = (host, host_port)
|
||||
return ports
|
||||
|
||||
|
||||
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
|
||||
Determine if image name is valid for docker using strict pattern.
|
||||
|
||||
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
|
||||
The definition uses a stricter pattern than the docker default.
|
||||
|
||||
Args:
|
||||
image_name: string representing a docker image name
|
||||
|
||||
Returns:
|
||||
True if image_name is valid else False
|
||||
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:
|
||||
Note:
|
||||
This function has a stricter pattern than
|
||||
https://github.com/docker/distribution/blob/master/reference/regexp.go
|
||||
|
||||
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
|
||||
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
|
||||
reference_regex = re.compile(r"""
|
||||
^ # Anchored at start and end of string
|
||||
|
||||
( # Start capturing name
|
||||
( # Start capturing name
|
||||
|
||||
(?: # start grouping the optional registry domain name part
|
||||
(?: # 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>'
|
||||
(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9]) # lowercase only '<domain-name-component>'
|
||||
|
||||
(?: # start optional group
|
||||
(?: # 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>'
|
||||
# multiple repetitions of pattern '.<domain-name-component>'
|
||||
(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+
|
||||
|
||||
)? # end optional grouping part of the '.' separated domain name
|
||||
)? # end optional grouping part of the '.' separated domain name
|
||||
|
||||
(?::[0-9]+)?/ # '<domain-name>' followed by an optional '<port>' component followed by '/' literal
|
||||
(?::[0-9]+)?/ # '<domain-name>' followed by an optional '<port>' component followed by '/' literal
|
||||
|
||||
)? # end grouping the optional registry domain part
|
||||
)? # 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 <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 optional name components
|
||||
|
||||
(?: # start multiple repetitions
|
||||
(?: # 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>
|
||||
/ # 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>'
|
||||
)+ # multiple repetitions of the pattern '/<name-component><separator><name-component>'
|
||||
|
||||
)? # optionally have the above group
|
||||
)? # optionally have the above group
|
||||
|
||||
) # end capturing name
|
||||
) # 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)
|
||||
(?::([\w][\w.-]{0,127}))? # optional capture <tag-pattern>=':<tag>'
|
||||
# optionally capture <digest-pattern>='@<digest>'
|
||||
(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,})?
|
||||
$
|
||||
""", re.VERBOSE)
|
||||
|
||||
return reference_regex.match(image_name) is not None
|
||||
|
||||
|
@ -227,11 +244,11 @@ class ByteSpecification(Integer):
|
|||
|
||||
def validate(self, obj, value):
|
||||
"""
|
||||
Validate that the passed in value is a valid memory specification
|
||||
Validate that the passed-in value is a valid memory specification
|
||||
|
||||
It could either be a pure int, when it is taken as a byte value.
|
||||
If it has one of the suffixes, it is converted into the appropriate
|
||||
pure byte value.
|
||||
If value is a pure int, it is taken as a byte value.
|
||||
If value has one of the unit suffixes, it is converted into the
|
||||
appropriate pure byte value.
|
||||
"""
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
|
@ -239,9 +256,17 @@ class ByteSpecification(Integer):
|
|||
try:
|
||||
num = float(value[:-1])
|
||||
except ValueError:
|
||||
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))
|
||||
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)
|
||||
)
|
||||
suffix = value[-1]
|
||||
if suffix not in self.UNIT_SUFFIXES:
|
||||
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))
|
||||
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])
|
||||
|
|
Ładowanie…
Reference in New Issue