import { time } from 'console';
import { create } from 'domain';
import { z } from 'zod';
const TagSchema = z.object({
tagId: z.string(),
value: z.string()
});
const SlaSchema = z.object({
slaId: z.string(),
nxClusterIds: z.array(z.string())
});
const SlaDetailsSchema = z.object({
primarySla: SlaSchema
});
const SnapshotTimeOfDaySchema = z.object({
hours: z.number().default(0),
minutes: z.number().default(0),
seconds: z.number().default(0)
});
const ContinuousScheduleSchema = z.object({
enabled: z.boolean().default(false),
logBackupInterval: z.number().optional().default(60),
snapshotsPerDay: z.number().optional().default(1)
});
const WeeklyScheduleSchema = z.object({
enabled: z.boolean().default(false),
dayOfWeek: z.string().optional().default("Sunday")
});
const MonthlyScheduleSchema = z.object({
enabled: z.boolean().default(false),
dayOfMonth: z.number().optional()
});
const QuartelyScheduleSchema = z.object({
enabled: z.boolean().default(false),
startMonth: z.string().optional().default("January"),
dayOfMonth: z.number().optional().default(1)
});
const YearlyScheduleSchema = z.object({
enabled: z.boolean().default(false),
month: z.string().optional().default("December"),
dayOfMonth: z.number().optional().default(31)
});
const ScheduleSchema = z.object({
snapshotTimeOfDay: SnapshotTimeOfDaySchema.optional(),
continuousSchedule: ContinuousScheduleSchema.optional(),
weeklySchedule: WeeklyScheduleSchema.optional(),
monthlySchedule: MonthlyScheduleSchema.optional(),
quartelySchedule: QuartelyScheduleSchema.optional(),
yearlySchedule: YearlyScheduleSchema.optional()
});
const TimeMachineInfoSchema = z.object({
name: z.string(),
description: z.string().optional(),
slaId: z.string(),
schedule: ScheduleSchema,
tags: z.array(TagSchema).optional(),
autoTuneLogDrive: z.boolean()
});
const NodePropertySchema = z.object({
name: z.enum(["node_type", "role", "failover_mode", "remote_archive_destination"]),
value: z.union([
z.enum(["haproxy", "database"]),
z.string(),
z.number(),
z.boolean()
])
});
const NodeSchema = z.object({
vmName: z.string(),
computeProfileId: z.string().optional(),
networkProfileId: z.string().optional(),
newDbServerTimeZone: z.string().optional(),
nxClusterId: z.string().optional(),
dbserverId: z.string().optional(),
properties: z.array(NodePropertySchema).optional()
});
const lcmConfigSchema = z.object({
databaseLCMConfig: z.object({
expiryDetails: z.object({
expiryInDays: z.number().min(1).default(30).optional(),
expiryDateTimezone: z.string().optional()
}),
refreshDetails: z.object({
refreshInDays: z.number().min(1).default(10).optional(),
refreshTime: z.string().optional(),
refreshDateTimezone: z.string().optional()
}),
})
});
const SupportedEngines = [
"postgres_database",
"oracle_database",
"sqlserver_database",
"mariadb_database",
"mysql_database",
"saphana_database",
"mongodb_database"
] as const;
export const ProvisionDatabaseRequestSchema = z.object({
databaseType: z.enum(SupportedEngines).optional(),
name: z.string().optional(),
createDbserver: z.boolean().optional(),
clustered: z.boolean().optional(),
nodes: z.array(NodeSchema).optional(),
// All other args are optional for base schema
databaseDescription: z.string().optional(),
softwareProfileId: z.string().optional(),
softwareProfileVersionId: z.string().optional(),
computeProfileId: z.string().optional(),
networkProfileId: z.string().optional(),
dbParameterProfileId: z.string().optional(),
newDbServerTimeZone: z.string().optional(),
timeMachineInfo: TimeMachineInfoSchema.optional(),
actionArguments: z.array(z.object({
name: z.string(),
value: z.union([z.string(), z.number(), z.boolean()])
})).optional(),
nodeCount: z.number().optional(),
nxClusterId: z.string().optional(),
dbserverId: z.string().optional(),
sshPublicKey: z.string().optional(),
autoTuneStagingDrive: z.boolean().optional()
}).superRefine((data, ctx) => {
// Enforce required fields (base and engine-specific)
if (!data.databaseType) {
ctx.addIssue({ path: ["databaseType"], message: "databaseType is required", code: z.ZodIssueCode.custom });
}
if (!data.name) {
ctx.addIssue({ path: ["name"], message: "name is required", code: z.ZodIssueCode.custom });
}
if (typeof data.createDbserver !== "boolean") {
ctx.addIssue({ path: ["createDbserver"], message: "createDbserver is required", code: z.ZodIssueCode.custom });
}
if (typeof data.clustered !== "boolean") {
ctx.addIssue({ path: ["clustered"], message: "clustered is required", code: z.ZodIssueCode.custom });
}
if (!Array.isArray(data.nodes) || data.nodes.length === 0) {
ctx.addIssue({ path: ["nodes"], message: "At least one node is required", code: z.ZodIssueCode.custom });
}
// Check for mandatory top-level fields
if (data.createDbserver) {
if (!data.softwareProfileId) {
ctx.addIssue({ path: ["softwareProfileId"], message: "softwareProfileId is required", code: z.ZodIssueCode.custom });
}
if (!data.softwareProfileVersionId) {
ctx.addIssue({ path: ["softwareProfileVersionId"], message: "softwareProfileVersionId is required", code: z.ZodIssueCode.custom });
}
if (!data.computeProfileId && data.clustered) {
ctx.addIssue({ path: ["computeProfileId"], message: "computeProfileId is required for clustered deployments", code: z.ZodIssueCode.custom });
}
if (!data.networkProfileId) {
ctx.addIssue({ path: ["networkProfileId"], message: "networkProfileId is required", code: z.ZodIssueCode.custom });
}
if (!data.nxClusterId) {
ctx.addIssue({ path: ["nxClusterId"], message: "nxClusterId is required", code: z.ZodIssueCode.custom });
}
if (!data.nodeCount || (data.nodes?.length !== data.nodeCount)) {
ctx.addIssue({ path: ["nodeCount"], message: "nodeCount is required or should be equal to the number of nodes", code: z.ZodIssueCode.custom });
}
} else {
if (!data.dbserverId && !data.clustered) {
ctx.addIssue({ path: ["dbserverId"], message: "dbserverId is required when createDbserver is false and target is not clustered", code: z.ZodIssueCode.custom });
}
}
if (!data.dbParameterProfileId) {
ctx.addIssue({ path: ["dbParameterProfileId"], message: "dbParameterProfileId is required", code: z.ZodIssueCode.custom });
}
// PostgreSQL-specific logic using deploy_haproxy from actionArguments
if (data.databaseType === "postgres_database" && data.clustered && data.createDbserver) {
const deployHaproxyArg = data.actionArguments?.find((arg: any) => arg.name === "deploy_haproxy");
if (deployHaproxyArg?.value) {
const haproxyNodes = data.nodes?.filter(node =>
node.properties?.some(p => p.name === "node_type" && p.value === "haproxy") || false
) || [];
if (haproxyNodes.length !== 2) {
ctx.addIssue({ path: ["nodes"], message: "Exactly two haproxy nodes are required when deploy_haproxy is true", code: z.ZodIssueCode.custom });
}
}
}
// Combined node validation loop
data.nodes?.forEach((node, idx) => {
if (data.createDbserver === true) {
const nodeType = node.properties?.find(p => p.name === "node_type")?.value;
// Generic validation for all nodes
if (!node.vmName) ctx.addIssue({ path: ["nodes", idx, "vmName"], message: "vmName is required for all nodes", code: z.ZodIssueCode.custom });
if (!node.nxClusterId) ctx.addIssue({ path: ["nodes", idx, "nxClusterId"], message: "nxClusterId is required for all nodes", code: z.ZodIssueCode.custom });
// Enforce node_type for PostgreSQL
if (data.databaseType === "postgres_database") {
if (nodeType !== "database" && nodeType !== "haproxy") {
ctx.addIssue({ path: ["nodes", idx, "properties"], message: "node_type must be either 'database' or 'haproxy' for PostgreSQL", code: z.ZodIssueCode.custom });
}
}
// Database node validation
if (nodeType === "database") {
if (!node.networkProfileId) {
ctx.addIssue({ path: ["nodes", idx, "networkProfileId"], message: "networkProfileId is required for database node", code: z.ZodIssueCode.custom });
}
if (data.clustered && !node.computeProfileId) {
ctx.addIssue({ path: ["nodes", idx, "computeProfileId"], message: "computeProfileId is required for clustered database node", code: z.ZodIssueCode.custom });
}
// PostgreSQL-specific validation for database nodes
if (data.databaseType === "postgres_database" && data.clustered) {
if (!node.properties?.find(p => p.name === "role")) {
ctx.addIssue({ path: ["nodes", idx, "properties"], message: "role is required for database node", code: z.ZodIssueCode.custom });
}
if (!node.properties?.find(p => p.name === "failover_mode")) {
ctx.addIssue({ path: ["nodes", idx, "properties"], message: "failover_mode is required for database node", code: z.ZodIssueCode.custom });
}
if (!nodeType) {
ctx.addIssue({ path: ["nodes", idx, "properties"], message: "node_type is required for database node", code: z.ZodIssueCode.custom });
}
// remote_archive_destination is optional
}
}
} else {
// Dbserver already exists, validate dbserverId and generic node fields
if (!node.dbserverId) {
ctx.addIssue({ path: ["nodes", idx, "dbserverId"], message: "dbserverId is required in nodes when createDbserver is false", code: z.ZodIssueCode.custom });
}
}
});
if (!data.timeMachineInfo) {
ctx.addIssue({ path: ["timeMachineInfo"], message: "timeMachineInfo is required", code: z.ZodIssueCode.custom });
} else {
if (!data.timeMachineInfo.name) {
ctx.addIssue({ path: ["timeMachineInfo", "name"], message: "timeMachineInfo.name is required", code: z.ZodIssueCode.custom });
}
if (!data.timeMachineInfo.slaId) {
ctx.addIssue({ path: ["timeMachineInfo", "slaId"], message: "timeMachineInfo.slaId is required", code: z.ZodIssueCode.custom });
}
if (!data.timeMachineInfo.schedule) {
ctx.addIssue({ path: ["timeMachineInfo", "schedule"], message: "timeMachineInfo.schedule is required", code: z.ZodIssueCode.custom });
}
}
});
export const RestoreDatabaseRequestSchema = z.object({
databaseId: z.string().nonempty("databaseId is required"),
userPitrTimestamp: z.string().optional().refine(
(val) => !val || !isNaN(Date.parse(val)),
"timestamp must be a valid ISO 8601 date-time string"
),
snapshotId: z.string().optional(),
actionArguments: z.array(
z.object({
name: z.enum(["sameLocation", "newLocationPath"]),
value: z.union([z.string(), z.boolean()])
})
).optional().superRefine((args, ctx) => {
const sameLocationArg = args?.find(arg => arg.name === "sameLocation");
const newLocationPathArg = args?.find(arg => arg.name === "newLocationPath");
if (!sameLocationArg) {
ctx.addIssue({
path: ["actionArguments"],
message: "sameLocation argument is required",
code: z.ZodIssueCode.custom
});
} else if (typeof sameLocationArg.value !== "boolean") {
ctx.addIssue({
path: ["actionArguments", "sameLocation"],
message: "sameLocation must be a boolean",
code: z.ZodIssueCode.custom
});
}
if (sameLocationArg?.value === false && !newLocationPathArg) {
ctx.addIssue({
path: ["actionArguments"],
message: "newLocationPath is required when sameLocation is false",
code: z.ZodIssueCode.custom
});
}
if (newLocationPathArg && typeof newLocationPathArg.value !== "string") {
ctx.addIssue({
path: ["actionArguments", "newLocationPath"],
message: "newLocationPath must be a string",
code: z.ZodIssueCode.custom
});
}
})
});
export const UpdateTimeMachineRequestSchema = z.object({
timemachine_id: z.string().uuid("timemachine_id must be a valid UUID"),
name: z.string().optional(),
description: z.string().optional(),
slaId: z.string().uuid("slaId must be a valid UUID").optional(),
schedule: ScheduleSchema.optional(),
tags: z.array(TagSchema).optional(),
scheduleId: z.string().optional(),
resetName: z.boolean().default(false),
resetDescription: z.boolean().default(false),
resetSla: z.boolean().default(false),
resetSchedule: z.boolean().default(false),
});
export const CreateCloneRequestSchema = z.object({
name: z.string().optional(),
description: z.string().optional(),
createDbserver: z.boolean().optional(),
clustered: z.boolean().default(false).optional(),
nxClusterId: z.string().optional(),
dbserverId: z.string().optional(),
dbserverClusterId: z.string().optional(),
dbserverLogicalClusterId: z.string().optional(),
timeMachineId: z.string().optional(),
snapshotId: z.string().optional(),
latestSnapshot: z.boolean().default(false).optional(),
nodeCount: z.number().min(1).optional(),
nodes: z.array(NodeSchema).optional(),
tags: z.array(TagSchema).optional(),
lcmConfig: lcmConfigSchema.optional(),
computeProfileId: z.string().optional(),
networkProfileId: z.string().optional(),
databaseParameterProfileId: z.string().optional(),
actionArguments: z
.array(
z.object({
name: z.string(),
value: z.union([z.string(), z.number(), z.boolean()]),
})
)
.optional(),
}).superRefine((data, ctx) => {
if (!data.name) {
ctx.addIssue({path: ["name"], message: "Clone name is required", code: z.ZodIssueCode.custom});
}
if (!data.timeMachineId) {
ctx.addIssue({path: ["timeMachineId"], message: "timeMachineId is required", code: z.ZodIssueCode.custom});
}
if (!data.databaseParameterProfileId) {
ctx.addIssue({path: ["databaseParameterProfileId"], message: "databaseParameterProfileId is required", code: z.ZodIssueCode.custom});
}
if (data.createDbserver === null) {
ctx.addIssue({path: ["createDbserver"], message: "You have to choose either you want to create a new dbserver or use an existing one", code: z.ZodIssueCode.custom});
}
if (data.createDbserver === false) {
if (!data.dbserverId && !data.clustered) {
ctx.addIssue({path: ["dbserverId"], message: "dbserverId is required when createDbserver is false and target is not clustered", code: z.ZodIssueCode.custom});
}
if (!data.dbserverClusterId && data.clustered) {
ctx.addIssue({path: ["dbserverClusterId"], message: "dbserverClusterId is required when createDbserver is false and target is clustered", code: z.ZodIssueCode.custom});
}
} else {
if (!data.computeProfileId) {
ctx.addIssue({path: ["computeProfileId"], message: "computeProfileId is required", code: z.ZodIssueCode.custom});
}
if (!data.networkProfileId) {
ctx.addIssue({path: ["networkProfileId"], message: "networkProfileId is required", code: z.ZodIssueCode.custom});
}
if (!data.nxClusterId) {
ctx.addIssue({path: ["nxClusterId"], message: "target nxClusterId is required if createDbserver is true", code: z.ZodIssueCode.custom});
}
}
if (!data.nodeCount || (data.nodes?.length !== data.nodeCount)) {
ctx.addIssue({ path: ["nodeCount"], message: "nodeCount is required or should be equal to the number of nodes", code: z.ZodIssueCode.custom });
}
// Combined node validation loop
if (!data.nodes || data.nodes?.length === 0) {
ctx.addIssue({ path: ["nodes"], message: "At least one node is required", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["nodes", "vmName"], message: "vmName is required for all nodes", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["nodes", "nxClusterId"], message: "nxClusterId is required for all nodes", code: z.ZodIssueCode.custom });
if (!data.createDbserver) {
ctx.addIssue({ path: ["nodes", "dbserverId"], message: "dbserverId is required when createDbserver is false", code: z.ZodIssueCode.custom });
} else {
ctx.addIssue({ path: ["nodes", "networkProfileId"], message: "networkProfileId is required when createDbserver is true", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["nodes", "computeProfileId"], message: "computeProfileId is required when createDbserver is true", code: z.ZodIssueCode.custom });
}
}
data.nodes?.forEach((node, idx) => {
// Generic validation for all nodes
if (!node.vmName) ctx.addIssue({ path: ["nodes", idx, "vmName"], message: "vmName is required for this nodes", code: z.ZodIssueCode.custom });
if (!node.nxClusterId) ctx.addIssue({ path: ["nodes", idx, "nxClusterId"], message: "nxClusterId is required for this nodes", code: z.ZodIssueCode.custom });
if (!data.createDbserver) {
if (!data.dbserverId) {
ctx.addIssue({ path: ["nodes", idx, "dbserverId"], message: "dbserverId is required when createDbserver is false", code: z.ZodIssueCode.custom });
}
} else {
if (!node.networkProfileId) ctx.addIssue({ path: ["nodes", idx, "networkProfileId"], message: "networkProfileId is required when createDbserver is true", code: z.ZodIssueCode.custom });
if (!node.computeProfileId) ctx.addIssue({ path: ["nodes", idx, "computeProfileId"], message: "computeProfileId is required when createDbserver is true", code: z.ZodIssueCode.custom });
}
});
});
export const CreateDbserverRequestSchema = z.object({
databaseType: z.enum(SupportedEngines).optional(),
name: z.string().optional(),
nxClusterId: z.string().optional(),
softwareProfileId: z.string().optional(),
softwareProfileVersionId: z.string().optional(),
timeMachineId: z.string().optional(),
snapshotId: z.string().optional(),
latestSnapshot: z.boolean().default(true).optional(),
computeProfileId: z.string().optional(),
networkProfileId: z.string().optional(),
newDbServerTimeZone: z.string().optional(),
actionArguments: z.array(
z.object({
name: z.string(),
value: z.union([z.string(), z.number(), z.boolean()])
})
).optional()
}).superRefine((data, ctx) => {
if (!data.name) {
ctx.addIssue({ path: ["name"], message: "name is required", code: z.ZodIssueCode.custom });
}
if (!data.nxClusterId) {
ctx.addIssue({ path: ["nxClusterId"], message: "nxClusterId is required", code: z.ZodIssueCode.custom });
}
if (!data.timeMachineId && !data.softwareProfileId) {
ctx.addIssue({ path: ["softwareProfileId"], message: "softwareProfileId or timeMachineId is required", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["timeMachineId"], message: "softwareProfileId or timeMachineId is required", code: z.ZodIssueCode.custom });
}
if (data.softwareProfileId && !data.softwareProfileVersionId) {
ctx.addIssue({ path: ["softwareProfileVersionId"], message: "softwareProfileVersionId is required", code: z.ZodIssueCode.custom });
}
if (data.timeMachineId && ((!data.snapshotId) && (!data.latestSnapshot))) {
ctx.addIssue({ path: ["snapshotId"], message: "snapshotId or latestSnapshot is required when using timeMachineId", code: z.ZodIssueCode.custom });
}
if (!data.computeProfileId) {
ctx.addIssue({ path: ["computeProfileId"], message: "computeProfileId is required", code: z.ZodIssueCode.custom });
}
if (!data.networkProfileId) {
ctx.addIssue({ path: ["networkProfileId"], message: "networkProfileId is required", code: z.ZodIssueCode.custom });
}
if (!data.actionArguments || data.actionArguments?.length === 0) {
ctx.addIssue({ path: ["actionArguments"], message: "actionArguments is required", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["actionArguments", "name"], message: "name parameter is required (should be vm_name)", code: z.ZodIssueCode.custom });
ctx.addIssue({ path: ["actionArguments", "value"], message: "value parameter is required (should be the name of the vm)", code: z.ZodIssueCode.custom });
}
});