@linode/validation
Version:
Yup validation schemas for use with the Linode APIv4
1,378 lines (1,369 loc) • 77.6 kB
JavaScript
// src/account.schema.ts
import { array, boolean, mixed, number, object, string } from "yup";
var updateAccountSchema = object({
email: string().max(128, "Email must be 128 characters or less."),
address_1: string().max(64, "Address must be 64 characters or less."),
city: string().max(24, "City must be 24 characters or less."),
company: string().max(128, "Company must be 128 characters or less."),
country: string().min(2, "Country code must be two letters.").max(2, "Country code must be two letters."),
first_name: string().max(50, "First name must be 50 characters or less."),
last_name: string().max(50, "Last name must be 50 characters or less."),
address_2: string().max(64, "Address must be 64 characters or less."),
phone: string().max(32, "Phone number must be 32 characters or less."),
state: string().max(24, "State must be 24 characters or less."),
tax_id: string().max(100, "Tax ID must be 100 characters or less."),
zip: string().max(16, "Zip code must be 16 characters or less.")
});
var createOAuthClientSchema = object({
label: string().required("Label is required.").min(1, "Label must be between 1 and 512 characters.").max(512, "Label must be between 1 and 512 characters."),
redirect_uri: string().required("Redirect URI is required.")
});
var updateOAuthClientSchema = object({
label: string().min(1, "Label must be between 1 and 512 characters.").max(512, "Label must be between 1 and 512 characters."),
redirect_uri: string()
});
var PaymentSchema = object({
usd: string().required("USD payment amount is required.")
});
var CreditCardSchema = object({
card_number: string().required("Credit card number is required.").min(13, "Credit card number must be between 13 and 23 characters.").max(23, "Credit card number must be between 13 and 23 characters."),
expiry_year: number().test(
"length",
"Expiration year must be 2 or 4 digits.",
(value) => [2, 4].includes(String(value).length)
).required("Expiration year is required.").typeError("Expiration year must be a number.").min((/* @__PURE__ */ new Date()).getFullYear(), "Expiration year must not be in the past.").max((/* @__PURE__ */ new Date()).getFullYear() + 20, "Expiry too far in the future."),
expiry_month: number().required("Expiration month is required.").typeError("Expiration month must be a number.").min(1, "Expiration month must be a number from 1 to 12.").max(12, "Expiration month must be a number from 1 to 12."),
cvv: string().required("Security code is required.").min(3, "Security code must be between 3 and 4 characters.").max(4, "Security code must be between 3 and 4 characters.")
});
var PaymentMethodSchema = object({
type: mixed().oneOf(
["credit_card", "payment_method_nonce"],
"Type must be credit_card or payment_method_nonce."
),
data: object().when("type", {
is: "credit_card",
then: () => CreditCardSchema,
otherwise: () => object({
nonce: string().required("Payment nonce is required.")
})
}),
is_default: boolean().required(
"You must indicate if this should be your default method of payment."
)
});
var CreateUserSchema = object({
username: string().required("Username is required.").min(3, "Username must be between 3 and 32 characters.").max(32, "Username must be between 3 and 32 characters."),
email: string().required("Email address is required.").email("Must be a valid email address."),
restricted: boolean().required(
"You must indicate if this user should have restricted access."
)
});
var UpdateUserSchema = object({
username: string().min(3, "Username must be between 3 and 32 characters.").max(32, "Username must be between 3 and 32 characters."),
email: string().email("Must be a valid email address."),
restricted: boolean()
});
var GrantSchema = object({
id: number().required("ID is required."),
permissions: string().oneOf(
["read_only", "read_write"],
"Permissions must be null, read_only, or read_write."
).nullable("Permissions must be null, read_only, or read_write.")
});
var UpdateGrantSchema = object({
global: object(),
linode: array().of(GrantSchema),
domain: array().of(GrantSchema),
nodebalancer: array().of(GrantSchema),
image: array().of(GrantSchema),
longview: array().of(GrantSchema),
stackscript: array().of(GrantSchema),
volume: array().of(GrantSchema)
});
var UpdateAccountSettingsSchema = object({
network_helper: boolean(),
backups_enabled: boolean(),
managed: boolean(),
longview_subscription: string().nullable(),
object_storage: string(),
interfaces_for_new_linodes: string()
});
var PromoCodeSchema = object({
promo_code: string().required("Promo code is required.").min(1, "Promo code must be between 1 and 32 characters.").max(32, "Promo code must be between 1 and 32 characters.")
});
// src/buckets.schema.ts
import { boolean as boolean2, object as object2, string as string2 } from "yup";
var ENDPOINT_TYPES = ["E0", "E1", "E2", "E3"];
var CreateBucketSchema = object2().shape(
{
label: string2().required("Bucket name is required.").min(3, "Bucket name must be between 3 and 63 characters.").matches(/^\S*$/, "Bucket name must not contain spaces.").matches(
/^[a-z0-9].*[a-z0-9]$/,
"Bucket name must start and end with a lowercase letter or number."
).matches(
/^(?!.*[.-]{2})[a-z0-9.-]+$/,
"Bucket name must contain only lowercase letters, numbers, periods (.), and hyphens (-). Adjacent periods and hyphens are not allowed."
).max(63, "Bucket name must be between 3 and 63 characters.").test(
"unique-label",
"A bucket with this name already exists in your selected region",
(value, context) => {
var _a;
const { cluster, region } = context.parent;
const buckets = (_a = context.options.context) == null ? void 0 : _a.buckets;
if (!Array.isArray(buckets)) {
return true;
}
return !buckets.some(
(bucket) => bucket.label === value && (bucket.cluster === cluster || bucket.region === region)
);
}
),
cluster: string2().when("region", {
is: (region) => !region || region.length === 0,
then: (schema) => schema.required("Cluster is required.")
}),
region: string2().when("cluster", {
is: (cluster) => !cluster || cluster.length === 0,
then: (schema) => schema.required("Region is required.")
}),
endpoint_type: string2().oneOf([...ENDPOINT_TYPES]).optional(),
cors_enabled: boolean2().optional(),
acl: string2().oneOf([
"private",
"public-read",
"authenticated-read",
"public-read-write"
]).optional(),
s3_endpoint: string2().optional()
},
[["cluster", "region"]]
).test("cors-enabled-check", "Invalid CORS configuration.", function(value) {
const { endpoint_type, cors_enabled } = value;
if ((endpoint_type === "E0" || endpoint_type === "E1") && !cors_enabled) {
return this.createError({
path: "cors_enabled",
message: "CORS must be enabled for endpoint type E0 or E1."
});
}
if ((endpoint_type === "E2" || endpoint_type === "E3") && cors_enabled) {
return this.createError({
path: "cors_enabled",
message: "CORS must be disabled for endpoint type E2 or E3."
});
}
return true;
});
var UploadCertificateSchema = object2({
certificate: string2().required("Certificate is required."),
private_key: string2().required("Private key is required.")
});
var UpdateBucketAccessSchema = object2({
acl: string2().oneOf([
"private",
"public-read",
"authenticated-read",
"public-read-write"
]).notRequired(),
cors_enabled: boolean2().notRequired()
});
// src/databases.schema.ts
import { boolean as boolean3, mixed as mixed2, number as number2 } from "yup";
import { array as array2, object as object3, string as string3 } from "yup";
var LABEL_MESSAGE = "Label must be between 3 and 32 characters";
var createDatabaseSchema = object3({
label: string3().required("Label is required").min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE),
engine: string3().required("Database Engine is required"),
region: string3().required("Region is required"),
type: string3().required("Type is required"),
cluster_size: number2().oneOf([1, 2, 3], "Nodes are required").required("Nodes are required"),
replication_type: string3().notRequired().nullable(),
// TODO (UIE-8214) remove POST GA
replication_commit_type: string3().notRequired().nullable()
// TODO (UIE-8214) remove POST GA
});
var updateDatabaseSchema = object3({
label: string3().notRequired().min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE),
allow_list: array2().of(string3()).notRequired(),
updates: object3().notRequired().shape({
frequency: string3().oneOf(["weekly", "monthly"]),
duration: number2(),
hour_of_day: number2(),
day_of_week: number2(),
week_of_month: number2().nullable()
}).nullable(),
type: string3().notRequired()
});
var createValidator = (key, field) => {
const fieldTypes = Array.isArray(field.type) ? field.type : [field.type];
switch (true) {
case fieldTypes.includes("integer"):
return number2().integer(`${key} must be a whole number`);
case fieldTypes.includes("number"):
return number2();
case fieldTypes.includes("string"):
return string3();
case fieldTypes.includes("boolean"):
return boolean3();
default:
return null;
}
};
var applyConstraints = (validator, key, field) => {
if (!validator) return null;
if (field.minimum !== void 0) {
validator = validator.min(
field.minimum,
`${key} must be at least ${field.minimum}`
);
}
if (field.maximum !== void 0) {
validator = validator.max(
field.maximum,
`${key} must be at most ${field.maximum}`
);
}
if (field.minLength !== void 0) {
validator = validator.min(
field.minLength,
`${key} must be at least ${field.minLength} characters`
);
}
if (field.maxLength !== void 0) {
validator = validator.max(
field.maxLength,
`${key} must be at most ${field.maxLength} characters`
);
}
if (field.pattern) {
let pattern = field.pattern;
if (key === "default_time_zone") {
pattern = "^(SYSTEM|[+-](0[0-9]|1[0-2]):([0-5][0-9]))$";
}
validator = validator.matches(
new RegExp(pattern),
`Please ensure that ${key} follows the format ${field.example}`
);
}
return validator;
};
var processField = (schemaShape, field) => {
if (!field.label || !field.type) {
return;
}
const key = field.label;
let validator = createValidator(key, field);
validator = applyConstraints(validator, key, field);
if (validator) {
schemaShape[key] = object3().shape({ value: validator });
}
};
var createDynamicAdvancedConfigSchema = (allConfigurations) => {
if (!Array.isArray(allConfigurations) || allConfigurations.length === 0) {
return object3().shape({});
}
const schemaShape = {};
allConfigurations.forEach((field) => processField(schemaShape, field));
return object3().shape({
configs: array2().of(
object3({
label: string3().required(),
value: mixed2().when("label", (label, schema) => {
var _a, _b;
if (Array.isArray(label)) {
label = label[0];
}
if (typeof label !== "string" || !schemaShape[label]) {
return schema;
}
const valueSchema = (_b = (_a = schemaShape[label]) == null ? void 0 : _a.fields) == null ? void 0 : _b.value;
return valueSchema ? valueSchema : schema;
})
})
)
});
};
// src/domains.schema.ts
import { array as array3, mixed as mixed3, number as number3, object as object4, string as string4 } from "yup";
var importZoneSchema = object4({
domain: string4().required("Domain is required."),
remote_nameserver: string4().required("Remote nameserver is required.")
});
var domainSchemaBase = object4().shape({
domain: string4().matches(
/([a-zA-Z0-9-_]+\.)+([a-zA-Z]{2,3}\.)?([a-zA-Z]{2,16}|XN--[a-zA-Z0-9]+)/,
"Domain is not valid."
),
status: mixed3().oneOf(["disabled", "active", "edit_mode", "has_errors"]),
tags: array3(),
description: string4().min(1, "Description must be between 1 and 253 characters.").max(253, "Description must be between 1 and 253 characters."),
retry_sec: number3(),
master_ips: array3().of(string4()),
axfr_ips: array3().of(string4()).typeError("Must be a comma-separated list of IP addresses."),
expire_sec: number3(),
refresh_sec: number3(),
ttl_sec: number3()
});
var createDomainSchema = domainSchemaBase.shape({
domain: string4().required("Domain is required.").matches(
/([a-zA-Z0-9-_]+\.)+([a-zA-Z]{2,3}\.)?([a-zA-Z]{2,16}|XN--[a-zA-Z0-9]+)/,
"Domain is not valid."
),
tags: array3().of(string4()),
type: mixed3().required().oneOf(["master", "slave"]),
soa_email: string4().when("type", {
is: "master",
then: (schema) => schema.required("SOA Email is required.")
}).email("SOA Email is not valid.").trim(),
master_ips: array3().of(string4()).when("type", {
is: "slave",
then: (schema) => schema.compact().ensure().required("At least one primary IP address is required.").min(1, "At least one primary IP address is required.")
})
});
var updateDomainSchema = domainSchemaBase.shape({
domainId: number3(),
soa_email: string4().email("SOA Email is not valid."),
axfr_ips: array3().of(string4()),
tags: array3().of(string4())
});
// src/firewalls.schema.ts
import ipaddr from "ipaddr.js";
import { array as array4, number as number4, object as object5, string as string5 } from "yup";
var IP_ERROR_MESSAGE = "Must be a valid IPv4 or IPv6 address or range.";
var validateIP = (ipAddress2) => {
if (ipAddress2 !== "" && !ipAddress2) {
return false;
}
const [, mask] = ipAddress2.split("/");
try {
if (mask) {
ipaddr.parseCIDR(ipAddress2);
} else {
ipaddr.parse(ipAddress2);
}
} catch (err) {
if (ipAddress2 !== "") {
return false;
}
}
return true;
};
var CreateFirewallDeviceSchema = object5({
linodes: array4().of(number4()),
nodebalancers: array4().of(number4())
});
var ipAddress = string5().defined().test({
name: "validateIP",
message: IP_ERROR_MESSAGE,
test: validateIP
});
var CUSTOM_PORTS_ERROR_MESSAGE = "";
var validatePort = (port) => {
CUSTOM_PORTS_ERROR_MESSAGE = "Ports must be an integer, range of integers, or a comma-separated list of integers.";
if (!port) {
CUSTOM_PORTS_ERROR_MESSAGE = "Must be 1-65535";
return false;
}
const convertedPort = parseInt(port, 10);
if (!(1 <= convertedPort && convertedPort <= 65535)) {
CUSTOM_PORTS_ERROR_MESSAGE = "Must be 1-65535";
return false;
}
if (port.startsWith("0")) {
CUSTOM_PORTS_ERROR_MESSAGE = "Port must not have leading zeroes";
return false;
}
if (String(convertedPort) !== port) {
return false;
}
return true;
};
var isCustomPortsValid = (ports) => {
const portList = (ports == null ? void 0 : ports.split(",")) || [];
let portLimitCount = 0;
for (const port of portList) {
const cleanedPort = port.trim();
if (cleanedPort.includes("-")) {
const portRange = cleanedPort.split("-");
if (!validatePort(portRange[0]) || !validatePort(portRange[1])) {
return false;
}
if (portRange.length !== 2) {
CUSTOM_PORTS_ERROR_MESSAGE = "Ranges must have 2 values";
return false;
}
if (parseInt(portRange[0], 10) >= parseInt(portRange[1], 10)) {
CUSTOM_PORTS_ERROR_MESSAGE = "Range must start with a smaller number and end with a larger number";
return false;
}
portLimitCount += 2;
} else {
if (!validatePort(cleanedPort)) {
return false;
}
portLimitCount++;
}
}
if (portLimitCount > 15) {
CUSTOM_PORTS_ERROR_MESSAGE = "Number of ports or port range endpoints exceeded. Max allowed is 15";
return false;
}
return true;
};
var validateFirewallPorts = string5().test({
name: "firewall-ports",
message: CUSTOM_PORTS_ERROR_MESSAGE,
test: (value) => {
if (!value) {
return false;
}
try {
isCustomPortsValid(value);
} catch (err) {
return false;
}
return true;
}
});
var FirewallRuleTypeSchema = object5().shape({
action: string5().oneOf(["ACCEPT", "DROP"]).required("Action is required"),
description: string5().nullable(),
label: string5().nullable(),
protocol: string5().oneOf(["ALL", "TCP", "UDP", "ICMP", "IPENCAP"]).required("Protocol is required."),
ports: string5().when("protocol", {
is: (val) => val !== "ICMP" && val !== "IPENCAP",
then: () => validateFirewallPorts,
// Workaround to get the test to fail if ports is defined when protocol === ICMP or IPENCAP
otherwise: (schema) => schema.test({
name: "protocol",
message: "Ports are not allowed for ICMP and IPENCAP protocols.",
test: (value) => typeof value === "undefined"
})
}),
addresses: object5().shape({
ipv4: array4().of(ipAddress).nullable(),
ipv6: array4().of(ipAddress).nullable()
}).strict(true).notRequired().nullable()
});
var FirewallRuleSchema = object5().shape({
inbound: array4(FirewallRuleTypeSchema).nullable(),
outbound: array4(FirewallRuleTypeSchema).nullable(),
inbound_policy: string5().oneOf(["ACCEPT", "DROP"]).required("Inbound policy is required."),
outbound_policy: string5().oneOf(["ACCEPT", "DROP"]).required("Outbound policy is required.")
});
var CreateFirewallDevicesSchema = object5().shape({
linodes: array4().of(number4().defined()),
nodebalancers: array4().of(number4().defined()),
interfaces: array4().of(number4().defined())
}).notRequired();
var CreateFirewallSchema = object5().shape({
label: string5().required("Label is required.").min(3, "Label must be between 3 and 32 characters.").max(32, "Label must be between 3 and 32 characters."),
// Label validation on the back end is more complicated, we only do basic checks here.
tags: array4().of(string5().defined()),
rules: FirewallRuleSchema,
devices: CreateFirewallDevicesSchema
});
var UpdateFirewallSchema = object5().shape({
label: string5(),
tags: array4().of(string5()),
status: string5().oneOf(["enabled", "disabled"])
// 'deleted' is also a status but it's not settable
});
var FirewallDeviceSchema = object5({
type: string5().oneOf(["linode", "nodebalancer"]).required("Device type is required."),
id: number4().required("ID is required.")
});
var UpdateFirewallSettingsSchema = object5({
default_firewall_ids: object5({
interface_public: number4().nullable(),
interface_vpc: number4().nullable(),
linode: number4().nullable(),
nodebalancer: number4().nullable()
})
});
// src/images.schema.ts
import { array as array5, boolean as boolean4, number as number5, object as object6, string as string6 } from "yup";
var labelSchema = string6().min(1, "Label must be between 1 and 50 characters.").max(50, "Label must be between 1 and 50 characters.").matches(
/^[a-zA-Z0-9,.?\-_\s']+$/,
"Image labels cannot contain special characters."
);
var baseImageSchema = object6({
label: labelSchema.optional(),
description: string6().optional().min(1).max(65e3),
cloud_init: boolean4().optional(),
tags: array5(string6().min(3).max(50).required()).max(500).optional()
});
var createImageSchema = baseImageSchema.shape({
disk_id: number5().typeError("Disk is required.").required("Disk is required.")
});
var uploadImageSchema = baseImageSchema.shape({
label: labelSchema.required("Label is required."),
region: string6().required("Region is required.")
});
var updateImageSchema = object6({
label: labelSchema.optional(),
description: string6().optional().max(65e3, "Length must be 65000 characters or less."),
tags: array5(string6().required()).optional()
});
var updateImageRegionsSchema = object6({
regions: array5(string6()).required("Regions are required.").min(1, "Must specify at least one region.")
});
// src/kubernetes.schema.ts
import { array as array6, number as number6, object as object7, string as string7, boolean as boolean5 } from "yup";
var nodePoolSchema = object7({
type: string7(),
count: number6()
});
var AutoscaleNodePoolSchema = object7({
enabled: boolean5(),
min: number6().when("enabled", {
is: true,
then: (schema) => schema.required("Minimum is a required field.").test(
"min",
"Minimum must be between 1 and 99 nodes and cannot be greater than Maximum.",
function(min) {
if (!min) {
return false;
}
if (min < 1 || min > 99) {
return false;
}
if (min > this.parent["max"]) {
return false;
}
return true;
}
)
}),
max: number6().when("enabled", {
is: true,
then: (schema) => schema.required("Maximum is a required field.").min(1, "Maximum must be between 1 and 100 nodes.").max(100, "Maximum must be between 1 and 100 nodes.")
})
});
var clusterLabelSchema = string7().required("Label is required.").matches(
/^[a-zA-Z0-9-]+$/,
"Cluster labels cannot contain special characters, spaces, or underscores."
).min(3, "Length must be between 3 and 32 characters.").max(32, "Length must be between 3 and 32 characters.");
var ipv4Address = string7().defined().test({
name: "validateIP",
message: "Must be a valid IPv4 address.",
test: validateIP
});
var ipv6Address = string7().defined().test({
name: "validateIP",
message: "Must be a valid IPv6 address.",
test: validateIP
});
var controlPlaneACLOptionsSchema = object7({
enabled: boolean5(),
"revision-id": string7(),
addresses: object7({
ipv4: array6().of(ipv4Address).nullable(),
ipv6: array6().of(ipv6Address).nullable()
}).notRequired()
});
var controlPlaneEnterpriseACLOptionsSchema = object7({
enabled: boolean5(),
"revision-id": string7(),
addresses: object7({
ipv4: array6().of(ipv4Address),
ipv6: array6().of(ipv6Address)
}).required()
});
var createKubeClusterSchema = object7({
label: clusterLabelSchema,
region: string7().required("Region is required."),
k8s_version: string7().required("Kubernetes version is required."),
node_pools: array6().of(nodePoolSchema).min(1, "Please add at least one node pool.")
});
var createKubeClusterWithRequiredACLSchema = object7({
control_plane: object7({
high_availability: boolean5(),
acl: object7({
enabled: boolean5(),
"revision-id": string7(),
addresses: object7({
ipv4: array6().of(ipv4Address),
ipv6: array6().of(ipv6Address)
})
})
}).test(
"validateIPForEnterprise",
"At least one IP address or CIDR range is required for LKE Enterprise.",
function(controlPlane) {
const { ipv4, ipv6 } = controlPlane.acl.addresses;
return ipv4 && ipv4.length > 0 || ipv6 && ipv6.length > 0;
}
).required()
});
var kubernetesControlPlaneACLPayloadSchema = object7({
acl: controlPlaneACLOptionsSchema
});
var kubernetesEnterpriseControlPlaneACLPayloadSchema = object7({
acl: controlPlaneEnterpriseACLOptionsSchema.test(
"validateIPForEnterprise",
"At least one IP address or CIDR range is required for LKE Enterprise.",
function(acl) {
const { ipv4, ipv6 } = acl.addresses || {};
return ipv4 && ipv4.length > 0 && ipv4[0] !== "" || ipv6 && ipv6.length > 0 && ipv6[0] !== "";
}
)
});
var alphaNumericValidCharactersRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-._]*[a-zA-Z0-9])?$/;
var dnsKeyRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-._/]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/;
var MAX_DNS_KEY_TOTAL_LENGTH = 128;
var MAX_DNS_KEY_SUFFIX_LENGTH = 62;
var MAX_SIMPLE_KEY_OR_VALUE_LENGTH = 63;
var validateKubernetesLabel = (labels) => {
if (!labels) {
return false;
}
for (const [labelKey, labelValue] of Object.entries(labels)) {
if (!labelKey || !labelValue) {
return false;
}
if (labelKey.includes("kubernetes.io") || labelKey.includes("linode.com")) {
return false;
}
if (labelKey.includes("/")) {
const suffix = labelKey.split("/")[0];
if (!dnsKeyRegex.test(labelKey)) {
return false;
}
if (labelKey.length > MAX_DNS_KEY_TOTAL_LENGTH || suffix.length > MAX_DNS_KEY_SUFFIX_LENGTH) {
return false;
}
} else {
if (labelKey.length > MAX_SIMPLE_KEY_OR_VALUE_LENGTH || !alphaNumericValidCharactersRegex.test(labelKey)) {
return false;
}
}
if (labelValue.length > MAX_SIMPLE_KEY_OR_VALUE_LENGTH || !alphaNumericValidCharactersRegex.test(labelValue)) {
return false;
}
}
return true;
};
var kubernetesLabelSchema = object7().test({
name: "validateLabels",
message: "Labels must be valid key-value pairs.",
test: validateKubernetesLabel
});
var kubernetesTaintSchema = object7({
key: string7().required("Key is required.").test(
"valid-key",
"Key must start with a letter or number and may contain letters, numbers, hyphens, dots, and underscores, up to 253 characters.",
(value) => {
return alphaNumericValidCharactersRegex.test(value) || dnsKeyRegex.test(value);
}
).max(253, "Key must be between 1 and 253 characters.").min(1, "Key must be between 1 and 253 characters."),
value: string7().matches(
alphaNumericValidCharactersRegex,
"Value must start with a letter or number and may contain letters, numbers, hyphens, dots, and underscores, up to 63 characters."
).max(63, "Value must be between 0 and 63 characters.").notOneOf(
["kubernetes.io", "linode.com"],
'Value cannot be "kubernetes.io" or "linode.com".'
).notRequired()
});
// src/linodes.schema.ts
import { array as array8, boolean as boolean6, lazy as lazy2, mixed as mixed4, number as number7, object as object9, string as string9 } from "yup";
import ipaddr3 from "ipaddr.js";
// src/vpcs.schema.ts
import ipaddr2 from "ipaddr.js";
import { array as array7, lazy, object as object8, string as string8 } from "yup";
var LABEL_MESSAGE2 = "Label must be between 1 and 64 characters.";
var LABEL_REQUIRED = "Label is required.";
var LABEL_REQUIREMENTS = "Label must include only ASCII letters, numbers, and dashes.";
var labelTestDetails = {
testName: "no two dashes in a row",
testMessage: "Label must not contain two dashes in a row."
};
var IP_EITHER_BOTH_NOT_NEITHER = "A subnet must have either IPv4 or IPv6, or both, but not neither.";
var TEMPORARY_IPV4_REQUIRED_MESSAGE = "A subnet must have an IPv4 range.";
var determineIPType = (ip) => {
try {
let addr;
const [, mask] = ip.split("/");
if (mask) {
const parsed = ipaddr2.parseCIDR(ip);
addr = parsed[0];
} else {
addr = ipaddr2.parse(ip);
}
return addr.kind();
} catch (e) {
return void 0;
}
};
var vpcsValidateIP = ({
value,
shouldHaveIPMask,
mustBeIPMask,
isIPv6Subnet
}) => {
if (!value) {
return false;
}
const [, mask] = value.trim().split("/");
if (mustBeIPMask) {
const valueIsMaskOnly = value === `/${mask}`;
return !mask ? false : ipaddr2.IPv6.subnetMaskFromPrefixLength(Number(mask)) !== null && valueIsMaskOnly && Number(mask) >= 64 && Number(mask) <= 125;
}
try {
const type = determineIPType(value);
const isIPv4 = type === "ipv4";
const isIPv6 = type === "ipv6";
if (!isIPv4 && !isIPv6) {
return false;
}
if (isIPv4) {
if (shouldHaveIPMask) {
ipaddr2.IPv4.parseCIDR(value);
} else {
ipaddr2.IPv4.isValid(value);
ipaddr2.IPv4.parse(value);
}
}
if (isIPv6) {
const invalidVPCIPv6Prefix = !["52", "48", "44"].includes(mask);
if (!isIPv6Subnet && invalidVPCIPv6Prefix) {
return false;
}
const invalidVPCIPv6SubnetPrefix = +mask < 52 || +mask > 62;
if (isIPv6Subnet && invalidVPCIPv6SubnetPrefix) {
return false;
}
if (shouldHaveIPMask) {
ipaddr2.IPv6.parseCIDR(value);
} else {
ipaddr2.IPv6.isValid(value);
ipaddr2.IPv6.parse(value);
}
}
return true;
} catch (err) {
return false;
}
};
var labelValidation = string8().test(
labelTestDetails.testName,
labelTestDetails.testMessage,
(value) => !(value == null ? void 0 : value.includes("--"))
).min(1, LABEL_MESSAGE2).max(64, LABEL_MESSAGE2).matches(/^[a-zA-Z0-9-]*$/, LABEL_REQUIREMENTS);
var updateVPCSchema = object8({
label: labelValidation,
description: string8()
});
var VPCIPv6Schema = object8({
range: string8().optional().test({
name: "IPv6 prefix length",
message: "Must be the prefix length 52, 48, or 44 of the IP, e.g. /52",
test: (value) => {
if (value && value.length > 0) {
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false
});
}
}
})
});
var VPCIPv6SubnetSchema = object8({
range: string8().required().test({
name: "IPv6 prefix length",
message: "Must be the prefix length (52-62) of the IP, e.g. /52",
test: (value) => {
if (value && value !== "auto" && value.length > 0) {
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
isIPv6Subnet: true
});
}
}
})
});
var createSubnetSchemaIPv4 = object8({
label: labelValidation.required(LABEL_REQUIRED),
ipv4: string8().when("ipv6", {
is: (value) => value === "" || value === null || value === void 0,
then: (schema) => schema.required(TEMPORARY_IPV4_REQUIRED_MESSAGE).test({
name: "IPv4 CIDR format",
message: "The IPv4 range must be in CIDR format.",
test: (value) => vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false
})
}),
otherwise: (schema) => lazy((value) => {
switch (typeof value) {
case "undefined":
return schema.notRequired().nullable();
case "string":
return schema.notRequired().test({
name: "IPv4 CIDR format",
message: "The IPv4 range must be in CIDR format.",
test: (value2) => vpcsValidateIP({
value: value2,
shouldHaveIPMask: true,
mustBeIPMask: false
})
});
default:
return schema.notRequired().nullable();
}
})
})
});
var createSubnetSchemaWithIPv6 = object8().shape(
{
label: labelValidation.required(LABEL_REQUIRED),
ipv4: string8().when("ipv6", {
is: (value) => value === "" || value === null || value === void 0,
then: (schema) => schema.required(IP_EITHER_BOTH_NOT_NEITHER).test({
name: "IPv4 CIDR format",
message: "The IPv4 range must be in CIDR format.",
test: (value) => vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false
})
}),
otherwise: (schema) => lazy((value) => {
switch (typeof value) {
case "undefined":
return schema.notRequired().nullable();
case "string":
return schema.notRequired().test({
name: "IPv4 CIDR format",
message: "The IPv4 range must be in CIDR format.",
test: (value2) => vpcsValidateIP({
value: value2,
shouldHaveIPMask: true,
mustBeIPMask: false
})
});
default:
return schema.notRequired().nullable();
}
})
}),
ipv6: array7().of(VPCIPv6SubnetSchema).when("ipv4", {
is: (value) => value === "" || value === null || value === void 0,
then: (schema) => schema.required(IP_EITHER_BOTH_NOT_NEITHER)
})
},
[
["ipv6", "ipv4"],
["ipv4", "ipv6"]
]
);
var createVPCIPv6Schema = VPCIPv6Schema.concat(
object8({
allocation_class: string8().optional()
})
);
var createVPCSchema = object8({
label: labelValidation.required(LABEL_REQUIRED),
description: string8(),
region: string8().required("Region is required"),
subnets: array7().of(createSubnetSchemaIPv4),
ipv6: array7().of(createVPCIPv6Schema).max(1).optional()
});
var modifySubnetSchema = object8({
label: labelValidation.required(LABEL_REQUIRED)
});
// src/linodes.schema.ts
var validateIP2 = (ipAddress2) => {
if (!ipAddress2) {
return true;
}
try {
ipaddr3.parseCIDR(ipAddress2);
} catch (err) {
return false;
}
return true;
};
var test_vpcsValidateIP = (value) => {
if (value === void 0 || value === null) {
return true;
}
return vpcsValidateIP({
value,
shouldHaveIPMask: false,
mustBeIPMask: false
});
};
var testnameDisallowedBasedOnPurpose = (allowedPurpose) => `Disallowed for non-${allowedPurpose} interfaces`;
var testmessageDisallowedBasedOnPurpose = (allowedPurpose, field) => `${field} is not allowed for interfaces that do not have a purpose set to ${allowedPurpose}.`;
var LINODE_LABEL_CHAR_REQUIREMENT = "Label must contain between 3 and 64 characters.";
var stackscript_data = object9().nullable();
var IPv4 = string9().notRequired().nullable().test({
name: "validateIPv4",
message: "Must be a valid IPv4 address, e.g. 192.168.2.0",
test: (value) => test_vpcsValidateIP(value)
});
var IPv6 = string9().notRequired().nullable().test({
name: "validateIPv6",
message: "Must be a valid IPv6 address, e.g. 2600:3c00::f03c:92ff:feeb:98f9.",
test: (value) => test_vpcsValidateIP(value)
});
var ipv4ConfigInterface = object9().when("purpose", {
is: "vpc",
then: (schema) => schema.shape({
vpc: IPv4,
nat_1_1: lazy2(
(value) => value === "any" ? string9().notRequired().nullable() : IPv4
)
}),
otherwise: (schema) => schema.nullable().test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "ipv4.vpc"),
/*
Workaround to get test to fail if field is populated when it should not be based
on purpose (inspired by similar approach in firewalls.schema.ts for ports field).
Similarly-structured logic (return typeof xyz === 'undefined') throughout this
file serves the same purpose.
*/
test: (value) => {
if (value == null ? void 0 : value.vpc) {
return typeof value.vpc === "undefined";
}
return true;
}
}).test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "ipv4.nat_1_1"),
test: (value) => {
if (value == null ? void 0 : value.nat_1_1) {
return typeof value.nat_1_1 === "undefined";
}
return true;
}
})
});
var ipv6ConfigInterface = object9().when("purpose", {
is: "vpc",
then: (schema) => schema.shape({
vpc: IPv6
}),
otherwise: (schema) => schema.nullable().test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "ipv6.vpc"),
test: (value) => {
if (value == null ? void 0 : value.vpc) {
return typeof value.vpc === "undefined";
}
return true;
}
})
});
var ConfigProfileInterfaceSchema = object9().shape({
purpose: string9().oneOf(
["public", "vlan", "vpc"],
"Purpose must be public, vlan, or vpc."
).defined().required(),
label: string9().when("purpose", {
is: "vlan",
then: (schema) => schema.required("VLAN label is required.").min(1, "VLAN label must be between 1 and 64 characters.").max(64, "VLAN label must be between 1 and 64 characters.").matches(
/[a-zA-Z0-9-]+/,
"Must include only ASCII letters, numbers, and dashes"
),
otherwise: (schema) => schema.when("label", {
is: null,
then: (s) => s.nullable(),
otherwise: (s) => s.test({
name: testnameDisallowedBasedOnPurpose("VLAN"),
message: testmessageDisallowedBasedOnPurpose("vlan", "label"),
test: (value) => typeof value === "undefined" || value === ""
})
})
}),
ipam_address: string9().when("purpose", {
is: "vlan",
then: (schema) => schema.notRequired().nullable().test({
name: "validateIPAM",
message: "Must be a valid IPv4 range, e.g. 192.0.2.0/24.",
test: validateIP2
}),
otherwise: (schema) => schema.when("ipam_address", {
is: null,
then: (s) => s.nullable(),
otherwise: (s) => s.test({
name: testnameDisallowedBasedOnPurpose("VLAN"),
message: testmessageDisallowedBasedOnPurpose(
"vlan",
"ipam_address"
),
test: (value) => typeof value === "undefined" || value === ""
})
})
}),
primary: boolean6().test(
"cant-use-with-vlan",
"VLAN interfaces can't be the primary interface",
(value, context) => {
const isVLANandIsSetToPrimary = value && context.parent.purpose === "vlan";
return !isVLANandIsSetToPrimary;
}
).optional(),
subnet_id: number7().when("purpose", {
is: "vpc",
then: (schema) => schema.transform((value) => isNaN(value) ? void 0 : value).required("Subnet is required."),
otherwise: (schema) => schema.notRequired().nullable().test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "subnet_id"),
test: (value) => typeof value === "undefined" || value === null
})
}),
vpc_id: number7().when("purpose", {
is: "vpc",
then: (schema) => schema.required("VPC is required."),
otherwise: (schema) => schema.notRequired().nullable().test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "vpc_id"),
test: (value) => typeof value === "undefined" || value === null
})
}),
ipv4: ipv4ConfigInterface,
ipv6: ipv6ConfigInterface,
ip_ranges: array8().of(string9().defined()).notRequired().nullable().when("purpose", {
is: "vpc",
then: (schema) => schema.of(
string9().test(
"valid-ip-range",
"Must be a valid IPv4 range, e.g. 192.0.2.0/24.",
validateIP2
)
).notRequired().nullable(),
otherwise: (schema) => schema.test({
name: testnameDisallowedBasedOnPurpose("VPC"),
message: testmessageDisallowedBasedOnPurpose("vpc", "ip_ranges"),
test: (value) => typeof value === "undefined" || value === null
}).notRequired().nullable()
})
});
var ConfigProfileInterfacesSchema = array8().of(ConfigProfileInterfaceSchema).test(
"unique-public-interface",
"Only one public interface per config is allowed.",
(list) => {
if (!list) {
return true;
}
return list.filter((thisSlot) => thisSlot.purpose === "public").length <= 1;
}
);
var UpdateConfigInterfaceOrderSchema = object9({
ids: array8().of(number7()).required("The list of interface IDs is required.")
});
var UpdateConfigInterfaceSchema = object9({
primary: boolean6().notRequired(),
ipv4: object9().notRequired().shape({
vpc: IPv4,
nat_1_1: lazy2(
(value) => value === "any" ? string9().notRequired().nullable() : IPv4
)
}),
ipv6: object9().notRequired().nullable().shape({
vpc: IPv6
}),
ip_ranges: array8().of(string9().test(validateIP2)).max(1).notRequired()
});
var ResizeLinodeDiskSchema = object9({
size: number7().required("Size is required.").min(1)
});
var UpdateLinodePasswordSchema = object9({
password: string9().required("Password is required.")
// .concat(rootPasswordValidation)
});
var MetadataSchema = object9({
user_data: string9().nullable().defined()
});
var PlacementGroupPayloadSchema = object9({
id: number7().required()
});
var DiskEncryptionSchema = string9().oneOf(["enabled", "disabled"]).notRequired();
var alerts = object9({
cpu: number7().typeError("CPU Usage must be a number").min(0, "Must be between 0 and 4800").max(4800, "Must be between 0 and 4800"),
network_in: number7(),
network_out: number7(),
transfer_quota: number7(),
io: number7()
}).notRequired();
var schedule = object9({
day: mixed4().oneOf(
[
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"Invalid day value."
),
window: mixed4().oneOf(
[
"W0",
"W2",
"W4",
"W6",
"W8",
"W10",
"W12",
"W14",
"W16",
"W18",
"W20",
"W22",
"W24"
],
"Invalid schedule value."
)
});
var backups = object9({
schedule,
enabled: boolean6()
});
var UpdateLinodeSchema = object9({
label: string9().transform((v) => v === "" ? void 0 : v).notRequired().min(3, LINODE_LABEL_CHAR_REQUIREMENT).max(64, LINODE_LABEL_CHAR_REQUIREMENT),
tags: array8().of(string9()).notRequired(),
watchdog_enabled: boolean6().notRequired(),
alerts,
backups
});
var RebuildLinodeSchema = object9({
image: string9().required("An image is required."),
root_pass: string9().required("Password is required."),
authorized_keys: array8().of(string9().required()),
authorized_users: array8().of(string9().required()),
stackscript_id: number7().optional(),
stackscript_data: stackscript_data.notRequired(),
booted: boolean6().optional(),
/**
* `metadata` is an optional object with required properties (see https://github.com/jquense/yup/issues/772)
*/
metadata: MetadataSchema.optional().default(void 0),
disk_encryption: string9().oneOf(["enabled", "disabled"]).optional()
});
var RebuildLinodeFromStackScriptSchema = RebuildLinodeSchema.shape({
stackscript_id: number7().required("A StackScript is required.")
});
var IPAllocationSchema = object9({
type: string9().required("IP address type (IPv4) is required.").oneOf(["ipv4"], "Only IPv4 addresses can be allocated."),
public: boolean6().required("Must specify public or private IP address.")
});
var CreateSnapshotSchema = object9({
label: string9().required("A snapshot label is required.").min(1, "Label must be between 1 and 255 characters.").max(255, "Label must be between 1 and 255 characters.")
});
var device = object9({
disk_id: number7().nullable(),
volume_id: number7().nullable()
}).nullable();
var devices = object9({
sda: device,
sdb: device,
sdc: device,
sdd: device,
sde: device,
sdf: device,
sdg: device,
sdh: device
});
var helpers = object9({
updatedb_disabled: boolean6(),
distro: boolean6(),
modules_dep: boolean6(),
network: boolean6(),
devtmpfs_automount: boolean6()
});
var CreateLinodeConfigSchema = object9({
label: string9().required("Label is required.").min(1, "Label must be between 1 and 48 characters.").max(48, "Label must be between 1 and 48 characters."),
devices: devices.required("A list of devices is required."),
kernel: string9(),
comments: string9(),
memory_limit: number7(),
run_level: mixed4().oneOf(["default", "single", "binbash"]),
virt_mode: mixed4().oneOf(["paravirt", "fullvirt"]),
helpers,
root_device: string9(),
interfaces: ConfigProfileInterfacesSchema
});
var UpdateLinodeConfigSchema = object9({
label: string9().min(1, "Label must be between 1 and 48 characters.").max(48, "Label must be between 1 and 48 characters."),
devices,
kernel: string9(),
comments: string9(),
memory_limit: number7(),
run_level: mixed4().oneOf(["default", "single", "binbash"]),
virt_mode: mixed4().oneOf(["paravirt", "fullvirt"]),
helpers,
root_device: string9(),
interfaces: ConfigProfileInterfacesSchema
});
var CreateLinodeDiskSchema = object9({
size: number7().required("Disk size is required."),
label: string9().required("A disk label is required.").min(1, "Label must be between 1 and 48 characters.").max(48, "Label must be between 1 and 48 characters."),
filesystem: mixed4().oneOf(["raw", "swap", "ext3", "ext4", "initrd"]),
read_only: boolean6(),
image: string9(),
authorized_keys: array8().of(string9()),
authorized_users: array8().of(string9()),
root_pass: string9().when("image", {
is: (value) => Boolean(value),
then: (schema) => schema.required(
"You must provide a root password when deploying from an image."
),
// .concat(rootPasswordValidation),
otherwise: (schema) => schema.notRequired()
}),
stackscript_id: number7(),
stackscript_data
});
var UpdateLinodeDiskSchema = object9({
label: string9().notRequired().min(1, "Label must be between 1 and 48 characters.").max(48, "Label must be between 1 and 48 characters."),
filesystem: mixed4().notRequired().oneOf(["raw", "swap", "ext3", "ext4", "initrd"])
});
var CreateLinodeDiskFromImageSchema = CreateLinodeDiskSchema.clone().shape(
{
image: string9().required("An image is required.").typeError("An image is required.")
}
);
var LABEL_LENGTH_MESSAGE = "Label must be between 1 and 64 characters.";
var LABEL_CHARACTER_TYPES = "Must include only ASCII letters, numbers, and dashes";
var UpgradeToLinodeInterfaceSchema = object9({
config_id: number7().nullable(),
dry_run: boolean6()
});
var UpdateLinodeInterfaceSettingsSchema = object9({
network_helper: boolean6().nullable(),
default_route: object9({
ipv4_interface_id: number7().nullable(),
ipv6_interface_id: number7().nullable()
})
});
var BaseInterfaceIPv4AddressSchema = object9({
address: string9().required(),
primary: boolean6()
});
var VPCInterfaceIPv4RangeSchema = object9({
range: string9().required("Range is required.")
});
var PublicInterfaceRangeSchema = object9({
range: string9().required().nullable()
});
var CreateVPCInterfaceIpv4AddressSchema = object9({
address: string9().required("VPC IPv4 is required."),
primary: boolean6(),
nat_1_1_address: string9().nullable()
});
var CreateVlanInterfaceSchema = object9({
vlan_label: string9().min(1, LABEL_LENGTH_MESSAGE).max(64, LABEL_LENGTH_MESSAGE).matches(/[a-zA-Z0-9-]+/, LABEL_CHARACTER_TYPES).required("VLAN label is required."),
ipam_address: string9().nullable()
});
var CreateVPCInterfaceSchema = object9({
subnet_id: number7().required("Subnet is required."),
ipv4: object9({
addresses: array8().of(CreateVPCInterfaceIpv4AddressSchema),
ranges: array8().of(VPCInterfaceIPv4RangeSchema)
}).notRequired()
});
var CreateLinodeInterfaceSchema = object9({
firewall_id: number7().nullable(),
default_route: object9({
ipv4: boolean6(),
ipv6: boolean6()
}).notRequired().default(null),
vpc: CreateVPCInterfaceSchema.notRequired().default(null),
public: object9({
ipv4: object9({
addresses: array8().of(BaseInterfaceIPv4AddressSchema)
}).notRequired(),
ipv6: object9({
ranges: array8().of(PublicInterfaceRangeSchema)
}).notRequired()
}).notRequired().default(null),
vlan: CreateVlanInterfaceSchema.notRequired().default(null)
});
var ModifyVPCInterfaceIpv4AddressSchema = object9({
address: string9(),
primary: boolean6().nullable(),
nat_1_1_address: string9().nullable()
});
var ModifyVlanInterfaceSchema = object9({
vlan_label: string9().required().nullable().min(1, LABEL_LENGTH_MESSAGE).max(64, LABEL_LENGTH_MESSAGE).matches(/[a-zA-Z0-9-]+/, LABEL_CHARACTER_TYPES),
ipam_address: string9().nullable()
}).notRequired().nullable();
var ModifyLinodeInterfaceSchema = object9({
default_route: object9({
ipv4: boolean6().nullable(),
ipv6: boolean6().nullable()
}).notRequired().nullable(),
vpc: object9({
subnet_id: number7().required(),
ipv4: object9({
addresses: array8().of(ModifyVPCInterfaceIpv4AddressSchema).notRequired().nullable(),
ranges: array8().of(VPCInterfaceIPv4RangeSchema).nullable()
}).notRequired().nullable()
}).notRequired().nullable(),
public: object9({
ipv4: object9({
addresses: array8().of(BaseInterfaceIPv4AddressSchema).nullable()
}).notRequired().nullable(),
ipv6: object9({
ranges: array8().of(PublicInterfaceRangeSchema).nullable()
}).notRequired().nullable()
}).notRequired().nullable(),
vlan: ModifyVlanInterfaceSchema
});
var CreateLinodeSchema = object9({
type: string9().ensure().required("Plan is required."),
region: string9().ensure().required("Region is required."),
stackscript_id: number7().nullable().notRequired(),
backup_id: number7().nullable().notRequired(),
swap_size: number7().notRequired(),
image: string9().when("stackscript_id", {
is: (value) => value !== void 0,
then: (schema) => schema.ensure().required("Image is required."),
otherwise: (schema) => schema.nullable().notRequired()
}),
authorized_keys: array8().of(string9().defined()).notRequired(),
backups_enabled: boolean6().notRequired(),
stackscript_data,
booted: boolean6().notRequired(),
label: string9().transform((v) => v === "" ? void 0 : v).notRequired().min(3, LINODE_LABEL_CHAR_REQUIREMENT).max(64, LINODE_LABEL_CHAR_REQUIREMENT),
tags: array8().of(string9().defined()).notRequired(),
private_ip: boolean6().notRequired(),
authorized_users: array8().of(string9().defined()).notRequired(),
root_pass: string9().when("image", {
is: (value) => Boolean(value),
then: (schema) => schema.required(
"You must provide a root password when deploying from an image."
),
otherwise: (schema) => schema.notRequired()
}),
interfaces: array8().when(
"interface_generation",
([interface_generation], schema) => {
if (interface_generation === "linode") {
return schema.of(CreateLinodeInterfaceSchema);
}
return ConfigProfileInterfacesSchema;
}
),
interface_generation: string9().oneOf(["legacy_config", "linode"]).notRequired(),
network_helper: boolean6(),
ipv4: array8().of(string9().defined()).when("interface_generation", {
is: "linode",
then: (schema) => schema.nullable().notRequired().test({
name: "IPv4 field is not allowed for Linode Interfaces",
message: "ipv4 field must be ommitted or empty if using Linode Interfaces",
test: (value) => !value || value.length === 0
})
}),
metadata: MetadataSchema.notRequired().default(void 0),
firewall_id: number7().nullable().notRequired(),
placement_group: PlacementGroupPayloadSchema.notRequi