UNPKG

@linode/validation

Version:

Yup validation schemas for use with the Linode APIv4

1,366 lines (1,355 loc) 88 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/cloudnats.schema.ts import { array as array2, number as number2, object as object3, string as string3 } from "yup"; var VALID_PORT_SIZES = [ 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 ]; var createCloudNATSchema = object3({ addresses: array2().of( object3({ address: string3().required("Address must be a string.") }) ).notRequired(), label: string3().required("Label is required.").max(150, "Label must be 150 characters or fewer."), port_prefix_default_len: number2().oneOf(VALID_PORT_SIZES, "Invalid port size.").notRequired(), region: string3().required("Region is required.") }); var updateCloudNATSchema = object3({ label: string3().max(150, "Label must be 150 characters or fewer.") }); // src/cloudpulse.schema.ts import { array as array3, number as number3, object as object4, string as string4 } from "yup"; var fieldErrorMessage = "This field is required."; var dimensionFilters = object4({ dimension_label: string4().required(fieldErrorMessage), operator: string4().oneOf(["eq", "neq", "startswith", "endswith"]).required(fieldErrorMessage), value: string4().required(fieldErrorMessage) }); var metricCriteria = object4({ metric: string4().required(fieldErrorMessage), aggregate_function: string4().oneOf(["avg", "count", "max", "min", "sum"]).required(fieldErrorMessage), operator: string4().oneOf(["eq", "gt", "lt", "gte", "lte"]).required(fieldErrorMessage), threshold: number3().required(fieldErrorMessage).positive("Enter a positive value.").typeError("The value should be a number."), dimension_filters: array3().of(dimensionFilters.defined()).optional() }); var triggerConditionValidation = object4({ criteria_condition: string4().oneOf(["ALL"]).required("Criteria condition is required"), polling_interval_seconds: number3().required(fieldErrorMessage), evaluation_period_seconds: number3().required(fieldErrorMessage), trigger_occurrences: number3().required(fieldErrorMessage).positive("Enter a positive value.").typeError("The value should be a number.") }); var specialStartEndRegex = /^[^a-zA-Z0-9]/; var createAlertDefinitionSchema = object4({ label: string4().required(fieldErrorMessage).matches( /^[^*#&+:<>"?@%{}\\\/]+$/, "Name cannot contain special characters: * # & + : < > ? @ % { } \\ /." ).max(100, "Name must be 100 characters or less.").test( "no-special-start-end", "Name cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ), description: string4().max(100, "Description must be 100 characters or less.").test( "no-special-start-end", "Description cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), severity: number3().oneOf([0, 1, 2, 3]).required(fieldErrorMessage), rule_criteria: object4({ rules: array3().of(metricCriteria).min(1, "At least one metric criteria is required.").required() }).required(), trigger_conditions: triggerConditionValidation, channel_ids: array3().of(number3().required()).required().min(1, "At least one notification channel is required."), tags: array3().of(string4().defined()).optional(), entity_ids: array3().of(string4().defined()).optional(), regions: array3().of(string4().defined()).optional(), scope: string4().oneOf(["entity", "region", "account"]).nullable().optional() }); var editAlertDefinitionSchema = object4({ channel_ids: array3().of(number3().required()).optional(), label: string4().matches( /^[^*#&+:<>"?@%{}\\/]+$/, "Name cannot contain special characters: * # & + : < > ? @ % { } \\ /." ).max(100, "Name must be 100 characters or less.").test( "no-special-start-end", "Name cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), description: string4().max(100, "Description must be 100 characters or less.").test( "no-special-start-end", "Description cannot start or end with a special character.", (value) => { return !specialStartEndRegex.test(value != null ? value : ""); } ).optional(), entity_ids: array3().of(string4().defined()).optional(), rule_criteria: object4({ rules: array3().of(metricCriteria).min(1, "At least one metric criteria is required.").required() }).optional().default(void 0), tags: array3().of(string4().defined()).optional(), trigger_conditions: triggerConditionValidation.optional().default(void 0), severity: number3().oneOf([0, 1, 2, 3]).optional(), status: string4().oneOf(["enabled", "disabled", "in progress", "failed"]).optional(), scope: string4().oneOf(["entity", "region", "account"]).nullable().optional(), regions: array3().of(string4().defined()).optional() }); // src/databases.schema.ts import { boolean as boolean3, mixed as mixed2, number as number4 } from "yup"; import { array as array4, object as object5, string as string5 } from "yup"; var LABEL_MESSAGE = "Label must be between 3 and 32 characters"; var createDatabaseSchema = object5({ label: string5().required("Label is required").min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE), engine: string5().required("Database Engine is required"), region: string5().required("Region is required"), type: string5().required("Type is required"), cluster_size: number4().oneOf([1, 2, 3], "Nodes are required").required("Nodes are required"), replication_type: string5().notRequired().nullable(), // TODO (UIE-8214) remove POST GA replication_commit_type: string5().notRequired().nullable() // TODO (UIE-8214) remove POST GA }); var getDynamicDatabaseSchema = (isVPCSelected) => { if (!isVPCSelected) { return createDatabaseSchema; } return createDatabaseSchema.shape({ private_network: object5().shape({ subnet_id: number4().nullable().required("Subnet is required.") }) }); }; var updateDatabaseSchema = object5({ label: string5().notRequired().min(3, LABEL_MESSAGE).max(32, LABEL_MESSAGE), allow_list: array4().of(string5()).notRequired(), updates: object5().notRequired().shape({ frequency: string5().oneOf(["weekly", "monthly"]), duration: number4(), hour_of_day: number4(), day_of_week: number4(), week_of_month: number4().nullable() }).nullable(), type: string5().notRequired() }); var updatePrivateNetworkSchema = object5({ private_network: object5().shape({ vpc_id: number4().required("VPC is required."), subnet_id: number4().required("Subnet is required."), public_access: boolean3().default(false) }) }); var createValidator = (key, field) => { const fieldTypes = Array.isArray(field.type) ? field.type : [field.type]; switch (true) { case fieldTypes.includes("integer"): return number4().transform((val, originalVal) => originalVal === "" ? void 0 : val).integer(`${key} must be a whole number`).required(`${key} is required`); case fieldTypes.includes("number"): return number4().transform((val, originalVal) => originalVal === "" ? void 0 : val).required(`${key} is required`); case fieldTypes.includes("string"): return string5(); 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) { if (field.maximum > Number.MAX_SAFE_INTEGER) { validator = validator.max( Number.MAX_SAFE_INTEGER, `${key} must be at most ${Number.MAX_SAFE_INTEGER}` ); } else { 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) { const pattern = field.pattern; if (key === "default_time_zone") { validator = validator.matches( new RegExp(pattern), `${key} must be an IANA timezone, 'SYSTEM', or a valid UTC offset (e.g., '+03:00')` ); } else { validator = validator.matches( new RegExp(pattern), `Please ensure that ${key} follows the format ${field.example}` ); } } if (key === "wal_sender_timeout") { validator = validator.test( "is-zero-or-in-range", `${key} must be 0 or between 5000 and 10800000`, (value) => { if (typeof value !== "number") return false; return value === 0 || value >= 5e3 && value <= 108e5; } ); } if (key === "timezone") { if (!field.value) { validator = validator.required("timezone cannot be empty"); } } if (key === "net_buffer_length") { validator = validator.test( "is-multiple-of-1024", `${key} must be a multiple of 1024`, (value) => { if (typeof value !== "number") return false; return value % 1024 === 0; } ); } 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] = object5().shape({ value: validator }); } }; var createDynamicAdvancedConfigSchema = (allConfigurations) => { if (!Array.isArray(allConfigurations) || allConfigurations.length === 0) { return object5().shape({}); } const schemaShape = {}; allConfigurations.forEach((field) => processField(schemaShape, field)); return object5().shape({ configs: array4().of( object5({ label: string5().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 array5, mixed as mixed3, number as number5, object as object6, string as string6 } from "yup"; var importZoneSchema = object6({ domain: string6().required("Domain is required."), remote_nameserver: string6().required("Remote nameserver is required.") }); var domainSchemaBase = object6().shape({ domain: string6().matches( // eslint-disable-next-line sonarjs/slow-regex /([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: array5(), description: string6().min(1, "Description must be between 1 and 253 characters.").max(253, "Description must be between 1 and 253 characters."), retry_sec: number5(), master_ips: array5().of(string6()), axfr_ips: array5().of(string6()).typeError("Must be a comma-separated list of IP addresses."), expire_sec: number5(), refresh_sec: number5(), ttl_sec: number5() }); var createDomainSchema = domainSchemaBase.shape({ domain: string6().required("Domain is required.").matches( // eslint-disable-next-line sonarjs/slow-regex /([a-zA-Z0-9-_]+\.)+([a-zA-Z]{2,3}\.)?([a-zA-Z]{2,16}|XN--[a-zA-Z0-9]+)/, "Domain is not valid." ), tags: array5().of(string6()), type: mixed3().required().oneOf(["master", "slave"]), soa_email: string6().when("type", { is: "master", then: (schema) => schema.required("SOA Email is required.") }).email("SOA Email is not valid.").trim(), master_ips: array5().of(string6()).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: number5(), soa_email: string6().email("SOA Email is not valid."), axfr_ips: array5().of(string6()), tags: array5().of(string6()) }); // src/firewalls.schema.ts import ipaddr from "ipaddr.js"; import { array as array6, number as number6, object as object7, string as string7 } 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 = object7({ linodes: array6().of(number6()), nodebalancers: array6().of(number6()) }); var ipAddress = string7().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 = string7().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 = object7().shape({ action: string7().oneOf(["ACCEPT", "DROP"]).required("Action is required"), description: string7().nullable(), label: string7().nullable(), protocol: string7().oneOf(["ALL", "TCP", "UDP", "ICMP", "IPENCAP"]).required("Protocol is required."), ports: string7().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: object7().shape({ ipv4: array6().of(ipAddress).nullable(), ipv6: array6().of(ipAddress).nullable() }).strict(true).notRequired().nullable() }); var FirewallRuleSchema = object7().shape({ inbound: array6(FirewallRuleTypeSchema).nullable(), outbound: array6(FirewallRuleTypeSchema).nullable(), inbound_policy: string7().oneOf(["ACCEPT", "DROP"]).required("Inbound policy is required."), outbound_policy: string7().oneOf(["ACCEPT", "DROP"]).required("Outbound policy is required.") }); var CreateFirewallDevicesSchema = object7().shape({ linodes: array6().of(number6().defined()), nodebalancers: array6().of(number6().defined()), linode_interfaces: array6().of(number6().defined()) }).notRequired(); var CreateFirewallSchema = object7().shape({ label: string7().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: array6().of(string7().defined()), rules: FirewallRuleSchema, devices: CreateFirewallDevicesSchema }); var UpdateFirewallSchema = object7().shape({ label: string7(), tags: array6().of(string7()), status: string7().oneOf(["enabled", "disabled"]) // 'deleted' is also a status but it's not settable }); var FirewallDeviceSchema = object7({ type: string7().oneOf(["linode", "nodebalancer", "linode_interface"]).required("Device type is required."), id: number6().required("ID is required.") }); var UpdateFirewallSettingsSchema = object7({ default_firewall_ids: object7({ interface_public: number6().nullable(), interface_vpc: number6().nullable(), linode: number6().nullable(), nodebalancer: number6().nullable() }) }); // src/images.schema.ts import { array as array7, boolean as boolean4, number as number7, object as object8, string as string8 } from "yup"; var labelSchema = string8().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 = object8({ label: labelSchema.optional(), description: string8().optional().min(1).max(65e3), cloud_init: boolean4().optional(), tags: array7(string8().min(3).max(50).required()).max(500).optional() }); var createImageSchema = baseImageSchema.shape({ disk_id: number7().typeError("Disk is required.").required("Disk is required.") }); var uploadImageSchema = baseImageSchema.shape({ label: labelSchema.required("Label is required."), region: string8().required("Region is required.") }); var updateImageSchema = object8({ label: labelSchema.optional(), description: string8().optional().max(65e3, "Length must be 65000 characters or less."), tags: array7(string8().required()).optional() }); var updateImageRegionsSchema = object8({ regions: array7(string8()).required("Regions are required.").min(1, "Must specify at least one region.") }); // src/kubernetes.schema.ts import { array as array8, boolean as boolean5, number as number8, object as object9, string as string9 } from "yup"; var nodePoolSchema = object9({ type: string9(), count: number8() }); var nodePoolBetaSchema = nodePoolSchema.concat( object9({ upgrade_strategy: string9(), k8_version: string9(), firewall_id: number8() }) ); var clusterLabelSchema = string9().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 = string9().defined().test({ name: "validateIP", message: "Must be a valid IPv4 address.", test: validateIP }); var ipv6Address = string9().defined().test({ name: "validateIP", message: "Must be a valid IPv6 address.", test: validateIP }); var controlPlaneACLOptionsSchema = object9({ enabled: boolean5(), "revision-id": string9(), addresses: object9({ ipv4: array8().of(ipv4Address).nullable(), ipv6: array8().of(ipv6Address).nullable() }).notRequired() }); var controlPlaneEnterpriseACLOptionsSchema = object9({ enabled: boolean5(), "revision-id": string9(), addresses: object9({ ipv4: array8().of(ipv4Address), ipv6: array8().of(ipv6Address) }).required() }); var createKubeClusterSchema = object9({ label: clusterLabelSchema, region: string9().required("Region is required."), k8s_version: string9().required("Kubernetes version is required."), node_pools: array8().of(nodePoolSchema).min(1, "Please add at least one node pool.") }); var createKubeClusterWithRequiredACLSchema = object9({ control_plane: object9({ high_availability: boolean5(), acl: object9({ enabled: boolean5(), "revision-id": string9(), addresses: object9({ ipv4: array8().of(ipv4Address), ipv6: array8().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 = object9({ acl: controlPlaneACLOptionsSchema }); var kubernetesEnterpriseControlPlaneACLPayloadSchema = object9({ 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 = object9().test({ name: "validateLabels", message: "Labels must be valid key-value pairs.", test: validateKubernetesLabel }); var kubernetesTaintSchema = object9({ key: string9().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: string9().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 ipaddr3 from "ipaddr.js"; import { array as array10, boolean as boolean6, lazy as lazy2, mixed as mixed4, number as number9, object as object11, string as string11 } from "yup"; // src/vpcs.schema.ts import ipaddr2 from "ipaddr.js"; import { array as array9, lazy, object as object10, string as string10 } 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, checkIPv6PrefixLengthIs64 }) => { 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) { if (checkIPv6PrefixLengthIs64) { return mask === "64"; } if (shouldHaveIPMask) { ipaddr2.IPv6.parseCIDR(value); } else { ipaddr2.IPv6.isValid(value); ipaddr2.IPv6.parse(value); } } return true; } catch (err) { return false; } }; var labelValidation = string10().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 = object10({ label: labelValidation, description: string10() }); var VPCIPv6Schema = object10({ range: string10().optional().test( "IPv6 prefix length", "Must be the prefix length 52, 48, or 44 of the IP, e.g. /52", (value) => { if (value && value.length > 0) { return ["/44", "/48", "/52"].includes(value); } } ) }); var VPCIPv6SubnetSchema = object10({ range: string10().required().test( "IPv6 prefix length", "Must be the prefix length (52-62) of the IP, e.g. /52", (value) => { if (value && value !== "auto" && value.length > 0) { const [, mask] = value.split("/"); return +mask >= 52 && +mask <= 62; } } ) }); var createSubnetSchemaIPv4 = object10({ label: labelValidation.required(LABEL_REQUIRED), ipv4: string10().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 "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 }) }); case "undefined": return schema.notRequired().nullable(); default: return schema.notRequired().nullable(); } }) }) }); var createSubnetSchemaWithIPv6 = object10().shape( { label: labelValidation.required(LABEL_REQUIRED), ipv4: string10().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 "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 }) }); case "undefined": return schema.notRequired().nullable(); default: return schema.notRequired().nullable(); } }) }), ipv6: array9().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( object10({ allocation_class: string10().optional() }) ); var createVPCSchema = object10({ label: labelValidation.required(LABEL_REQUIRED), description: string10(), region: string10().required("Region is required"), subnets: array9().of(createSubnetSchemaIPv4), ipv6: array9().of(createVPCIPv6Schema).max(1).optional() }); var modifySubnetSchema = object10({ label: labelValidation.required(LABEL_REQUIRED) }); // src/linodes.schema.ts var VPC_INTERFACE_IP_RULE = "A VPC interface must have an IPv4, an IPv6, or both, but not neither."; 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 validateIPv6PrefixLengthIs64 = (value) => { if (value === void 0 || value === null) { return false; } if (value === "auto") { return true; } return vpcsValidateIP({ value, shouldHaveIPMask: true, mustBeIPMask: false, checkIPv6PrefixLengthIs64: true }); }; 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 = object11().nullable(); var IPv4 = string11().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 = string11().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 = object11().when("purpose", { is: "vpc", then: (schema) => schema.shape({ vpc: IPv4, nat_1_1: lazy2( (value) => value === "any" ? string11().notRequired().nullable() : IPv4 ) }).when("ipv6", { is: (value) => value === null || value === void 0, then: (schema2) => schema2.required(VPC_INTERFACE_IP_RULE) }), 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 slaacSchema = object11().shape({ range: string11().required().test({ name: "IPv6 prefix length", message: "Must be a /64 IPv6 network CIDR", test: (value) => validateIPv6PrefixLengthIs64(value) }) }); var IPv6ConfigInterfaceRangesSchema = object11({ range: string11().optional().test({ name: "IPv6 prefix length", message: "Must be a /64 IPv6 network CIDR", test: (value) => validateIPv6PrefixLengthIs64(value) }) }); var ipv6ConfigInterface = object11().when("purpose", { is: "vpc", then: (schema) => schema.shape({ slaac: array10().of(slaacSchema).test({ name: "slaac field must have zero or one entry", message: "ipv6.slaac field must have zero or one entry", test: (value) => !value ? true : (value == null ? void 0 : value.length) === 0 || (value == null ? void 0 : value.length) === 1 }), ranges: array10().of(IPv6ConfigInterfaceRangesSchema), is_public: boolean6() }).notRequired().when("ipv4", { is: (value) => value === null || value === void 0, then: (schema2) => schema2.required(VPC_INTERFACE_IP_RULE) }), otherwise: (schema) => schema.nullable().test({ name: testnameDisallowedBasedOnPurpose("VPC"), message: testmessageDisallowedBasedOnPurpose("vpc", "ipv6.slaac"), test: (value) => { if (value == null ? void 0 : value.slaac) { return typeof value.slaac === "undefined"; } return true; } }).test({ name: testnameDisallowedBasedOnPurpose("VPC"), message: testmessageDisallowedBasedOnPurpose("vpc", "ipv6.ranges"), test: (value) => { if (value == null ? void 0 : value.ranges) { return typeof value.ranges === "undefined"; } return true; } }).test({ name: testnameDisallowedBasedOnPurpose("VPC"), message: testmessageDisallowedBasedOnPurpose("vpc", "ipv6.is_public"), test: (value) => { if (value == null ? void 0 : value.is_public) { return typeof value.is_public === "undefined"; } return true; } }) }); var ConfigProfileInterfaceSchema = object11().shape( { purpose: string11().oneOf( ["public", "vlan", "vpc"], "Purpose must be public, vlan, or vpc." ).defined().required(), label: string11().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: string11().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: number9().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: number9().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: array10().of(string11().defined()).notRequired().nullable().when("purpose", { is: "vpc", then: (schema) => schema.of( string11().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() }) }, [ ["ipv6", "ipv4"], ["ipv4", "ipv6"] ] ); var ConfigProfileInterfacesSchema = array10().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 = object11({ ids: array10().of(number9()).required("The list of interface IDs is required.") }); var UpdateConfigInterfaceSchema = object11({ primary: boolean6().notRequired(), ipv4: object11().notRequired().shape({ vpc: IPv4, nat_1_1: lazy2( (value) => value === "any" ? string11().notRequired().nullable() : IPv4 ) }), ipv6: object11().notRequired().nullable().shape({ vpc: IPv6 }), ip_ranges: array10().of(string11().test(validateIP2)).max(1).notRequired() }); var ResizeLinodeDiskSchema = object11({ size: number9().required("Size is required.").min(1) }); var UpdateLinodePasswordSchema = object11({ password: string11().required("Password is required.") // .concat(rootPasswordValidation) }); var MetadataSchema = object11({ user_data: string11().nullable().defined() }); var PlacementGroupPayloadSchema = object11({ id: number9().required() }); var DiskEncryptionSchema = string11().oneOf(["enabled", "disabled"]).notRequired(); var alerts = object11({ cpu: number9().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: number9(), network_out: number9(), transfer_quota: number9(), io: number9() }).notRequired(); var schedule = object11({ 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 = object11({ schedule, enabled: boolean6() }); var UpdateLinodeSchema = object11({ label: string11().transform((v) => v === "" ? void 0 : v).notRequired().min(3, LINODE_LABEL_CHAR_REQUIREMENT).max(64, LINODE_LABEL_CHAR_REQUIREMENT), tags: array10().of(string11()).notRequired(), watchdog_enabled: boolean6().notRequired(), alerts, backups }); var RebuildLinodeSchema = object11({ image: string11().required("An image is required."), root_pass: string11().required("Password is required."), authorized_keys: array10().of(string11().required()), authorized_users: array10().of(string11().required()), stackscript_id: number9().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: string11().oneOf(["enabled", "disabled"]).optional() }); var RebuildLinodeFromStackScriptSchema = RebuildLinodeSchema.shape({ stackscript_id: number9().required("A StackScript is required.") }); var IPAllocationSchema = object11({ type: string11().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 = object11({ label: string1