Tip
💡
Thank you for your interest in collaborating! The docker-to-iac module will remain open source forever, helping simplify deployments across cloud providers without vendor lock-in.
Parser Implementation
Create a new file inside src/parsers/new-provider.ts:
import {
BaseParser,
ParserInfo,
TemplateFormat,
ParserConfig,
FileOutput,
DockerImageInfo
} from './base-parser';
import { ApplicationConfig } from '../types/container-config';
import { parsePort } from '../utils/parsePort';
import { parseCommand } from '../utils/parseCommand';
// Define default configuration for your parser
const defaultParserConfig: ParserConfig = {
files: [
{
path: 'awesome-iac.yaml',
templateFormat: TemplateFormat.yaml,
isMain: true,
description: 'Main IaC configuration file'
},
{
path: 'templates/resources.yaml',
templateFormat: TemplateFormat.yaml,
description: 'Additional resources configuration'
}
],
cpu: 512,
memory: '1GB',
region: 'default-region',
subscriptionName: 'basic-tier'
};
// Optional: Add helper functions for your specific provider
function getNewProviderServiceType(imageInfo: DockerImageInfo): string {
// Logic to determine service type based on image
// Example: Check if it's a database, web service, etc.
return 'web-service'; // Default type
}
// Optional: Add function to determine if an image is a managed service
function isNewProviderManagedService(imageInfo: DockerImageInfo): boolean {
// Check if this image should be handled as a managed service
const imageUrl = `${imageInfo.repository}:${imageInfo.tag || 'latest'}`;
return imageUrl.includes('postgres') || imageUrl.includes('redis');
}
class NewProviderParser extends BaseParser {
// Multi-file implementation - required by BaseParser
parseFiles(config: ApplicationConfig): { [path: string]: FileOutput } {
// Initialize result containers
const services: Array<any> = [];
const managedServices: Array<any> = [];
// Track service mappings for managed services
const managedServiceMap = new Map<string, string>();
// First pass: identify and register managed services
for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
if (isNewProviderManagedService(serviceConfig.image)) {
// Create a managed service instead of a regular service
const managedName = `${serviceName}-managed`;
// Track the mapping for service connections later
managedServiceMap.set(serviceName, managedName);
// Add to managed services collection
managedServices.push({
name: managedName,
type: getNewProviderServiceType(serviceConfig.image),
// Add provider-specific managed service properties
plan: defaultParserConfig.subscriptionName
});
// Skip further processing of this service
continue;
}
// Regular services will be processed in the second pass
}
// Second pass: process regular services with their connections
for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
// Skip managed services already processed
if (managedServiceMap.has(serviceName)) {
continue;
}
// Extract ports from service configuration
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);
}
}
});
}
// Prepare basic service definition
const service: any = {
name: serviceName,
type: getNewProviderServiceType(serviceConfig.image),
image: serviceConfig.image,
command: parseCommand(serviceConfig.command),
environment: []
};
// Add ports if available
if (ports.size > 0) {
service.ports = Array.from(ports);
}
// Process service connections if available
if (config.serviceConnections) {
// First add regular environment variables
for (const [key, value] of Object.entries(serviceConfig.environment)) {
// Check if this variable is handled by service connections
const isHandledByConnection = config.serviceConnections.some(conn =>
conn.fromService === serviceName &&
Object.keys(conn.variables).includes(key)
);
if (!isHandledByConnection) {
// Regular environment variable
service.environment.push({
key,
value: value.toString()
});
}
}
// Then add service connection variables with provider-specific syntax
for (const connection of config.serviceConnections) {
if (connection.fromService === serviceName) {
for (const [varName, varInfo] of Object.entries(connection.variables)) {
// Check if target is a managed service
if (managedServiceMap.has(connection.toService)) {
const targetName = managedServiceMap.get(connection.toService);
// Use provider-specific reference syntax
service.environment.push({
key: varName,
// Example: ${resources.MANAGED_SERVICE_NAME.CONNECTION_STRING}
value: `\${resources.${targetName}.${connection.property || 'connectionString'}}`
});
} else {
// Regular service connection
service.environment.push({
key: varName,
// Example: ${services.SERVICE_NAME.HOST_PORT}
value: `\${services.${connection.toService}.${connection.property || 'hostport'}}`
});
}
}
}
}
} else {
// No service connections, just add all environment variables
service.environment = Object.entries(serviceConfig.environment).map(([key, value]) => ({
key,
value: value.toString()
}));
}
// Add service to collection
services.push(service);
}
// Create main configuration
const mainConfig = {
version: '1.0',
provider: 'new-provider',
region: defaultParserConfig.region,
services
};
// Create resources configuration if we have managed services
const resourcesConfig = managedServices.length > 0 ? {
version: '1.0',
managedResources: managedServices
} : {};
// Return file mappings - the main file is required
const result: { [path: string]: FileOutput } = {
'awesome-iac.yaml': {
content: this.formatFileContent(mainConfig, TemplateFormat.yaml),
format: TemplateFormat.yaml,
isMain: true
}
};
// Add resources file if we have managed services
if (managedServices.length > 0) {
result['templates/resources.yaml'] = {
content: this.formatFileContent(resourcesConfig, TemplateFormat.yaml),
format: TemplateFormat.yaml
};
}
return result;
}
getInfo(): ParserInfo {
return {
providerWebsite: "https://newprovider.example.com",
providerName: "New Provider Cloud",
providerNameAbbreviation: "NP",
languageOfficialDocs: "https://docs.newprovider.example.com/iac",
languageAbbreviation: "NP",
languageName: "New Provider IaC",
defaultParserConfig
};
}
}
export default new NewProviderParser();
Configuration and Provider-Specific Logic
Service Type Detection
Create a file for service type configuration in src/config/newprovider/service-types.ts:
interface NewProviderServiceTypeConfig {
type: string;
description: string;
versions: string;
isManaged?: boolean;
}
interface NewProviderServiceTypesConfig {
serviceTypes: {
[key: string]: NewProviderServiceTypeConfig;
};
}
export const newProviderServiceTypesConfig: NewProviderServiceTypesConfig = {
serviceTypes: {
'docker.io/library/mariadb': {
type: 'database',
description: 'MariaDB database service',
versions: '*'
},
'docker.io/library/postgres': {
type: 'database',
description: 'PostgreSQL database',
versions: '*',
isManaged: true
},
'docker.io/library/redis': {
type: 'cache',
description: 'Redis cache',
versions: '*',
isManaged: true
}
}
};
export function getNewProviderServiceType(imageString: string): string {
const baseImage = imageString.split(':')[0];
return newProviderServiceTypesConfig.serviceTypes[baseImage]?.type || 'web';
}
export function isNewProviderManagedService(imageString: string): boolean {
const baseImage = imageString.split(':')[0];
return !!newProviderServiceTypesConfig.serviceTypes[baseImage]?.isManaged;
}
export type { NewProviderServiceTypeConfig, NewProviderServiceTypesConfig };
Service Connection Properties
Update the service connection properties in src/config/connection-properties.ts:
export const servicePropertyMappings: Record<string, PropertyMapping> = {
'host': {
render: 'host',
digitalOcean: 'PRIVATE_DOMAIN',
newProvider: 'HOST' // Add your provider mapping
},
'port': {
render: 'port',
digitalOcean: 'PRIVATE_PORT',
newProvider: 'PORT' // Add your provider mapping
},
'hostport': {
render: 'hostport',
digitalOcean: 'PRIVATE_URL',
newProvider: 'ENDPOINT' // Add your provider mapping
}
};
export const databasePropertyMappings: Record<string, PropertyMapping> = {
'connectionString': {
render: 'connectionString',
digitalOcean: 'DATABASE_URL',
newProvider: 'CONNECTION_STRING' // Add your provider mapping
},
// Add other mappings...
};
Adding Your Parser to the System
Update src/index.ts to include your new parser:
// Import your new parser
import newProviderParserInstance from './parsers/new-provider';
// Add it to the parsers array
const parsers: BaseParser[] = [
cloudFormationParserInstance,
renderParserInstance,
digitalOceanParserInstance,
newProviderParserInstance // Add your parser here
];
Testing
Please read our guidelines for testing parsers in the Testing section.
New parser documentation
Please update documentation in the github.com/deploystackio/documentation repository.
Checklist
- Support both input types:
Docker run commands
Docker Compose files - Handle all service types:
Regular web/application services
Managed database services
Cache services - Handle resource mappings consistently:
Container ports
Environment variables
Volume mounts
Resource limits
Service connections - Process service connections correctly:
Service-to-service references
Service-to-managed-service references
Use provider-specific connection syntax - Provide clear error messages for:
Unsupported features
Invalid configurations
Missing required fields - Test edge cases:
Multiple services with interdependencies
Complex configurations with service connections
Various image formats and service types