Chapter 3: The Core Workflow
Topic 17

Data Sources

ConceptRead-only

A data source reads information about something Terraform does not manage — an existing image, the current project, your own resolved credentials — and makes it available to your config without taking ownership of it. It is a lookup, not a declaration: the read happens, the value is yours to use, and nothing in the cloud is created, changed, or deleted.

On GCP this is how you pin event-processor's base image to a real google_compute_image, discover the project number behind hatch-pipeline-dev, or list the zones in us-central1 to spread instances across. The rule is one word: read, never manage. Everything you intend to create is a resource; everything you only need to reference is a data source.

Read vs Manage

A resource block creates, updates, and destroys an object — it owns it across its whole lifecycle. A data block only reads an existing object and never owns it. The practical tell is the plan: a data source never appears as a + create or - destroy action, only as a <= read. Delete a data block from your config and nothing in the cloud changes, because Terraform never owned the thing it was reading.

A data source reads an existing image; it is never created
data "google_compute_image" "cos" {
  family  = "cos-stable"
  project = "cos-cloud"
}

This resolves the Container-Optimized OS stable family in Google's public cos-cloud project to a concrete image, which you then reference as data.google_compute_image.cos.self_link. You did not create the image and you cannot destroy it — you are only looking it up.

Read versus own
data source
A read-only lookup of something that already exists, owned elsewhere; it shows as a <= read and never a create or destroy. Deleting the block changes nothing in the cloud.
resource
Desired state Terraform creates, updates, and owns across its whole lifecycle. Deleting the block destroys the object.

The Workhorse GCP Data Sources

Four data sources cover most day-to-day needs. google_compute_image resolves an image family to a concrete image ID. google_project returns the number and labels of a project — useful because many IAM and resource references need the numeric project id, not the human-readable one. google_client_config exposes the caller's resolved access token and active project. And google_compute_zones lists the available zones in a region so you can spread instances without hardcoding zone names.

google_client_config as the Auth Smoke Test

Before you debug anything deeper, reach for google_client_config. Reading data "google_client_config" "current" and outputting its project is the trivial plan that proves Application Default Credentials are resolving and that Terraform knows which project it is pointed at. If that one-line plan succeeds, your auth works; if it fails, you have isolated the problem to credentials before touching a single real resource.

The one-line auth check
data "google_client_config" "current" {}

output "active_project" {
  value = data.google_client_config.current.project
}

The Plan-Time Read Gotcha

Data sources resolve during plan when their arguments are already known. A google_compute_image with a literal family and project reads immediately, and its result is concrete in the plan. But if a data source's arguments depend on a value that is (known after apply) from a not-yet-created resource, its read cannot happen at plan time — it is deferred to apply.

That deferral cascades. Once the read is pushed to apply, every attribute downstream of it also becomes (known after apply), which can make a small change render as a much larger-looking plan full of unknowns. The plan is not wrong; it is honestly reflecting that a chain of values cannot be computed until the upstream resource exists.

depends_on on a Data Source

Occasionally a data source must read something a managed resource produces — a just-enabled API, a freshly created project. By default the plan-time read would run before that resource exists and return stale or empty results. Adding depends_on to the data source forces its read to wait until the named resource is in place.

The cost is that depends_on defers the read to apply time, because Terraform can no longer prove the dependency is satisfied at plan time. That is the correct trade — a slightly larger plan in exchange for a read that is actually valid. Reach for it only when the ordering is genuine, not as a reflex.

data source vs resource

A resource is desired state Terraform creates and owns; destroying the block destroys the object. Use it only for what you intend to manage end to end.

A data source is a read-only lookup of something that already exists, owned by someone or something else. Use it to reference an image, project, or network you did not create. The test is ownership: if you would be horrified to see it destroyed, it should not be a resource in this config.

Common Mistakes
  • Using a data "google_compute_image" that filters to a family with no matching image (wrong project or deprecated family) — the read fails at plan time with "no image found" and blocks the whole run.
  • Expecting a data source to reflect a resource created in the same apply without depends_on — it reads stale or empty because the plan-time read ran before the resource existed.
  • Adding depends_on to a data source and being surprised the plan now shows (known after apply) everywhere downstream — the dependency correctly defers the read to apply time, which is the cost of the ordering.
  • Reaching for a data source to look up a resource your own config already manages — reference the resource directly instead, since the data source adds a redundant read and a possible ordering trap.
  • Pinning event-processor to data.google_compute_image.cos.self_link of an image family and being surprised when a plan shows a change after Google publishes a new family image — a moving family means a moving data result.
  • Treating a data source as a way to "import" an object into management — it never takes ownership, so the object stays unmanaged no matter how many attributes you read from it.
Best Practices
  • Use data "google_client_config" as a one-line plan to verify ADC and the active project before deeper debugging.
  • Reference managed resources directly and reserve data sources for objects outside this configuration's ownership.
  • Pin data-source filters tightly — exact project, image family, region — so the read is deterministic and cannot resolve to the wrong object.
  • Add depends_on to a data source only when it genuinely must read after a managed resource, and accept the apply-time read that results.
  • Pin to a specific image rather than a moving family on anything where an unexpected plan diff after Google republishes the family would be disruptive.
Comparable tools Pulumi get and lookup functions are the analog Config Connector references existing resources via external refs gcloud ... describe is the imperative read, with no integration into a graph

Knowledge Check

What action symbol does a data source produce in a plan?

  • A read (<=) — never a create or destroy, because Terraform does not own what it reads
  • A create (+) on the first run and an update (~) on every later run thereafter
  • A replace (-/+) whenever the underlying object it reads changes upstream
  • No symbol at all, since data sources stay invisible to the plan and never show up in the diff

A data source's arguments reference a value that is (known after apply) from a resource being created in the same run. When does the read happen?

  • At apply time, after the upstream resource exists — and everything downstream becomes (known after apply)
  • At plan time, returning whatever value the upstream resource will eventually be assigned
  • It never reads at all, because a data source cannot depend on a resource in the same run
  • Twice — once at plan time and again at apply, keeping the second result and quietly discarding the first one

What is google_client_config most useful for?

  • A one-line plan that confirms ADC resolves and reveals the active project before deeper debugging
  • Creating a dedicated service account for Terraform to authenticate as on each run
  • Rotating the short-lived access token and the credentials that Terraform authenticates with on every single apply
  • Listing every project across the org that the calling identity has access to

When should you use a data source instead of referencing a resource directly?

  • When the object is owned outside this configuration and you only need to read it
  • Whenever you want to avoid creating a dependency edge between the two resources in the graph
  • When the resource has too many attributes to manage inside one resource block
  • When you need the object to be destroyed along with the rest of the config

You got correct