February 15, 2017·4 min read·engineeringterraformconfigurationdata-sources

Terraform adds list slicing and more ways to reference existing infrastructure

Terraform 0.8.7 adds slice and new data sources. The useful distinction is between selecting part of an existing collection and taking ownership of the objects it describes.

An infrastructure configuration often needs only part of the information available to it. A cloud API can return the available zones in a region, while a particular deployment needs a smaller selection. A database can exist outside the configuration while its attributes remain inputs to resources inside it. Terraform 0.8.7, released on February 15, adds tools for both situations. Its release notes introduce the slice interpolation function and data sources for existing infrastructure.

The interesting connection is how these changes let configuration authors express dependencies without expanding ownership. Selecting elements from a list does not create those elements. Reading a database through a data source does not declare that Terraform should create that database. These distinctions make a general infrastructure language useful across boundaries between configurations.

I value Terraform because the same configuration language can describe relationships across different APIs. That abstraction needs ways to accept infrastructure managed elsewhere. Otherwise, reuse requires every module to assume responsibility for objects that its caller may already have.

Selecting a contiguous part of a list

The proposal merged as pull request 9729 defines slice(list, from, to). The starting index is inclusive and the ending index is exclusive. This means that selecting positions zero through two uses an end argument of three. The end argument describes the boundary after the last selected element.

That convention makes adjacent selections easy to describe. One selection can end at the same index where another starts without overlapping an element. It also distinguishes a range from an element count. Treating the last argument as a count produces the wrong selection whenever the starting index is nonzero.

The proposal gives an example that removes the final element by using length(var.list_of_strings) - 1 as the end boundary. Its meaning follows directly from the exclusive end. A list of four strings has indices zero through three, and an end boundary of three excludes the string at index three. The expression still needs an input list for which those boundaries make sense.

Slicing expresses a positional decision. It does not explain why a particular element belongs in a deployment. That responsibility remains with the list producer and the configuration author. A prefix of a list is useful when its order already carries the selection policy. When the policy depends on a named attribute, position alone does not state that policy.

Discovery needs an explicit selection policy

The new Google Compute zones data source returns the zones available in a region for a project. Its names attribute is a list. The optional region argument defaults to the region configured on the provider.

The documentation also provides a status filter that accepts UP or DOWN. With no filter, it includes zones in both states. This is a consequential default for a configuration that intends to launch resources immediately. Availability in the returned collection and suitability for a particular deployment are separate questions.

data "google_compute_zones" "available" {
  region = "us-central1"
  status = "UP"
}

output "available_zones" {
  value = "${data.google_compute_zones.available.names}"
}

This example assumes the Google provider has the required project and authentication configuration. The data block asks for information, and the output exposes the resulting list. It does not create zones or declare any instances. The interpolation syntax is the syntax used in the tagged documentation for this release.

The documentation demonstrates using the list length as a resource count and count.index to choose a zone for each instance group manager. That couples the number of managed objects to the discovery result. A deliberate fixed selection has different semantics. Discovery supplies possible inputs, while the configuration decides how much of that result determines infrastructure size.

Looking up a database without declaring its lifecycle

Terraform 0.8.7 also adds aws_db_instance as a data source. The tagged reference requires db_instance_identifier and lists attributes such as engine version, allocated storage and availability zone. It gives a direct lookup by database identifier.

data "aws_db_instance" "database" {
  db_instance_identifier = "my-test-database"
}

output "database_engine" {
  value = "${data.aws_db_instance.database.engine}"
}

The distinction between a data source and a resource is visible in the declaration. The data source describes a lookup of an existing database. It does not contain the inputs necessary to create a new database, and it should not be read as an ownership declaration for that database.

This supports a narrower module contract. A caller can identify the database, and a configuration can derive its dependent settings from the lookup. Copying the engine or availability zone into unrelated variables creates a second description that someone must maintain. A lookup establishes where that information comes from, although the configuration still depends on the named database being present and accessible.

I prefer this separation of ownership because it keeps module responsibilities understandable. A module that consumes a database should make that dependency clear without implying that deleting the module also deletes the database.

A broader provider release with a language consequence

The release adds the Arukas provider and several resource types, including a Vault policy resource and an AWS VPC peering connection accepter. Those additions extend the set of API operations available through Terraform. The list function and data sources address how configurations combine information across those operations.

The same release notes record fixes to dependency ordering around create-before-destroy resources and resources inside modules. Those fixes concern execution relationships, while data references and collection expressions concern how authors describe relationships. Both affect whether a configuration can state its intended dependencies accurately.

A useful module interface can therefore accept an existing object identifier, obtain the attributes it needs through a data source, and expose an explicitly chosen collection to its resources. Terraform 0.8.7 supplies more of the operations needed to write that interface in configuration. The remaining design decision is which facts the module discovers and which choices its caller must make explicitly.