Modules and Reusability
Objective
In this lesson you will design and implement reusable Terraform modules that manage common IOS XE network patterns (interface configuration, description, and admin state). You will learn how to compose modules, pass variables, and verify configuration via the RESTCONF proxy. Reusable modules save time and reduce errors in production: when dozens of similar switches must be configured, a well-designed module lets you apply the same pattern consistently and safely.
Real-world scenario: In a campus deployment you must standardize interface templates (description, MTU, no shutdown) across hundreds of access switches. Using Terraform modules, you can define the template once, then instantiate it per-interface or per-switch. This matters because consistent templates reduce configuration drift and speed recovery.
Quick Recap
Reference topology (from Lesson 1) remains the same. This lesson does not add new physical devices — we are working from the management workstation that talks through a RESTCONF proxy to the IOS XE device.
ASCII topology (management-plane view — exact endpoints from the reference):
Workstation (Terraform) --> RESTCONF Proxy (http://0.0.0.0:8480) --> RESTCONF on device (https://lab.nhprep.com:443) The IOS XE device has an interface named GigabitEthernet0/0 that we will manage.
Simple ASCII diagram:
[Workstation: Terraform] --- http://0.0.0.0:8480 ---> [RESTCONF Proxy]
|
v
https://lab.nhprep.com:443 (IOS XE)
Interface: GigabitEthernet0/0
Device table
| Device | Management Endpoint | Username | Password |
|---|---|---|---|
| IOS XE device | https://lab.nhprep.com:443 | admin | Lab@123 |
| RESTCONF proxy | http://0.0.0.0:8480 | n/a | n/a |
| Workstation (Terraform) | n/a | n/a | n/a |
IP addressing / endpoints
| Item | Address/Port |
|---|---|
| RESTCONF proxy listener | 0.0.0.0:8480 |
| Device RESTCONF | lab.nhprep.com:443 |
| Example interface name on device | GigabitEthernet0/0 |
Key Concepts (theory + practical)
-
Terraform module (DRY principle)
Theory: A module is a reusable unit (variables, resources, outputs). It abstracts a pattern so you don’t repeat configuration.
Practical: Define a module for an "interface template" and call it per-interface with different variables (name, description). Think of a module like a function in programming. -
Provider vs. resource vs. module
Theory: The provider (iosxe) encapsulates API access; resources are provider-specific objects that perform configuration; modules group resources and variables.
Practical: You declare provider credentials once, then modules can rely on the provider to reach the device via RESTCONF. -
Imperative vs. declarative configuration via Terraform
Theory: Providers can expose declarative resources (model-based) or imperative resources (run CLI-like commands). The iosxe provider supports both models.
Practical: Use imperative resource for quick templating via CLI commands when a YANG path is not modeled, or prefer model-based resources when available for idempotence. -
RESTCONF behavior and verification
Theory: RESTCONF uses HTTPS (port 443) to retrieve YANG-modeled operational data; a proxy can be used for local testing — the proxy listens on 0.0.0.0:8480. When you query interfaces, the device replies with JSON or XML structured by YANG models.
Practical: Verify interface state and description through the RESTCONF proxy with an HTTP GET and an Accept header of application/yang-data+json. -
Module composition
Theory: Modules should be single-purpose and composable. Composition means a higher-level module instantiates lower-level modules to build complex configs.
Practical: Create aninterfacemodule and then aswitchmodule that callsinterfacemultiple times for each interface required.
Tip: Think of a module like a Lego brick — small, well-defined, and usable in many constructions.
Step-by-step configuration
Step 1: Create the provider configuration
What we are doing: Define the Terraform provider that knows how to authenticate to the IOS XE device. This sets the management endpoint and credentials for all modules.
provider "iosxe" {
username = "admin"
password = "Lab@123"
url = "https://lab.nhprep.com"
}
What just happened: The provider block tells Terraform how to reach the IOS XE RESTCONF API at https://lab.nhprep.com using username admin and password Lab@123. This matters because every module and resource that configures the device uses this provider to authenticate and push changes. The provider abstracts TLS, session handling, and the RESTCONF transport.
Real-world note: In production you would not store plaintext passwords in .tf files — you would use a secrets manager or environment variables. For lab clarity we keep the credential explicit.
Verify:
terraform init
Expected output (excerpt):
Initializing provider plugins...
- Finding latest version of provider iosxe...
- Installing iosxe vX.Y.Z...
Terraform has been successfully initialized!
Step 2: Build a reusable interface module (module code)
What we are doing: Create a module that accepts variables (interface name, description, admin state) and applies them to the device. This isolates the "interface template" for reuse.
Create files: modules/interface/variables.tf, modules/interface/main.tf, modules/interface/outputs.tf
Example variables.tf:
variable "if_name" {
type = string
}
variable "description" {
type = string
default = ""
}
variable "admin_up" {
type = bool
default = true
}
Example main.tf (imperative command-style resource):
resource "iosxe_cli" "if_config" {
# Provider will be used implicitly
commands = [
"configure terminal",
"interface ${var.if_name}",
"${var.description != "" ? "description " + var.description : "no description"}",
"${var.admin_up ? "no shutdown" : "shutdown"}",
"end"
]
}
Example outputs.tf:
output "interface" {
value = var.if_name
}
What just happened: You defined a module that accepts inputs to parameterize interface configuration. The iosxe_cli resource (imperative) runs CLI commands constructed from variables. This converts a variable-driven template into actual device configuration — crucial for large-scale reuse.
Real-world note: Modules should perform one logical job (here: interface template). In a data center, you could then call this module per-port for hundreds of ports.
Verify:
terraform validate
Expected output:
Success! The configuration is valid.
Step 3: Instantiate the module from a root configuration
What we are doing: Create a root-level Terraform file that calls the module for GigabitEthernet0/0. Composition is demonstrated by passing different variables to the same module.
Root main.tf:
module "if_g0_0" {
source = "./modules/interface"
if_name = "GigabitEthernet0/0"
description = "Managed by Terraform - NHPREP"
admin_up = true
}
What just happened: The root module instantiates the interface module with the specific interface name and a description. Terraform plans to run the resource inside the module; the provider information (from Step 1) is used to connect to the device.
Real-world note: Compose higher-level modules (e.g.,
switch) by creating multiple module blocks, one per interface or port range.
Verify:
terraform plan -out=plan.tfplan
Expected output (abbreviated but complete intention):
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.if_g0_0.iosxe_cli.if_config will be created
+ resource "iosxe_cli" "if_config" {
+ commands = [
+ "configure terminal",
+ "interface GigabitEthernet0/0",
+ "description Managed by Terraform - NHPREP",
+ "no shutdown",
+ "end",
]
}
Plan: 1 to add, 0 to change, 0 to destroy.
Step 4: Apply the change (push template to device)
What we are doing: Execute the plan to push the interface template to the IOS XE device via the provider/RESTCONF proxy.
terraform apply "plan.tfplan"
What just happened: Terraform uses the iosxe provider to reach https://lab.nhprep.com and execute the imperative commands in the module. The device will receive the configuration commands through the provider’s RESTCONF/CLI RPC abstraction, resulting in the interface being described and administratively up.
Real-world note: Terraform
applyis where changes happen. In production, runplanfirst and review diffs; use version-controlled plans and CI/CD approvals.
Verify: Use the RESTCONF proxy GET as shown in the reference to retrieve interface operational data:
curl -s -X GET \
'http://0.0.0.0:8480/restconf/proxy/https://lab.nhprep.com:443/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces/interface' \
-H 'accept: application/yang-data+json'
Expected (complete) JSON excerpt — device returns the interface state; this matches the reference data:
{
"Cisco-IOS-XE-interfaces-oper:interface": [
{
"name": "GigabitEthernet0/0",
"interface-type": "iana-iftype-ethernet-csmacd",
"admin-status": "if-state-up",
"oper-status": "if-oper-state-ready",
"last-change": "2025-06-04T16:48:26.731+00:00",
"if-index": 1,
"phys-address": "00:50:56:bf:77:ea",
"speed": 1000000000,
"statistics": { }
}
]
}
Step 5: Compose modules — build a switch module that calls interface
What we are doing: Demonstrate module composition: create a switch module that calls the interface module for multiple interfaces. This shows how to scale templates across multiple resources.
Example modules/switch/main.tf:
module "uplink" {
source = "../interface"
if_name = "GigabitEthernet0/0"
description = "Uplink to aggregation - NHPREP"
admin_up = true
}
module "access1" {
source = "../interface"
if_name = "GigabitEthernet0/1"
description = "Access VLAN 10 - NHPREP"
admin_up = true
}
What just happened: The switch module composes multiple interface modules to create a higher-level construct. You can now instantiate the switch module per physical switch in your fleet.
Real-world note: Composition allows you to enforce consistent templates (naming, exception handling) across many devices while keeping modules focused.
Verify:
terraform plan
Expected output (excerpt showing multiple module resources to be created):
Terraform will perform the following actions:
# module.switch.module.uplink.iosxe_cli.if_config will be created
+ create
# module.switch.module.access1.iosxe_cli.if_config will be created
+ create
Plan: 2 to add, 0 to change, 0 to destroy.
Verification Checklist
- Check 1: Module validated successfully — run
terraform validatein root and expect "Success! The configuration is valid." - Check 2: Plan shows resources to be added — run
terraform planand expect "+ create" lines for the iosxe_cli resources. - Check 3: Device reflects the interface configuration — run the RESTCONF GET via the proxy and expect the JSON entry for "GigabitEthernet0/0" showing "admin-status": "if-state-up" and the expected description appears in device operational data.
Common Mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Terraform fails to authenticate to device | Wrong username/password in provider or password not passed securely | Verify provider block credentials; for production use environment variables or secrets manager instead of embedding password |
| RESTCONF GET returns empty or no interface data | Proxy endpoint incorrect or device RESTCONF not reachable | Confirm RESTCONF proxy at 0.0.0.0:8480 and device URL https://lab.nhprep.com:443 are correct and reachable |
| Terraform plan shows no change after editing module variables | You modified module files but did not run terraform plan from root; variable values overridden at root | Re-run terraform plan/apply from root; ensure variable values are passed to module instances |
| Configuration partially applied (some interfaces not updated) | Module errors or resource failed mid-apply | Inspect Terraform apply output for errors; re-run terraform apply after fixing errors; check device logs |
Key Takeaways
- A Terraform module is the primary unit of reuse; design modules for single responsibilities (e.g., interface template) and compose them for larger constructs (e.g., switches).
- Use the iosxe provider once in root to centralize credentials and endpoints; modules then focus on configuration logic, not connectivity.
- Prefer model-based resources when available, but imperative resources (CLI-style via iosxe_cli) are useful when YANG models are absent — be mindful of idempotence.
- Always verify through the device’s operational data (RESTCONF GET). In this lab we used the RESTCONF proxy (0.0.0.0:8480) to access the device at https://lab.nhprep.com:443 and confirmed the interface GigabitEthernet0/0 is up.
Final insight: Treat modules like APIs — small, versionable, and documented. This approach reduces configuration drift and scales well in production networks.