Skip to content

Syntax

Carina configuration files use the .crn extension. A file consists of a sequence of top-level statements that declare infrastructure resources and their relationships.

File Structure

A .crn file contains zero or more top-level statements in any order:

# Provider configuration
provider awscc {
region = awscc.Region.ap_northeast_1
}
# Backend configuration (optional)
backend s3 {
bucket = 'my-state-bucket'
key = 'infra/carina.state.json'
region = 'ap-northeast-1'
}
# Resource declarations
let vpc = awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = 'main'
}
}
awscc.ec2.internet_gateway {
tags = {
Name = 'main'
}
}

Statements

The following statements are valid at the top level of a .crn file:

StatementPurpose
providerConfigure a cloud provider
backendConfigure remote state storage
letBind a name to a resource or value
Anonymous resourceDeclare a resource without a binding
import (state)Import an existing cloud resource into state
removedRemove a resource from state without deleting it
movedRename a resource in state
forIterate to create multiple resources
ifConditionally create resources
fnDefine a reusable function
requireAssert a condition with an error message
argumentsDeclare module input parameters
attributesDeclare module output values

Provider Block

Every .crn file that declares resources must configure at least one provider. The provider block specifies which cloud provider to use and its settings.

provider awscc {
region = awscc.Region.ap_northeast_1
}

Multiple providers can be configured in the same file:

provider awscc {
region = awscc.Region.ap_northeast_1
}
provider aws {
region = aws.Region.ap_northeast_1
}

Resource Blocks

Resources represent cloud infrastructure objects. A resource block specifies the provider, service, and resource type using dot notation, followed by a block of attributes.

Anonymous Resources

When you do not need to reference a resource elsewhere, declare it without a binding:

awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = 'main'
}
}

The resource is identified in state by its name attribute or a hash of its attributes.

Named Resources (Let Bindings)

Use let to bind a name to a resource so you can reference its attributes:

let vpc = awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = 'main'
}
}
let subnet = awscc.ec2.subnet {
vpc_id = vpc.vpc_id
cidr_block = '10.0.1.0/24'
availability_zone = 'ap-northeast-1a'
}

The let keyword is also used to bind non-resource values:

let cidr = '10.0.0.0/16'
let environments = ['dev', 'stg', 'prod']
let config = {
dev = '10.0.0.0/16'
stg = '10.1.0.0/16'
}

Discard Pattern

Use let _ = when you want to evaluate an expression but do not need to reference the result:

let _ = awscc.ec2.vpc_gateway_attachment {
vpc_id = vpc.vpc_id
internet_gateway_id = igw.internet_gateway_id
}

Data Sources (Read Resources)

Use the read keyword to query existing cloud resources without managing them:

let identity = read aws.sts.caller_identity {}

The returned value can be referenced like any other bound resource:

let identity = read aws.sts.caller_identity {}
awscc.ec2.ipam_pool {
source_resource = {
resource_owner = identity.account_id
}
}

Attributes

Attributes are key-value pairs inside a resource block:

awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
enable_dns_support = true
enable_dns_hostnames = true
}

Nested Blocks

Some resources accept nested blocks for repeated or structured configuration:

awscc.ec2.ipam {
tier = advanced
operating_region {
region_name = 'ap-northeast-1'
}
}

Local Bindings Inside Blocks

Use let inside a resource block to create block-scoped variables. These are evaluated during parsing but are not sent to the provider:

awscc.ec2.vpc {
let base_cidr = '10.0.0.0/16'
cidr_block = base_cidr
tags = {
Name = "vpc-${base_cidr}"
}
}

Backend Block

The backend block configures where Carina stores state:

backend s3 {
bucket = 'my-state-bucket'
key = 'infra/carina.state.json'
region = 'ap-northeast-1'
}

When no backend is configured, state is stored locally in carina.state.json.

State Manipulation

Import

Import an existing cloud resource into Carina’s state:

import {
to = awscc.ec2.vpc 'imported_vpc'
id = 'vpc-0123456789abcdef0'
}
awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = 'imported'
}
}

Moved

Rename a resource in state without destroying and recreating it:

moved {
from = awscc.ec2.vpc 'old_name'
to = awscc.ec2.vpc 'new_name'
}

Removed

Remove a resource from Carina’s state without deleting the actual cloud resource:

removed {
from = awscc.ec2.vpc 'old_vpc'
}

Remote State

Reference resources managed by another Carina project:

let network = remote_state {
path = 'network.state.json'
}
awscc.ec2.security_group {
vpc_id = network.vpc.vpc_id
}

Remote state with an S3 backend:

let network = remote_state 's3' {
bucket = 'my-state-bucket'
key = 'network/carina.state.json'
region = 'ap-northeast-1'
}

Comments

Carina supports three comment styles:

# Hash-style line comment
// Slash-style line comment
/* Block comment
can span multiple lines */
/* Block comments /* can be nested */ like this */

All comments are ignored by the parser.

User-Defined Functions

Define reusable functions with fn:

fn tag_name(env: string, service: string): string {
join('-', [env, service, 'vpc'])
}
awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
tags = {
Name = tag_name('prod', 'web')
}
}

See Expressions for details on function definitions.

Require Statement

Assert conditions that must be true, with a custom error message if they fail:

require length(subnets) > 0, 'At least one subnet must be specified'
require cidr_block != '', 'CIDR block cannot be empty'

The condition is a validate expression that supports comparison operators (==, !=, >, <, >=, <=) and logical operators (&&, ||, !).