UNPKG

@linode/validation

Version:

Yup validation schemas for use with the Linode APIv4

1,378 lines (1,369 loc) 77.6 kB
// 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