Working with template directories
Atlas supports working with dynamic template-based directories, where their content is computed based on the data
variables injected at runtime. These directories adopt the Go-templates format, the very same format used by popular
CLIs such as kubectl, docker or helm.
To create a template directory, you first need to create an Atlas configuration file (atlas.hcl) and define the
template_dir data source there:
data "template_dir" "migrations" {
path = "migrations"
vars = {}
}
env "dev" {
migration {
dir = data.template_dir.migrations.url
}
}
The path defines a path to a local directory, and vars defines a map of variables that will be used to interpolate
the templates in the directory.
Basic Example
We start our guide with a simple MySQL-based example where migration files are manually written and the auto-increment
initial value is configuration based. Let's run atlas migrate new with the --edit flag and paste the following statement:
-- Create "users" table.
CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role` enum('user', 'admin') NOT NULL,
`data` json,
PRIMARY KEY (`id`)
) AUTO_INCREMENT={{ .users_initial_id }};
After creating our first migration file, the users_initial_id variable should be defined in atlas.hcl. Otherwise,
Atlas will fail to interpolate the template.
data "template_dir" "migrations" {
path = "migrations"
vars = {
users_initial_id = 1000
}
}
env "dev" {
dev = "docker://mysql/8/dev"
migration {
dir = data.template_dir.migrations.url
}
}
In order to test our migration directory, we can run atlas migrate apply on a temporary MySQL container that Atlas
will spin up and tear down automatically for us:
atlas migrate apply \
--env dev \
--url docker://mysql/8/dev
Example output
Migrating to version 20230719093802 (1 migrations in total):
-- migrating version 20230719093802
-> CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role` enum('user', 'admin') NOT NULL,
`data` json,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1000;
-- ok (30.953207ms)
-------------------------
-- 74.773738ms
-- 1 migrations
-- 1 sql statements
Inject Data Variables From Command Line
Variables are not always static, and there are times when we need to inject them from the command line. The Atlas
configuration file supports this injection using the --var flag. Let's modify our atlas.hcl file such that the
value of the users_initial_id variable isn't statically defined and must be provided by the user executing the CLI:
variable "users_initial_id" {
type = number
}
data "template_dir" "migrations" {
path = "migrations"
vars = {
users_initial_id = var.users_initial_id
}
}
env "dev" {
dev = "docker://mysql/8/dev"
migration {
dir = data.template_dir.migrations.url
}
}
Trying to execute atlas migrate apply without providing the users_initial_id variable, will result in an error:
Error: missing value for required variable "users_initial_id"
Let's run it the right way and provide the variable from the command line:
atlas migrate apply \
--env dev \
--url docker://mysql/8/dev \
--var users_initial_id=1000
Example output
Migrating to version 20230719093802 (1 migrations in total):
-- migrating version 20230719093802
-> CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role` enum('user', 'admin') NOT NULL,
`data` json,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1000;
-- ok (30.953207ms)
-------------------------
-- 74.773738ms
-- 1 migrations
-- 1 sql statements
Read Data Variables From File
Let's add a bit more complexity to our example by inserting seed data to the users table. But, to keep our
configuration file tidy, we'll keep the seed data in a different file (seed_data.json) and read it from there.
First, we'll create a new migration file by running atlas migrate new seed_users --edit and paste the following
statement:
{{ range $line := .seed_users }}
INSERT INTO `users` (`role`, `data`) VALUES ('user', '{{ $line }}');
{{ end }}
The file above expects a data variable named seed_users of type []string. It then loops over this variable and
INSERTs a record into the users table for each JSON line.
For the sake of this example, let's define an example seed_users.json file and update the atlas.hcl file to inject
the data variable from its content:
{"name": "Ariel"}
{"name": "Rotem"}
variable "users_initial_id" {
type = number
}
locals {
# The path is relative to the `atlas.hcl` file.
seed_users = split("\n", file("seed_users.json"))
}
data "template_dir" "migrations" {
path = "migrations"
vars = {
seed_users = local.seed_users
users_initial_id = var.users_initial_id
}
}
env "dev" {
dev = "docker://mysql/8/dev"
migration {
dir = data.template_dir.migrations.url
}
}
To check that our data interpolation works as expected, let's run atlas migrate apply on a temporary MySQL container
that Atlas will spin up and tear down automatically for us:
atlas migrate apply \
--env dev \
--url docker://mysql/8/dev \
--var users_initial_id=1000
Example output
Migrating to version 20230719102332 (2 migrations in total):
-- migrating version 20230719093802
-> CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role` enum('user', 'admin') NOT NULL,
`data` json,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1000;
-- ok (38.380244ms)
-- migrating version 20230719102332
-> INSERT INTO `users` (`role`, `data`) VALUES ('user', '{"name": "Ariel"}');
-> INSERT INTO `users` (`role`, `data`) VALUES ('user', '{"name": "Rotem"}');
-- ok (13.313962ms)
-------------------------
-- 95.387439ms
-- 2 migrations
-- 3 sql statements