Multi-Cloud Support
MakisDSL enables true multi-cloud infrastructure as code. Write your infrastructure once and deploy to AWS, Azure, or GCP using the same DSL syntax.
🔗 Overview
MakisDSL abstracts away cloud provider differences, allowing you to define infrastructure using a unified syntax. The same DSL code can generate:
- AWS CloudFormation templates (.json)
- Azure ARM templates (.json)
- GCP Deployment Manager templates (.yaml/.json)
Single DSL, Multiple Outputs
// One DSL definition works for all providers
val infrastructure = cloudApp(provider = AWS) { // Change provider here
val storage = objectStorage("my-app-data")
.withVersioning(true)
val api = serverlessFunction("my-api")
.withRuntime("nodejs18.x")
.withHandler("index.handler")
.withCode("exports.handler = async () => ({ statusCode: 200, body: 'Hello!' })")
.build
val database = noSqlTable("my-data")
.withHashKey("id", "S")
.build
}
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Resources" : {
"my-app-data" : {
"Type" : "AWS::S3::Bucket",
"Properties" : {
"VersioningConfiguration" : {
"Status" : "Enabled"
}
}
},
"my-api" : {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Runtime" : "nodejs18.x",
"Handler" : "index.handler",
"Code" : {
"ZipFile" : "exports.handler = async () => ({ statusCode: 200, body: 'Hello!' })"
}
}
},
"my-data" : {
"Type" : "AWS::DynamoDB::Table",
"Properties" : {
"KeySchema" : [ {
"AttributeName" : "id",
"KeyType" : "HASH"
} ],
"AttributeDefinitions" : [ {
"AttributeName" : "id",
"AttributeType" : "S"
} ],
"BillingMode" : "PAY_PER_REQUEST"
}
}
}
}
{
"$schema" : "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion" : "1.0.0.0",
"resources" : [ {
"type" : "Microsoft.Storage/storageAccounts",
"apiVersion" : "2023-01-01",
"name" : "my-app-data",
"location" : "[resourceGroup().location]",
"properties" : {
"sku" : {
"name" : "Standard_LRS"
},
"kind" : "StorageV2",
"accessTier" : "Hot",
"isVersioningEnabled" : true
}
}, {
"type" : "Microsoft.Web/sites",
"apiVersion" : "2023-01-01",
"name" : "my-api",
"location" : "[resourceGroup().location]",
"properties" : {
"kind" : "functionapp",
"siteConfig" : {
"appSettings" : [ {
"name" : "FUNCTIONS_WORKER_RUNTIME",
"value" : "node"
}, {
"name" : "WEBSITE_NODE_DEFAULT_VERSION",
"value" : "~18"
} ]
}
}
}, {
"type" : "Microsoft.DocumentDB/databaseAccounts",
"apiVersion" : "2023-04-15",
"name" : "my-data",
"location" : "[resourceGroup().location]",
"properties" : {
"databaseAccountOfferType" : "Standard",
"consistencyPolicy" : {
"defaultConsistencyLevel" : "Session"
},
"locations" : [ {
"locationName" : "[resourceGroup().location]",
"failoverPriority" : 0
} ]
}
} ]
}
{
"resources" : [ {
"name" : "my-app-data",
"type" : "storage.v1.bucket",
"properties" : {
"location" : "US",
"storageClass" : "STANDARD",
"versioning" : {
"enabled" : true
}
}
}, {
"name" : "my-api",
"type" : "cloudfunctions.v1.function",
"properties" : {
"location" : "us-central1",
"runtime" : "nodejs18",
"entryPoint" : "index",
"sourceArchiveUrl" : "gs://my-source-bucket/function.zip"
}
}, {
"name" : "my-data",
"type" : "firestore.v1.database",
"properties" : {
"location" : "us-central1",
"type" : "FIRESTORE_NATIVE"
}
} ]
}
🔗 AWS CloudFormation
MakisDSL generates AWS CloudFormation templates that follow AWS best practices and resource naming conventions.
AWS Generator Usage
import cloud.providers.aws.CloudFormationGenerator
import io.circe.parser.*
val awsApp = cloudApp(provider = AWS) {
// Define your infrastructure
}
val cfTemplate = CloudFormationGenerator.generate(awsApp)
val jsonString = cfTemplate.spaces2
// Save to file or deploy with AWS CLI
println(jsonString)
AWS Resource Types
MakisDSL Resource | AWS CloudFormation Type | AWS Service |
---|---|---|
objectStorage |
AWS::S3::Bucket |
Amazon S3 |
serverlessFunction |
AWS::Lambda::Function |
AWS Lambda |
noSqlTable |
AWS::DynamoDB::Table |
Amazon DynamoDB |
virtualNetwork |
AWS::EC2::VPC |
Amazon VPC |
securityGroup |
AWS::EC2::SecurityGroup |
Amazon EC2 |
applicationLoadBalancer |
AWS::ElasticLoadBalancingV2::LoadBalancer |
Elastic Load Balancing |
AWS-Specific Features
- Dependencies - Mapped to CloudFormation
DependsOn
property - Resource References - Uses CloudFormation
Ref
andGetAtt
functions - Template Format - Standard CloudFormation JSON with
AWSTemplateFormatVersion
🔗 Azure ARM
MakisDSL generates Azure Resource Manager (ARM) templates compatible with Azure deployment tools.
Azure Generator Usage
import cloud.providers.azure.ARMGenerator
import io.circe.parser.*
val azureApp = cloudApp(provider = Azure) {
// Same infrastructure definition as AWS
}
val armTemplate = ARMGenerator.generate(azureApp)
val jsonString = armTemplate.spaces2
// Deploy with Azure CLI: az deployment group create --template-file template.json
println(jsonString)
Azure Resource Types
MakisDSL Resource | Azure ARM Type | Azure Service |
---|---|---|
objectStorage |
Microsoft.Storage/storageAccounts |
Azure Storage |
serverlessFunction |
Microsoft.Web/sites |
Azure Functions |
noSqlTable |
Microsoft.DocumentDB/databaseAccounts |
Azure Cosmos DB |
virtualNetwork |
Microsoft.Network/virtualNetworks |
Azure Virtual Network |
securityGroup |
Microsoft.Network/networkSecurityGroups |
Azure Network Security Groups |
applicationLoadBalancer |
Microsoft.Network/loadBalancers |
Azure Load Balancer |
Azure-Specific Features
- Resource Groups - All resources automatically scoped to resource group
- Location - Uses
[resourceGroup().location]
for automatic region placement - API Versions - Latest stable API versions for each resource type
- Dependencies - Mapped to ARM
dependsOn
arrays
🔗 GCP Deployment Manager
MakisDSL generates Google Cloud Deployment Manager templates for GCP resource provisioning.
GCP Generator Usage
import cloud.providers.gcp.DeploymentManagerGenerator
import io.circe.parser.*
val gcpApp = cloudApp(provider = GCP) {
// Same infrastructure definition as AWS/Azure
}
val gcpTemplate = DeploymentManagerGenerator.generate(gcpApp)
val jsonString = gcpTemplate.spaces2
// Deploy with gcloud: gcloud deployment-manager deployments create my-app --config template.yaml
println(jsonString)
GCP Resource Types
MakisDSL Resource | GCP Type | GCP Service |
---|---|---|
objectStorage |
storage.v1.bucket |
Cloud Storage |
serverlessFunction |
cloudfunctions.v1.function |
Cloud Functions |
noSqlTable |
firestore.v1.database |
Cloud Firestore |
virtualNetwork |
compute.v1.network |
VPC Network |
securityGroup |
compute.v1.firewall |
VPC Firewall |
applicationLoadBalancer |
compute.v1.forwardingRule |
Cloud Load Balancing |
GCP-Specific Features
- Projects - Resources automatically scoped to current GCP project
- Regions/Zones - Default placement in
us-central1
- Dependencies - Expressed through
metadata.dependsOn
- Resource Names - Follow GCP naming conventions
🔗 Resource Mapping
MakisDSL automatically maps abstract resource concepts to provider-specific implementations:
Storage Mapping Example
// Abstract storage concept
val storage = objectStorage("my-data")
.withVersioning(true)
// Maps to S3 Bucket with versioning
{
"Type" : "AWS::S3::Bucket",
"Properties" : {
"VersioningConfiguration" : {
"Status" : "Enabled"
}
}
}
// Maps to Storage Account with versioning
{
"type" : "Microsoft.Storage/storageAccounts",
"properties" : {
"sku" : { "name" : "Standard_LRS" },
"kind" : "StorageV2",
"isVersioningEnabled" : true
}
}
// Maps to Cloud Storage bucket with versioning
{
"type" : "storage.v1.bucket",
"properties" : {
"versioning" : {
"enabled" : true
}
}
}
🔗 Provider Differences
While MakisDSL abstracts most differences, some provider-specific behaviors are important to understand:
Compute Runtime Mapping
MakisDSL Runtime | AWS Lambda | Azure Functions | GCP Functions |
---|---|---|---|
nodejs18.x |
nodejs18.x | node (~18) | nodejs18 |
python3.9 |
python3.9 | python (~3.9) | python39 |
java11 |
java11 | java (~11) | java17 |
Database Differences
- AWS DynamoDB - Key-value store with hash/range keys
- Azure Cosmos DB - Multi-model database with document support
- GCP Firestore - Document database with collection-based structure
Networking Differences
- AWS VPC - Full control over network topology
- Azure VNet - Integrated with resource groups and subscriptions
- GCP VPC - Global networks with regional subnets
🔗 Best Practices
Provider-Agnostic Design
- Use Standard Runtimes - Stick to commonly supported runtimes like Node.js 18.x
- Abstract Resource Names - Use logical names that work across providers
- Avoid Provider-Specific Features - Focus on common functionality available everywhere
Testing Across Providers
class MultiCloudTest extends munit.FunSuite {
def testProvider(provider: CloudProvider) = {
val app = cloudApp(provider = provider) {
val storage = objectStorage("test-bucket")
.withVersioning(true)
val function = serverlessFunction("test-function")
.withRuntime("nodejs18.x")
.withHandler("index.handler")
.build
}
// Test that generation works for each provider
val template = provider match {
case AWS => CloudFormationGenerator.generate(app)
case Azure => ARMGenerator.generate(app)
case GCP => DeploymentManagerGenerator.generate(app)
}
assert(template != null)
}
test("AWS generation") { testProvider(AWS) }
test("Azure generation") { testProvider(Azure) }
test("GCP generation") { testProvider(GCP) }
}
Environment-Specific Configurations
// Use environment variables for provider-specific settings
val provider = sys.env.getOrElse("CLOUD_PROVIDER", "AWS") match {
case "AWS" => AWS
case "Azure" => Azure
case "GCP" => GCP
}
val app = cloudApp(provider = provider) {
val bucketName = env("BUCKET_NAME", "default-bucket")
val storage = objectStorage(bucketName)
.withVersioning(true)
}