@linode/validation
Version:
Yup validation schemas for use with the Linode APIv4
1,366 lines (1,355 loc) • 88 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/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