diff --git a/src/config/digitalocean/database-types.ts b/src/config/digitalocean/database-types.ts index e6589af..62c3ba9 100644 --- a/src/config/digitalocean/database-types.ts +++ b/src/config/digitalocean/database-types.ts @@ -1,7 +1,7 @@ interface DatabaseConfig { engine: string; - versions: string[]; description: string; + portNumber: number; } interface DigitalOceanDatabaseConfig { @@ -14,30 +14,39 @@ export const digitalOceanDatabaseConfig: DigitalOceanDatabaseConfig = { databases: { 'docker.io/library/mysql': { engine: 'MYSQL', - versions: ['8'], - description: 'MySQL database service - requires managed database service due to TCP protocol' + description: 'MySQL database service - requires managed database service due to TCP protocol', + portNumber: 3306 }, 'docker.io/library/mariadb': { engine: 'MYSQL', - versions: ['8'], - description: 'MariaDB database service - maps to MySQL managed database due to compatibility' + description: 'MariaDB database service - maps to MySQL managed database due to compatibility', + portNumber: 3306 }, 'docker.io/library/postgres': { engine: 'PG', - versions: ['13', '14', '15'], - description: 'PostgreSQL database service - requires managed database service due to TCP protocol' + description: 'PostgreSQL database service - requires managed database service due to TCP protocol', + portNumber: 5432 }, 'docker.io/library/redis': { engine: 'REDIS', - versions: ['6', '7'], - description: 'Redis database service - requires managed database service due to TCP protocol' + description: 'Redis database service - requires managed database service due to TCP protocol', + portNumber: 6379 }, 'docker.io/library/mongodb': { engine: 'MONGODB', - versions: ['6.0', '7.0'], - description: 'MongoDB database service - requires managed database service due to TCP protocol' + description: 'MongoDB database service - requires managed database service due to TCP protocol', + portNumber: 27017 } } }; -export type { DatabaseConfig, DigitalOceanDatabaseConfig }; \ No newline at end of file +export function isDatabaseService(imageString: string): boolean { + return imageString.split(':')[0] in digitalOceanDatabaseConfig.databases; +} + +export function getDatabaseConfig(imageString: string): DatabaseConfig | null { + const baseImage = imageString.split(':')[0]; + return digitalOceanDatabaseConfig.databases[baseImage] || null; +} + +export type { DatabaseConfig, DigitalOceanDatabaseConfig }; diff --git a/src/parsers/digitalocean.ts b/src/parsers/digitalocean.ts index 2d68d64..8af4073 100644 --- a/src/parsers/digitalocean.ts +++ b/src/parsers/digitalocean.ts @@ -4,7 +4,7 @@ import { parsePort } from '../utils/parsePort'; import { parseCommand } from '../utils/parseCommand'; import { digitalOceanParserServiceName } from '../utils/digitalOceanParserServiceName'; import { parseEnvironmentVariables } from '../utils/parseEnvironmentVariables'; -import { constructImageString } from '../utils/constructImageString'; +import { normalizeDigitalOceanImageInfo } from '../utils/normalizeDigitalOceanImageInfo'; import { getDigitalOceanDatabaseType } from '../utils/getDigitalOceanDatabaseType'; const defaultParserConfig: DefaultParserConfig = { @@ -27,54 +27,21 @@ function getRegistryType(dockerImageInfo: DockerImageInfo): string { class DigitalOceanParser extends BaseParser { parse(config: ApplicationConfig, templateFormat: TemplateFormat = defaultParserConfig.templateFormat): any { const services: Array = []; - const databases: Array = []; let isFirstService = true; for (const [serviceName, serviceConfig] of Object.entries(config.services)) { - const databaseInfo = getDigitalOceanDatabaseType(serviceConfig.image); - - if (databaseInfo) { - databases.push({ - name: serviceName, - engine: databaseInfo.engine, - version: databaseInfo.version, - production: false - }); - continue; - } - - const ports = new Set(); - - if (serviceConfig.ports) { - serviceConfig.ports.forEach(port => { - if (typeof port === 'object' && port !== null) { - ports.add(port.container); - } else { - const parsedPort = parsePort(port); - if (parsedPort) { - ports.add(parsedPort); - } - } - }); - } - const dockerImageInfo = serviceConfig.image; - const imageString = constructImageString(dockerImageInfo); - const [repository, tag] = imageString.split(':'); - const imageComponents = repository.split('/'); + const databaseConfig = getDigitalOceanDatabaseType(dockerImageInfo); + const normalizedImage = normalizeDigitalOceanImageInfo(dockerImageInfo); - const routePath = isFirstService ? '/' : `/${serviceName}`; - isFirstService = false; - - const service = { + const baseService = { name: digitalOceanParserServiceName(serviceName), image: { registry_type: getRegistryType(dockerImageInfo), - registry: imageComponents[1], - repository: imageComponents[2], - tag: tag || 'latest' + registry: normalizedImage.registry, + repository: normalizedImage.repository, + tag: dockerImageInfo.tag || 'latest' }, - http_port: ports.size > 0 ? Array.from(ports)[0] : undefined, instance_count: 1, instance_size_slug: defaultParserConfig.subscriptionName, run_command: parseCommand(serviceConfig.command), @@ -82,28 +49,49 @@ class DigitalOceanParser extends BaseParser { .map(([key, value]) => ({ key, value: value.toString() - })), - routes: [{ path: routePath }] + })) }; - services.push(service); + if (databaseConfig) { + // Database/TCP service configuration + services.push({ + ...baseService, + health_check: { + port: databaseConfig.portNumber + }, + internal_ports: [databaseConfig.portNumber] + }); + } else { + const ports = new Set(); + if (serviceConfig.ports) { + serviceConfig.ports.forEach(port => { + if (typeof port === 'object' && port !== null) { + ports.add(port.container); + } else { + const parsedPort = parsePort(port); + if (parsedPort) { + ports.add(parsedPort); + } + } + }); + } + + const routePath = isFirstService ? '/' : `/${serviceName}`; + isFirstService = false; + + services.push({ + ...baseService, + http_port: ports.size > 0 ? Array.from(ports)[0] : undefined, + routes: [{ path: routePath }] + }); + } } const digitalOceanConfig = { spec: { name: 'deploystack', region: defaultParserConfig.region, - ...(databases.length > 0 && { databases }), - services: services.map(service => ({ - name: service.name, - image: service.image, - http_port: service.http_port, - instance_count: service.instance_count, - instance_size_slug: service.instance_size_slug, - run_command: service.run_command, - envs: service.envs, - routes: service.routes - })) + services } }; diff --git a/src/utils/getDigitalOceanDatabaseType.ts b/src/utils/getDigitalOceanDatabaseType.ts index b385144..98fe178 100644 --- a/src/utils/getDigitalOceanDatabaseType.ts +++ b/src/utils/getDigitalOceanDatabaseType.ts @@ -1,24 +1,16 @@ import { DockerImageInfo } from '../parsers/base-parser'; import { getImageUrl } from './getImageUrl'; import { constructImageString } from './constructImageString'; -import { digitalOceanDatabaseConfig } from '../config/digitalocean/database-types'; +import { digitalOceanDatabaseConfig, DatabaseConfig } from '../config/digitalocean/database-types'; -export interface DatabaseInfo { - engine: string; - version: string; -} - -export function getDigitalOceanDatabaseType(image: DockerImageInfo): DatabaseInfo | null { +export function getDigitalOceanDatabaseType(image: DockerImageInfo): DatabaseConfig | null { const normalizedImage = getImageUrl(constructImageString(image)); for (const [configImage, dbConfig] of Object.entries(digitalOceanDatabaseConfig.databases)) { if (normalizedImage.includes(configImage)) { - return { - engine: dbConfig.engine, - version: dbConfig.versions[0] - }; + return dbConfig; } } return null; -} \ No newline at end of file +} diff --git a/src/utils/normalizeDigitalOceanImageInfo.ts b/src/utils/normalizeDigitalOceanImageInfo.ts new file mode 100644 index 0000000..fff956b --- /dev/null +++ b/src/utils/normalizeDigitalOceanImageInfo.ts @@ -0,0 +1,41 @@ +import { DockerImageInfo } from '../parsers/base-parser'; + +export function normalizeDigitalOceanImageInfo(dockerImageInfo: DockerImageInfo): { + registry: string; + repository: string; +} { + // Get fully qualified name from registry + const registryPath = dockerImageInfo.registry || 'docker.io'; + const repositoryPath = dockerImageInfo.repository; + + // For docker.io/library/[name] format + if (registryPath === 'docker.io' && repositoryPath.startsWith('library/')) { + return { + registry: 'library', + repository: repositoryPath.replace('library/', '') + }; + } + + // For docker.io/[owner]/[name] format + if (registryPath === 'docker.io') { + // Check if it's a direct image name without owner (official image) + if (!repositoryPath.includes('/')) { + return { + registry: 'library', + repository: repositoryPath + }; + } + + const parts = repositoryPath.split('/'); + return { + registry: parts[0], + repository: parts[1] || parts[0] + }; + } + + // For other registries, keep as is + return { + registry: dockerImageInfo.registry || 'library', + repository: dockerImageInfo.repository + }; +}