mirror of https://github.com/rancher/dashboard.git
Service validation
Adds the following validation: Service Ports - adds extra checks for port name, fixes invalid check for externalname services ClusterIP - Conditional Checks on service types and ip none check ExternalName
This commit is contained in:
parent
55a946fe32
commit
aa33b16785
|
|
@ -1235,6 +1235,10 @@ validation:
|
||||||
required: '"{key}" is required'
|
required: '"{key}" is required'
|
||||||
requiredOrOverride: '"{key}" is required or must allow override'
|
requiredOrOverride: '"{key}" is required or must allow override'
|
||||||
service:
|
service:
|
||||||
|
clusterIp:
|
||||||
|
none: 'Service Type is Headless which requires Cluster IP to be set to "None"'
|
||||||
|
externalName:
|
||||||
|
none: 'External Name is required on an ExternalName Service.'
|
||||||
ports:
|
ports:
|
||||||
name:
|
name:
|
||||||
required: "Port Rule [{position}] - Name is required."
|
required: "Port Rule [{position}] - Name is required."
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,25 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nullable: false,
|
nullable: false,
|
||||||
path: 'spec.ports',
|
path: 'spec',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'array',
|
type: 'array',
|
||||||
validators: ['servicePort'],
|
validators: ['servicePort'],
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
nullable: true,
|
||||||
|
path: 'spec',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
validators: ['clusterIp'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nullable: true,
|
||||||
|
path: 'spec',
|
||||||
|
required: true,
|
||||||
|
type: 'array',
|
||||||
|
validators: ['externalName'],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { flowOutput } from '@/utils/validators/flow-output';
|
import { flowOutput } from '@/utils/validators/flow-output';
|
||||||
import { servicePort } from '@/utils/validators/service-port';
|
import { clusterIp, externalName, servicePort } from '@/utils/validators/service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom validation functions beyond normal scalr types
|
* Custom validation functions beyond normal scalr types
|
||||||
* Validator must export a function name should match the validator name on the customValidationRules rule
|
* Validator must export a function name should match the validator name on the customValidationRules rule
|
||||||
* Exported function is used as a lookup key in resource-instance:validationErrors:customValidationRules loop
|
* Exported function is used as a lookup key in resource-instance:validationErrors:customValidationRules loop
|
||||||
*/
|
*/
|
||||||
export default { flowOutput, servicePort };
|
export default {
|
||||||
|
clusterIp,
|
||||||
|
externalName,
|
||||||
|
flowOutput,
|
||||||
|
servicePort,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import { validateDnsLabel } from '@/utils/validators';
|
|
||||||
|
|
||||||
export function servicePort(ports, getters, errors, validatorArgs) {
|
|
||||||
if (isEmpty(ports)) {
|
|
||||||
errors.push(getters['i18n/t']('validation.required', { key: 'Port Rules' }));
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
ports.forEach((port, ind, ary) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
nodePort,
|
|
||||||
port: pPort,
|
|
||||||
targetPort,
|
|
||||||
} = port;
|
|
||||||
const idx = ind + 1;
|
|
||||||
|
|
||||||
if (ary.length > 1 && isEmpty(name)) {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.name.required', { position: idx }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodePort) {
|
|
||||||
const np = parseInt(nodePort, 10);
|
|
||||||
|
|
||||||
if (isNaN(np)) {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.nodePort.requiredInt', { position: idx }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pPort) {
|
|
||||||
const p = parseInt(pPort, 10);
|
|
||||||
|
|
||||||
if (isNaN(p)) {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.port.requiredInt', { position: idx }));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.port.required', { position: idx }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetPort) {
|
|
||||||
const tp = parseInt(targetPort, 10);
|
|
||||||
|
|
||||||
if (isNaN(tp)) {
|
|
||||||
const tpIanaDisplayKey = getters['i18n/t']('validation.service.ports.targetPort.ianaAt', { position: idx });
|
|
||||||
/* [rfc6335](https://tools.ietf.org/rfc/rfc6335.txt) port name (IANA_SVC_NAME)
|
|
||||||
An alphanumeric (a-z, and 0-9) string, with a maximum length of 15 characters,
|
|
||||||
with the '-' character allowed anywhere except the first or the last character or adjacent to another '-' character,
|
|
||||||
it must contain at least a(a - z) character
|
|
||||||
validateChars(str, { validChars: 'A-Za-z0-9_.-' }, displayKey, intl, errors); */
|
|
||||||
const opts = {
|
|
||||||
ianaServiceName: true,
|
|
||||||
maxLength: 15,
|
|
||||||
validChars: 'A-Za-z0-9-',
|
|
||||||
};
|
|
||||||
const isIanaServiceNameErrors = validateDnsLabel(targetPort, tpIanaDisplayKey, getters, opts, errors);
|
|
||||||
|
|
||||||
if (!isEmpty(isIanaServiceNameErrors)) {
|
|
||||||
errors.push(...isIanaServiceNameErrors);
|
|
||||||
}
|
|
||||||
} else if (tp < 1 || tp > 65535) {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.targetPort.between', { position: idx }));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.push(getters['i18n/t']('validation.service.ports.targetPort.required', { position: idx }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { validateDnsLabel, validateHostname } from '@/utils/validators';
|
||||||
|
|
||||||
|
export function servicePort(spec, getters, errors, validatorArgs) {
|
||||||
|
const { ports, type: serviceType } = spec;
|
||||||
|
|
||||||
|
if (serviceType === 'ExternalName') {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty(ports)) {
|
||||||
|
errors.push(getters['i18n/t']('validation.required', { key: 'Port Rules' }));
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
ports.forEach((port, ind, ary) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
nodePort,
|
||||||
|
port: pPort,
|
||||||
|
targetPort,
|
||||||
|
} = port;
|
||||||
|
const idx = ind + 1;
|
||||||
|
|
||||||
|
if (ary.length > 1 && isEmpty(name)) {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.name.required', { position: idx }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmpty(name)) {
|
||||||
|
const nameErrors = validateDnsLabel(name, 'name', getters, undefined, errors);
|
||||||
|
|
||||||
|
if (!isEmpty(nameErrors)) {
|
||||||
|
if (errors.length && errors.length > 0) {
|
||||||
|
errors = [...errors, ...nameErrors];
|
||||||
|
} else {
|
||||||
|
errors = nameErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodePort) {
|
||||||
|
const np = parseInt(nodePort, 10);
|
||||||
|
|
||||||
|
if (isNaN(np)) {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.nodePort.requiredInt', { position: idx }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pPort) {
|
||||||
|
const p = parseInt(pPort, 10);
|
||||||
|
|
||||||
|
if (isNaN(p)) {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.port.requiredInt', { position: idx }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.port.required', { position: idx }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetPort) {
|
||||||
|
const tp = parseInt(targetPort, 10);
|
||||||
|
|
||||||
|
if (isNaN(tp)) {
|
||||||
|
const tpIanaDisplayKey = getters['i18n/t']('validation.service.ports.targetPort.ianaAt', { position: idx });
|
||||||
|
/* [rfc6335](https://tools.ietf.org/rfc/rfc6335.txt) port name (IANA_SVC_NAME)
|
||||||
|
An alphanumeric (a-z, and 0-9) string, with a maximum length of 15 characters,
|
||||||
|
with the '-' character allowed anywhere except the first or the last character or adjacent to another '-' character,
|
||||||
|
it must contain at least a(a - z) character
|
||||||
|
validateChars(str, { validChars: 'A-Za-z0-9_.-' }, displayKey, intl, errors); */
|
||||||
|
const opts = {
|
||||||
|
ianaServiceName: true,
|
||||||
|
maxLength: 15,
|
||||||
|
validChars: 'A-Za-z0-9-',
|
||||||
|
};
|
||||||
|
const isIanaServiceNameErrors = validateDnsLabel(targetPort, tpIanaDisplayKey, getters, opts, errors);
|
||||||
|
|
||||||
|
if (!isEmpty(isIanaServiceNameErrors)) {
|
||||||
|
errors.push(...isIanaServiceNameErrors);
|
||||||
|
}
|
||||||
|
} else if (tp < 1 || tp > 65535) {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.targetPort.between', { position: idx }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.ports.targetPort.required', { position: idx }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clusterIp(spec, getters, errors, validatorArgs) {
|
||||||
|
/*
|
||||||
|
clusterIP is the IP address of the service and is usually assigned randomly by the master.
|
||||||
|
If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail.
|
||||||
|
This field can not be changed through updates.
|
||||||
|
Valid values are \"None\", empty string (\"\"), or a valid IP address. \"None\" can be specified for headless services when proxying is not required.
|
||||||
|
Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName.
|
||||||
|
More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
|
||||||
|
*/
|
||||||
|
const typesToCheck = ['ClusterIP', 'NodePort', 'LoadBalancer'];
|
||||||
|
const serviceType = spec?.type;
|
||||||
|
|
||||||
|
if (!typesToCheck.includes(serviceType)) {
|
||||||
|
// validation only applies to services in the types to check
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceType === 'Headless' && spec?.clusterIp !== 'None') {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.clusterIp.none'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function externalName(spec, getters, errors, validatorArgs) {
|
||||||
|
/*
|
||||||
|
externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service.
|
||||||
|
No proxying will be involved.
|
||||||
|
Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName.
|
||||||
|
*/
|
||||||
|
if (spec?.type !== 'ExternalName' && isEmpty(spec?.externalName)) {
|
||||||
|
errors.push(getters['i18n/t']('validation.service.clusterIp.none'));
|
||||||
|
} else {
|
||||||
|
const hostNameErrors = validateHostname(spec.externalName, 'ExternalName', getters, undefined, errors);
|
||||||
|
|
||||||
|
if (!isEmpty(hostNameErrors)) {
|
||||||
|
if (errors.length && errors.length > 0) {
|
||||||
|
errors = [...errors, ...hostNameErrors];
|
||||||
|
} else {
|
||||||
|
errors = hostNameErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue