mirror of
https://github.com/ansible/awx.git
synced 2026-01-09 23:12:08 -03:30
Add validator for ee image field name
awxkit default ee image name is now a fixed valid (but bogus) name, rather than random unicode
This commit is contained in:
parent
ef67f9c65d
commit
61846e88ca
24
awx/main/migrations/0147_validate_ee_image_field.py
Normal file
24
awx/main/migrations/0147_validate_ee_image_field.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 2.2.16 on 2021-06-15 02:49
|
||||
|
||||
import awx.main.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0146_add_insights_inventory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='executionenvironment',
|
||||
name='image',
|
||||
field=models.CharField(
|
||||
help_text='The full image location, including the container registry, image name, and version tag.',
|
||||
max_length=1024,
|
||||
validators=[awx.main.validators.validate_container_image_name],
|
||||
verbose_name='image location',
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -3,6 +3,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models.base import CommonModel
|
||||
from awx.main.validators import validate_container_image_name
|
||||
|
||||
|
||||
__all__ = ['ExecutionEnvironment']
|
||||
@ -31,6 +32,7 @@ class ExecutionEnvironment(CommonModel):
|
||||
max_length=1024,
|
||||
verbose_name=_('image location'),
|
||||
help_text=_("The full image location, including the container registry, image name, and version tag."),
|
||||
validators=[validate_container_image_name],
|
||||
)
|
||||
managed_by_tower = models.BooleanField(default=False, editable=False)
|
||||
credential = models.ForeignKey(
|
||||
|
||||
@ -4,6 +4,7 @@ from awx.main.validators import (
|
||||
validate_certificate,
|
||||
validate_ssh_private_key,
|
||||
vars_validate_or_raise,
|
||||
validate_container_image_name,
|
||||
)
|
||||
from awx.main.tests.data.ssh import (
|
||||
TEST_SSH_RSA1_KEY_DATA,
|
||||
@ -163,3 +164,39 @@ def test_valid_vars(var_str):
|
||||
def test_invalid_vars(var_str):
|
||||
with pytest.raises(RestValidationError):
|
||||
vars_validate_or_raise(var_str)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("image_name", "is_valid"),
|
||||
[
|
||||
("localhost", True),
|
||||
("short", True),
|
||||
("simple/name", True),
|
||||
("ab/ab/ab/ab", True),
|
||||
("foo.com/", False),
|
||||
("", False),
|
||||
("localhost/foo", True),
|
||||
("3asdasdf3", True),
|
||||
("xn--7o8h.com/myimage", True),
|
||||
("Asdf.com/foo/bar", True),
|
||||
("Foo/FarB", False),
|
||||
("registry.com:8080/myapp:tag", True),
|
||||
("registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", True),
|
||||
("registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", True),
|
||||
("registry.com:8080/myapp@sha256:badbadbadbad", False),
|
||||
("registry.com:8080/myapp:invalid~tag", False),
|
||||
("bad_hostname.com:8080/myapp:tag", False),
|
||||
("localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", True),
|
||||
("localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", True),
|
||||
("localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", False),
|
||||
("localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", True),
|
||||
("registry.com:8080/myapp@bad", False),
|
||||
("registry.com:8080/myapp@2bad", False),
|
||||
],
|
||||
)
|
||||
def test_valid_container_image_name(image_name, is_valid):
|
||||
if is_valid:
|
||||
validate_container_image_name(image_name)
|
||||
else:
|
||||
with pytest.raises(ValidationError):
|
||||
validate_container_image_name(image_name)
|
||||
|
||||
@ -195,3 +195,95 @@ def vars_validate_or_raise(vars_str):
|
||||
return vars_str
|
||||
except ParseError as e:
|
||||
raise RestValidationError(str(e))
|
||||
|
||||
|
||||
def validate_container_image_name(value):
|
||||
"""
|
||||
from https://github.com/distribution/distribution/blob/af8ac809336c2316c81b08605d92d94f8670ad15/reference/reference.go#L4
|
||||
|
||||
Grammar
|
||||
|
||||
reference := name [ ":" tag ] [ "@" digest ]
|
||||
name := [domain '/'] path-component ['/' path-component]*
|
||||
domain := domain-component ['.' domain-component]* [':' port-number]
|
||||
domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
port-number := /[0-9]+/
|
||||
path-component := alpha-numeric [separator alpha-numeric]*
|
||||
alpha-numeric := /[a-z0-9]+/
|
||||
separator := /[_.]|__|[-]*/
|
||||
|
||||
tag := /[\w][\w.-]{0,127}/
|
||||
|
||||
digest := digest-algorithm ":" digest-hex
|
||||
digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
|
||||
digest-algorithm-separator := /[+.-_]/
|
||||
digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
|
||||
digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
||||
|
||||
The regex below is the printed value of the following GO variable, which represents the 'reference' line above, i.e. the full image name + tag or digest
|
||||
https://github.com/distribution/distribution/blob/af8ac809336c2316c81b08605d92d94f8670ad15/reference/regexp.go#L72
|
||||
"""
|
||||
# fmt: off
|
||||
regex = re.compile(r"""
|
||||
^
|
||||
( # name
|
||||
(?: # domain (optional)
|
||||
(?:
|
||||
[a-zA-Z0-9] # domain-component
|
||||
|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]
|
||||
)
|
||||
(?:
|
||||
(?:
|
||||
\. # additional domain-components, separated by a dot
|
||||
(?:
|
||||
[a-zA-Z0-9]
|
||||
|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]
|
||||
)
|
||||
)+
|
||||
)?
|
||||
(?::[0-9]+)? # port number
|
||||
/ # domain should end in slash
|
||||
)?
|
||||
[a-z0-9]+ # path-component
|
||||
(?:
|
||||
(?:
|
||||
(?:
|
||||
[_.]|__|[-]* # path-components can contain separators
|
||||
)
|
||||
[a-z0-9]+
|
||||
)+
|
||||
)?
|
||||
(?:
|
||||
(?:
|
||||
/ # additional path-components, separated by a /
|
||||
[a-z0-9]+
|
||||
(?:
|
||||
(?:
|
||||
(?:
|
||||
[_.]|__|[-]*
|
||||
)
|
||||
[a-z0-9]+
|
||||
)+
|
||||
)?
|
||||
)+
|
||||
)?
|
||||
) # name end
|
||||
(?:
|
||||
: # tag (optional)
|
||||
([\w][\w.-]{0,127}) # tag limited to 128 characters
|
||||
)?
|
||||
(?:
|
||||
@ # digest (optional)
|
||||
(
|
||||
[A-Za-z][A-Za-z0-9]* # digest-algorithm-component, e.g. sha256
|
||||
(?: # additional digest-alorithm components, separated by [-_+.]
|
||||
[-_+.][A-Za-z][A-Za-z0-9]*
|
||||
)*
|
||||
[:][0-9a-fA-F]{32,} # digest-hex
|
||||
)
|
||||
)?
|
||||
$
|
||||
""", re.VERBOSE)
|
||||
# fmt: on
|
||||
if not regex.fullmatch(value):
|
||||
raise ValidationError(_(f"The container image name {value} is not valid"))
|
||||
|
||||
@ -40,7 +40,7 @@ class ExecutionEnvironment(HasCreate, HasCopy, base.Base):
|
||||
def payload(self, name='', image=None, organization=None, credential=None, pull='', **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=name or "EE - {}".format(random_title()),
|
||||
image=image or random_title(10),
|
||||
image=image or "example.invalid/component:tagname",
|
||||
organization=organization.id if organization else None,
|
||||
credential=credential.id if credential else None,
|
||||
pull=pull,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user