@deploystack/docker-to-iac
Version:
Translate docker run and docker compose file to Infrastructure as Code
190 lines (189 loc) • 9.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const base_parser_1 = require("./base-parser");
const parsePort_1 = require("../utils/parsePort");
const parseCommand_1 = require("../utils/parseCommand");
const digitalOceanParserServiceName_1 = require("../utils/digitalOceanParserServiceName");
const normalizeDigitalOceanImageInfo_1 = require("../utils/normalizeDigitalOceanImageInfo");
const getDigitalOceanDatabaseType_1 = require("../utils/getDigitalOceanDatabaseType");
const isDigitalOceanManagedDatabase_1 = require("../utils/isDigitalOceanManagedDatabase");
const defaultParserConfig = {
files: [
{
path: '.do/deploy.template.yaml',
templateFormat: base_parser_1.TemplateFormat.yaml,
isMain: true,
description: 'DigitalOcean App Platform deployment template'
}
],
region: 'nyc',
subscriptionName: 'basic-xxs'
};
function getRegistryType(dockerImageInfo) {
switch (dockerImageInfo.registry_type) {
case 'GHCR':
return 'GHCR';
case 'DOCKER_HUB':
default:
return 'DOCKER_HUB';
}
}
class DigitalOceanParser extends base_parser_1.BaseParser {
parse(config, templateFormat = base_parser_1.TemplateFormat.yaml) {
return super.parse(config, templateFormat);
}
parseFiles(config) {
const services = [];
const databases = [];
const databaseServiceMap = new Map();
let isFirstService = true;
// First pass: identify database services that should be managed databases
for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
if ((0, isDigitalOceanManagedDatabase_1.isDigitalOceanManagedDatabase)(serviceConfig.image)) {
// Create a database entry
const dbName = (0, digitalOceanParserServiceName_1.digitalOceanParserServiceName)(`${serviceName}-db`);
// Track the mapping between service name and database name
databaseServiceMap.set(serviceName, dbName);
// Get database config
const dbConfig = (0, getDigitalOceanDatabaseType_1.getDigitalOceanDatabaseType)(serviceConfig.image);
// Create database config object with proper typing
const dbEntry = {
name: dbName,
engine: dbConfig?.engine || 'PG' // Default to PostgreSQL if unknown
};
databases.push(dbEntry);
// Skip creating a service for this database
continue;
}
const dockerImageInfo = serviceConfig.image;
const databaseConfig = (0, getDigitalOceanDatabaseType_1.getDigitalOceanDatabaseType)(dockerImageInfo);
const normalizedImage = (0, normalizeDigitalOceanImageInfo_1.normalizeDigitalOceanImageInfo)(dockerImageInfo);
// Prepare base service configuration
const baseService = {
name: (0, digitalOceanParserServiceName_1.digitalOceanParserServiceName)(serviceName),
image: {
registry_type: getRegistryType(dockerImageInfo),
registry: normalizedImage.registry,
repository: normalizedImage.repository,
tag: dockerImageInfo.tag || 'latest'
},
instance_count: 1,
instance_size_slug: defaultParserConfig.subscriptionName,
run_command: (0, parseCommand_1.parseCommand)(serviceConfig.command),
envs: Object.entries(serviceConfig.environment)
.map(([key, value]) => ({
key,
value: value.toString(),
scope: 'RUN_TIME'
}))
};
// Process service connections - this is provider specific logic
if (config.serviceConnections) {
for (const connection of config.serviceConnections) {
if (connection.fromService === serviceName) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [varName, varInfo] of Object.entries(connection.variables)) {
// Find the environment variable index
const envIndex = baseService.envs.findIndex(env => env.key === varName);
// Check if target service is a managed database
if (databaseServiceMap.has(connection.toService)) {
const dbServiceName = databaseServiceMap.get(connection.toService);
const transformedValue = `\${${dbServiceName}.DATABASE_URL}`;
if (envIndex !== -1) {
// Update existing variable
baseService.envs[envIndex].value = transformedValue;
}
else {
// Add new variable
baseService.envs.push({
key: varName,
value: transformedValue,
scope: 'RUN_TIME'
});
}
}
else {
// Regular service connection - not typically used in DigitalOcean but included for completeness
const targetServiceName = (0, digitalOceanParserServiceName_1.digitalOceanParserServiceName)(connection.toService);
const transformedValue = `\${${targetServiceName}.PRIVATE_URL}`;
if (envIndex !== -1) {
baseService.envs[envIndex].value = transformedValue;
}
else {
baseService.envs.push({
key: varName,
value: transformedValue,
scope: 'RUN_TIME'
});
}
}
}
}
}
}
if (databaseConfig && !(0, isDigitalOceanManagedDatabase_1.isDigitalOceanManagedDatabase)(dockerImageInfo)) {
// Non-managed 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 = (0, parsePort_1.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,
services
}
};
// Add databases section if we have any
if (databases.length > 0) {
digitalOceanConfig.spec.databases = databases;
}
return {
'.do/deploy.template.yaml': {
content: this.formatFileContent(digitalOceanConfig, base_parser_1.TemplateFormat.yaml),
format: base_parser_1.TemplateFormat.yaml,
isMain: true
}
};
}
getInfo() {
return {
providerWebsite: 'https://www.digitalocean.com/',
providerName: 'DigitalOcean',
providerNameAbbreviation: 'DO',
languageOfficialDocs: 'https://docs.digitalocean.com/products/app-platform/',
languageAbbreviation: 'DOP',
languageName: 'DigitalOcean App Spec',
defaultParserConfig: defaultParserConfig
};
}
}
exports.default = new DigitalOceanParser();