diff --git a/endpoints/building.py b/endpoints/building.py index bf2c2b161..b6d2f4904 100644 --- a/endpoints/building.py +++ b/endpoints/building.py @@ -8,6 +8,7 @@ from data import model from data.database import db from auth.auth_context import get_authenticated_user from endpoints.notificationhelper import spawn_notification +from util.names import escape_tag logger = logging.getLogger(__name__) @@ -160,7 +161,7 @@ class PreparedBuild(object): if self._tags: raise Exception('Property tags already set') - self._tags = list(value) + self._tags = [escape_tag(tag, default='latest') for tag in value] @property def build_name(self): diff --git a/endpoints/v2/manifest.py b/endpoints/v2/manifest.py index 3c87c199f..c30ae63f5 100644 --- a/endpoints/v2/manifest.py +++ b/endpoints/v2/manifest.py @@ -24,6 +24,7 @@ from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, from endpoints.trackhelper import track_and_log from endpoints.notificationhelper import spawn_notification from util.registry.replication import queue_storage_replication +from util.names import VALID_TAG_PATTERN from digest import digest_tools from data import model from data.database import RepositoryTag @@ -31,8 +32,6 @@ from data.database import RepositoryTag logger = logging.getLogger(__name__) -VALID_TAG_PATTERN = r'[\w][\w.-]{0,127}' - BASE_MANIFEST_ROUTE = '//manifests/' MANIFEST_DIGEST_ROUTE = BASE_MANIFEST_ROUTE.format(digest_tools.DIGEST_PATTERN) MANIFEST_TAGNAME_ROUTE = BASE_MANIFEST_ROUTE.format(VALID_TAG_PATTERN) diff --git a/test/test_digest_tools.py b/test/test_digest_tools.py index 8fcb71237..fd873cefa 100644 --- a/test/test_digest_tools.py +++ b/test/test_digest_tools.py @@ -50,3 +50,8 @@ class TestDigestPath(unittest.TestCase): for digest, path in examples: self.assertEquals(content_path(digest), path) + + +if __name__ == '__main__': + unittest.main() + diff --git a/test/test_names.py b/test/test_names.py new file mode 100644 index 000000000..71de44046 --- /dev/null +++ b/test/test_names.py @@ -0,0 +1,20 @@ +import unittest + +from util.names import escape_tag + +class TestEscapeTag(unittest.TestCase): + def assertTag(self, input_tag, expected): + self.assertEquals(expected, escape_tag(input_tag)) + + def test_basic_tag(self): + self.assertTag('latest', 'latest') + self.assertTag('latest124', 'latest124') + self.assertTag('5de1e98d', '5de1e98d') + + def test_invalid_tag(self): + self.assertTag('detailed_view#61', 'detailed_view_61') + self.assertTag('-detailed_view#61', '_detailed_view_61') + +if __name__ == '__main__': + unittest.main() + diff --git a/util/names.py b/util/names.py index c943bfc52..e25eaa1c0 100644 --- a/util/names.py +++ b/util/names.py @@ -1,15 +1,26 @@ import urllib import re -from functools import wraps from uuid import uuid4 REPOSITORY_NAME_REGEX = re.compile(r'^[\.a-zA-Z0-9_-]+$') -TAG_REGEX = re.compile(r'^[\w][\w\.-]{0,127}$') +VALID_TAG_PATTERN = r'[\w][\w.-]{0,127}' +FULL_TAG_PATTERN = r'^[\w][\w.-]{0,127}$' + +TAG_REGEX = re.compile(FULL_TAG_PATTERN) TAG_ERROR = ('Invalid tag: must match [A-Za-z0-9_.-], NOT start with "." or "-", ' 'and can contain 1-128 characters') +def escape_tag(tag, default='latest'): + """ Escapes a Docker tag, ensuring it matches the tag regular expression. """ + if not tag: + return default + + tag = re.sub(r'^[^\w]', '_', tag) + tag = re.sub(r'[^\w\.-]', '_', tag) + return tag[0:127] + def parse_namespace_repository(repository, library_namespace, include_tag=False): parts = repository.rstrip('/').split('/', 1)