Skip to content

Commit 995036f

Browse files
committed
Merge pull request #296 from digitalfishpond/validation-on-deploy
Validation and layout on deploy page
2 parents fbcd85d + 49679f7 commit 995036f

File tree

8 files changed

+135
-30
lines changed

8 files changed

+135
-30
lines changed

src/app/frontend/deploy/deploy.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ <h3 class="md-headline kd-deploy-form-title">Deploy a Containerized App</h3>
3131
</kd-help-section>
3232

3333
<div ng-switch="ctrl.selection">
34-
<deploy-from-settings ng-switch-when="Settings" name="ctrl.name"
34+
<deploy-from-settings ng-switch-when="Settings" class="md-body-1" name="ctrl.name"
3535
namespaces="ctrl.namespaces" detail="ctrl.detail" form="ctrl.deployForm" protocols="ctrl.protocols">
3636
</deploy-from-settings>
37-
<deploy-from-file ng-switch-when="File" name="ctrl.name" detail="ctrl.detail" form="ctrl.deployForm">
37+
<deploy-from-file ng-switch-when="File" class="md-body-1" name="ctrl.name" detail="ctrl.detail"
38+
form="ctrl.deployForm">
3839
</deploy-from-file>
3940
</div>
4041

src/app/frontend/deploy/deployfromsettings.html

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,22 @@
1717
<kd-help-section>
1818
<md-input-container class="md-block" md-is-error="ctrl.isNameError()">
1919
<label>App name</label>
20-
<input ng-model="ctrl.name" name="name" namespace="ctrl.namespace" required kd-unique-name
21-
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }">
22-
<md-progress-linear class="kd-deploy-form-progress" md-mode="indeterminate"
23-
ng-show="ctrl.form.name.$pending">
20+
<input ng-model="ctrl.name" name="name" namespace="ctrl.namespace" required ng-pattern="ctrl.namePattern"
21+
ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }" kd-unique-name
22+
ng-maxlength="ctrl.nameMaxLength">
23+
<md-progress-linear class="kd-deploy-form-progress" md-mode="indeterminate" ng-show="ctrl.form.name.$pending">
2424
</md-progress-linear>
2525
<ng-messages for="ctrl.form.name.$error" role="alert" multiple>
2626
<ng-message when="required">Application name is required.</ng-message>
27-
<ng-message when="uniqueName">
28-
Application with this name already exists within namespace <i>{{ctrl.namespace}}</i>.
27+
<ng-message when="uniqueName">Application with this name
28+
already exists within namespace <i>{{ctrl.namespace}}</i>.</ng-message>
29+
<ng-message when="pattern">Application name should contain only lowercase letters, numbers, and '-' between words
2930
</ng-message>
31+
<ng-message when="maxlength">Application name should have no more than 63 characters</ng-message>
3032
</ng-messages>
33+
3134
</md-input-container>
35+
3236
<kd-user-help>
3337
An 'app' label with this value will be added to the Replication Controller and Service that get deployed.
3438
<a href="http://kubernetes.io/v1.1/docs/user-guide/labels.html" target="_blank">Learn more</a>
@@ -55,8 +59,9 @@
5559
<label>Number of pods</label>
5660
<input ng-model="ctrl.replicas" type="number" required min="1" name="replicas">
5761
<ng-messages for="ctrl.form.replicas.$error" role="alert" multiple>
62+
<ng-message when="required">Number of pods is required.</ng-message>
5863
<ng-message when="number">Number of pods must be a positive integer.</ng-message>
59-
<ng-message when="min">Number of pods must be positive.</ng-message>
64+
<ng-message when="min">Number of pods must be at least 1.</ng-message>
6065
</ng-messages>
6166
</md-input-container>
6267
<kd-user-help>
@@ -81,11 +86,12 @@
8186

8287
<kd-help-section>
8388
<md-checkbox ng-model="ctrl.isExternal" class="md-primary"
84-
ng-model-options="{ debounce: { 'default': 500, 'blur': 0 } }">
89+
ng-model-options="{ debounce: { 'default': 500, 'blur': 0 } }">
8590
Expose service externally
8691
</md-checkbox>
8792
</kd-help-section>
8893

94+
<!-- advanced options -->
8995
<div ng-show="ctrl.isMoreOptionsEnabled()">
9096
<kd-help-section>
9197
<md-input-container>
@@ -99,18 +105,18 @@
99105
</kd-help-section>
100106

101107
<kd-help-section>
102-
<div>
103-
<div>Labels</div>
104-
<div layout="column">
105-
<div layout="row">
106-
<p flex>Key</p>
107-
<p flex>Value</p>
108-
</div>
109-
<div ng-repeat="label in ctrl.labels">
110-
<kd-deploy-label layout="row" flex label="label" labels="ctrl.labels"></kd-deploy-label>
111-
</div>
108+
<div layout="column">
109+
<div class="kd-label-title md-body-2">Labels</div>
110+
<div layout="row" class="kd-label-header-row">
111+
<div flex="45">Key</div>
112+
<div flex="5"></div>
113+
<div flex="40">Value</div>
114+
</div>
115+
<div ng-repeat="label in ctrl.labels">
116+
<kd-deploy-label layout="row" flex label="label" labels="ctrl.labels"></kd-deploy-label>
112117
</div>
113118
</div>
119+
114120
<kd-user-help>
115121
The specified labels will be applied to the created Replication Controller, Service (if any) and Pods.
116122
Common labels include release, environment, tier, partition and track.
@@ -165,6 +171,7 @@
165171
<ng-message when="min">CPU requirement must be given as a positive number.</ng-message>
166172
</ng-messages>
167173
</md-input-container>
174+
<div flex="5"></div>
168175
<md-input-container flex>
169176
<label>Memory requirement (MiB)</label>
170177
<input ng-model="ctrl.memoryRequirement" type="number" name="memoryRequirement" min="0">
@@ -200,9 +207,11 @@
200207
</kd-help-section>
201208

202209
<kd-help-section>
203-
<md-switch ng-model="ctrl.runAsPrivileged" class="md-primary">
204-
Run as privileged
205-
</md-switch>
210+
<div class="md-block">
211+
<md-checkbox ng-model="ctrl.runAsPrivileged" class="md-primary">
212+
Run as privileged
213+
</md-checkbox>
214+
</div>
206215
<kd-user-help>
207216
Processes in privileged containers are equivalent to processes running as root on the host.
208217
</kd-user-help>
@@ -219,14 +228,14 @@
219228
</kd-help-section>
220229
</div>
221230

231+
<!-- show/hide advanced options toggle -->
222232
<md-button class="md-primary kd-deploy-moreoptions-button" type="button"
223233
ng-click="ctrl.switchMoreOptions()"
224234
ng-hide="ctrl.isMoreOptionsEnabled()">
225-
<md-icon>arrow_drop_down</md-icon>More options
235+
<md-icon>arrow_drop_down</md-icon>show advanced options
226236
</md-button>
227-
228237
<md-button class="md-primary kd-deploy-moreoptions-button" type="button"
229238
ng-click="ctrl.switchMoreOptions()"
230239
ng-show="ctrl.isMoreOptionsEnabled()">
231-
<md-icon>arrow_drop_up</md-icon>Less options
240+
<md-icon>arrow_drop_up</md-icon>hide advanced options
232241
</md-button>

src/app/frontend/deploy/deployfromsettings.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
// limitations under the License.
1414

1515
@import '../variables';
16+
$bar-height: 2px;
1617

1718
md-progress-linear {
1819
&.kd-deploy-form-progress {
19-
$bar-height: 2px;
20-
2120
clear: left;
2221
height: $bar-height;
2322
margin-bottom: -$bar-height;

src/app/frontend/deploy/deployfromsettings_controller.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ export default class DeployFromSettingsController {
121121
*/
122122
this.name = '';
123123

124+
/**
125+
* Checks that a name begins and ends with a lowercase letter
126+
* and contains nothing but lowercase letters and hyphens ("-")
127+
* (leading and trailing spaces are ignored by default)
128+
* @export {RegExp}
129+
*/
130+
this.namePattern = new RegExp('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$');
131+
132+
/**
133+
* Maximum length for Application name
134+
* @export {string}
135+
*/
136+
this.nameMaxLength = '63';
137+
124138
/**
125139
* Whether to run the container as privileged user.
126140
* @export {boolean}

src/app/frontend/deploy/environmentvariables.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
limitations under the License.
1515
-->
1616

17-
<div class="kd-environment-variables-title">Environment variables</div>
17+
<div class="kd-environment-variables-title md-body-2">Environment variables</div>
1818
<div ng-repeat="variable in ctrl.variables">
1919
<ng-form name="variablesForm" layout="row">
2020
<md-input-container flex class="kd-deploy-input-row">
@@ -25,6 +25,7 @@
2525
<ng-message when="pattern">Variable name must be a valid C identifier.</ng-message>
2626
</ng-messages>
2727
</md-input-container>
28+
<div flex="5"></div>
2829
<md-input-container flex class="kd-deploy-input-row">
2930
<label>Value</label>
3031
<input ng-model="variable.value" name="value">

src/app/frontend/deploy/environmentvariables.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
@import '../variables';
1616

1717
.kd-environment-variables-title {
18-
margin: $baseline-grid 0;
18+
margin: $baseline-grid * 2 0;
1919
}

src/app/frontend/deploy/portmappings.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<ng-message when="max">Port must less than 65536.</ng-message>
2727
</ng-messages>
2828
</md-input-container>
29+
<div flex="5"></div>
2930
<md-input-container flex class="kd-deploy-input-row">
3031
<label>Target port</label>
3132
<input ng-model="portMapping.targetPort" ng-change="ctrl.addProtocolIfNeeed()"
@@ -36,6 +37,7 @@
3637
<ng-message when="max">Target port must less than 65536.</ng-message>
3738
</ng-messages>
3839
</md-input-container>
40+
<div flex="5"></div>
3941
<md-input-container flex="none" class="kd-deploy-input-row">
4042
<label>Protocol</label>
4143
<md-select ng-model="portMapping.protocol" name="protocol" is-external="ctrl.isExternal"

src/test/frontend/deploy/deployfromsettings_controller_test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,83 @@ describe('DeployFromSettings controller', () => {
359359
expect(ctrl.imagePullSecret).toEqual('');
360360
});
361361
});
362+
363+
/**
364+
* The value entered for ‘App Name” is used implicitly as the name for several resources (pod, rc,
365+
* svc, label). Therefore, the app-name validation pattern is based on the RC-pattern, but must
366+
* conform with all validation patterns of all the created resources.
367+
* Currently, the ui pattern that conforms with all patterns is alpha-numeric with dashes between.
368+
*/
369+
it('should allow strings that conform to the patterns of all created resources', () => {
370+
// given the pattern used by the App Name field in the UI
371+
let namePattern = ctrl.namePattern;
372+
// given the patterns of all the names that are implicitly created
373+
let allPatterns = {
374+
servicePattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*',
375+
labelPattern: '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?',
376+
rcPattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?',
377+
appNamePattern: namePattern,
378+
};
379+
380+
// then
381+
for (let pattern in allPatterns) {
382+
expect('mylowercasename'.match(allPatterns[pattern])).toBeDefined();
383+
expect('my-name-with-dashes-between'.match(allPatterns[pattern])).toBeDefined();
384+
expect('my-n4m3-with-numb3r5'.match(allPatterns[pattern])).toBeDefined();
385+
}
386+
});
387+
388+
/**
389+
* The value entered for 'App Name' is used implicitly as the name for several resources (pod, rc,
390+
* svc, label).
391+
* Remark: The app-name pattern excludes some service names and label values, which could be
392+
* created manually. This is a restriction of the current design.
393+
*/
394+
it('should reject names that fail to conform to appNamePattern', () => {
395+
396+
// given
397+
let appNamePattern = ctrl.namePattern;
398+
399+
// then the following valid service names will be rejected
400+
expect('validservice.com'.match(appNamePattern)).toBeNull();
401+
expect('validservice/path'.match(appNamePattern)).toBeNull();
402+
403+
// then the following valid label names will be rejected
404+
expect('valid_label'.match(appNamePattern)).toBeNull();
405+
expect('ValidLabel'.match(appNamePattern)).toBeNull();
406+
expect('validLabel'.match(appNamePattern)).toBeNull();
407+
408+
// then these input values will be rejected
409+
expect('@myname-with-illegal-char-prefix'.match(appNamePattern)).toBeNull();
410+
expect('myname-with-illegal-char-suffix#'.match(appNamePattern)).toBeNull();
411+
expect('myname-with-illegal_char-in-middle'.match(appNamePattern)).toBeNull();
412+
expect('-myname-with-hyphen-prefix'.match(appNamePattern)).toBeNull();
413+
expect('myname-with-hyphen-suffix-'.match(appNamePattern)).toBeNull();
414+
expect('Myname-With-Capital-Letters'.match(appNamePattern)).toBeNull();
415+
expect('myname-with-german-umlaut-äö'.match(appNamePattern)).toBeNull();
416+
expect('my name with spaces'.match(appNamePattern)).toBeNull();
417+
expect(' '.match(appNamePattern)).toBeNull();
418+
});
419+
420+
/**
421+
* The data from the App Name field of the deploy form is used implicitly for the creation of
422+
* Service Name, Label Name and RC name. Pod names are truncated by the api server and therefore
423+
* ignored.
424+
* Remark: The maximum characters number should match all three, thereby excluding
425+
* service names of more than 63 chars via this form, while it is possible to create service names
426+
* of 253 chars manually. This is a restriction of the current design.
427+
*
428+
* ctrl.maxNameLength = 63
429+
*/
430+
it('should limit input that conforms to all created resources', () => {
431+
432+
// service names are max. 253 chars
433+
expect(ctrl.maxNameLength <= 253);
434+
435+
// label are max. 63 chars. the 256 prefix cannot be entered
436+
expect(ctrl.maxNameLength <= 63);
437+
438+
// RC name are max. 63 chars.
439+
expect(ctrl.maxNameLength <= 63);
440+
});
362441
});

0 commit comments

Comments
 (0)