Courseiva

CCNA Terraform Modules Questions

58 questions · Terraform Modules · All types, answers revealed

1
Multi-Selectmedium

Which THREE of the following are components of the 'source' string when using the Terraform Registry?

Select 3 answers
A.Git branch name.
B.Provider.
C.Operating system.
D.Module name.
E.Namespace.
AnswersB, D, E

The provider segment identifies which plugin Terraform must download, formatted as `namespace/type` (for example `hashicorp/aws`), and optionally constrains the version. It satisfies the stem's requirement by naming a mandatory component of the source address, alongside the hostname and module subdirectory, enabling correct registry resolution.

Why this answer

The Terraform Registry module source string follows the format <NAMESPACE>/<MODULE_NAME>/<PROVIDER>, so option B (Provider) is correct because the provider segment (e.g., aws, azurerm, google) identifies which provider the module targets. Option D (Module name) is correct because it is the middle segment that names the specific module being referenced. Option E (Namespace) is correct because it is the first segment, identifying the organization or user that publishes the module.

Option A (Git branch name) is not part of the Registry source string; branch/ref syntax (e.g., ?ref=) applies to generic Git sources, not the Registry's three-part address. Option C (Operating system) is irrelevant, as the Registry source string does not encode any OS information.

Exam trap

The trap is confusing the registry source format with Git-based module sources — candidates may include 'branch' or 'ref' thinking it is always required, but the registry shorthand only needs namespace, name, and provider.

2
MCQeasy

What is the recommended approach for managing a module that has grown too large?

A.Use 'terraform import' to split the resources.
B.Combine all files into a single 'main.tf'.
C.Rename the module to something more generic.
D.Break the module into smaller, focused sub-modules.
AnswerD

Splitting an oversized module restores single responsibility, so each sub-module owns one concern and exposes a narrow interface. This directly resolves the stem's constraint of a module that has grown too large, cutting blast radius and making plans, reviews and reuse manageable.

Why this answer

Breaking a large module into smaller, focused sub-modules is the recommended approach because it improves reusability, testability, and blast-radius control. Each sub-module encapsulates a single logical concern (e.g., networking, compute, IAM), making plans faster and state operations safer. Terraform's module composition model is designed for this kind of decomposition, and refactoring can be done with 'moved' blocks to avoid destroying resources.

Exam trap

The trap here is confusing 'terraform import' (a state-management tool) with a refactoring tool — candidates see 'split the resources' and assume import is involved, but import never restructures modules.

How to eliminate wrong answers

Option A is wrong because 'terraform import' only brings existing infrastructure under Terraform state management — it does not split or refactor a module, and using it to 'split' resources would cause state churn and potential destruction. Option B is wrong because merging everything into a single 'main.tf' makes the module larger and harder to maintain, the opposite of the recommended practice. Option C is wrong because renaming a module changes nothing about its size, coupling, or maintainability — it is a cosmetic change with no architectural benefit.

3
MCQeasy

Where do module outputs appear after a 'terraform apply'?

A.In a file named 'outputs.json'.
B.They are automatically emailed to the Terraform administrator.
C.In the CLI output and the state file.
D.In the '.terraform/modules' directory.
AnswerC

Terraform prints root module outputs to the CLI after apply, and persists every output value in the state file for later retrieval. This satisfies the question's requirement that outputs appear in both locations, since child module outputs remain accessible via the state even when not displayed directly.

Why this answer

After 'terraform apply', module outputs are displayed in the CLI output (as part of the apply summary) and are persisted in the Terraform state file. The state file records output values so they can be referenced by other configurations or retrieved later via 'terraform output'.

Exam trap

Terraform exams often test the misconception that outputs are written to a separate file or emailed, when they actually appear in CLI output and the state file.

How to eliminate wrong answers

Option A is wrong because Terraform does not create an 'outputs.json' file automatically — outputs live in state and CLI, not a standalone JSON file (though 'terraform output -json' can print them). Option B is wrong because Terraform has no email notification feature; that's not part of its behavior. Option D is wrong because '.terraform/modules' stores downloaded/cached module source code, not output values.

4
MCQeasy

You have a module `terraform-aws-modules/vpc/aws` version `3.14.0` from the public Terraform Registry. You want to ensure that your configuration always uses this exact version and does not automatically upgrade to newer versions. Which version constraint should you specify in the module block?

A.version = "~> 3.14.0"
B.version = "3.14.0"
C.version = "3.14.*"
D.version = ">= 3.14.0"
AnswerB

Specifying `version = "3.14.0"` without any operators pins the module to exactly that version. Terraform will not select any other version, ensuring consistency. This satisfies the requirement to always use the exact version and prevents automatic upgrades. It is the correct way to lock a module version in a module block.

Why this answer

To pin a module to an exact version, specify the version string without any operators or wildcards. This ensures Terraform uses only that version. Other constraints like `~>`, `>=`, or wildcards allow some degree of version flexibility, which can lead to unexpected changes.

Exact pinning is appropriate when stability is paramount.

Exam trap

The trap here is confusing the pessimistic constraint operator `~>` with exact pinning; it allows patch-level updates.

5
Multi-Selectmedium

You are creating a Terraform module that will be used by multiple teams. You want to ensure that the module is reusable and maintainable. Which TWO practices should you follow when designing the module's interface? (Choose two.)

Select 2 answers
A.Validate variable inputs using validation blocks where appropriate.
B.Expose outputs for all resources created by the module.
C.Use a single variable of type any to accept all inputs for simplicity.
D.Provide default values for all variables to make the module easy to use.
E.Define variables with descriptive names and include descriptions.
AnswersA, E

Validation blocks allow you to enforce constraints on variable values, catching errors early. This improves module robustness and provides clear error messages to consumers. It is a best practice to validate critical inputs, such as ensuring CIDR blocks are valid or instance types are in an allowed list.

Why this answer

Descriptive variable names with descriptions and validation blocks are key to a well-designed module interface. They enhance clarity, usability, and error prevention. Avoiding type any and unnecessary defaults or outputs keeps the module focused and secure.

These practices make the module easier to understand, use, and maintain across teams.

Exam trap

The trap here is thinking that providing defaults for every variable always improves usability, but some variables should remain required to prevent misconfiguration.

6
MCQmedium

A module author wants to prevent callers from accidentally overriding a critical naming convention. The module already computes a `bucket_name` local. The author wants to expose it to callers without allowing them to set it. Which approach correctly enforces this behavior?

A.Declare the value as an input variable marked `sensitive = true` so callers cannot read or modify it.
B.Declare the value as an input variable with `nullable = false` and a validation block that rejects changes.
C.Declare the value as a local and expose it through an output block so callers can reference it read-only.
D.Declare the value as an input variable with a default, and document that callers should not change it.
AnswerC

A local value is computed entirely inside the module and cannot be set by callers. Exposing it through an output makes the value available to the root configuration as a read-only reference, such as `module.vpc.bucket_name`. This achieves the goal of sharing the naming convention while structurally preventing any override.

Why this answer

Input variables are inherently caller-controlled, so any value that must remain fixed belongs in a local and is shared outward through an output. Outputs are read-only references from the caller's perspective, meaning the module can publish `bucket_name` without offering a mechanism to override it. Validation and sensitivity flags affect acceptance and display, not write protection.

Exam trap

The trap here is treating variable validation or the sensitive flag as a mechanism for enforcing immutability, when only removing the input entirely prevents overrides.

7
MCQeasy

Which argument in a module block allows you to provide a custom name to the module instance?

A.The label immediately following the 'module' keyword.
B.'name'
C.'instance_name'
D.'alias'
AnswerA

The identifier written directly after the module keyword is the local instance name, letting you reference outputs as module.<label>.<output>. It is not an argument inside the block; the block's source argument only supplies the module's location.

Why this answer

In Terraform, a module block is declared as `module "<LABEL>" { ... }`, where the label immediately following the `module` keyword is the local name (instance name) used to reference the module within the configuration. This label is how you distinguish multiple instances of the same module source and how you reference module outputs (e.g., `module.my_module.output_name`).

Exam trap

The trap is looking for a named argument like `name` or `instance_name` when Terraform actually uses the block label syntax `module "label" {}` to identify module instances.

How to eliminate wrong answers

Option B is wrong because `name` is not a valid argument in a module block — Terraform uses the block label, not an argument, to name the instance. Option C is wrong because `instance_name` is not a recognized Terraform argument; it does not exist in the module block schema. Option D is wrong because `alias` is used with provider blocks to create alternate provider configurations, not with module blocks to name instances.

8
Multi-Selectmedium

Which TWO of the following are true regarding variable validation in modules?

Select 2 answers
A.Validation blocks must return a boolean value.
B.Validation errors will only show during 'terraform apply'.
C.Validation blocks can read from other modules.
D.Variable names can be arbitrary but must be unique.
E.You can only validate string variables.
AnswersA, D

A validation block's condition argument must evaluate to true or false; Terraform reports the error_message when it returns false. This satisfies the stem's boolean requirement, since non-boolean expressions such as strings or numbers cannot be evaluated as pass/fail conditions.

Why this answer

Option A is correct because a Terraform variable validation block uses a condition expression that must evaluate to a boolean (true/false); if it evaluates to false, Terraform raises the error_message. Option D is correct because within a module, input variable names are arbitrary identifiers chosen by the author, but each variable name must be unique within that module's variable namespace. Option B is wrong because validation failures are surfaced during terraform plan (and validate), not only at apply time.

Option C is wrong because validation conditions can only reference the variable itself and cannot read values from other modules or resources. Option E is wrong because validation blocks work with any variable type, including number, bool, list, map, and object, not just strings.

Exam trap

The trap here is confusing when validation errors appear (validate/plan vs apply) and assuming validation can cross module boundaries or is limited to strings.

9
MCQeasy

A module outputs an AWS S3 bucket name. The root configuration needs to pass this output to another module as an input variable. How should the root configuration reference the output from the first module?

A.var.bucket.bucket_name
B.output.bucket.bucket_name
C.data.terraform_remote_state.bucket.bucket_name
D.module.bucket.bucket_name
AnswerD

In Terraform, outputs from a module are accessed using the syntax module.<MODULE_NAME>.<OUTPUT_NAME>. If the module is named bucket and it defines an output named bucket_name, the root configuration references it as module.bucket.bucket_name. This value can then be passed to another module's input variable. This is the standard way to chain modules.

Why this answer

Module outputs are referenced using the module.<MODULE_NAME>.<OUTPUT_NAME> syntax. This allows the root configuration to consume values from child modules and pass them to other modules or resources. The other options refer to different constructs: output blocks declare root outputs, var references input variables, and terraform_remote_state accesses external state files.

The correct syntax is module.bucket.bucket_name.

Exam trap

The trap here is confusing module output references with root output declarations or input variable references, which use different prefixes.

10
MCQhard

A module uses for_each over a map to create several IAM roles, and the map keys are derived from a variable that callers may change. The team wants to avoid replacing roles when only the map's values change. Which change to the module is required?

A.Replace for_each with count and index into the list
B.Add lifecycle { create_before_destroy = true } to the role resource
C.Mark the for_each map as sensitive in the variable definition
D.Ensure the for_each keys are stable and derived only from values that should not change
AnswerD

Terraform identifies for_each instances by their map keys. If keys remain constant while values change, Terraform updates attributes in place rather than replacing resources. Stable keys are therefore the correct fix to prevent unnecessary role replacement.

Why this answer

for_each instances are addressed by their map keys, so key stability is what determines whether Terraform updates or replaces resources. When only values change, in-place updates occur. count and lifecycle rules do not change instance identity, and sensitivity is unrelated to addressing, so ensuring stable keys is the correct approach.

Exam trap

The trap here is assuming create_before_destroy prevents replacement, when it only changes the order of replacement operations.

11
MCQhard

You are authoring a module that creates an AWS RDS instance. The module must enforce that the `engine` input is one of `mysql`, `postgres`, or `mariadb`. A consumer passes `engine = "aurora"`. What is the most appropriate way to handle this in the module to provide a clear error message and enforce the constraint?

A.Rely on the AWS provider's own validation of the `engine` argument, since the provider will reject invalid values when the API call is made.
B.Use a `validation` block inside the `variable "engine"` declaration with a `condition` that checks `contains(["mysql", "postgres", "mariadb"], var.engine)` and an `error_message` describing the allowed values.
C.Use a `locals` block to compute a boolean and then use the `count` meta-argument on the `aws_db_instance` resource to create zero instances when the engine is invalid.
D.Add a `precondition` block to the `aws_db_instance` resource that checks the engine value and fails if it is not in the allowed list.
AnswerB

Variable validation blocks are evaluated during plan and apply, and they produce a clear, custom error message when the condition is false. This enforces the constraint at the module boundary and gives consumers immediate feedback. It is the intended Terraform mechanism for validating input values and is evaluated before any resources are created, preventing invalid configurations from reaching AWS.

Why this answer

Variable validation blocks are the correct Terraform construct for enforcing constraints on input values. They run during plan and apply, produce a custom error message, and fail fast before any resources are provisioned. This gives module consumers immediate, actionable feedback and keeps invalid values from reaching the AWS provider or API.

Exam trap

The trap here is thinking that provider-level validation or resource preconditions are a substitute for input variable validation when the goal is a clear, early error message.

12
MCQmedium

You are building a module that needs to be consumed by multiple teams. How should you structure your module to ensure that the root module can provide values for variable definitions without hardcoding them in the module itself?

A.Hardcode the values into the resource blocks directly.
B.Use terraform.tfvars files inside the module folder.
C.Define all values in a locals.tf file within the module.
D.Define variables in a variables.tf file within the module directory.
AnswerD

Declaring inputs in variables.tf exposes them as named parameters, letting each consuming root module supply values through its own module block or tfvars. This satisfies the requirement that callers provide values without hardcoding them inside the module.

Why this answer

Using input variables allows the module to be generic and reusable by accepting values defined in the calling configuration.

13
MCQmedium

A platform team maintains a module that provisions a VPC and subnets. They want to expose the subnet IDs to callers without allowing callers to modify them. Which Terraform construct should they use in the module?

A.A resource block with provisioner 'local-exec' that writes the IDs to a file
B.An output block in the module that references the subnet IDs
C.A locals block in the module that stores the subnet IDs
D.A variable block in the module with type = list(string)
AnswerB

Output blocks expose module results to the caller as read-only values. The caller references them via module.<name>.<output>, and cannot assign to them. This cleanly publishes the computed subnet IDs while keeping them immutable from the caller's perspective.

Why this answer

Output blocks are the supported mechanism for a module to return values to its caller. Because outputs are read-only from the caller's perspective, they naturally expose computed attributes such as subnet IDs without giving callers a way to overwrite them. Variables, locals, and provisioners do not provide that read-only outward-facing contract.

Exam trap

The trap here is confusing module inputs (variables) with module outputs, when only outputs return computed values to the caller.

14
MCQeasy

Which directory structure is considered standard for a Terraform module?

A.A directory containing main.tf, variables.tf, and outputs.tf.
B.One single file named 'all.tf'.
C.A binary executable file that compiles the infrastructure.
D.A separate folder for every single resource.
AnswerA

Splitting inputs, outputs, and resource declarations into variables.tf, outputs.tf, and main.tf is the documented convention, giving consumers a predictable interface. This satisfies the standard structure expected when a directory is consumed as a reusable module.

Why this answer

The standard Terraform module structure is a directory containing 'main.tf' (primary resource definitions), 'variables.tf' (input variable declarations), and 'outputs.tf' (output value declarations). This convention is documented by HashiCorp and used by the Terraform Registry, making modules predictable and easy to consume. Additional files like 'versions.tf' and 'README.md' are common but the three core files define the canonical layout.

Exam trap

The trap is overthinking the structure — candidates may pick 'separate folder per resource' thinking it is more organized, but Terraform's convention is file-based separation by concern, not folder-per-resource.

How to eliminate wrong answers

Option B is wrong because a single 'all.tf' file is an anti-pattern — it hurts readability, makes code review harder, and does not follow the registry's conventions. Option C is wrong because Terraform modules are collections of HCL configuration files, not compiled binaries; Terraform interprets HCL at plan/apply time and there is no compilation step. Option D is wrong because creating a separate folder for every resource is unnecessarily granular and breaks the module's cohesion — resources are typically grouped by logical function within the standard files.

15
MCQmedium

You are refactoring a module that previously used `count` to create multiple AWS IAM users. The module now uses `for_each` with a map of usernames to user details. After the refactor, you run `terraform plan` and see that Terraform wants to destroy and recreate all existing IAM users. What is the most likely cause, and how can you avoid this disruption?

A.The resource address changed from `aws_iam_user.users[0]` to `aws_iam_user.users["alice"]`; use `terraform state mv` to move each user to the new address.
B.The IAM user resource does not support `for_each`; you must revert to `count` to avoid recreation.
C.The `for_each` map keys must be numeric to match the previous count indices; change the map keys to numbers.
D.You need to add `lifecycle { create_before_destroy = true }` to the resource to prevent downtime during recreation.
AnswerA

Switching from count to for_each changes the resource instance keys from numeric indices to string keys. Terraform sees these as different resources and plans to destroy the old and create new ones. Using `terraform state mv` to remap each existing user to the new address preserves the resources and avoids recreation. This is the standard refactoring procedure.

Why this answer

Changing from count to for_each alters the resource instance keys, causing Terraform to treat existing instances as removed and new ones as added. To avoid this, you must move the existing state entries to the new addresses using `terraform state mv`. This preserves the actual cloud resources and prevents unnecessary destruction.

Exam trap

The trap here is assuming that for_each and count are interchangeable without state migration, when in fact their instance key formats are incompatible.

16
MCQmedium

When refactoring a large infrastructure configuration, you move a resource into a child module. How do you handle the existing state to prevent Terraform from destroying and recreating the resource?

A.Run 'terraform refresh' to automatically detect the move.
B.Use the 'terraform state mv' command to move the resource address.
C.Rename the resource in the configuration to match the old state.
D.Delete the state file and run 'terraform apply'.
AnswerB

Using `terraform state mv` rewrites the resource's address in the state file from its root-level path to the new child-module path, so Terraform maps the existing remote object to the relocated configuration block. This satisfies the stem's constraint of preventing destroy-and-recreate, since no infrastructure changes are applied.

Why this answer

When a resource is moved into a child module, its address in state changes from something like 'aws_instance.web' to 'module.child.aws_instance.web'. Terraform sees these as different resources and would plan a destroy/create. The 'terraform state mv' command updates the state address in place, preserving the existing resource without any API calls.

Exam trap

The trap here is assuming Terraform can infer resource moves from configuration changes; in reality, address changes require explicit state manipulation via 'state mv' or 'moved' blocks.

How to eliminate wrong answers

Option A is wrong because 'terraform refresh' only reconciles state with real infrastructure; it does not change resource addresses and will not detect a module refactor. Option C is wrong because renaming the resource in configuration does not change the state address; Terraform still sees a mismatch and plans replacement. Option D is wrong because deleting the state file causes Terraform to lose track of all existing resources, leading to duplicate creation and orphaned infrastructure.

17
MCQeasy

What is the primary benefit of using a module registry compared to a local file path for module distribution?

A.It allows you to run Terraform commands faster.
B.It removes the need for input variables.
C.It automatically converts your code to a higher-level language.
D.It enables version control and easier sharing across the organization.
AnswerD

A registry provides published, versioned modules that teams can pin and reuse, unlike a local file path which offers no versioning or central distribution. This satisfies the cross-organisation sharing and version-control requirement in the stem.

Why this answer

A module registry (such as the public Terraform Registry or a private registry like Terraform Cloud's) provides a centralized, versioned catalog of modules. When you reference a module via a registry source, you can pin an exact version (e.g., `version = "1.2.0"`), which enables reproducible deployments and safe upgrades. It also makes modules discoverable and shareable across teams and organizations without manually copying files or managing relative paths.

Local file paths offer none of these versioning or sharing capabilities.

Exam trap

The trap here is confusing module distribution with module functionality—candidates might think a registry speeds up execution or removes the need for variables, but the core benefit is versioning and sharing.

How to eliminate wrong answers

Option A is wrong because using a registry does not inherently speed up Terraform commands; in fact, it may introduce network latency when downloading modules, and execution speed depends on provider APIs and resource graph complexity. Option B is wrong because input variables are still required to parameterize modules regardless of source; the registry does not eliminate the need for variables—it only changes how the module code is fetched. Option C is wrong because Terraform modules are written in HCL (or JSON) and are not automatically converted to a higher-level language by a registry; the registry simply stores and distributes the same configuration code.

18
MCQmedium

You are updating a shared Terraform module that provisions an Amazon RDS instance. The module currently defines an input variable `engine_version` with `default = "14.7"`. Your team wants to ensure that any consumer who does not explicitly set `engine_version` still gets a valid value, but you also want to prevent consumers from accidentally passing a null value that would cause an API error. Which change should you make to the variable definition?

A.Add a validation block that checks `var.engine_version != null` and returns an error message if the value is null.
B.Add `nullable = false` to the variable block so Terraform rejects null values and uses the default instead.
C.Use a `sensitive = true` attribute on the variable to mask null values and fall back to the default.
D.Remove the default and mark the variable as required so callers must always provide a value.
AnswerB

Setting `nullable = false` ensures that if a caller passes `null` for `engine_version`, Terraform uses the default value rather than overriding it with null. This prevents the AWS provider from receiving a null argument, which would cause a validation error. The default remains available for callers who omit the variable entirely, satisfying both requirements.

Why this answer

The `nullable` argument, introduced in Terraform 1.1, controls whether a variable can be set to null. When `nullable = false`, a null value passed by a caller is treated as if the variable were omitted, so the default is used. This is the precise mechanism for ensuring a fallback to the default while still allowing callers to omit the variable.

Exam trap

The trap here is assuming that a validation block can replace a null value with the default, when validation only rejects values and cannot perform fallback substitution.

19
Multi-Selecthard

Which THREE actions are best practices when publishing a module to a private registry?

Select 3 answers
A.Use Semantic Versioning (SemVer) for all releases.
B.Include a 'examples/' directory to show usage.
C.Keep all files in the root folder, including tests.
D.Include a README.md file in the root of the module.
E.Prefix the repository name with 'terraform-provider-'.
AnswersA, B, D

Semantic Versioning communicates breaking, feature and patch changes predictably, letting consumers pin safe ranges. Applying SemVer to every release satisfies the stem's best-practice requirement for a private registry, where consumers depend on clear version signals.

Why this answer

Option A is correct because Semantic Versioning (MAJOR.MINOR.PATCH) gives consumers a predictable contract: breaking changes bump MAJOR, backward-compatible features bump MINOR, and fixes bump PATCH, which private registries rely on for version constraints and dependency resolution. Option B is correct because an 'examples/' directory provides runnable usage snippets that document real inputs/outputs and let consumers validate the module before adopting it. Option D is correct because a root-level README.md is the primary documentation surface a private registry renders, explaining purpose, inputs, outputs, and usage.

Option C is not a best practice because mixing tests and all files in the root harms maintainability; tests belong in a 'tests/' or 'test/' directory and modules should be organized into logical subfolders. Option E is not a best practice for a module because the 'terraform-provider-' prefix is a naming convention for Terraform providers, not reusable modules, so it would be misleading.

Exam trap

AWS provider exams often test whether candidates know the reserved naming conventions — the trap is confusing provider repository prefixes (terraform-provider-) with module repository prefixes (terraform-<PROVIDER>-<NAME>), and assuming test files belong at the module root.

20
MCQeasy

What is the purpose of the 'terraform init' command when working with modules?

A.It compiles the modules into machine code.
B.It pushes your local modules to the registry.
C.It downloads and installs the modules specified in the configuration.
D.It updates the state file to match the modules.
AnswerC

terraform init initialises the working directory by downloading and installing modules declared in module blocks, along with providers and backend configuration. This satisfies the scenario's need to make module source code available locally before plan or apply can reference it.

Why this answer

'terraform init' downloads and installs the modules specified in the configuration into the '.terraform/modules' directory.

21
Multi-Selecthard

Which THREE of these are benefits of using modules in Terraform?

Select 3 answers
A.Increasing the execution speed of API calls.
B.Automatically preventing all resource drift.
C.Enforcing standardized configurations across teams.
D.Reducing code duplication across environments.
E.Encapsulating complexity into simple interfaces.
AnswersC, D, E

Modules encapsulate resource definitions behind typed input variables, so every team instantiates the same reviewed code rather than hand-writing resources. This directly satisfies the stem's standardisation constraint: configuration drift between teams is eliminated because changes propagate from one versioned source, and inputs are validated at plan time.

Why this answer

Option C is correct because modules let platform teams publish a single, versioned implementation of a resource pattern (for example, a standard VPC or S3 bucket baseline) that every team consumes, so required tags, naming conventions, and security settings are applied consistently instead of being re-implemented per team. Option D is correct because a module is written once and then instantiated with different inputs for dev, staging, and production, eliminating the copy-paste of near-identical .tf resource blocks across environments. Option E is correct because a module exposes a small, typed interface of input variables and outputs while hiding the internal resource graph, so callers pass a few values and receive outputs without needing to understand every underlying resource and dependency.

Option A is not a module benefit: Terraform still makes the same provider API calls, and module expansion happens at plan time, so execution speed is not inherently improved. Option B is not a module benefit: drift detection and remediation depend on state refresh and plan/apply behavior, and modules do not automatically prevent out-of-band changes to resources.

Exam trap

The trap here is confusing modules with runtime or governance features — candidates pick 'prevents drift' or 'speeds up API calls' because modules feel like a magic fix, when they are purely a code-organization and reuse mechanism.

22
MCQeasy

You are writing a module that creates an AWS Security Group. The module accepts a variable `ingress_rules` defined as a list of objects with `from_port`, `to_port`, and `protocol`. You want to ensure that the module's interface is flexible enough to allow callers to add or remove rules without modifying the module code. Which Terraform feature should you use to iterate over the list and create dynamic ingress blocks?

A.A `count` meta-argument on the `aws_security_group` resource with `count = length(var.ingress_rules)`.
B.A `for` expression in the `ingress` argument of the security group resource, returning a list of rule objects.
C.A `for_each` meta-argument on the `aws_security_group` resource, iterating over the list.
D.A `dynamic` block named `ingress` that iterates over `var.ingress_rules` using `for_each`.
AnswerD

A `dynamic` block allows generating nested blocks dynamically based on a collection. By using `dynamic "ingress"` with `for_each = var.ingress_rules`, the module can create one ingress block per rule. This keeps the module flexible and avoids hardcoding rules, enabling callers to pass any number of rules without changing module code.

Why this answer

Dynamic blocks are designed to generate nested blocks from a collection. Using `dynamic "ingress"` with `for_each` over the list of rule objects allows the module to create multiple ingress blocks within a single security group resource. This approach is idiomatic and maintains a clean, flexible interface.

Exam trap

The trap here is confusing resource-level iteration (count/for_each) with nested block generation, which requires a dynamic block.

23
MCQmedium

You maintain a Terraform module that provisions an AWS Application Load Balancer and its target groups. Consumers frequently need to attach additional security groups to the ALB, but the module currently hardcodes a single security group. You want to allow consumers to supply a list of security group IDs without breaking existing module calls. Which change should you make to the module's variables and resource configuration?

A.Add a new variable `security_group_ids` of type `list(string)` with no default, and replace the existing hardcoded security group with this variable.
B.Add a new variable `additional_security_group_ids` of type `list(string)` with a default of `[]`, then merge it with the module's internally created security group ID using `concat()` in the `security_groups` argument.
C.Use the `for_each` meta-argument on the `aws_lb` resource to iterate over a map of security group IDs provided by the consumer.
D.Change the existing `security_group_id` variable to type `list(string)` and update the `security_groups` argument to reference it directly.
AnswerB

This approach preserves backward compatibility because existing callers do not need to set the new variable; the default empty list means no additional groups are attached. Using `concat()` to combine the internally managed security group with consumer-supplied IDs correctly composes the final list. It is the idiomatic way to extend module behavior without forcing a breaking change on existing consumers.

Why this answer

Adding an optional input variable with a sensible default and composing it with the module's internal resource preserves backward compatibility. Existing consumers continue to work unchanged, while new consumers can pass extra security group IDs. Concatenating the internally managed security group with the additional ones ensures the ALB retains its baseline security posture and gains the requested extra groups.

Exam trap

The trap here is assuming that modifying an existing variable's type is backward compatible as long as the underlying resource argument accepts a list.

24
MCQmedium

A platform team publishes a shared networking module to a private Terraform registry. Several consuming teams pin the module using `version = "~> 2.1.0"`. The platform team later adds a new optional input variable to the module and publishes it as version 2.2.0 without changing any existing behavior. What is the effect on the consuming teams' next `terraform init`?

A.terraform init fails because the version constraint does not allow patch-level upgrades.
B.terraform init continues using the newest 2.1.x version satisfying the constraint and does not select 2.2.0.
C.terraform init prompts for confirmation before upgrading across minor versions, then proceeds if approved.
D.terraform init upgrades the module to 2.2.0 because adding an optional variable is a non-breaking change.
AnswerB

The pessimistic constraint `~> 2.1.0` allows only versions greater than or equal to 2.1.0 and less than 2.2.0. Terraform's module installer selects the highest available version within that allowed range, so 2.2.0 is never chosen. Consuming teams remain on the newest 2.1.x release until they update their version constraint.

Why this answer

The pessimistic constraint operator restricts acceptable versions to a defined range. For `~> 2.1.0`, the upper bound is exclusive at 2.2.0, so the newly published 2.2.0 is invisible to consumers. Terraform installs the highest version that still satisfies the constraint, leaving teams on the latest 2.1.x release until they deliberately broaden the constraint.

Exam trap

The trap here is assuming Terraform evaluates semantic versioning compatibility when choosing a module version, when it actually performs a literal constraint match.

25
Multi-Selecthard

Which THREE items should be included in a well-documented Terraform module?

Select 3 answers
A.A list of all possible provider configurations.
B.Clear documentation for each output.
C.A description for each input variable.
D.The raw state file of the module's resources.
E.An example of how to call the module.
AnswersB, C, E

Documenting each output states what the value represents and how consumers should use it, which is essential when the module is called elsewhere. Without it, callers must read the source to interpret returned values, defeating the stem's requirement for a well-documented module.

Why this answer

Option B is correct because documenting each output (its name, type, and meaning) lets consumers know what values the module returns and how to reference them, which is essential for reusable module interfaces. Option C is correct because every input variable should have a description so users understand its purpose, expected type, default, and whether it is required, making the module self-explanatory. Option E is correct because providing a usage example showing how to call the module with realistic inputs demonstrates correct invocation and accelerates adoption.

Option A is not appropriate because listing all possible provider configurations is impractical and unnecessary; provider requirements should instead be declared via required_providers and documented as constraints. Option D is wrong because raw state files contain sensitive data and environment-specific resource mappings, should be stored remotely with locking, and are never part of module documentation.

Exam trap

The trap is confusing module documentation with operational artifacts like state files or provider configs; candidates may select state file inclusion, not realizing it is a security anti-pattern.

26
Multi-Selecthard

You are writing a reusable module that creates an AWS S3 bucket and an associated bucket policy. The module will be consumed by multiple teams. Which TWO practices should you follow to make the module robust and maintainable? (Choose two.)

Select 2 answers
A.Define all input variables with explicit types and provide sensible defaults for optional ones.
B.Use the `terraform-aws-modules/s3-bucket/aws` public module internally and wrap it without exposing any of its variables.
C.Expose outputs for the bucket ID and ARN so consumers can reference them in their own configurations.
D.Store the bucket policy as a separate JSON file and load it with `file()` so it can be edited without changing Terraform code.
E.Hardcode the AWS region in the provider configuration inside the module to ensure consistency.
AnswersA, C

Explicit types enable Terraform to validate inputs and provide better error messages, while defaults make optional variables easier to use. This improves the module's usability and reduces the chance of misconfiguration. It also documents the expected input shape, which is essential for a module shared across teams with varying requirements.

Why this answer

Exposing outputs and defining typed variables with defaults are core practices for reusable modules. Outputs allow consumers to integrate the module's resources into their own configurations, while typed variables with defaults make the module predictable and easy to use. Together they form a clear contract between the module and its consumers, which is essential for maintainability across teams.

Exam trap

The trap here is thinking that wrapping a public module or using external files for policies automatically improves maintainability, when in fact it can reduce flexibility and transparency.

27
Multi-Selecthard

A platform team is preparing a reusable module for internal distribution. They want to ensure that consumers can reliably upgrade the module without unexpected breaking changes. Which TWO practices best support this goal? (Choose two.)

Select 2 answers
A.Publish each change as a new tagged release and expose the version through the module registry so consumers can pin constraints.
B.Avoid using semantic version numbers and instead increment a build counter with each commit to the default branch.
C.Store all module code in a single monolithic file to simplify diffing between releases.
D.Document every input variable and output, including type, description, and default, so consumers understand the interface contract.
E.Require all consumers to use the latest commit hash from the default branch to stay current with fixes.
AnswersA, D

Tagged releases create immutable version identifiers that consumers reference through version constraints. This lets teams adopt changes deliberately and roll back by adjusting constraints. Without tags, the registry cannot resolve versions, and consumers would be forced onto whatever the default branch contains, making controlled upgrades impossible.

Why this answer

Reliable upgrades depend on two things: immutable, semantically versioned releases that consumers can constrain, and a well-documented interface that makes the contract explicit. Tagged releases let teams pin and roll back. Documentation communicates the impact of changes.

Practices like build counters, moving branch references, or file layout do not provide the versioning discipline or contract clarity required for safe upgrades.

Exam trap

The trap here is equating frequent updates with safe upgrades, when controlled versioning and documented interfaces are what actually prevent breaking changes.

28
MCQeasy

You are writing a module that creates an AWS EC2 instance and an associated security group. The module has a variable `instance_type` with a default of `t3.micro`. A consumer wants to override this default for a production environment. How should the consumer pass a different value when calling the module?

A.Create a `terraform.tfvars` file in the module's source directory containing `instance_type = "m5.large"`.
B.Set an environment variable `TF_VAR_instance_type=m5.large` before running `terraform apply`.
C.Use the `-var` flag with the module path, such as `terraform apply -var 'module.ec2.instance_type=m5.large'`.
D.Add an argument `instance_type = "m5.large"` inside the module block in the root configuration.
AnswerD

Module inputs are set by passing arguments inside the `module` block, using the variable name as the argument name. This directly overrides the default value for that instance of the module. The consumer does not need to edit the module source or use environment variables, making this the correct and standard approach.

Why this answer

Module input variables are configured by passing arguments within the `module` block in the calling configuration. The argument name matches the variable name declared in the module, and the value provided overrides the default. Environment variables and tfvars files inside the module directory do not affect child module inputs.

Exam trap

The trap here is assuming that environment variables or a tfvars file placed in the module directory can set child module inputs, when only arguments in the module block can do so.

29
MCQhard

If a module has a required variable but you do not provide it in the calling configuration, what happens?

A.Terraform uses 'null' for the value.
B.Terraform uses the variable value from the previous state.
C.Terraform fails with an error during the planning phase.
D.Terraform automatically sets the value to an empty string.
AnswerC

Terraform validates required input variables during the plan, so omitting one halts execution with an error before any resources are provisioned. This satisfies the stem's constraint: a required variable has no default, and the calling configuration supplies no value, so planning cannot resolve the reference.

Why this answer

If a module declares a variable without a default and the calling configuration does not supply a value, Terraform raises an error during the planning phase, stating that the required variable has not been set. Terraform does not invent a value or fall back to prior state. This fail-fast behavior ensures module contracts are honored.

Exam trap

The trap is assuming Terraform is lenient with missing inputs — candidates pick null or empty string because those are common defaults in other languages, but Terraform treats missing required variables as a hard error.

How to eliminate wrong answers

Option A is wrong because Terraform does not default missing required variables to null — null is only used when explicitly set or when a variable has a default of null. Option B is wrong because Terraform does not read variable values from previous state; variables are inputs, not state attributes. Option D is wrong because Terraform does not coerce missing values to empty strings — that would silently mask configuration errors.

30
MCQhard

An operations engineer maintains a module that creates an AWS RDS instance. The module block in the root configuration includes a `providers` argument mapping the module's `aws` provider to an aliased provider configuration targeting a secondary region. A new resource is added inside the module that uses `aws.global` as an explicit provider. What happens when the engineer runs `terraform plan`?

A.Terraform automatically inherits the root module's `aws.global` provider configuration for the child module.
B.Terraform errors because the module does not declare `aws.global` in its required_providers block.
C.Terraform errors because the module only received a mapping for the default `aws` configuration, so `aws.global` has no configuration passed in.
D.Terraform silently falls back to the default `aws` provider configuration in the child module and plans successfully.
AnswerC

A module can only use provider configurations explicitly passed to it through the `providers` argument. Since the root only mapped the default `aws` provider, the child module has no configuration for the `aws.global` alias. The resource referencing `aws.global` therefore has no valid provider configuration, and Terraform reports an error during plan.

Why this answer

Modules receive provider configurations only through explicit `providers` mappings in the module block. When a resource inside the module references an aliased provider such as `aws.global`, that alias must have been passed in. Because the root only supplied the default `aws` configuration, the alias is undefined inside the module and planning fails with an error about the missing provider configuration.

Exam trap

The trap here is believing aliased provider configurations propagate into child modules automatically, when only explicitly passed configurations are available.

31
Multi-Selecthard

Which TWO of the following are valid ways to pass a provider configuration to a child module?

Select 2 answers
A.Defining the provider inside the module's 'main.tf'.
B.Using the 'providers' meta-argument inside the module block.
C.Passing the provider as a string input variable.
D.Using a global variable for provider credentials.
E.Inheritance from the root module's provider configuration.
AnswersB, E

The providers meta-argument explicitly maps each provider configuration the child module expects to a specific configuration from the calling module, such as aws = aws.usw2. This is the documented explicit passing mechanism, satisfying the requirement for a valid way to supply provider configuration.

Why this answer

Option B is correct because Terraform supports the 'providers' meta-argument inside a module block, which explicitly maps a provider configuration from the parent (root) module to a specific provider alias expected by the child module, e.g. 'providers = { aws = aws.usw2 }'. Option E is correct because Terraform automatically inherits the default (unaliased) provider configurations from the root module into child modules, so a child module using 'aws' without its own provider block receives the root's default AWS provider configuration. Option A is not a way to pass a configuration from the parent; defining a provider inside the module's main.tf creates a separate provider configuration local to the module rather than passing one in.

Option C is invalid because provider configurations are complex objects, not strings, and cannot be supplied via input variables. Option D is invalid because Terraform has no concept of a 'global variable' for provider credentials; credentials belong in provider blocks or environment variables, not a global variable mechanism.

Exam trap

The trap is thinking providers can be passed as string variables or defined freely inside child modules — Terraform treats providers as configuration objects passed via the `providers` meta-argument or inherited from the root, not as ordinary input values.

32
MCQmedium

You are using a module from the public Terraform Registry in a production configuration. After running terraform init -upgrade, you notice that the module version changed unexpectedly and caused a breaking change in your infrastructure. You want to prevent this from happening again. Which approach should you take?

A.Run terraform init without the -upgrade flag after the initial installation.
B.Set the version argument to a range like ">= 1.0.0" to allow flexibility while avoiding major version changes.
C.Pin the module to a specific version using the version argument in the module block.
D.Use a local path to a copy of the module that you maintain manually.
AnswerC

Specifying an exact version constraint, such as version = "1.2.0", ensures that Terraform only uses that version. This prevents automatic upgrades from introducing breaking changes. The version argument is the standard mechanism for version pinning in Terraform modules, and it applies to modules from the Terraform Registry or other sources that support versioning.

Why this answer

Pinning the module to a specific version using the version argument ensures that Terraform always uses that exact version, preventing unexpected upgrades that could introduce breaking changes. While other methods like local copies or avoiding -upgrade might reduce risk, they do not provide the same level of control and are not enforced by Terraform. The version argument is the recommended and reliable way to lock a module version.

Exam trap

The trap here is assuming that running terraform init without -upgrade prevents future upgrades, but it only avoids upgrades during that specific command; it does not enforce a version constraint.

33
MCQhard

A module author wants to conditionally create an AWS CloudWatch log group only when a variable named enable_logging is true. The log group resource is defined inside a child module. Which approach correctly implements this conditional creation within the module?

A.Add a lifecycle block with create_before_destroy = var.enable_logging.
B.Use a count meta-argument on the resource with count = var.enable_logging ? 1 : 0.
C.Use a null_resource with a trigger that runs a local-exec provisioner to create the log group via AWS CLI.
D.Wrap the resource in a dynamic block with for_each = var.enable_logging ? [1] : [].
AnswerB

The count meta-argument can be used on any resource to conditionally create instances. Setting count = var.enable_logging ? 1 : 0 creates one instance when enable_logging is true and zero when false. This is a standard and supported pattern inside modules. The resource is then referenced using index syntax, such as aws_cloudwatch_log_group.example[0], when needed.

Why this answer

Conditional resource creation in Terraform is achieved using the count or for_each meta-arguments. Applying count = var.enable_logging ? 1 : 0 directly on the resource allows it to be created only when the variable is true. This pattern works inside modules and is the idiomatic way to conditionally manage resources.

Other methods like dynamic blocks or lifecycle arguments do not control resource existence.

Exam trap

The trap here is confusing dynamic blocks, which generate nested blocks, with meta-arguments like count that control whether a resource is created at all.

34
MCQeasy

You have written a Terraform module that outputs the ARN of an AWS S3 bucket. A user of the module wants to reference this output in their root configuration. How should they access it?

A.data.terraform_remote_state.<MODULE_NAME>.bucket_arn
B.output.<MODULE_NAME>.bucket_arn
C.module.<MODULE_NAME>.outputs.bucket_arn
D.module.<MODULE_NAME>.bucket_arn
AnswerD

Module outputs are accessed directly as attributes on the module object. The syntax is module.<MODULE_NAME>.<OUTPUT_NAME>. This allows the root configuration to use the output value in other resources or outputs. It is the standard way to reference module outputs in Terraform.

Why this answer

Module outputs are exposed as attributes on the module object. The correct reference is module.<MODULE_NAME>.<OUTPUT_NAME>. This allows the root configuration to consume the output value seamlessly.

Other options represent incorrect syntax or refer to different mechanisms like remote state.

Exam trap

The trap here is assuming an 'outputs' intermediate attribute, which does not exist in Terraform's module output referencing syntax.

35
MCQhard

You are writing a Terraform module that creates an AWS S3 bucket. You want to ensure that the bucket name is globally unique and follows a naming convention that includes the AWS account ID and region. How can you generate a unique name within the module?

A.Use the random_id resource to generate a unique suffix and append it to the bucket name.
B.Use the timestamp() function to generate a unique name based on the current time.
C.Use the format() function with the account ID and region, and rely on AWS to enforce uniqueness.
D.Use the uuid() function to generate a unique string and use it as the bucket name.
AnswerA

The random_id resource generates a unique identifier that can be used as a suffix. By combining it with the account ID and region, you can create a globally unique bucket name that follows the convention. This is a common pattern for ensuring uniqueness in AWS resource names.

Why this answer

The random_id resource generates a unique, stable identifier that can be used as a suffix to ensure global uniqueness. When combined with account ID and region, it creates a name that follows the convention and is highly unlikely to collide. Other methods like uuid() or timestamp() either do not guarantee uniqueness or cause instability in the name across runs.

Exam trap

The trap here is assuming that account ID and region alone are sufficient for global uniqueness, but S3 bucket names must be unique across all AWS accounts, so additional randomness is required.

36
MCQhard

Your team maintains a Terraform module that provisions an AWS VPC. The module is used by multiple teams. You need to allow callers to customize the CIDR block but enforce a default if not provided. How should you define the variable in the module?

A.variable "vpc_cidr" { type = string }
B.variable "vpc_cidr" { type = string default = null }
C.variable "vpc_cidr" { type = string default = "10.0.0.0/16" }
D.variable "vpc_cidr" { type = string validation { condition = can(cidrhost(var.vpc_cidr, 0)) error_message = "Invalid CIDR" } }
AnswerC

This declaration defines a variable with a type and a default value. If the caller does not provide a value, Terraform uses the default. If the caller provides a value, it overrides the default. This meets the requirement to allow customization while enforcing a default when no value is given.

Why this answer

To allow callers to customize a value while providing a fallback, you define a variable with a default. The default is used only when the caller does not supply a value. This provides flexibility and ensures the module works even if the variable is omitted.

The other options either make the variable required or do not provide a valid default.

Exam trap

The trap here is confusing validation with defaulting; a validation block does not make a variable optional, and omitting a default still makes the variable required.

37
MCQhard

A module accepts a variable 'environment' used only to build a name prefix. The team wants to prevent callers from passing values outside a fixed set without duplicating validation in every caller. What is the correct approach?

A.Declare the variable with type = string and rely on callers to pass correct values
B.Add a validation block inside the variable definition in the module
C.Use a precondition block on each resource that consumes the variable
D.Add a null_resource with a local-exec provisioner that validates the value at apply time
AnswerB

A validation block in the variable definition enforces the rule where the variable is declared, so every caller is checked automatically. Using a condition with contains() against the allowed set produces a clear error before any resources are planned, centralizing enforcement in the module.

Why this answer

Validation blocks inside a variable definition enforce constraints at the point of declaration, so all callers are checked consistently and errors surface during plan. Preconditions, type constraints, and provisioners either require duplication, do not restrict values, or run too late to be reliable for this requirement.

Exam trap

The trap here is choosing a precondition on resources, which works but must be duplicated and evaluates later than variable validation.

38
MCQmedium

You are developing a Terraform module that creates an AWS EC2 instance. You want to expose the instance's private IP address to the caller. How should you define this in the module?

A.Define a local value in the module and reference it in the root module.
B.Add an output block in the module that references the instance's private_ip attribute.
C.Use a variable in the module to pass the private IP address to the caller.
D.Use a data source in the root module to look up the EC2 instance by its tags.
AnswerB

Outputs are used to expose values from a module to the parent configuration. By defining an output that references the EC2 instance's private_ip attribute, the caller can access this value. This is the correct way to share resource attributes from a child module.

Why this answer

Outputs are the mechanism for a module to return values to its caller. By defining an output that references the EC2 instance's private_ip attribute, the parent module can access it using module.<module_name>.<output_name>. This is the intended and most efficient way to expose resource attributes from a child module.

Other options either do not work or are overly complicated.

Exam trap

The trap here is thinking that variables can be used bidirectionally; variables only pass data into a module, while outputs pass data out.

39
MCQmedium

Your team requires a custom module to be versioned strictly to prevent breaking changes during deployments. What is the recommended way to reference a module from a private Git repository?

A.Use the 'ref' parameter in the source URL string.
B.Set the 'version' block inside the module definition.
C.Copy the module code locally every time a new version is released.
D.Use the 'module_version' argument in the module block.
AnswerA

Using the `ref` parameter pins the module source to a specific Git tag, branch or commit, so deployments always fetch that immutable revision rather than the repository's moving default branch. This directly satisfies the strict versioning constraint, preventing upstream breaking changes from silently affecting your infrastructure during deployment.

Why this answer

For modules sourced from a private Git repository, Terraform does not support a separate 'version' argument; instead, you pin to a specific commit, tag, or branch using the 'ref' query parameter appended to the source URL (e.g., 'git::https://example.com/module.git?ref=v1.2.0'). This locks the module to an immutable Git reference, preventing unexpected breaking changes when the repository's default branch moves forward. The 'ref' parameter is the only supported mechanism for version pinning with Git-based module sources.

Exam trap

The trap here is confusing the registry-only 'version' argument with Git-based version pinning — candidates often assume 'version' works universally, but Terraform only supports 'ref' for Git sources, and the exam deliberately includes both to test that distinction.

How to eliminate wrong answers

Option B is wrong because the 'version' block is only valid for modules sourced from a Terraform Registry (public or private), not for arbitrary Git URLs — Terraform will error with 'Invalid version constraint' or simply ignore it for Git sources. Option C is wrong because manually copying module code into the local filesystem defeats the purpose of module reuse, introduces drift, and is not a versioning mechanism — it's an anti-pattern that breaks maintainability. Option D is wrong because 'module_version' is not a real Terraform argument; no such meta-argument exists in the module block, and candidates often confuse it with the registry-only 'version' argument.

40
MCQhard

How can you protect a module's inputs from receiving incorrect types of data?

A.Use the 'provider' block to enforce input types.
B.Use a 'validation' block with a condition inside the variable declaration.
C.Write a custom bash script to check the inputs.
D.Terraform automatically validates all inputs as strings.
AnswerB

A validation block inside the variable declaration evaluates a condition against the supplied value and returns an error message when it fails. This rejects malformed input at plan time, satisfying the requirement to guard against incorrect data types before resources are created.

Why this answer

Terraform's variable validation block, introduced in Terraform 0.13, allows you to define a condition expression that must evaluate to true for the variable value to be accepted. When placed inside a variable declaration, it can check the type, format, or range of the input and produce a custom error message if the condition fails. This is the native, declarative way to enforce input constraints at plan time, preventing invalid data from propagating into the module.

Exam trap

The trap here is confusing Terraform's automatic type checking with custom validation, leading candidates to think that simply declaring a type (e.g., string) is enough to protect against incorrect data, when in fact a validation block is needed for format or value constraints.

How to eliminate wrong answers

Option A is wrong because the 'provider' block configures the cloud provider (e.g., AWS region, credentials) and has no mechanism to validate module input types. Option C is wrong because a custom bash script is an external, imperative workaround that is not integrated with Terraform's validation lifecycle and would not run automatically during terraform plan or apply. Option D is wrong because Terraform does not automatically validate all inputs as strings; it performs type checking based on the declared type constraint, but it does not enforce custom validation rules unless a validation block is explicitly added.

41
MCQhard

You need to output a complex object from a child module to be used by the root module. What is the best practice for defining the output?

A.Assign the output to a global variable in the root module.
B.Use a data source to read the child module's internal state.
C.Define an output block in the child module and reference it in the root module using the module prefix.
D.Write the output to a local file inside the module and read it in the root.
AnswerC

Outputs expose values across module boundaries; declaring an output block in the child module and referencing it as module.<name>.<output> in the root module is the standard mechanism. This satisfies the requirement to pass a complex object upward without duplicating logic.

Why this answer

Using explicit output blocks in the child module makes the data available for access via 'module.<module_name>.<output_name>'.

42
Multi-Selectmedium

A team is designing a reusable Terraform module that provisions an AWS VPC. They want to ensure the module is flexible and follows best practices for input variables. Which two practices should they implement? (Choose two.)

Select 2 answers
A.Provide default values for all input variables to simplify usage.
B.Avoid documenting variables in README to keep the module concise.
C.Mark variables that contain sensitive information with sensitive = true.
D.Define all variables as type = any to maximize flexibility.
E.Use validation blocks to enforce constraints on variable values.
AnswersC, E

Setting sensitive = true on a variable prevents its value from being displayed in Terraform plan and apply output. This is important for variables like passwords or API keys. It helps prevent accidental exposure of secrets in logs or console output. This practice is recommended for any variable that carries sensitive data, ensuring that the module adheres to security best practices.

Why this answer

Marking sensitive variables with sensitive = true and using validation blocks are both best practices for reusable modules. Sensitivity prevents secret leakage, while validation enforces input constraints and improves error handling. Avoiding defaults for critical variables, using explicit types, and documenting variables are also important.

These practices collectively make the module more secure, robust, and user-friendly.

Exam trap

The trap here is thinking that providing defaults for every variable or using type = any makes a module more flexible, when it actually reduces safety and clarity.

43
MCQmedium

You need to share a module across your entire organization. Which registry type should you use?

A.The public Terraform Registry.
B.A shared network drive.
C.An S3 bucket with public read access.
D.A private module registry in Terraform Cloud or Enterprise.
AnswerD

A private module registry in Terraform Cloud or Enterprise publishes modules to a single organisation-wide namespace, so every team consumes the same versioned source. This satisfies the requirement to share one module across the entire organisation rather than per-workspace or public consumption.

Why this answer

A private module registry in Terraform Cloud or Enterprise is the correct answer because it is purpose-built for sharing Terraform modules within an organization. It provides versioned, discoverable modules with access control, integrates natively with Terraform CLI/Cloud workflows, and supports private module sourcing via the `terraform init` command. This ensures secure, consistent, and governed reuse across teams, which is exactly what the scenario requires.

Exam trap

The trap here is confusing general-purpose storage (like S3 or network drives) with a Terraform-native private registry, or assuming the public registry can be used for private sharing; candidates often overlook the need for access control, versioning, and integration with Terraform workflows.

How to eliminate wrong answers

Option A is wrong because the public Terraform Registry is open to everyone and is intended for publicly available modules, not for private organizational sharing; using it would expose proprietary code and lacks access controls. Option B is wrong because a shared network drive is not a Terraform-native module source; Terraform cannot directly reference modules from arbitrary file shares without custom setup, and it provides no versioning, access control, or integration with Terraform workflows. Option C is wrong because an S3 bucket with public read access is insecure and not a supported module registry; while Terraform can fetch modules from S3, public read access violates security best practices and lacks the governance, versioning, and discoverability features of a private registry.

44
MCQmedium

You have a nested module structure. How can you pass a variable from the root module through an intermediate module to a leaf module?

A.Define the variable only in the leaf module and refer to root by index.
B.You must define the variable in the intermediate module and pass it to the leaf module.
C.You can use the 'global_vars' block in the root.
D.The intermediate module automatically inherits all root variables.
AnswerB

Terraform modules have no implicit variable inheritance, so the intermediate module must declare the variable in its own variables block and explicitly forward it via an argument in its module call to the leaf. This explicit pass-through satisfies the root-to-leaf requirement.

Why this answer

In Terraform, variables are not automatically inherited by child modules. To pass a variable from the root module to a leaf module through an intermediate module, you must explicitly define the variable in the intermediate module and pass it to the leaf module. This means declaring the variable in the intermediate module's variables.tf and then using it in the module block that calls the leaf module.

Exam trap

AWS provider exam often tests the misconception that variables are automatically inherited by child modules or that there is a global variable mechanism. The trap is assuming implicit inheritance.

How to eliminate wrong answers

Option A is wrong because you cannot refer to root module variables by index from the leaf module; modules do not have implicit access to parent variables. Option C is wrong because there is no 'global_vars' block in Terraform; variables must be explicitly passed. Option D is wrong because intermediate modules do not automatically inherit root variables; all variables must be explicitly declared and passed.

45
MCQhard

You are designing a module that should conditionally create resources based on a boolean input. What feature should you use?

A.Define multiple resources and use 'depends_on' to link them.
B.Use the 'count' meta-argument with a ternary expression.
C.Use an 'if' block around the resource definition.
D.Use the 'module' meta-argument inside the resource.
AnswerB

The count meta-argument combined with a ternary expression evaluates the boolean to either 1 or 0, creating or omitting the resource instances accordingly. This directly satisfies the stem's requirement for conditional resource creation from a boolean input.

Why this answer

The `count` meta-argument accepts a numeric value, and combining it with a ternary expression (e.g., `count = var.enabled ? 1 : 0`) is the canonical Terraform pattern for conditionally creating a resource. When count evaluates to 0, no instances are created; when it evaluates to 1, a single instance is provisioned. This is the idiomatic way to implement conditional resource creation in Terraform.

Exam trap

The trap is assuming Terraform has procedural `if` statements like a general-purpose language — it does not; conditionals must be expressed through expressions such as ternary operators combined with `count` or `for_each`.

How to eliminate wrong answers

Option A is wrong because `depends_on` only establishes explicit ordering between resources — it does not conditionally create or skip resources. Option C is wrong because Terraform's HCL does not have an `if` block construct for wrapping resource definitions; conditionals are expressed through expressions like ternary operators. Option D is wrong because `module` is a top-level block type, not a meta-argument that can be nested inside a resource block.

46
MCQmedium

A platform team maintains a shared module in a private Terraform registry. They want to ensure that any configuration consuming this module always pulls the exact same version, even if a newer version is published. The module source is specified as "app.terraform.io/example-corp/vpc/aws". Which source argument change should be applied?

A.Use a Git-style ref in the source string, such as ?ref=1.2.0.
B.Add a version argument to the module block, e.g., version = "1.2.0".
C.Append ?version=latest to the source string to pin the latest version.
D.Set the environment variable TF_MODULE_VERSION to 1.2.0 before running terraform init.
AnswerB

For registry modules, Terraform uses the separate version argument to select a specific version. Setting version = "1.2.0" pins the module to that exact release, so even if newer versions are published, the consumer continues to use 1.2.0. This is the documented and supported way to achieve strict version pinning for registry-hosted modules.

Why this answer

Registry modules require a version argument within the module block to select a specific release. By setting version = "1.2.0", the consumer always retrieves that exact version, preventing unintended upgrades. Other methods such as query parameters or environment variables are not supported for registry modules and would not enforce version pinning.

This is the standard practice for reproducible deployments.

Exam trap

The trap here is assuming that version pinning for registry modules can be done via a query string or environment variable, when it actually requires a dedicated version argument.

47
MCQmedium

Your team publishes an internal Terraform module to a private registry. A consumer wants to pin the module to a specific version to avoid unexpected changes. The module source is `app.terraform.io/example-org/vpc/aws`. Which source argument correctly pins the module to version 2.3.0?

A.source = "git::https://app.terraform.io/example-org/vpc/aws.git?ref=v2.3.0"
B.source = "app.terraform.io/example-org/vpc/aws/2.3.0"
C.source = "app.terraform.io/example-org/vpc/aws" version = "2.3.0"
D.source = "app.terraform.io/example-org/vpc/aws?version=2.3.0"
AnswerC

For registry modules, the version argument is specified separately from the source string. Using an exact version constraint like "2.3.0" pins the module to that specific release, ensuring reproducible deployments. This is the documented syntax for private and public Terraform registry modules and is the recommended way to avoid unexpected changes.

Why this answer

Registry modules require the version argument to be set separately from the source string. Pinning to an exact version such as "2.3.0" ensures that the same module code is used across all runs, which is critical for reproducibility and preventing unintended changes. The source format for a registry module is hostname/namespace/name/provider without any version embedded.

Exam trap

The trap here is assuming that the version can be embedded in the source URL, as is done with Git refs, rather than provided as a separate argument.

48
MCQmedium

You are using a module that depends on a specific provider version. Where should this constraint be defined?

A.In the 'required_providers' block within the module's 'terraform' block.
B.In the 'provider' block of the calling root module.
C.In the root module only.
D.In a shell environment variable.
AnswerA

Provider version constraints belong in the required_providers block inside the terraform block, which each module declares independently. Placing it there satisfies the stem's requirement, since module-level constraints are honoured during init alongside the root configuration.

Why this answer

Provider version constraints belong in the 'required_providers' block inside the module's 'terraform' block. This ensures the module declares its own compatibility requirements, which Terraform merges with the root module's constraints during initialization. It makes the module self-contained and portable.

Exam trap

The trap is confusing provider configuration ('provider' block) with provider version declaration ('required_providers'), leading candidates to place constraints in the wrong block.

How to eliminate wrong answers

Option B is wrong because the 'provider' block configures provider behavior (region, credentials) but does not declare version constraints. Option C is wrong because defining constraints only in the root module does not protect the module when reused elsewhere and violates module encapsulation. Option D is wrong because shell environment variables cannot express Terraform provider version constraints; they are used for credentials and feature flags, not dependency resolution.

49
MCQmedium

You want to use a module that is hosted in a private GitHub repository. Which authentication method should Terraform use to access it?

A.Use the same credentials that 'git clone' would use for that repository.
B.The module must be public for Terraform to access it.
C.Terraform requires a special 'git_token' variable in the module block.
D.Embed your GitHub password in the source URL.
AnswerA

Terraform's module downloader shells out to Git, so it inherits whatever credential helper or SSH key 'git clone' already uses. Supplying those same credentials satisfies the private-repository access constraint without configuring a separate Terraform-native authentication mechanism.

Why this answer

Terraform's module source supports Git URLs and delegates authentication to the underlying Git client. For a private GitHub repository, Terraform uses the same credentials that 'git clone' would use, such as SSH keys, a credential helper, or a personal access token configured in '~/.netrc' or Git config.

Exam trap

The trap is assuming Terraform has its own authentication mechanism for private modules; in reality, it relies entirely on the host's Git credential configuration.

How to eliminate wrong answers

Option B is wrong because private repositories are fully supported; Terraform does not require modules to be public. Option C is wrong because there is no 'git_token' variable in the module block; authentication is handled by Git, not Terraform-specific variables. Option D is wrong because embedding a password in the source URL is insecure, exposes credentials in state and logs, and is not the recommended mechanism.

50
Multi-Selecteasy

Which TWO of the following are true about the '.terraform' directory?

Select 2 answers
A.It contains the module source code downloaded from registries.
B.It should be committed to version control.
C.It contains the permanent state file.
D.It is created by 'terraform init'.
E.It is where custom provider binaries are compiled.
AnswersA, D

The `.terraform` directory caches modules fetched from registries into `.terraform/modules`, so remote module source code is available locally without re-downloading during each run. This satisfies the stem's requirement that downloaded module source code resides there, distinct from provider plugins stored under `.terraform/providers`.

Why this answer

Option A is correct because the .terraform directory is where terraform init caches downloaded modules, including module source code fetched from registries or other sources, under .terraform/modules. Option D is correct because terraform init is the command that initializes a working directory and creates/populates the .terraform directory with providers, modules, and backend metadata. Option B is not correct because .terraform is a local cache that should be excluded from version control, typically via .gitignore.

Option C is not correct because the state file (terraform.tfstate) is stored in the working directory or a configured remote backend, not permanently inside .terraform. Option E is not correct because provider binaries are downloaded as precompiled plugins into .terraform/providers, not compiled there.

Exam trap

The trap is confusing the .terraform directory with the state file location — candidates often think the state lives inside .terraform, but it is actually terraform.tfstate or a remote backend.

51
MCQhard

When should you use the 'for_each' meta-argument within a module?

A.To iterate over a list of variables inside a resource block.
B.To replace the 'count' meta-argument when you only need one instance.
C.To instantiate multiple copies of a module based on a map or set.
D.To speed up the execution of the provider.
AnswerC

for_each accepts a map or set and creates one module instance per element, keyed by that element. This satisfies the scenario's need to instantiate multiple module copies from a map or set rather than a fixed count.

Why this answer

The for_each meta-argument is used to instantiate multiple copies of a resource or module based on a map or set of values, giving each instance a stable key. When applied to a module block, it creates one module instance per element in the map or set, and each instance can be referenced by its key. This is the correct use case for for_each within a module.

Exam trap

The trap is conflating for_each with count — candidates often think for_each is just a list iterator, but the exam expects you to know it works on maps/sets and is used for stable, keyed module instantiation.

How to eliminate wrong answers

Option A is wrong because iterating over a list of variables inside a resource block is done with dynamic blocks or count, not for_each on a module; for_each on a module creates module instances, not internal iterations. Option B is wrong because for_each is not a replacement for count when you need one instance — count = 1 is simpler and for_each is specifically for multiple instances keyed by a map or set. Option D is wrong because for_each has no effect on provider execution speed; it is a structural meta-argument, not a performance tuning feature.

52
MCQeasy

You have a root module that calls a child module to create an AWS VPC. The child module defines an output named `vpc_id`. After running `terraform apply`, where can you find the value of `vpc_id`?

A.Only in the child module's source directory as a generated file named `outputs.tfstate`.
B.Automatically in the root module's outputs, because Terraform promotes all child module outputs to the root level.
C.In the AWS provider's response to the `terraform apply` command, which lists all outputs from all modules.
D.In the root module's state file, under the child module's outputs, and accessible via `terraform output` if the root module also declares an output that references it.
AnswerD

Child module outputs are stored in the state file under the module's path. To make them visible at the root level via `terraform output`, the root module must declare its own output that references the child module's output. This is the standard way to expose child module values to the user or to other parts of the configuration.

Why this answer

Child module outputs are stored in the state file under the module's address, but they are only displayed by `terraform output` if the root module declares an output that references them. This explicit referencing is how Terraform maintains a clear contract between modules and the root configuration, and it allows consumers to control what is exposed.

Exam trap

The trap here is assuming that child module outputs are automatically visible at the root level without an explicit output declaration in the root module.

53
MCQeasy

What command do you run to view the documentation for a module that has been published to a registry?

A.'terraform view-module'
B.'terraform show'
C.Check the registry website for the module page.
D.'terraform doc <module>'
AnswerC

Checking the registry website displays the module's published documentation, including inputs, outputs and usage examples, directly satisfying the requirement to view docs for a registry-published module. It avoids needing local installation or source access, since the registry page is the authoritative, versioned reference for that module.

Why this answer

Terraform has no CLI subcommand for browsing registry module documentation; the canonical way to read a published module's inputs, outputs, and examples is the registry website page for that module. The registry renders the README and auto-generates the input/output tables from the module's variables.tf and outputs.tf. So checking the registry website is the correct answer.

Exam trap

The trap is inventing a CLI command — candidates assume Terraform must have a 'doc' or 'view-module' subcommand like other tools, when documentation for registry modules lives only on the web.

How to eliminate wrong answers

Option A is wrong because 'terraform view-module' is not a real Terraform command — no such subcommand exists. Option B is wrong because 'terraform show' displays state or a saved plan file, not module documentation. Option D is wrong because 'terraform doc <module>' is not a valid command; Terraform has no built-in documentation browser for registry modules.

54
MCQhard

You are developing a module that creates an AWS Lambda function. The module accepts a variable `environment` and must set the Lambda function's `environment` block with variables `LOG_LEVEL` and `STAGE`. The values for these should be derived from the `environment` variable. How should you define the variable and use it within the module?

A.Define variable "environment" { type = map(string) } and set environment { variables = var.environment }
B.Define variable "environment" { type = object({ log_level = string, stage = string }) } and set environment { variables = { LOG_LEVEL = var.environment.log_level, STAGE = var.environment.stage } }
C.Define variable "environment" { type = string } and inside the resource, set environment { variables = { LOG_LEVEL = var.environment, STAGE = var.environment } }
D.Define variable "environment" { type = string } and set environment { variables = { LOG_LEVEL = "debug", STAGE = var.environment } }
AnswerB

Using an object type allows the caller to pass a structured value containing both log_level and stage. This provides flexibility and clarity, ensuring that each environment variable gets the appropriate value. It also enables validation and documentation of the expected inputs. This is a robust way to handle multiple related settings derived from a single variable.

Why this answer

Using an object type for the environment variable allows the caller to specify both log_level and stage explicitly, ensuring each Lambda environment variable receives the correct value. This approach is type-safe, self-documenting, and flexible. It also enables validation rules if needed.

The other options either hardcode values, use overly permissive types, or incorrectly assign the same value to both settings.

Exam trap

The trap here is assuming that a simple string variable is sufficient when multiple distinct values need to be derived, leading to hardcoded or incorrect assignments.

55
MCQhard

Why would you use a 'locals' block within a module instead of passing everything through variables?

A.To reduce repetition and perform transformations on inputs.
B.To allow the user to override internal values easily.
C.To bypass input validation.
D.To store state data securely.
AnswerA

A locals block names intermediate expressions once, then reuses them throughout the module, cutting duplication and letting you transform variable inputs. It satisfies the goal of avoiding repeated inline expressions without expanding the module's public variable interface.

Why this answer

A 'locals' block lets you define named expressions that can be reused throughout the module, reducing repetition and enabling transformations (e.g., string concatenation, merging maps, computing tags) on input variables. Locals are computed once per plan and referenced as 'local.name'.

Exam trap

The trap is assuming locals are externally configurable like variables; in fact, locals are internal-only and cannot be overridden by module callers.

How to eliminate wrong answers

Option B is wrong because locals are internal to the module and cannot be overridden by callers; only variables are inputs. Option C is wrong because locals do not bypass input validation; variable validation blocks still run on inputs before locals are evaluated. Option D is wrong because locals are stored in memory during plan/apply and are not a secure storage mechanism; sensitive data should use variables marked 'sensitive' or a secrets manager.

56
MCQmedium

What is the impact of using 'sensitive = true' in a module output?

A.It disables the ability to use that output in other modules.
B.It encrypts the data in the state file.
C.It automatically rotates the sensitive credentials.
D.It prevents the value from being printed in the CLI output.
AnswerD

Marking an output sensitive makes Terraform redact its value, displaying "(sensitive value)" in plan and apply output rather than the plaintext. It satisfies the constraint of hiding secrets from CLI display, though the value still exists in state.

Why this answer

Marking an output as 'sensitive = true' tells Terraform to redact the value from CLI output, including plan and apply logs, so credentials are not displayed on screen. The value is still stored in state and can still be consumed by other modules or resources. It is a display-level protection, not an encryption or access-control mechanism.

Exam trap

The trap here is confusing 'sensitive = true' with state encryption or access control — candidates often assume it encrypts the value, when it only redacts CLI display.

How to eliminate wrong answers

Option A is wrong because sensitive outputs can still be referenced by other modules; sensitivity only affects display, not usability. Option B is wrong because 'sensitive = true' does not encrypt state — state encryption requires backend-level encryption (e.g., S3 SSE, Terraform Cloud). Option C is wrong because Terraform does not rotate credentials; rotation is handled by external systems like Vault or AWS Secrets Manager.

57
Multi-Selectmedium

You are designing a reusable Terraform module that will be consumed by multiple teams. To ensure the module is maintainable and follows best practices, which TWO of the following should you implement? (Choose two.)

Select 2 answers
A.Use a consistent naming convention and tag all resources with a module identifier.
B.Hardcode the AWS region and account ID to simplify configuration for consumers.
C.Expose all internal resources as outputs so consumers can reference them directly.
D.Include a `terraform.tfstate` file in the module repository to track state.
E.Provide a README that documents the module's purpose, inputs, outputs, and usage examples.
AnswersA, E

Consistent naming and tagging improve resource organization, cost allocation, and troubleshooting. Tagging with a module identifier helps identify which module created which resources, especially in shared AWS accounts. This practice aids in auditing and automation. It is widely recommended for reusable modules to maintain clarity and operational efficiency.

Why this answer

The correct practices are providing thorough documentation and using consistent naming and tagging. Documentation ensures consumers can use the module effectively, while naming and tagging aid in management and identification. These practices enhance maintainability, reduce errors, and support operational excellence in reusable modules.

Exam trap

The trap here is thinking that exposing all resources as outputs is helpful, when it actually increases coupling and hinders module evolution.

58
MCQmedium

When a module is updated, how do you ensure that all environments using this module get the update?

A.The module updates automatically on every 'apply'.
B.Run 'terraform init -upgrade' to fetch the latest version allowed by the constraints.
C.Delete the state file and re-run apply.
D.Restart the Terraform CLI process.
AnswerB

terraform init -upgrade re-resolves module version constraints and downloads the newest release each constraint permits, refreshing the cached module. This propagates the update to every environment that runs it, satisfying the requirement that all consumers receive the new version.

Why this answer

Terraform modules sourced from a registry or VCS are cached locally after the first 'terraform init'. To pull a newer version that satisfies the version constraint in the module block, you must run 'terraform init -upgrade', which re-resolves the version and downloads the latest allowed release. Without this flag, Terraform reuses the cached module even if a newer version exists, so environments won't pick up the update.

Exam trap

The trap here is assuming that Terraform automatically fetches the latest module version on every apply or that deleting the state file forces a refresh, when in reality module updates require an explicit 'terraform init -upgrade' to bypass the local cache.

How to eliminate wrong answers

Option A is wrong because Terraform does not automatically re-fetch module versions on every 'apply'; it uses the cached module from the last 'init' unless explicitly told to upgrade. Option C is wrong because deleting the state file does not affect module source code retrieval and would destroy tracking of existing resources, causing Terraform to attempt to recreate everything. Option D is wrong because restarting the CLI has no effect on module caching or version resolution; the cache persists on disk and the same version is used.

Ready to test yourself?

Try a timed practice session using only Terraform Modules questions.