Skip to content

Writing Resources

This guide walks you through defining infrastructure resources in Carina’s .crn files. You will learn the two ways to declare resources, how to set attributes, reference other resources, and use nested blocks.

Provider configuration

Every .crn file that declares resources needs a provider block to specify which cloud provider and region to use:

provider awscc {
region = awscc.Region.ap_northeast_1
}

Anonymous resources

The simplest way to define a resource is an anonymous resource. Use this when no other resource needs to reference it:

awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
enable_dns_support = true
enable_dns_hostnames = true
instance_tenancy = default
tags = {
Environment = 'production'
}
}

The resource type follows the pattern <provider>.<service>.<resource_type>. Carina derives the resource’s identity from its name tag or other identifying attributes.

Named resources with let

When another resource needs to reference attributes from a resource, use a let binding:

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

The let binding gives the resource a name (vpc) so you can reference its attributes (like vpc.vpc_id) from other resources. Carina automatically determines the dependency order — the subnet will be created after the VPC.

Use anonymous resources when the binding is unused. Unnecessary let bindings add noise.

Attribute types

Resource attributes support several value types:

awscc.ec2.vpc {
# String
cidr_block = '10.0.0.0/16'
# Boolean
enable_dns_support = true
# Integer
# (used in some resource attributes)
# Namespaced enum identifier
instance_tenancy = default
# Map
tags = {
Name = 'my-vpc'
Environment = 'production'
}
}

String interpolation

Strings support interpolation with ${}:

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

Nested blocks

Some resources have nested configuration blocks. Repeat the block name to add multiple entries:

let vpc = awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
}
awscc.ec2.security_group {
vpc_id = vpc.vpc_id
group_description = 'Web server security group'
security_group_ingress {
ip_protocol = 'tcp'
from_port = 80
to_port = 80
cidr_ip = '0.0.0.0/0'
description = 'Allow HTTP'
}
security_group_ingress {
ip_protocol = 'tcp'
from_port = 443
to_port = 443
cidr_ip = '0.0.0.0/0'
description = 'Allow HTTPS'
}
tags = {
Name = 'web-sg'
}
}

Local let bindings inside blocks

You can define local variables inside a resource block with let. These are scoped to the block and are not sent to the provider:

awscc.ec2.vpc {
let env = 'production'
let name = "local-let-${env}"
cidr_block = '10.0.0.0/16'
tags = {
Name = name
Env = upper(env)
}
}

Data sources with read

To look up an existing resource without managing it, use the read keyword:

let caller = read aws.sts.caller_identity {}

The read expression fetches resource data from the provider at plan/apply time. You can then reference its attributes just like a managed resource.

Comments

Carina supports line comments with // or #, and block comments with /* ... */:

# This is a line comment
// This is also a line comment
/* This is a
block comment */
awscc.ec2.vpc {
cidr_block = '10.0.0.0/16' # inline comment
}

Putting it together

Here is a complete example that creates a VPC with a public subnet and a route table:

provider awscc {
region = awscc.Region.ap_northeast_1
}
let vpc = awscc.ec2.vpc {
cidr_block = '10.0.0.0/16'
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = 'my-vpc'
}
}
awscc.ec2.subnet {
vpc_id = vpc.vpc_id
cidr_block = '10.0.1.0/24'
availability_zone = 'ap-northeast-1a'
map_public_ip_on_launch = true
tags = {
Name = 'public-subnet'
}
}
awscc.ec2.route_table {
vpc_id = vpc.vpc_id
tags = {
Name = 'public-rt'
}
}

Run carina plan main.crn to preview the changes, and carina apply main.crn to create the resources.