From 6cc9e81a0ed4bac1006054a5bd49c280e6951485 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Sun, 25 Apr 2021 18:11:03 +0530 Subject: [PATCH] mgr/dashboard: Detailed error messages in rgw bucket name validation Explain the rgw bucket name constrains for each bucket name validation errors. Fixes: https://tracker.ceph.com/issues/50516 Signed-off-by: Nizamudeen A (cherry picked from commit ab04e536684024c8d3613907d4bcd72fddf2ef20) --- .../cypress/integration/rgw/buckets.po.ts | 5 ++- .../rgw-bucket-form.component.html | 15 ++++++++ .../rgw-bucket-form.component.spec.ts | 33 +++++++++++------ .../rgw-bucket-form.component.ts | 36 +++++++++++++++---- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts index 6183ae25790cc..6950227aee97b 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts @@ -129,7 +129,10 @@ export class BucketsPageHelper extends PageHelper { .and('have.class', 'ng-invalid'); // Check that error message was printed under name input field - cy.get('#bid + .invalid-feedback').should('have.text', 'The value is not valid.'); + cy.get('#bid + .invalid-feedback').should( + 'have.text', + 'Bucket names must be 3 to 63 characters long.' + ); // Test invalid owner input // select some valid option. The owner drop down error message will not appear unless a valid user was selected at diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html index 310ec3d171f55..be54ee84efb51 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html @@ -50,6 +50,21 @@ The chosen name is already in use. + Bucket names must not contain uppercase characters or underscores. + Each label must start and end with a lowercase letter or a number. + Bucket names cannot be formatted as IP address. + Bucket names can only contain lowercase letters, numbers, and hyphens. + Bucket names must be 3 to 63 characters long. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts index 82e1a1e47f2bf..aa02c4cc0304c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts @@ -54,14 +54,14 @@ describe('RgwBucketFormComponent', () => { }); describe('bucketNameValidator', () => { - const testValidator = (name: string, valid: boolean) => { + const testValidator = (name: string, valid: boolean, expectedError?: string) => { rgwBucketServiceGetSpy.and.returnValue(throwError('foo')); formHelper.setValue('bid', name, true); - tick(500); + tick(); if (valid) { formHelper.expectValid('bid'); } else { - formHelper.expectError('bid', 'bucketNameInvalid'); + formHelper.expectError('bid', expectedError); } }; @@ -70,11 +70,14 @@ describe('RgwBucketFormComponent', () => { })); it('bucket names cannot be formatted as IP address', fakeAsync(() => { - testValidator('172.10.4.51', false); + const testIPs = ['1.1.1.01', '001.1.1.01', '127.0.0.1']; + for (const ip of testIPs) { + testValidator(ip, false, 'ipAddress'); + } })); it('bucket name must be >= 3 characters long (1/2)', fakeAsync(() => { - testValidator('ab', false); + testValidator('ab', false, 'shouldBeInRange'); })); it('bucket name must be >= 3 characters long (2/2)', fakeAsync(() => { @@ -82,7 +85,7 @@ describe('RgwBucketFormComponent', () => { })); it('bucket name must be <= than 63 characters long (1/2)', fakeAsync(() => { - testValidator(_.repeat('a', 64), false); + testValidator(_.repeat('a', 64), false, 'shouldBeInRange'); })); it('bucket name must be <= than 63 characters long (2/2)', fakeAsync(() => { @@ -90,23 +93,31 @@ describe('RgwBucketFormComponent', () => { })); it('bucket names must not contain uppercase characters or underscores (1/2)', fakeAsync(() => { - testValidator('iAmInvalid', false); + testValidator('iAmInvalid', false, 'containsUpperCase'); + })); + + it('bucket names can only contain lowercase letters, numbers, and hyphens', fakeAsync(() => { + testValidator('$$$', false, 'onlyLowerCaseAndNumbers'); })); it('bucket names must not contain uppercase characters or underscores (2/2)', fakeAsync(() => { - testValidator('i_am_invalid', false); + testValidator('i_am_invalid', false, 'containsUpperCase'); + })); + + it('bucket names must start and end with letters or numbers', fakeAsync(() => { + testValidator('abcd-', false, 'lowerCaseOrNumber'); })); it('bucket names with invalid labels (1/3)', fakeAsync(() => { - testValidator('abc.1def.Ghi2', false); + testValidator('abc.1def.Ghi2', false, 'containsUpperCase'); })); it('bucket names with invalid labels (2/3)', fakeAsync(() => { - testValidator('abc.1_xy', false); + testValidator('abc.1_xy', false, 'containsUpperCase'); })); it('bucket names with invalid labels (3/3)', fakeAsync(() => { - testValidator('abc.*def', false); + testValidator('abc.*def', false, 'lowerCaseOrNumber'); })); it('bucket names must be a series of one or more labels and can contain lowercase letters, numbers, and hyphens (1/3)', fakeAsync(() => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts index bde9390dcf310..da3fb9f3e0b12 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts @@ -250,19 +250,27 @@ export class RgwBucketFormComponent extends CdForm implements OnInit { return observableOf(null); } const constraints = []; + let errorName: string; // - Bucket names cannot be formatted as IP address. constraints.push(() => { const ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i; const ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i; const name = this.bucketForm.get('bid').value; - let notAnIP = true; + let notIP = true; if (ipv4Rgx.test(name) || ipv6Rgx.test(name)) { - notAnIP = false; + errorName = 'ipAddress'; + notIP = false; } - return notAnIP; + return notIP; }); // - Bucket names can be between 3 and 63 characters long. - constraints.push((name: string) => _.inRange(name.length, 3, 64)); + constraints.push((name: string) => { + if (!_.inRange(name.length, 3, 64)) { + errorName = 'shouldBeInRange'; + return false; + } + return true; + }); // - Bucket names must not contain uppercase characters or underscores. // - Bucket names must start with a lowercase letter or number. // - Bucket names must be a series of one or more labels. Adjacent @@ -274,14 +282,17 @@ export class RgwBucketFormComponent extends CdForm implements OnInit { return _.every(labels, (label) => { // Bucket names must not contain uppercase characters or underscores. if (label !== _.toLower(label) || label.includes('_')) { + errorName = 'containsUpperCase'; return false; } // Bucket names can contain lowercase letters, numbers, and hyphens. if (!/[0-9a-z-]/.test(label)) { + errorName = 'onlyLowerCaseAndNumbers'; return false; } // Each label must start and end with a lowercase letter or a number. - return _.every([0, label.length], (index) => { + return _.every([0, label.length - 1], (index) => { + errorName = 'lowerCaseOrNumber'; return /[a-z]/.test(label[index]) || _.isInteger(_.parseInt(label[index])); }); }); @@ -289,7 +300,20 @@ export class RgwBucketFormComponent extends CdForm implements OnInit { if (!_.every(constraints, (func: Function) => func(control.value))) { return observableTimer().pipe( map(() => { - return { bucketNameInvalid: true }; + switch (errorName) { + case 'onlyLowerCaseAndNumbers': + return { onlyLowerCaseAndNumbers: true }; + case 'shouldBeInRange': + return { shouldBeInRange: true }; + case 'ipAddress': + return { ipAddress: true }; + case 'containsUpperCase': + return { containsUpperCase: true }; + case 'lowerCaseOrNumber': + return { lowerCaseOrNumber: true }; + default: + return { bucketNameInvalid: true }; + } }) ); } -- 2.39.5