Merge pull request #2291 from coreos-inc/namespace-adjustment
Allow namespaces to be between 2 and 255 characters in length
This commit is contained in:
commit
c116ef2987
8 changed files with 43 additions and 39 deletions
|
@ -9,14 +9,12 @@
|
||||||
|
|
||||||
<form class="form-signup" name="signupForm" ng-submit="register()" ng-show="!awaitingConfirmation && !registering">
|
<form class="form-signup" name="signupForm" ng-submit="register()" ng-show="!awaitingConfirmation && !registering">
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
<span class="namespace-input" binding="newUser.username" is-back-incompat="isBackIncompat" namespace-title="Requested username"></span>
|
<span class="namespace-input" binding="newUser.username" back-incompat-message="backIncompatMessage" namespace-title="Requested username"></span>
|
||||||
|
|
||||||
<div class="expandable" ng-class="{'expanded': isBackIncompat || (!signupForm.namespaceField.$error.required && signupForm.namespaceField.$invalid)}">
|
<div class="expandable" ng-class="{'expanded': backIncompatMessage || (!signupForm.namespaceField.$error.required && signupForm.namespaceField.$invalid)}">
|
||||||
<div class="co-alert co-alert-warning thin" ng-show="isBackIncompat">
|
<div class="co-alert co-alert-warning thin" ng-show="backIncompatMessage">{{ backIncompatMessage }}</div>
|
||||||
Usernames with dots or dashes are incompatible with Docker version 1.8 or older
|
|
||||||
</div>
|
|
||||||
<div class="co-alert co-alert-danger thin" ng-show="!signupForm.namespaceField.$error.required && signupForm.namespaceField.$invalid">
|
<div class="co-alert co-alert-danger thin" ng-show="!signupForm.namespaceField.$error.required && signupForm.namespaceField.$invalid">
|
||||||
Usernames must be alphanumeric and between four and thirty characters in length
|
Usernames must be alphanumeric and between 2 and 255 characters in length
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
var TEAM_PATTERN = '^[a-z][a-z0-9]+$';
|
var TEAM_PATTERN = '^[a-z][a-z0-9]+$';
|
||||||
var ROBOT_PATTERN = '^[a-z][a-z0-9_]{3,29}$';
|
var ROBOT_PATTERN = '^[a-z][a-z0-9_]{1,254}$';
|
||||||
var USERNAME_PATTERN = '^(?=.{4,30}$)([a-z0-9]+(?:[._-][a-z0-9]+)*)$';
|
var USERNAME_PATTERN = '^(?=.{2,255}$)([a-z0-9]+(?:[._-][a-z0-9]+)*)$';
|
||||||
|
|
||||||
// Define the pages module.
|
// Define the pages module.
|
||||||
quayPages = angular.module('quayPages', [], function(){});
|
quayPages = angular.module('quayPages', [], function(){});
|
||||||
|
|
|
@ -10,7 +10,7 @@ angular.module('quay').directive('namespaceInput', function () {
|
||||||
restrict: 'C',
|
restrict: 'C',
|
||||||
scope: {
|
scope: {
|
||||||
'binding': '=binding',
|
'binding': '=binding',
|
||||||
'isBackIncompat': '=isBackIncompat',
|
'backIncompatMessage': '=backIncompatMessage',
|
||||||
'hasExternalError': '=?hasExternalError',
|
'hasExternalError': '=?hasExternalError',
|
||||||
|
|
||||||
'namespaceTitle': '@namespaceTitle',
|
'namespaceTitle': '@namespaceTitle',
|
||||||
|
@ -21,11 +21,17 @@ angular.module('quay').directive('namespaceInput', function () {
|
||||||
|
|
||||||
$scope.$watch('binding', function(binding) {
|
$scope.$watch('binding', function(binding) {
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
$scope.isBackIncompat = false;
|
$scope.backIncompatMessage = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.isBackIncompat = (binding.indexOf('-') > 0 || binding.indexOf('.') > 0);
|
if (binding.indexOf('-') > 0 || binding.indexOf('.') > 0) {
|
||||||
|
$scope.backIncompatMessage = 'Namespaces with dashes or dots are only compatible with Docker 1.9+';
|
||||||
|
} else if (binding.length < 4 || binding.length > 30) {
|
||||||
|
$scope.backIncompatMessage = 'Namespaces less than 4 or more than 30 characters are only compatible with Docker 1.6+';
|
||||||
|
} else {
|
||||||
|
$scope.backIncompatMessage = null;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,16 +42,14 @@
|
||||||
<label for="orgName">Organization Name</label>
|
<label for="orgName">Organization Name</label>
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<span class="field-container">
|
<span class="field-container">
|
||||||
<span class="namespace-input" binding="org.name" is-back-incompat="isBackIncompat" namespace-title="Organization name"></span>
|
<span class="namespace-input" binding="org.name" back-incompat-message="backIncompatMessage" namespace-title="Organization name"></span>
|
||||||
</span>
|
|
||||||
<span class="co-alert co-alert-warning thin" ng-show="isBackIncompat">
|
|
||||||
Organization names with dots or dashes are incompatible with Docker version 1.8 or older
|
|
||||||
</span>
|
</span>
|
||||||
|
<span class="co-alert co-alert-warning thin" ng-show="backIncompatMessage">{{ backIncompatMessage }}</span>
|
||||||
<span class="co-alert co-alert-danger thin" ng-show="!newOrgForm.namespaceField.$error.required && newOrgForm.namespaceField.$invalid">
|
<span class="co-alert co-alert-danger thin" ng-show="!newOrgForm.namespaceField.$error.required && newOrgForm.namespaceField.$invalid">
|
||||||
Organization names must be alphanumeric, be at least four characters in length and max thirty characters in length
|
Organization names must be alphanumeric, be at least 2 characters in length and max 255 characters in length
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="description">This will also be the namespace for your repositories. Must be alphanumeric, all lowercase, at least four characters long and at most thirty characters long.</span>
|
<span class="description">This will also be the namespace for your repositories. Must be alphanumeric, all lowercase, at least 2 characters long and at most 255 characters long.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group nested" quay-require="['MAILING']">
|
<div class="form-group nested" quay-require="['MAILING']">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</p>
|
</p>
|
||||||
<p>Please confirm the selected username or enter a different username below:</p>
|
<p>Please confirm the selected username or enter a different username below:</p>
|
||||||
<form name="usernameForm" ng-submit="updateUser({'username': username})">
|
<form name="usernameForm" ng-submit="updateUser({'username': username})">
|
||||||
<div class="namespace-input" binding="username" is-back-incompat="isBackIncompat"
|
<div class="namespace-input" binding="username" back-incompat-message="backIncompatMessage"
|
||||||
namespace-title="Username" style="margin-bottom: 20px;"
|
namespace-title="Username" style="margin-bottom: 20px;"
|
||||||
has-external-error="state == 'existing'"></div>
|
has-external-error="state == 'existing'"></div>
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
ng-disabled="usernameForm.$invalid || state != 'confirmed'"
|
ng-disabled="usernameForm.$invalid || state != 'confirmed'"
|
||||||
value="Confirm Username">
|
value="Confirm Username">
|
||||||
<span class="cor-loader-inline" ng-show="state == 'confirming'"></span>
|
<span class="cor-loader-inline" ng-show="state == 'confirming'"></span>
|
||||||
<span class="username-status" ng-show="state == 'confirmed' && !isBackIncompat">
|
<span class="username-status" ng-show="state == 'confirmed' && !backIncompatMessage">
|
||||||
<i class="fa fa-check-circle"></i> Username valid
|
<i class="fa fa-check-circle"></i> Username valid
|
||||||
</span>
|
</span>
|
||||||
<span class="username-status" ng-show="state == 'existing'">
|
<span class="username-status" ng-show="state == 'existing'">
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
<span class="username-status" ng-show="state == 'editing' && usernameForm.$invalid">
|
<span class="username-status" ng-show="state == 'editing' && usernameForm.$invalid">
|
||||||
Usernames must be alphanumeric and be at least four characters in length
|
Usernames must be alphanumeric and be at least four characters in length
|
||||||
</span>
|
</span>
|
||||||
<span class="username-status" ng-show="state == 'confirmed' && isBackIncompat">
|
<span class="username-status" ng-show="state == 'confirmed' && backIncompatMessage">
|
||||||
<i class="fa fa-exclamation-triangle"></i> Note: Usernames with dots or dashes are incompatible with Docker version 1.8 or older
|
<i class="fa fa-exclamation-triangle"></i>{{ backIncompatMessage }}
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -651,7 +651,7 @@ class TestCreateNewUser(ApiTestCase):
|
||||||
email='test@example.com'),
|
email='test@example.com'),
|
||||||
expected_code=400)
|
expected_code=400)
|
||||||
|
|
||||||
self.assertEquals('Invalid namespace a: Namespace must be between 4 and 30 characters in length',
|
self.assertEquals('Invalid namespace a: Namespace must be between 2 and 255 characters in length',
|
||||||
json['detail'])
|
json['detail'])
|
||||||
|
|
||||||
def test_trycreateregexmismatch(self):
|
def test_trycreateregexmismatch(self):
|
||||||
|
|
|
@ -20,6 +20,8 @@ class TestUsernameValidation(unittest.TestCase):
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
|
||||||
def test_valid(self):
|
def test_valid(self):
|
||||||
|
self.assertValid('ja')
|
||||||
|
self.assertValid('jak')
|
||||||
self.assertValid('jake')
|
self.assertValid('jake')
|
||||||
self.assertValid('ja_ke')
|
self.assertValid('ja_ke')
|
||||||
self.assertValid('te-st')
|
self.assertValid('te-st')
|
||||||
|
@ -27,14 +29,13 @@ class TestUsernameValidation(unittest.TestCase):
|
||||||
|
|
||||||
def test_properlength(self):
|
def test_properlength(self):
|
||||||
self.assertValid('z' * 30)
|
self.assertValid('z' * 30)
|
||||||
|
self.assertValid('z' * 255)
|
||||||
|
|
||||||
def test_tooshort(self):
|
def test_tooshort(self):
|
||||||
self.assertInvalid('j')
|
self.assertInvalid('j')
|
||||||
self.assertInvalid('ja')
|
|
||||||
self.assertInvalid('jk')
|
|
||||||
|
|
||||||
def test_toolong(self):
|
def test_toolong(self):
|
||||||
self.assertInvalid('z' * 31)
|
self.assertInvalid('z' * 256)
|
||||||
|
|
||||||
def test_invalids(self):
|
def test_invalids(self):
|
||||||
self.assertInvalid('_test')
|
self.assertInvalid('_test')
|
||||||
|
@ -158,20 +159,21 @@ class TestUsernameGenerator(unittest.TestCase):
|
||||||
self.assert_generated_output('ja___ke', 'ja_ke')
|
self.assert_generated_output('ja___ke', 'ja_ke')
|
||||||
|
|
||||||
def test_trailing_underscores(self):
|
def test_trailing_underscores(self):
|
||||||
self.assert_generated_output('ja__', 'ja00')
|
self.assert_generated_output('ja__', 'ja')
|
||||||
self.assert_generated_output('jake__', 'jake')
|
self.assert_generated_output('jake__', 'jake')
|
||||||
|
|
||||||
def test_starting_underscore(self):
|
def test_starting_underscore(self):
|
||||||
self.assert_generated_output('_jake', 'jake')
|
self.assert_generated_output('_jake', 'jake')
|
||||||
|
|
||||||
def test_short_names(self):
|
def test_short_names(self):
|
||||||
self.assert_generated_output('a', 'a000')
|
self.assert_generated_output('a', 'a0')
|
||||||
self.assert_generated_output('ab', 'ab00')
|
self.assert_generated_output('ab', 'ab')
|
||||||
self.assert_generated_output('abc', 'abc0')
|
self.assert_generated_output('abc', 'abc')
|
||||||
|
|
||||||
def test_long_names(self):
|
def test_long_names(self):
|
||||||
self.assert_generated_output('abcdefghijklmnopqrstuvwxyz1234567890',
|
self.assert_generated_output('abcdefghijklmnopqrstuvwxyz1234567890',
|
||||||
'abcdefghijklmnopqrstuvwxyz1234')
|
'abcdefghijklmnopqrstuvwxyz1234567890')
|
||||||
|
self.assert_generated_output('c' * 256, 'c' * 255)
|
||||||
|
|
||||||
def test_unicode_transliteration(self):
|
def test_unicode_transliteration(self):
|
||||||
self.assert_generated_output(u'\xc6neid', 'aeneid')
|
self.assert_generated_output(u'\xc6neid', 'aeneid')
|
||||||
|
@ -184,18 +186,18 @@ class TestUsernameGenerator(unittest.TestCase):
|
||||||
self.assert_generated_output(u'\u0985\u09ad\u09bf\u099c\u09c0\u09a4', 'abhijiit')
|
self.assert_generated_output(u'\u0985\u09ad\u09bf\u099c\u09c0\u09a4', 'abhijiit')
|
||||||
self.assert_generated_output(u'\u0d05\u0d2d\u0d3f\u0d1c\u0d40\u0d24', 'abhijiit')
|
self.assert_generated_output(u'\u0d05\u0d2d\u0d3f\u0d1c\u0d40\u0d24', 'abhijiit')
|
||||||
self.assert_generated_output(u'\u0d2e\u0d32\u0d2f\u0d3e\u0d32\u0d2e\u0d4d', 'mlyaalm')
|
self.assert_generated_output(u'\u0d2e\u0d32\u0d2f\u0d3e\u0d32\u0d2e\u0d4d', 'mlyaalm')
|
||||||
self.assert_generated_output(u'\ue000', '0000')
|
self.assert_generated_output(u'\ue000', '00')
|
||||||
self.assert_generated_output(u'\u03ff', '0000')
|
self.assert_generated_output(u'\u03ff', '00')
|
||||||
|
|
||||||
self.assert_generated_output(u'\u0d2e\u0d32\u03ff\u03ff\u0d2e\u0d32', 'mlml')
|
self.assert_generated_output(u'\u0d2e\u0d32\u03ff\u03ff\u0d2e\u0d32', 'mlml')
|
||||||
|
|
||||||
def test_multiple_suggestions(self):
|
def test_multiple_suggestions(self):
|
||||||
name_gen = generate_valid_usernames('a')
|
name_gen = generate_valid_usernames('a')
|
||||||
generated_output = list(islice(name_gen, 4))
|
generated_output = list(islice(name_gen, 4))
|
||||||
self.assertEquals('a000', generated_output[0])
|
self.assertEquals('a0', generated_output[0])
|
||||||
self.assertEquals('a001', generated_output[1])
|
self.assertEquals('a1', generated_output[1])
|
||||||
self.assertEquals('a002', generated_output[2])
|
self.assertEquals('a2', generated_output[2])
|
||||||
self.assertEquals('a003', generated_output[3])
|
self.assertEquals('a3', generated_output[3])
|
||||||
|
|
||||||
|
|
||||||
class TestDockerVersionParsing(unittest.TestCase):
|
class TestDockerVersionParsing(unittest.TestCase):
|
||||||
|
|
|
@ -9,8 +9,8 @@ INVALID_PASSWORD_MESSAGE = 'Invalid password, password must be at least ' + \
|
||||||
'8 characters and contain no whitespace.'
|
'8 characters and contain no whitespace.'
|
||||||
VALID_CHARACTERS = string.digits + string.lowercase
|
VALID_CHARACTERS = string.digits + string.lowercase
|
||||||
|
|
||||||
MIN_USERNAME_LENGTH = 4
|
MIN_USERNAME_LENGTH = 2
|
||||||
MAX_USERNAME_LENGTH = 30
|
MAX_USERNAME_LENGTH = 255
|
||||||
|
|
||||||
VALID_LABEL_KEY_REGEX = r'^[a-z0-9](([a-z0-9]|[-.](?![.-]))*[a-z0-9])?$'
|
VALID_LABEL_KEY_REGEX = r'^[a-z0-9](([a-z0-9]|[-.](?![.-]))*[a-z0-9])?$'
|
||||||
VALID_USERNAME_REGEX = r'^([a-z0-9]+(?:[._-][a-z0-9]+)*)$'
|
VALID_USERNAME_REGEX = r'^([a-z0-9]+(?:[._-][a-z0-9]+)*)$'
|
||||||
|
@ -63,7 +63,7 @@ def _gen_filler_chars(num_filler_chars):
|
||||||
|
|
||||||
def generate_valid_usernames(input_username):
|
def generate_valid_usernames(input_username):
|
||||||
normalized = input_username.encode('unidecode', 'ignore').strip().lower()
|
normalized = input_username.encode('unidecode', 'ignore').strip().lower()
|
||||||
prefix = re.sub(INVALID_USERNAME_CHARACTERS, '_', normalized)[:30]
|
prefix = re.sub(INVALID_USERNAME_CHARACTERS, '_', normalized)[:MAX_USERNAME_LENGTH]
|
||||||
prefix = re.sub(r'_{2,}', '_', prefix)
|
prefix = re.sub(r'_{2,}', '_', prefix)
|
||||||
|
|
||||||
if prefix.endswith('_'):
|
if prefix.endswith('_'):
|
||||||
|
|
Reference in a new issue