Modules
Modules are reusable units of Carina configuration. A module defines a set of resources along with input parameters (arguments) and output values (attributes).
Module Structure
A module is a .crn file (or a directory containing main.crn) that declares arguments and attributes blocks.
arguments { cidr_block : string subnet_cidr: string az : string}
let vpc = awscc.ec2.vpc { cidr_block = cidr_block
tags = { Name = 'module-test' }}
let subnet = awscc.ec2.subnet { vpc_id = vpc.vpc_id cidr_block = subnet_cidr availability_zone = az
tags = { Name = 'module-test-subnet' }}
attributes { vpc_id : awscc.ec2.vpc = vpc.vpc_id subnet_id: awscc.ec2.subnet = subnet.subnet_id}Arguments Block
The arguments block defines the parameters a module accepts from its caller.
Basic Parameters
Each parameter has a name and a type:
arguments { cidr_block: string count : int enabled : bool}Default Values
Parameters can have default values:
arguments { cidr_block: string az : string = 'ap-northeast-1a' enable_dns: bool = true}Block Form with Description and Validation
Parameters can use an expanded block form for description, default values, and validation rules:
arguments { instance_count: int { description = 'Number of instances to create' default = 1 validation { condition = instance_count >= 1 && instance_count <= 10 error_message = 'Instance count must be between 1 and 10' } }
cidr_block: string { description = 'The CIDR block for the VPC' validation { condition = length(cidr_block) > 0 error_message = 'CIDR block cannot be empty' } }}Supported Types
Parameter types can be any type expression:
arguments { name : string count : int ratio : float enabled : bool subnet_ids : list(string) tags : map(string) cidr_block : cidr role_arn : arn}Attributes Block
The attributes block defines the output values that a module exposes to its caller.
Each attribute has a name, an optional type, and a value expression:
attributes { vpc_id : awscc.ec2.vpc = vpc.vpc_id subnet_id : awscc.ec2.subnet = subnet.subnet_id route_table_id: awscc.ec2.route_table = rt.route_table_id}The type annotation is optional; you can also use the simpler form:
attributes { vpc_id = vpc.vpc_id}Importing Modules
Use import to load a module, then call it by name with arguments:
let network = import './modules/network'
network { cidr_block = '10.0.0.0/16' subnet_cidr = '10.0.1.0/24' az = 'ap-northeast-1a'}Accessing Module Outputs
When a module call is bound with let, its attributes values can be accessed with dot notation:
let network = import './modules/network'
let net = network { cidr_block = '10.0.0.0/16' subnet_cidr = '10.0.1.0/24' az = 'ap-northeast-1a'}
awscc.ec2.security_group { vpc_id = net.vpc_id}Directory Modules
A module can be either:
- A single file:
import "./modules/network.crn"(the.crnextension can be omitted) - A directory:
import "./modules/network"which loads./modules/network/main.crn
Directory modules are useful when a module needs helper files or becomes complex enough to warrant its own directory.
Module Resolution
Import paths are resolved relative to the file containing the import statement:
# From project/main.crn, imports project/modules/network/main.crnlet network = import './modules/network'
# Relative path from current filelet helper = import '../shared/helper'Nested Modules
Modules can import and use other modules:
let network = import '../network'
arguments { cidr_block : string subnet_cidr: string az : string}
let net = network { cidr_block = cidr_block subnet_cidr = subnet_cidr az = az}
let rt = awscc.ec2.route_table { vpc_id = net.vpc_id
tags = { Name = 'nested-module-test-rt' }}
attributes { vpc_id : awscc.ec2.vpc = net.vpc_id route_table_id: awscc.ec2.route_table = rt.route_table_id}Modules with For Expressions
Modules can be used inside for expressions to create multiple instances:
let network = import './modules/network'
let cidrs = { dev = '10.0.0.0/16' stg = '10.1.0.0/16'}
let networks = for name, cidr in cidrs { network { cidr_block = cidr subnet_cidr = cidr_subnet(cidr, 8, 1) az = 'ap-northeast-1a' }}This creates a separate set of network resources for each entry in the map. The name variable (dev, stg) is used to distinguish the resources in state.
Provider Configuration
Modules do not declare their own provider blocks. The provider configuration from the root .crn file is inherited by all modules. A module only needs to declare arguments, attributes, and resources.