Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(do): DigitalOcean services vs. managed DB fix #91

Merged
merged 1 commit into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
};
}
Loading