Skip to main content

Terraform Loop Iteration

Terraform can manage multiple similar resourcesExternal LinkThis link leads to an external website and will open in a new tab by iterating through defined data structures. You reuse your Terraform code and keep similar resources managed consistently.

The Terraform for_each Meta-ArgumentExternal LinkThis link leads to an external website and will open in a new tab is the mechanism for this. The example below uses it to manage multiple similar Files.com resources.

Repeated Client Resources

Suppose each client needs a folder under /Exchange/Clients, with Orders and Reports subfolders. Each client has an upload Group, while an internal collection process uses a download Group. Internal teams that contribute or retrieve files from a particular subfolder have separate Groups for those permissions.

The folder and Group arrangement is the same for every client. for_each creates an instance of each resource for every client name, and dependencies ensure folders and Groups exist before their permissions are assigned. Adding a client to the input set extends the same arrangement rather than requiring another copy of every resource definition.

This example demonstrates regular-user and Group resources. For external organizations, review the Partner User model before choosing account resources: Partner Users share Partner permissions and cannot join Groups.

for_each Usage Example

Use the for_each Meta-Argument to iterate through a variable when managing resources:

# Variable Containing List of Clients

variable "clients" {
  type = set(string)
  default = [
    "Client A",
    "Client B",
    "Client C"
  ]
}

variable "client_passwords" {
  type        = map(string)
  description = "A unique initial password for each client, keyed by client name."
  sensitive   = true
}

# Create User Accounts for each Client by using a loop

resource "files_user" "user_account" {
  for_each = var.clients
  authentication_method        = "password"
  name                         = each.value
  company                      = each.value
  password                     = var.client_passwords[each.key]
  self_managed                 = true
  username                     = replace(lower(each.value), " ", "_")
}

The code above defines a variable containing a list of client names. It uses the for_each meta-argument to iterate through the list and create corresponding user accounts on your Files.com site. The value for each client in the variable becomes the name, company, and username of the user account. For the username, the value is converted to lower case and any spaces are replaced with an underscore.

The client name is also the resource's identity key in this example. Removing a name from the set plans deletion of that client's resources; changing the name plans deletion under the old key and creation under the new key. For a production roster, use stable client identifiers as keys and keep display names as separate values. Preserve existing resource associations through a resource move when changing the key structure.

Supply a unique password for every client through a protected variable source. Keep the password map out of committed files and protect Terraform stateExternal LinkThis link leads to an external website and will open in a new tab, which can retain these values.

Client Resource Definitions

The implementation uses for_each loops to perform these steps for each client:

Create Client Folder

Client folders go inside the /Exchange/Clients/ folder of the site. A Terraform best practice is to have Terraform manage all dependencies, including the existence of parent folders in the hierarchy. This avoids issues later when Terraform modifies or removes existing resources.

Configure Terraform to manage both the /Exchange/ and the /Exchange/Clients/ folders, and make the latter depend on the former. The following code does that:

# Create top level folder for Exchange

resource "files_folder" "exchange_folder" {
  path = "Exchange"
}

# Create Clients folder within the Exchange folder

resource "files_folder" "exchange_clients_folder" {
  path = "${files_folder.exchange_folder.path}/Clients"
  depends_on = [ files_folder.exchange_folder ]
}

A for_each loop then creates and manages the client folders:

# Create home folders for each client

resource "files_folder" "client_folder" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}"
  depends_on = [ files_folder.exchange_clients_folder ]
}

Create Groups for the Client Home Folder

Use similar for_each loops to create and manage the groups that are assigned upload (write only) and download (read only) access permissions.

The following code creates the 2 groups:

# Create group to upload to client home folder

resource "files_group" "client_upload_group" {
  for_each = var.clients
  notes              = "Upload Group for ${each.value}."
  ftp_permission     = true
  sftp_permission    = true
  dav_permission     = true
  restapi_permission = true
  name               = "${each.value} Upload Group"
}

# Create group to retrieve (download only) from client home folder

resource "files_group" "client_download_group" {
  for_each = var.clients
  notes              = "Download Group for ${each.value}."
  ftp_permission     = true
  sftp_permission    = true
  dav_permission     = true
  restapi_permission = true
  name               = "${each.value} Download Group"
}

Assign Access Permissions to the Home Folder Groups

Assign access permissions to the 2 home folder groups with the following code:

# Assign upload access permission to upload group

resource "files_permission" "client_group_upload_permission" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}"
  group_name = "${each.value} Upload Group"
  permission = "writeonly"
  depends_on = [files_group.client_upload_group, files_folder.client_folder]
}

# Assign download access permission to retrieve group

resource "files_permission" "client_group_download_permission" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}"
  group_name = "${each.value} Download Group"
  permission = "readonly"
  depends_on = [files_group.client_download_group, files_folder.client_folder]
}

The code above assigns access permissions to the previously created groups. It specifies dependencies so that the permissions are only assigned after the groups and their corresponding home folders are successfully created.

Create Client Subfolders

For each subfolder within the client home folder, repeat the following code, adjusting the folder name as needed:

# Create Orders subfolder for each client

resource "files_folder" "client_orders_folder" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}/Orders"
  depends_on = [ files_folder.client_folder ]
}

The code above creates an Orders subfolder within the client home folder. It specifies a dependency so that the subfolder is only created after the home folder has been created.

Duplicate this step for each required subfolder. For the Reports subfolder, use reports in Terraform resource labels and Reports in folder paths and group names.

Create Groups for the Client Subfolders

As before, create 2 groups that are assigned upload (write only) and download (read only) access permissions to the subfolders:

# Create group to upload to client orders folder

resource "files_group" "client_orders_upload_group" {
  for_each = var.clients
  notes              = "Orders Upload Group for ${each.value}."
  ftp_permission     = true
  sftp_permission    = true
  dav_permission     = true
  restapi_permission = true
  name               = "${each.value} Orders Upload Group"
}

# Create group to download from client orders folder

resource "files_group" "client_orders_download_group" {
  for_each = var.clients
  notes              = "Orders Download Group for ${each.value}."
  ftp_permission     = true
  sftp_permission    = true
  dav_permission     = true
  restapi_permission = true
  name               = "${each.value} Orders Download Group"
}

Duplicate this step for each required subfolder. For the Reports subfolder, use reports in Terraform resource labels and Reports in folder paths and group names.

Assign Access Permissions to the Subfolder Groups

Assign access permissions to the 2 subfolder groups with the following code:

# Assign upload access permission to orders upload group

resource "files_permission" "client_group_orders_upload_permission" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}/Orders"
  group_name = "${each.value} Orders Upload Group"
  permission = "writeonly"
  depends_on = [files_group.client_orders_upload_group, files_folder.client_orders_folder]
}

# Assign download access permission to orders download group

resource "files_permission" "client_group_orders_download_permission" {
  for_each = var.clients
  path = "${files_folder.exchange_clients_folder.path}/${each.value}/Orders"
  group_name = "${each.value} Orders Download Group"
  permission = "readonly"
  depends_on = [files_group.client_orders_download_group, files_folder.client_orders_folder]
}

The code above assigns access permissions to the groups and specifies dependencies so that the permissions are only assigned after the groups and their corresponding subfolders are successfully created.

Duplicate this step for each required subfolder. For the Reports subfolder, use reports in Terraform resource labels and Reports in folder paths and group names.

Assign Group Membership

Add the following attribute to the files_user.user_account resource above so each client joins its upload group:

group_ids = tostring(files_group.client_upload_group[each.key].id)

Assign the appropriate internal system and support accounts to the download and subfolder groups. Creating groups and permissions does not automatically populate those groups.

Execution

When the code above runs, Terraform creates all these resources and manages the dependencies between them.

Each time you add a client to the variable, Terraform manages 16 corresponding resources for that client: 1 user account, 3 folders, 6 groups, and 6 access permissions. Add the corresponding password entry and configure internal group membership before granting access.

An onboarding system can maintain the client input set, credentials, and group membership. Review the Terraform plan before applying it, including any removals when a client leaves the set.