WintelGuy.com

Terraform Associate Exam Cram - Part 3

Understand Terraform basics

This is the Part 3 of the Terraform Associate Exam Cram. It covers the following Terraform Associate Certification exam objectives:

3a. Install and Version Terraform Providers

Providers

Terraform relies on plugins called "providers" to interact with remote systems (AWS, Azure, GCP, Kubernetes, etc.). Terraform configurations must declare which providers they require, so that Terraform can install and use them.

Provider requirements are declared in a required_providers block nested inside the top-level terraform block. A provider requirement consists of a local name, a source location, and a version constraint:

terraform { required_version = "~> 1.3.0" # Terraform version constraints required_providers { aws = { # Provider's local name source = "hashicorp/aws" # Provider's source address version = "~> 5.0" # Provider's version constraints } } # ... }
  • Local Name: A unique, module-specific provider name. Configurations always refer to providers by these local names. While any name can be chosen, it's best to use the provider's preferred local name (e.g., aws for hashicorp/aws). The preferred name is used a prefix for all of provider's resource types (aws_instance, aws_security_group, etc.). Using the preferred name eliminates the need for the provider meta-argument for most resources.
  • Source: The global source address for the provider, specifies the primary download location. The provider's fully-qualified address consist of three parts delimited by slashes (/): [<HOSTNAME>/]<NAMESPACE>/<TYPE>, where:
    • Hostname (optional): The hostname of the Terraform registry that distributes the provider (defaults to registry.terraform.io)
    • Namespace: An organizational namespace within the specified registry (e.g., hashicorp)
    • Type: A short name for the platform or system the provider manages (e.g., aws)
    If the source argument is omitted, Terraform uses an implied source address of registry.terraform.io/hashicorp/<LOCAL_NAME>.
  • Version : Contains one or more version constraint conditions separated by commas: version = "<operator> <version>, ..."
    • = (or no operator): Exact version number. Cannot be combined with other conditions. For example: = 3.5.0
    • !=: Excludes an exact version number, e.g., != 3.5.0
    • >, >=, <, <=: Compares to a specified version, for example: >= 3.0
    • >= 3.0, <= 3.5: Defines version range
    • ~>: Allows only the right-most version component to increment, e.g., ~> 3.5 means >= 3.5.0 and < 4.0
    • A pre-release version is a version number that contains a suffix preceded by a dash 1.2.0-beta. Terraform does not match pre-release versions on >, >=, <, <=, or ~> operators
    If the version argument is omitted, Terraform installs the newest version by default.

Each module must declare its own provider requirements in required_providers block.

Terraform installs all of the providers needed for a configuration when running terraform init. Terraform Cloud, CLI, and Enterprise will all take into account both the version constraints in the configuration and the version selections recorded in the dependency lock file (if present).

Re-run terraform init after any changes to providers configurations.

Dependency Lock File

The dependency lock file (.terraform.lock.hcl) contains information about installed providers and insures that terraform init always installs the same provider versions for a given configuration.

If a particular provider has no existing lock file records, Terraform will select the newest available version that matches the specified version constraint, and then update the lock file to include the selected version number and the corresponding package checksums.

If a particular provider already has a selection recorded in the lock file, Terraform will always re-select that version for installation, even if a newer version is available. This behavior can be overridden by using the terraform init -upgrade command. In this case Terraform will disregard the existing selections and once again select the newest available version matching the version constraint.

If Terraform configuration is stored in a source control repository, commit the .terraform.lock.hcl file along with the configuration files.

Back to Top

3b. Describe Plugin-Based Architecture

Terraform is logically split into two main parts: Terraform Core and Terraform Plugins. Terraform Core discovers and loads Terraform Plugins and uses remote procedure calls (RPC) to communicate with them. Terraform Plugins provide integration with specific services, such as AWS, or provisioners, such as Bash, etc.

  • Terraform Core (executable binary):
    • Handles CLI commands (init, plan, apply, etc.)
    • Reads and interpolates configuration files and modules
    • Performs resource state management
    • Constructs the Resource Graph (dependency graph)
    • Loads plugins/providers
    • Performs orchestration and plan execution
    • Communicates with plugins over RPC
  • Provider Plugins
    • Extend Terraform functionality by adding additional resource types and/or data sources that Terraform can manage
    • Released separately from Terraform itself and have their own version numbers
    • Published by Terraform ("official providers"), cloud & platform vendors ("partners"), or community members, etc.
    • Dynamically downloaded from a Terraform registry, or loaded from a local mirror or cache during the unit process
    • Handle interactions (API calls, authentication, etc.) with infra/cloud services
    • Responsible for creating, updating, and deleting infrastructure resources
  • Provisioner Plugins
    • Execute commands or scripts, interact with local/remote systems, etc. (local-exec, remote-exec, file, etc.)

When terraform init is run, Terraform reads configuration files in the working directory to determine which plugins are necessary, searches for installed plugins in several locations, decides which plugin versions to use, downloads plugins (if required), and writes a lock file to ensure Terraform will use the same plugin versions in this directory until terraform init runs again.

Back to Top

3c. Write Terraform Configuration Using Multiple Providers

The provider block is used to define provider's configuration:

provider "<LOCAL_NAME>" { # Provider's local name <PROVIDER_ARGUMENTS> # Provider-specific arguments alias = "<ALIAS_NAME>" # Unique identifier for a specific provider configuration version = "<VERSION_CONSTRAINT>" # Version constraint (deprecated) }
  • <LOCAL_NAME> is the local name of the provider to configure. This provider should already be included in a required_providers block.
  • alias meta-argument is used to define different configuration for the same provider.
  • version meta-argument specifies a version constraint for a provider, and works the same way as the version argument in a required_providers block.

The alias and version arguments are optional, only one can be used in the same provider block.

If multiple providers with the same <LOCAL_NAME> (e.g., multiple aws providers) are defined in one configuration:

  • alias argument distinguishes multiple provider configurations
  • provider without alias argument is considered the default provider
  • if every provider block uses an alias, Terraform creates an implied empty default provider configuration
  • resources use default provider if not explicitly set with the provider argument

Providers can be referenced in the provider argument of the following blocks:

  • resource blocks
  • data blocks
  • module blocks

The provider argument instructs Terraform to use an alternate provider configuration to provision the resource. If a resource doesn't specify which provider configuration to use, Terraform interprets the first word of the resource type as a local provider name (aws for aws_instance, etc.).

Use the <LOCAL_NAME>.<ALIAS_NAME> format to reference a provider with a defined alias block.

Example:

provider "aws" { # Default provider - aws region = "us-east-1" } provider "aws" { # Aliased provider - aws.west alias = "west" region = "us-west-2" } resource "aws_instance" "east_vm" { # Uses default provider ami = "ami-123456" instance_type = "t2.micro" } resource "aws_instance" "west_vm" { # Uses aliased provider provider = aws.west ami = "ami-654321" instance_type = "t2.micro" }

Provider Selection Logic

Resource Blocks:

  • With provider argument: Terraform explicitly uses the referenced provider configuration (e.g., provider = aws.beta uses the provider with local name aws and alias = "beta").
  • With no provider argument: Terraform infers the provider local name from the resource type prefix (the part before the underscore). For example: aws_instance uses the provider with the preferred local name aws. Terraform then searches for an unaliased (default) provider configuration with the matching local name (e.g., provider "aws" { ... }).

Provider Configuration Blocks:

  • Terraform maps each required provider (identified by its local name) to either an aliased or unaliased (default) provider configuration block (e.g., provider "aws" { ... } for aws provider).
  • A default (unaliased) provider configuration is a provider block without an alias.
  • If no unaliased provider block is defined, Terraform creates an implied empty default provider configuration (e.g., provider "aws" { }).

required_providers Blocks:

  • Each provider local name used in a configuration should have a corresponding entry (sub-block) in the required_providers block within the root terraform block.
  • The provider's sub-block (within the required_providers block) defines its source and version.
  • If a provider is not explicitly declared in required_providers, Terraform automatically infers the source address (e.g., hashicorp/aws) from the resource type prefix and downloads the latest version from the Terraform Registry.

In summary, Terraform maps:

  • The provider argument or resource type prefix to the provider's local name and configuration.
  • The provider's local name to one or more provider configuration blocks (default and aliased).
  • The provider's local name to its definition in the required_providers block, which specifies its source and version.

Providers Within Modules:

  • Provider configurations are global to an entire Terraform configuration and can be shared across module boundaries.
  • Provider configurations can be defined only in a root Terraform module. A module intended to be called by one or more other modules must not contain any provider blocks.
  • A child module automatically (implicitly) inherits only the default provider configurations from its parent.
  • Child modules do not inherit provider source or version requirements.
  • Each module must declare its own provider requirements in required_providers block.
  • Additional provider configurations (with alias argument set) are never inherited automatically by child modules.
  • To use an aliased provider configuration in a child module:
    • the parent module must explicitly reference it in the providers map of the module block, and
    • the child module must declare the alias using the configuration_aliases argument in its required_providers block.

Example:

# main.tf provider "aws" { # Aliased provider aws.west alias = "west" region = "us-west-2" } module "web-server" { source = "./modules/web-server" providers = { aws.ami = aws.west # Provider config passed to the module } }
# modules/web-server/main.tf terraform { required_providers { aws = { # Provider's local name source = "hashicorp/aws" # Provider's source address version = "~> 5.0" # Version constraint configuration_aliases = [ aws.ami ] # Provider config passed to the module } } } data "aws_ami" "amazon_linux" { provider = aws.ami # Using aliased provider aws.ami mapped to aws.west from the root module #... }

Back to Top

3d. Describe How Terraform Finds and Fetches Providers

The default way to install provider plugins is from a provider registry. The origin registry for a provider is encoded in the provider's source address in the required_providers block nested inside the top-level terraform block.

terraform { required_providers { aws = { # Provider's local name source = "hashicorp/aws" # Provider's source address version = "~> 5.0" # Version constraint } } # ... }

Provider source addresses (fully-qualified address) consist of three parts delimited by slashes (/): [<HOSTNAME>/]<NAMESPACE>/<TYPE>, where

  • Hostname (optional): The hostname of the Terraform registry that distributes the provider. If omitted, this defaults to the hostname of the public Terraform Registry - registry.terraform.io.
  • Namespace: An organizational namespace within the specified registry (hashicorp in this example). For the public Terraform Registry and for HCP Terraform's private registry, this represents the organization that publishes the provider.
  • Type: A short name for the platform or system the provider manages (aws in this example). The type is usually the provider's preferred local name.

If the source argument is omitted, Terraform uses an implied source address of registry.terraform.io/hashicorp/<LOCAL_NAME>.

During init, Terraform searches the configuration for both direct and indirect references to providers and attempts to install the plugins for those providers.

Provider lookup order:

  • Local cache (.terraform directory or the location as per plugin_cache_dir setting in the CLI configuration file).
  • Terraform Registry or private registries, filesystem, network or local mirrors (as per the provider_installation block in the CLI configuration file). Terraform will try all of the specified methods whose include and exclude patterns match a given provider, and select the newest version available across all of those methods that matches the version constraint given in the configuration.

The CLI configuration file (.terraformrc or terraform.rc depending on the host OS) configures per-user settings for CLI behaviors, which apply across all Terraform working directories.

Plugin installation settings in the CLI configuration file:

  • plugin_cache_dir - enables plugin caching and specifies, the location of the plugin cache directory.
  • provider_installation - customizes the installation methods used by terraform init when installing provider plugins.

Example:

plugin_cache_dir = "$HOME/.terraform.d/plugin-cache" provider_installation { filesystem_mirror { path = "/usr/share/terraform/providers" include = ["example.com/*/*"] } direct { exclude = ["example.com/*/*"] } }

Each of the nested blocks inside the provider_installation block specifies one installation method (direct, filesystem_mirror, or network_mirror). Each installation method can take both include and exclude patterns that specify which providers a particular installation method can be used for.

When Terraform initializes a new workspace, it creates a lock file named .terraform.lock.hcl and the .terraform directory.

Terraform uses the .terraform directory to store the required providers and modules. Do not check it into version control system, and do not directly modify this directory's contents

Terraform's lock file, .terraform.lock.hcl, records the versions and hashes of the providers used by the configuration. This ensures consistent Terraform runs in different environments, since Terraform by default will download the versions recorded in the lock file. Commit the .terraform.lock.hcl file into version control system along with the configuration files.

Plugin Management Commands

terraform version - Display the current version of Terraform and all installed plugins. With an optional flag -json, the version information is formatted as a JSON object.

terraform providers [DIR] - Show information about all of the provider requirements across all modules of the referenced configuration. This helps to understand why particular provider plugins are needed and why particular versions are selected.

terraform providers lock [options] [providers...] - Write out dependency locks for the configured providers.
Options:

  • -fs-mirror=dir - Consult the given filesystem mirror directory instead of the origin registry for each of the given providers
  • -net-mirror=url - Consult the given network mirror (given as a base URL) instead of the origin registry for each of the given providers
  • -platform=os_arch - Choose a target platform to request package checksums for

terraform providers mirror [options] <target-dir> - Save local copies of all required provider plugins.
Options:

  • -platform=os_arch - Choose which target platform to build a mirror for

terraform providers schema -json - Show schemas for the providers used in the configuration.

Back to Top

Practice Questions

What command should you use to list all providers in Terraform configuration?
Which command is used to upgrade provider plugin in Terraform?
Describe version requirements for Terraform and Terraform providers in a single configuration.
How do you constrain a provider to a minimum version of 2.5 in Terraform?
How can you enable verbose debug messages to see from which paths Terraform is loading providers?
How do you define a required version of Terraform for a configuration?
Which command can be used to upgrade the provider versions in your terraform configuration?
How do you define a required provider in a Terraform configuration?
How does Terraform determine which provider to use for the following resource? resource "google_compute_instance" "vm" {
 # ...
}

Back to Top