Merge pull request #213 from willingc/docstring-conda

Add docstrings and minor style fixes for application files and JuliaBuildPack
pull/271/head
Min RK 2018-03-19 18:22:02 +01:00 zatwierdzone przez GitHub
commit 05671879f5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 259 dodań i 144 usunięć

Wyświetl plik

@ -1,9 +1,11 @@
from .app import Repo2Docker
def main():
f = Repo2Docker()
f.initialize()
f.start()
if __name__ == '__main__':
main()

Wyświetl plik

@ -7,43 +7,46 @@ Usage:
python -m repo2docker https://github.com/you/your-repo
"""
import sys
import json
import os
import time
import logging
import argparse
import tempfile
from pythonjsonlogger import jsonlogger
import escapism
import json
import sys
import logging
import os
import pwd
import subprocess
import tempfile
import time
from traitlets.config import Application
from traitlets import Unicode, List, default, Any, Dict, Int
import docker
from docker.utils import kwargs_from_env
from docker.errors import DockerException
import escapism
from pythonjsonlogger import jsonlogger
import subprocess
from traitlets import Any, Dict, Int, List, Unicode, default
from traitlets.config import Application
from . import __version__
from .buildpacks import (
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage,
RBuildPack
)
from .utils import execute_cmd, ByteSpecification, maybe_cleanup, is_valid_docker_image_name, validate_and_generate_port_mapping
from . import __version__
from .utils import (
execute_cmd, ByteSpecification, maybe_cleanup, is_valid_docker_image_name,
validate_and_generate_port_mapping
)
class Repo2Docker(Application):
"""An application for converting git repositories to docker images"""
name = 'jupyter-repo2docker'
version = __version__
description = __doc__
@default('log_level')
def _default_log_level(self):
"""The application's default log level"""
return logging.INFO
git_workdir = Unicode(
@ -51,7 +54,7 @@ class Repo2Docker(Application):
config=True,
allow_none=True,
help="""
Working directory to check out git repositories to.
Working directory to use for check out of git repositories.
The default is to use the system's temporary directory. Should be
somewhere ephemeral, such as /tmp.
@ -70,7 +73,7 @@ class Repo2Docker(Application):
],
config=True,
help="""
Ordered list of BuildPacks to try to use to build a git repository.
Ordered list of BuildPacks to try when building a git repository.
"""
)
@ -78,7 +81,7 @@ class Repo2Docker(Application):
PythonBuildPack,
config=True,
help="""
The build pack to use when no buildpacks are found
The default build pack to use when no other buildpacks are found.
"""
)
@ -97,13 +100,15 @@ class Repo2Docker(Application):
help="""
Volumes to mount when running the container.
Only used when running, not during build!
Only used when running, not during build process!
Should be a key value pair, with the key being the volume source &
value being the destination. Both can be relative - sources are
resolved relative to the current working directory on the host,
destination is resolved relative to the working directory of the image -
($HOME by default)
Use a key-value pair, with the key being the volume source &
value being the destination volume.
Both source and destination can be relative. Source is resolved
relative to the current working directory on the host, and
destination is resolved relative to the working directory of the
image - ($HOME by default)
""",
config=True
)
@ -133,11 +138,11 @@ class Repo2Docker(Application):
help="""
Username of the user to create inside the built image.
Should be a username that is not currently used by anything in the image,
and should conform to the restrictions on user names for Linux.
Should be a username that is not currently used by anything in the
image, and should conform to the restrictions on user names for Linux.
Defaults to username of currently running user, since that is the most
common case when running r2d manually.
common case when running repo2docker manually.
""",
config=True
)
@ -160,6 +165,7 @@ class Repo2Docker(Application):
)
def fetch(self, url, ref, checkout_path):
"""Check out a repo using url and ref to the checkout_path location"""
try:
for line in execute_cmd(['git', 'clone', '--recursive', url, checkout_path],
capture=self.json_logs):
@ -182,7 +188,11 @@ class Repo2Docker(Application):
def validate_image_name(self, image_name):
"""
Validate image_name read by argparse contains only lowercase characters
Validate image_name read by argparse
Note: Container names must start with an alphanumeric character and
can then use _ . or - in addition to alphanumeric.
[a-zA-Z0-9][a-zA-Z0-9_.-]+
Args:
image_name (string): argument read by the argument parser
@ -191,15 +201,21 @@ class Repo2Docker(Application):
unmodified image_name
Raises:
ArgumentTypeError: if image_name contains characters that are not lowercase
ArgumentTypeError: if image_name contains characters that do not
meet the logic that container names must start
with an alphanumeric character and can then
use _ . or - in addition to alphanumeric.
[a-zA-Z0-9][a-zA-Z0-9_.-]+
"""
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
msg = ("%r is not a valid docker image name. Image name"
"must start with an alphanumeric character and"
"can then use _ . or - in addition to alphanumeric." % image_name)
raise argparse.ArgumentTypeError(msg)
return image_name
def get_argparser(self):
"""Get arguments that may be used by repo2docker"""
argparser = argparse.ArgumentParser()
argparser.add_argument(
'--config',
@ -268,7 +284,8 @@ class Repo2Docker(Application):
'--publish', '-p',
dest='ports',
action='append',
help='Specify port mappings for the image. Needs a command to run in the container.'
help=('Specify port mappings for the image. Needs a command to '
'run in the container.')
)
argparser.add_argument(
@ -336,6 +353,7 @@ class Repo2Docker(Application):
extra=dict(phase='failed'))
def initialize(self, argv=None):
"""Init repo2docker configuration before start"""
if argv is None:
argv = sys.argv[1:]
args = self.get_argparser().parse_args(argv)
@ -401,10 +419,12 @@ class Repo2Docker(Application):
self.run = False
self.push = False
# check against self.run and not args.run as self.run is false on --no-build
# check against self.run and not args.run as self.run is false on
# --no-build
if args.volumes and not self.run:
# Can't mount if we aren't running
print("To Mount volumes with -v, you also need to run the container")
print('To Mount volumes with -v, you also need to run the '
'container')
sys.exit(1)
for v in args.volumes:
@ -414,15 +434,18 @@ class Repo2Docker(Application):
self.run_cmd = args.cmd
if args.all_ports and not self.run:
print('To publish user defined port mappings, the container must also be run')
print('To publish user defined port mappings, the container must '
'also be run')
sys.exit(1)
if args.ports and not self.run:
print('To publish user defined port mappings, the container must also be run')
print('To publish user defined port mappings, the container must '
'also be run')
sys.exit(1)
if args.ports and not self.run_cmd:
print('To publish user defined port mapping, user must specify the command to run in the container')
print('To publish user defined port mapping, user must specify '
'the command to run in the container')
sys.exit(1)
self.ports = validate_and_generate_port_mapping(args.ports)
@ -437,12 +460,14 @@ class Repo2Docker(Application):
self.build_memory_limit = args.build_memory_limit
if args.environment and not self.run:
print("To specify environment variables, you also need to run the container")
print('To specify environment variables, you also need to run '
'the container')
sys.exit(1)
self.environment = args.environment
def push_image(self):
"""Push docker image to registry"""
client = docker.APIClient(version='auto', **kwargs_from_env())
# Build a progress setup for each layer, and only emit per-layer
# info every 1.5s
@ -465,6 +490,7 @@ class Repo2Docker(Application):
last_emit_time = time.time()
def run_image(self):
"""Run docker container from built image"""
client = docker.from_env(version='auto')
if not self.run_cmd:
port = str(self._get_free_port())
@ -533,8 +559,8 @@ class Repo2Docker(Application):
s.close()
return port
def start(self):
"""Start execution of repo2docker"""
# Check if r2d can connect to docker daemon
if self.build:
try:
@ -542,7 +568,8 @@ class Repo2Docker(Application):
**kwargs_from_env())
del client
except DockerException as e:
print("Docker client initialization error. Check if docker is running on the host.")
print("Docker client initialization error. Check if docker is"
" running on the host.")
print(e)
if self.log_level == logging.DEBUG:
raise e
@ -560,11 +587,7 @@ class Repo2Docker(Application):
# cleanup if things go wrong
with maybe_cleanup(checkout_path, self.cleanup_checkout):
if self.repo_type == 'remote':
self.fetch(
self.repo,
self.ref,
checkout_path
)
self.fetch(self.repo, self.ref, checkout_path)
os.chdir(checkout_path)
@ -588,7 +611,9 @@ class Repo2Docker(Application):
}
self.log.info('Using %s builder\n', bp.__class__.__name__,
extra=dict(phase='building'))
for l in picked_buildpack.build(self.output_image_spec, self.build_memory_limit, build_args):
for l in picked_buildpack.build(self.output_image_spec,
self.build_memory_limit, build_args):
if 'stream' in l:
self.log.info(l['stream'],
extra=dict(phase='building'))

Wyświetl plik

@ -1,18 +1,36 @@
"""
Generates a variety of Dockerfiles based on an input matrix
"""
"""Generates a Dockerfile based on an input matrix for Julia"""
import os
from .conda import CondaBuildPack
class JuliaBuildPack(CondaBuildPack):
"""
Julia + Conda build pack
Julia build pack which uses conda.
The Julia build pack always uses the parent, `CondaBuildPack`,
since Julia does not work with Python virtual environments.
See https://github.com/JuliaPy/PyCall.jl/issues/410
Julia does not work with Virtual Envs,
see https://github.com/JuliaPy/PyCall.jl/issues/410
"""
def get_env(self):
"""Get additional environment settings for Julia and Jupyter
Returns:
an ordered list of environment setting tuples
The tuples contain a string of the environment variable name and
a string of the environment setting:
- `JULIA_PATH`: base path where all Julia Binaries and libraries
will be installed
- `JULIA_HOME`: path where all Julia Binaries will be installed
- `JULIA_PKGDIR`: path where all Julia libraries will be installed
- `JULIA_VERSION`: default version of julia to be installed
- `JUPYTER`: environment variable required by IJulia to point to
the `jupyter` executable
For example, a tuple may be `('JULIA_VERSION', '0.6.0')`.
"""
return super().get_env() + [
('JULIA_PATH', '${APP_BASE}/julia'),
('JULIA_HOME', '${JULIA_PATH}/bin'),
@ -22,9 +40,26 @@ class JuliaBuildPack(CondaBuildPack):
]
def get_path(self):
return super().get_path() + ['${JULIA_PATH}/bin']
"""Adds path to Julia binaries to user's PATH.
Returns:
an ordered list of path strings. The path to the Julia
executable is added to the list.
"""
return super().get_path() + ['${JULIA_HOME}']
def get_build_scripts(self):
"""
Return series of build-steps common to "ALL" Julia repositories
All scripts found here should be independent of contents of a
particular repository.
This creates a directory with permissions for installing julia packages
(from get_assemble_scripts).
"""
return super().get_build_scripts() + [
(
"root",
@ -52,11 +87,19 @@ class JuliaBuildPack(CondaBuildPack):
]
def get_assemble_scripts(self):
"""
Return series of build-steps specific to "this" Julia repository
Precompile all Julia libraries found in the repository's REQUIRE
file. The parent, CondaBuildPack, will add the build steps for
any needed Python packages found in environment.yml.
"""
require = self.binder_path('REQUIRE')
return super().get_assemble_scripts() + [(
"${NB_USER}",
# Pre-compile all libraries if they've opted into it. `using {libraryname}` does the
# right thing
# Pre-compile all libraries if they've opted into it.
# `using {libraryname}` does the right thing
r"""
cat "%(require)s" >> ${JULIA_PKGDIR}/v0.6/REQUIRE && \
julia -e ' \
@ -69,4 +112,14 @@ class JuliaBuildPack(CondaBuildPack):
)]
def detect(self):
return os.path.exists(self.binder_path('REQUIRE')) and super().detect()
"""
Check if current repo should be built with the Julia Build pack
super().detect() is not called in this function - it would return
false unless an `environment.yml` is present and we do not want to
require the presence of a `environment.yml` to use Julia.
Instead we just check if the path to `REQUIRE` exists
"""
return os.path.exists(self.binder_path('REQUIRE'))

Wyświetl plik

@ -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):
"""Delete the directory at passed path if 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])

Wyświetl plik

@ -34,8 +34,11 @@ def test_image_name_fail():
builddir = os.path.dirname(__file__)
image_name = 'Test/Invalid_name:1.0.0'
args_list = ['--no-run', '--no-build', '--image-name', image_name]
expected = "error: argument --image-name: %r is not a valid docker image name. " \
"Image name can contain only lowercase characters." % image_name
expected = (
"%r is not a valid docker image name. Image name"
"must start with an alphanumeric character and"
"can then use _ . or - in addition to alphanumeric." % image_name
)
assert not validate_arguments(builddir, args_list, expected)
@ -47,9 +50,11 @@ def test_image_name_underscore_fail():
builddir = os.path.dirname(__file__)
image_name = '_test/invalid_name:1.0.0'
args_list = ['--no-run', '--no-build', '--image-name', image_name]
expected = "error: argument --image-name: %r is not a valid docker image name. " \
"Image name can contain only lowercase characters." % image_name
expected = (
"%r is not a valid docker image name. Image name"
"must start with an alphanumeric character and"
"can then use _ . or - in addition to alphanumeric." % image_name
)
assert not validate_arguments(builddir, args_list, expected)
@ -61,9 +66,11 @@ def test_image_name_double_dot_fail():
builddir = os.path.dirname(__file__)
image_name = 'test..com/invalid_name:1.0.0'
args_list = ['--no-run', '--no-build', '--image-name', image_name]
expected = "error: argument --image-name: %r is not a valid docker image name. " \
"Image name can contain only lowercase characters." % image_name
expected = (
"%r is not a valid docker image name. Image name"
"must start with an alphanumeric character and"
"can then use _ . or - in addition to alphanumeric." % image_name
)
assert not validate_arguments(builddir, args_list, expected)
@ -76,8 +83,11 @@ def test_image_name_valid_restircted_registry_domain_name_fail():
builddir = os.path.dirname(__file__)
image_name = 'Test.com/valid_name:1.0.0'
args_list = ['--no-run', '--no-build', '--image-name', image_name]
expected = "error: argument --image-name: %r is not a valid docker image name. " \
"Image name can contain only lowercase characters." % image_name
expected = (
"%r is not a valid docker image name. Image name"
"must start with an alphanumeric character and"
"can then use _ . or - in addition to alphanumeric." % image_name
)
assert not validate_arguments(builddir, args_list, expected)