iop
Version:
Ship Docker Anywhere
257 lines • 9.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigValidator = void 0;
exports.validateConfig = validateConfig;
exports.formatValidationErrors = formatValidationErrors;
const port_checker_1 = require("./port-checker");
class ConfigValidator {
constructor(config) {
this.config = config;
}
/**
* Validates the entire configuration for common issues
*/
validate() {
const errors = [];
// Check for reserved names
const reservedNameErrors = this.checkReservedNames();
errors.push(...reservedNameErrors);
// Check for port conflicts within the same project
const portConflicts = this.checkIntraProjectPortConflicts();
errors.push(...portConflicts);
// Check for invalid port configurations
const invalidPorts = this.checkInvalidPorts();
errors.push(...invalidPorts);
return errors;
}
/**
* Checks for reserved command names being used as app/service names
*/
checkReservedNames() {
const errors = [];
const allEntries = this.getAllEntries();
for (const entry of allEntries) {
if (ConfigValidator.RESERVED_NAMES.includes(entry.name)) {
errors.push({
type: "reserved_name",
message: `App/service name "${entry.name}" is reserved and cannot be used`,
entries: [entry.name],
server: entry.server,
suggestions: this.generateReservedNameSuggestions(entry.name),
});
}
}
return errors;
}
/**
* Checks for port conflicts within the same project configuration
*/
checkIntraProjectPortConflicts() {
const errors = [];
const serverPortUsage = new Map();
// Get all entries (apps and services)
const allEntries = this.getAllEntries();
// Group port usage by server
for (const entry of allEntries) {
if (!entry.ports)
continue;
const serverName = entry.server;
if (!serverPortUsage.has(serverName)) {
serverPortUsage.set(serverName, new Map());
}
const serverPorts = serverPortUsage.get(serverName);
try {
const portMappings = (0, port_checker_1.parsePortMappings)(entry.ports);
for (const mapping of portMappings) {
const hostPort = mapping.hostPort;
if (!serverPorts.has(hostPort)) {
serverPorts.set(hostPort, []);
}
serverPorts.get(hostPort).push(entry.name);
}
}
catch (error) {
// Handle invalid port format - will be caught by checkInvalidPorts
continue;
}
}
// Check for conflicts
for (const [serverName, portMap] of serverPortUsage) {
for (const [port, entries] of portMap) {
if (entries.length > 1) {
errors.push({
type: "port_conflict",
message: `Port ${port} is used by multiple services on server ${serverName}`,
entries,
server: serverName,
port,
suggestions: this.generatePortConflictSuggestions(port, entries, serverName),
});
}
}
}
return errors;
}
/**
* Checks for invalid port configurations
*/
checkInvalidPorts() {
const errors = [];
const allEntries = this.getAllEntries();
for (const entry of allEntries) {
if (!entry.ports)
continue;
for (const portSpec of entry.ports) {
if (!this.isValidPortSpec(portSpec)) {
errors.push({
type: "invalid_port",
message: `Invalid port specification: "${portSpec}" in ${entry.name}`,
entries: [entry.name],
server: entry.server,
suggestions: [
"Valid port formats:",
'- "80:80" (host:container)',
'- "8080:80" (different host and container ports)',
'- "127.0.0.1:8080:80" (bind to specific IP)',
'- "80" (same port for host and container)',
'- "80/tcp" or "80/udp" (specify protocol)',
],
});
}
}
}
return errors;
}
/**
* Validates a port specification format
*/
isValidPortSpec(portSpec) {
// Valid formats:
// "80", "80/tcp", "80/udp"
// "80:80", "80:80/tcp", "80:80/udp"
// "127.0.0.1:80:80", "127.0.0.1:80:80/tcp"
const patterns = [
/^(\d+)(?:\/(tcp|udp))?$/, // "80" or "80/tcp"
/^(\d+):(\d+)(?:\/(tcp|udp))?$/, // "80:80" or "80:80/tcp"
/^(\d+\.\d+\.\d+\.\d+):(\d+):(\d+)(?:\/(tcp|udp))?$/, // "127.0.0.1:80:80"
];
return patterns.some((pattern) => pattern.test(portSpec));
}
/**
* Gets all entries from the services configuration
*/
getAllEntries() {
const entries = [];
// Add services
if (this.config.services) {
if (Array.isArray(this.config.services)) {
entries.push(...this.config.services);
}
else {
// Convert object format to array
for (const [name, service] of Object.entries(this.config.services)) {
entries.push({ ...service, name });
}
}
}
return entries;
}
/**
* Generates suggestions for resolving reserved name conflicts
*/
generateReservedNameSuggestions(reservedName) {
const suggestions = [
`The name "${reservedName}" is reserved for CLI commands.`,
"",
"Reserved names: " + ConfigValidator.RESERVED_NAMES.join(", "),
"",
"Try using a different name like:",
`• ${reservedName}-app`,
`• ${reservedName}-service`,
`• web-${reservedName}`,
`• api-${reservedName}`,
`• my-${reservedName}`,
"",
"Example fix:",
"# Before:",
`services:`,
` ${reservedName}:`,
" # ... your config",
"",
"# After:",
`services:`,
` ${reservedName}-app:`,
" # ... your config",
];
return suggestions;
}
/**
* Generates suggestions for resolving port conflicts
*/
generatePortConflictSuggestions(port, conflictingEntries, serverName) {
const suggestions = [
`Port ${port} conflict between: ${conflictingEntries.join(", ")}`,
"",
"Solutions:",
`1. Use different host ports (e.g., ${port + 1000}, ${port + 2000})`,
"2. Remove port mappings if external access is not needed",
"3. Deploy conflicting services to different servers",
"",
"Example fix:",
`# Original (conflicting):`,
`services:`,
` ${conflictingEntries[0]}:`,
` ports: ["${port}:${port}"]`,
` ${conflictingEntries[1]}:`,
` ports: ["${port}:${port}"]`,
"",
"# Fixed:",
`services:`,
` ${conflictingEntries[0]}:`,
` ports: ["${port}:${port}"]`,
` ${conflictingEntries[1]}:`,
` ports: ["${port + 1000}:${port}"] # Different host port`,
"",
"Or remove external access (recommended for databases):",
`services:`,
` ${conflictingEntries[0]}:`,
` # ports: [...] # Remove for internal-only access`,
` ${conflictingEntries[1]}:`,
` # ports: [...] # Remove for internal-only access`,
];
return suggestions;
}
}
exports.ConfigValidator = ConfigValidator;
// Reserved command names that cannot be used as app/service names
ConfigValidator.RESERVED_NAMES = ["init", "status", "proxy"];
/**
* Validates a iop configuration and returns any errors found
*/
function validateConfig(config) {
const validator = new ConfigValidator(config);
return validator.validate();
}
/**
* Formats validation errors for display
*/
function formatValidationErrors(errors) {
const formatted = [];
if (errors.length === 0) {
return formatted;
}
formatted.push("✗ Configuration validation failed:");
formatted.push("");
for (const error of errors) {
formatted.push(`• ${error.message}`);
if (error.suggestions) {
formatted.push("");
for (const suggestion of error.suggestions) {
formatted.push(` ${suggestion}`);
}
}
formatted.push("");
}
return formatted;
}
//# sourceMappingURL=config-validator.js.map