Skip to content

Commit

Permalink
Merge pull request #91 from deploystackio/feat/do-service-db-fix
Browse files Browse the repository at this point in the history
fix(do): DigitalOcean services vs. managed DB fix
  • Loading branch information
Lasim authored Jan 4, 2025
2 parents 8837bf7 + 386c5cc commit c2c5676
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 78 deletions.
33 changes: 21 additions & 12 deletions src/config/digitalocean/database-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
interface DatabaseConfig {
engine: string;
versions: string[];
description: string;
portNumber: number;
}

interface DigitalOceanDatabaseConfig {
Expand All @@ -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 };
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 };
96 changes: 42 additions & 54 deletions src/parsers/digitalocean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -27,83 +27,71 @@ function getRegistryType(dockerImageInfo: DockerImageInfo): string {
class DigitalOceanParser extends BaseParser {
parse(config: ApplicationConfig, templateFormat: TemplateFormat = defaultParserConfig.templateFormat): any {
const services: Array<any> = [];
const databases: Array<any> = [];
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<number>();

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),
envs: Object.entries(parseEnvironmentVariables(serviceConfig.environment))
.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<number>();
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
}
};

Expand Down
16 changes: 4 additions & 12 deletions src/utils/getDigitalOceanDatabaseType.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
41 changes: 41 additions & 0 deletions src/utils/normalizeDigitalOceanImageInfo.ts
Original file line number Diff line number Diff line change
@@ -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
};
}

0 comments on commit c2c5676

Please sign in to comment.