Skip to main content

Overview

The primary goal of the Acornfile is to quickly and easily describe how to build, develop, and run containerized applications.

The syntax is very similar to JSON and YAML that you're probably already familiar with from other tools.

The resulting artifact defined by the Acornfile and produced during acorn build is a complete package of your application. It includes all the container images, secrets, data, nested Acorns, etc. in a single OCI image that can be distributed through a registry.

The primary building blocks

Objects

In the Acornfile file the primary building block is an object. The generic syntax for any object is:

key: {
// ... fields ...
}

They start with a name key and wrap a collection of fields and values in {}. A more Acorn specific example is:

containers: {
"my-app": {
// ...
}
}

In the above example, there is an object called containers, which contains another object called my-app. Keys do not need to be quoted, unless they contain a -.

For convenience, you can collapse objects which have only one field to a single : line and omit the braces. For example these:

containers: foo: image: "nginx"

containers: bar: build: {
context: "."
target: "static"
}

are equivalent to:

containers: {
foo: {
image: "nginx"
}
}

containers: {
bar: {
build: {
context: "."
target: "static"
}
}
}

Lists

The other main building block is a list.

containers: {
myapp: {
// ...
ports: [
"80/http",
"8080/http",
]
}
}

Lists are surrounded by []. Items are separated by a comma. The last item can include an optional trailing comma.

Fields

A field is a label and a value, we have seen multiple examples of fields in the previous examples. Here we will dive deeper.

Field names

In an Acornfile fields can be strings with [a-zA-Z0-9_] without being wrapped in double quotes. You can use -, /, and . if you use double quotes around the field name.

// Valid field names
aLongField: ""
"/a/file/path": ""
"my-application-container": ""

Assigning field values

Variables allow you to store values and later reference them elsewhere in the Acornfile. The syntax for defining a variable is shown below. Values can be a string, number, boolean, list, object, or null.

localData: {
myString: ""
myInteger: 5
myBool: true
myObject: {}
myList: []
}

Strings

Strings can be a single line or multiline. A single line string is surrounded by " quotes.

Multiline strings are enclosed in triple quotes """. The opening """ must be followed by a newline. The closing """ must also be on it's own line. The whitespace directly preceding the closing quotes must match the preceding whitespace on all other lines and is not included in the value. This allows you to indent the text to match current level without the indenting becoming part of the actual value.

singleLine: "Hi!"
multiLine: """
Hello
World!
"""
# multiLine is equivalent to "Hello \nWorld!"

Numbers

Numbers are integers by default unless they contain a decimal.

int: 4
float: 4.2

Boolean

Booleans are true or false.

Null

Null is null.

Comments

You can add comments to document your Acornfile. Comments start with // and continue for the rest of the line

// This is a comment
some: "value" // This is a comment about this line

Accessing fields

To reference a variable elsewhere in the file, you separate the key fields of the path by a ..

Given these variables:

localData: {
myVariable: ""
myInteger: 5
myBool: true
myObject: {
aKey: "value"
}
"my-app": {
// ...
}
}

// Can Be accessed like

v: localData.myVariable
i: localData.myInteger
b: localData.myBool
s: localData.myObject
s0: localData.myObject.aKey
s1: localData.myObject["aKey"]
a: localData."my-app"

Scopes

Fields that reference another field will look for a value starting at the nearest enclosing scope and working upwards until reaching the top-level.

port: 3307
containers: app: {
ports: localData.port // Evaluates to 3306
}
data: port // Evaluates to 3307
localData: {
port: 3306
exposedServicePort: port // Evaluates to 3306
}

In the above example, containers.app.ports would be 3306 along with localData.exposedServicePort.

Aliases

Because of scoping in the previous example, it would not be possible in the above example to set any value under localData to a value of port(3307). One way to address this is to use the let operator to alias the port variable.

let topLevelPort = port
port: 3307
containers: app: {
ports: localData.port // Evaluates to 3306
}
data: port // Evaluates to 3307
localData: {
port: 3306
exposedServicePort: topLevelPort // Evaluates to 3307
}

In the example the top level port variable is aliased to topLevelPort and assigned to localData.exposedServicePort.

String interpolation

Variables can be inserted into a string by wrapping their name with \(). For example:

args: {
userAdjective: "cool"
}

localData: {
config: {
key: "This is something \(args.userAdjective)"
}
}

# localData.config.key is "This is something cool"

Interpolation can also be used for field names:

localData: {
index: 3
}

containers: {
"my-app-\(localData.index)": {
// ...
}
}

# A container named "my-app-3" is being defined

Assigning a variable to another field

Assigning a variable to a field uses the variable accessor.

localData: {
myTokenLength: 64
}

secrets: {
"my-secret": {
type: "token"
params: {
length: localData.myTokenLength // length is now 64
}
}
}

Basic Operators

All the basic math and comparison operators you'd find in a typical programming language are supported:

OperatorSymbolExampleResult
Addition+1 + 12
Subtraction-4 - 13
Multiplication*4 * 28
Division/5 / 22.5
Greater than>2 > 1true
Greater than or equal>=2 >= 2true
Less than<1 < 1false
Less than or equal<=1 <= 1true
Equals==1 == 2false
Does not equal!=1 != 2true
Not!!truefalse
Or||true || falsetrue
And&&true && falsefalse

- can also be used to negate a value:

a: 42
b: -a // -42

Operations can be grouped with parenthesis and combined with && and ||:

a: 5
b: a/(1 + 1) // 2.5
c: (2+2)*4/8 // 2
d: -c * 10 // -20
e: b > c // true
f: e && b > 5 // false

String Operators

Strings can be concatenated, repeated, and compared:

OperatorSymbolExampleResult
Concatenate+"hello " + "world""hello world"
Repeat*"hi"*5"hihihihihi"
Greater than>"hi" > "bye"true
Greater than or equal>="foo" >= "bar"true
Less than<"foo" < "foofalse
Less than or equal<="foo" <= "foo"true
Equals=="foo" == "bar"false
Does not equal!="foo" != "bartrue
Matches regular expression=~"hi bob" =~ "^h"true
Does not match regular expression!~"hi bob" !~ "^h"false

Regular Expressions

Regular expression syntax is the one accepted by RE2 outlined here https://github.com/google/re2/wiki/Syntax. One exception is \C is not accepted.

Conditionals

If statements

Support for standard if statements is available in an Acornfile. If conditions evaluate to a boolean, and apply their body if the condition is true.

localData: {
scale: 1
}

if localData > 1 {
// ... Do something
}

if statements can be added at any level or nested within each other.

If-else expressions

value: 1

if value == 2 {
output: "value is 2"
} else if value == 3 {
output: "value is 3"
} else {
output: "value is not 2 or 3"
}

The above will output:

{
value: 1
output: "value is not 2 or 3"
}

The following example will publish either port 3000 or 80 depending on args.dev:

containers: {
app: {
ports: publish: if args.dev {"3000/http"} else {"80/http"}
}
}

For loops

The Acornfile syntax provides a for loop construct to iterate through objects and lists.

for i in std.range(0, 10) {
containers: {
"my-instance-\(i)": {
// ...
}
}
}

Object field comprehensions

localData:{
dataVols: {
dbData: "/var/lib/mysql"
backups: "/backups"
}
}

for k, v in localData.dataVols {
volumes: {
"\(k)": {}
}
containers: {
// ...
dirs: {
"\(v)": "volumes://\(k)"
}
// ...
}
}

List comprehensions

Acornfile

localData: {
list: ["one", "two", "three"]
}

localData: {
multiLineContent: std.join([for i in localData.list {"Item: \(i)"}], "\n")
}

Renders to:

localData: {
list: ["one", "two", "three"]
multiLineContent: """
Item: one
Item: two
Item: three
"""
}