Skip to content

Device

Devices represent individual IOS-XE network devices (switches, routers) that will be configured and managed. Each device is identified by a unique name and connects via either NETCONF or RESTCONF.

Device configurations have the highest precedence in the configuration hierarchy, allowing you to override global and device group settings for specific devices. This provides fine-grained control while maintaining consistency across your network infrastructure.

Key device attributes:

  • name: Unique identifier for the device
  • url: Management URL for RESTCONF connectivity (e.g., https://10.1.1.10). Only used when protocol is set to restconf. Deprecated in favor of host
  • host: Management IP or hostname for device connectivity. Works with both NETCONF (default) and RESTCONF. Use "ip:port" syntax for custom ports (e.g., devices behind PAT)
  • protocol: Connection protocol — netconf (default) or restconf
  • managed: Boolean flag to temporarily exclude devices from configuration (useful for maintenance)
  • device_groups: List of device groups this device belongs to
  • variables: Device-specific variables that override group and global variables
  • interface_group_policy: Controls how interface groups are resolved between device_group and device layers — merge (default) or replace
  • configuration: Device-specific configuration that takes highest precedence

Devices connect using one of two protocols:

  • NETCONF (default): Set host to the device IP or hostname. The default port is 830. To use a custom NETCONF port (e.g., for devices behind PAT), specify host as "ip:port" (e.g., "10.1.1.10:10022"). The protocol attribute can be omitted since netconf is the default.
  • RESTCONF: Set host to the device IP or hostname and protocol: restconf. The default port is 443. Alternatively, use url with a full URL (e.g., https://10.1.1.10), though url is deprecated.

Device credentials are configured via the IOSXE_USERNAME and IOSXE_PASSWORD environment variables. All devices share a single credential set — per-device credentials are not currently supported.

The managed attribute can be set to false to temporarily skip a device during deployment, which is useful for maintenance windows or staged rollouts.

By default, when the NETCONF candidate datastore is enabled on the device (netconf-yang feature candidate-datastore), each resource (interface, routing protocol, ACL, etc.) is independently pushed to the candidate datastore, validated, and committed before moving on to the next resource. Without the candidate datastore, the Terraform provider edits the running configuration directly. In either case, the default behavior applies changes on a per-resource basis, meaning that a failure partway through a terraform apply can leave the device in a partially configured state.

Enabling transactional commits for devices changes this by grouping all configuration changes into a single atomic commit. All resources are staged in the candidate datastore without being committed, and only after every resource has been successfully staged does a single commit push all changes to the running configuration at once. If any resource fails during the staging phase, the commit never fires and the device remains untouched.

This is particularly useful when partial configuration would leave the device in an invalid or insecure state, when compliance requirements mandate all-or-nothing deployments, or when changes span interdependent resources that must be applied together.

The device must have the NETCONF candidate datastore feature enabled:

netconf-yang
netconf-yang feature candidate-datastore

Verify with show netconf-yang datastores — both running and candidate must be listed:

Router#show netconf-yang datastores
Datastore Name : running
Datastore Name : candidate
Router#

Without the candidate datastore, the Terraform provider falls back to editing the running configuration directly and the transaction mode has no effect.

Set device_transaction = true when invoking the Terraform module through your main.tf file:

module "iosxe" {
source = "netascode/nac-iosxe/iosxe"
yaml_directories = ["./data"]
device_transaction = true
save_config = true # Optional: persist to startup-config after commit
}

With this attribute set, all data model changes defined in your YAML files will be staged and committed as a single transaction per device — either every change is applied, or none are.

You can verify this behavior using a simple data model with multiple resources, such as three loopback interfaces:

iosxe:
devices:
- name: Router1
host: 10.1.1.1
protocol: netconf
configuration:
interfaces:
loopbacks:
- id: "301"
description: "Transaction Test 1"
ipv4:
address: 30.0.1.1
address_mask: 255.255.255.255
- id: "302"
description: "Transaction Test 2"
ipv4:
address: 30.0.2.1
address_mask: 255.255.255.255
- id: "303"
description: "Transaction Test 3"
ipv4:
address: 30.0.3.1
address_mask: 255.255.255.255

Enable the IOS-XE configuration archive to observe commit boundaries:

archive
log config
logging enable
logging size 500

After a terraform apply, run show archive log config all to inspect the commit history. Each commit is assigned a unique session number in the sess column.

In the default mode, each resource gets its own session, as shown by the show archive log config all command:

Router#show archive log config all
idx sess user@line Logged command
110 19 admin@vty42949 |interface Loopback301
111 19 admin@vty42949 | description Transaction Test 1
112 19 admin@vty42949 | no shutdown
113 19 admin@vty42949 | ip address 30.0.1.1 255.255.255.255
114 19 admin@vty42949 | exit
115 20 admin@vty42949 |interface Loopback302
116 20 admin@vty42949 | description Transaction Test 2
117 20 admin@vty42949 | no shutdown
118 20 admin@vty42949 | ip address 30.0.2.1 255.255.255.255
119 20 admin@vty42949 | exit
120 21 admin@vty42949 |interface Loopback303
121 21 admin@vty42949 | description Transaction Test 3
122 21 admin@vty42949 | no shutdown
123 21 admin@vty42949 | ip address 30.0.3.1 255.255.255.255
124 21 admin@vty42949 | exit
Router#

In the transaction mode, all resources share a single session, as shown by the show archive log config all command:

Router#show archive log config all
idx sess user@line Logged command
192 38 admin@vty42949 |interface Loopback301
193 38 admin@vty42949 | description Transaction Test 1
194 38 admin@vty42949 | no shutdown
195 38 admin@vty42949 | ip address 30.0.1.1 255.255.255.255
196 38 admin@vty42949 | exit
197 38 admin@vty42949 |interface Loopback302
198 38 admin@vty42949 | description Transaction Test 2
199 38 admin@vty42949 | no shutdown
200 38 admin@vty42949 | ip address 30.0.2.1 255.255.255.255
201 38 admin@vty42949 | exit
202 38 admin@vty42949 |interface Loopback303
203 38 admin@vty42949 | description Transaction Test 3
204 38 admin@vty42949 | no shutdown
205 38 admin@vty42949 | ip address 30.0.3.1 255.255.255.255
206 38 admin@vty42949 | exit
Router#

To demonstrate the difference in failure handling between the two modes, consider a data model where one resource references a VRF that does not exist on the device:

iosxe:
devices:
- name: Router1
host: 10.1.1.1
protocol: netconf
configuration:
interfaces:
loopbacks:
- id: "301"
description: "Transaction Test 1"
ipv4:
address: 30.0.1.1
address_mask: 255.255.255.255
- id: "302"
description: "Transaction Test 2"
ipv4:
address: 30.0.2.1
address_mask: 255.255.255.255
- id: "303"
description: "This will fail"
vrf_forwarding: "NONEXISTENT_VRF"
ipv4:
address: 30.0.3.1
address_mask: 255.255.255.255

In the default mode, Loopback301 and Loopback302 are each committed independently before Loopback303 is attempted. When Loopback303 fails, the device is left in a partially configured state — the archive log confirms that Loopback301 and Loopback302 were committed in separate sessions:

Router#show ip interface brief | include Loopback
Loopback301 30.0.1.1 YES other up up
Loopback302 30.0.2.1 YES other up up
Router#show archive log config all
idx sess user@line Logged command
237 56 admin@vty42949 |interface Loopback301
238 56 admin@vty42949 | description Transaction Test 1
239 56 admin@vty42949 | no shutdown
240 56 admin@vty42949 | ip address 30.0.1.1 255.255.255.255
241 56 admin@vty42949 | exit
242 58 admin@vty42949 |interface Loopback302
243 58 admin@vty42949 | description Transaction Test 2
244 58 admin@vty42949 | no shutdown
245 58 admin@vty42949 | ip address 30.0.2.1 255.255.255.255
246 58 admin@vty42949 | exit
Router#

In the transaction mode, all resources are staged to the candidate datastore before any commit occurs. When Loopback303 fails during staging, the iosxe_commit resource never executes and no changes are applied to the running configuration. The device remains completely untouched — the archive log confirms that no new entries were recorded:

Router#show ip interface brief | include Loopback
Router#show archive log config all
idx sess user@line Logged command
Router#

State Drift on Failed Transactional Deletes

Section titled “State Drift on Failed Transactional Deletes”

When destroying resources in transaction mode, Terraform removes each resource from its state file as the individual destroy operations succeed during the staging phase. However, the actual device configuration is not removed until the final iosxe_commit resource executes the atomic commit. If that commit fails (for example, because another resource in the same transaction has a validation error), the device retains all of its original configuration, but Terraform’s state no longer tracks the “deleted” resources.

The result is a divergence: the device still has the configuration, but Terraform believes it has been removed. A subsequent terraform plan will not detect the orphaned configuration because Terraform has no state entry to compare against.

To recover from this situation:

  1. Re-import the affected resources using terraform import to bring them back into state
  2. Alternatively, run terraform plan with the resources re-declared in your data model — Terraform will detect them as new and plan to create them (resulting in a no-op on the device since the configuration already exists)

This limitation is inherent to how Terraform processes destroy operations and is not specific to IOS-XE as Code — it applies to any provider using a deferred-commit pattern.

Diagram
NameTypeConstraintMandatoryDefault Value
nameStringYes
urlStringNo
hostStringNo
managedBooleantrue, falseNotrue
protocolChoicerestconf, netconfNo
versionStringNo
device_groupsListStringNo
variablesMapNo
templatesListStringNo
interface_group_policyChoicemerge, replaceNo

iosxe:
devices:
# Basic device configuration, where NETCONF is used (since it is the default).
- name: Access-SW-01
host: 10.1.1.10
managed: true
# Basic device configuration, where RESTCONF is used.
- name: Access-SW-02
host: 10.1.1.11
protocol: restconf
managed: true
# Basic device configuration with non-default NETCONF port.
- name: Access-SW-03
host: "10.1.11.12:9999"
managed: true
# Device with group membership and variables
- name: Core-SW-01
host: 10.1.1.1
device_groups:
- CORE_SWITCHES
- OSPF_AREA_0
variables:
router_id: "1.1.1.1"
mgmt_vlan: 100
# Device with specific configuration override
- name: Edge-SW-01
host: 10.1.1.20
device_groups: [ACCESS_SWITCHES]
configuration:
system:
hostname: EDGE-SWITCH-01
interfaces:
ethernets:
- type: GigabitEthernet
id: "1/0/1"
description: "Uplink to Core"
# Device temporarily excluded from management
- name: Maint-SW-01
host: 10.1.1.99
managed: false # Skip during maintenance

Devices represent individual IOS-XE network devices (switches, routers) that will be configured and managed. Each device is identified by a unique name and requires a management URL for connectivity.

Device configurations have the highest precedence in the configuration hierarchy, allowing you to override global and device group settings for specific devices. This provides fine-grained control while maintaining consistency across your network infrastructure.

Key device attributes:

  • name: Unique identifier for the device
  • host: Management IP for device connectivity (HTTPS/RESTCONF)
  • managed: Boolean flag to temporarily exclude devices from configuration (useful for maintenance)
  • device_groups: List of device groups this device belongs to
  • variables: Device-specific variables that override group and global variables
  • configuration: Device-specific configuration that takes highest precedence

The managed attribute can be set to false to temporarily skip a device during deployment, which is useful for maintenance windows or staged rollouts.

Diagram
NameTypeConstraintMandatoryDefault Value
nameStringYes
hostStringNo
managedBooleantrue, falseNotrue
protocolChoicerestconf, netconfNo
versionStringNo
device_groupsListStringNo
variablesMapNo
templatesListStringNo

iosxe:
devices:
# Basic device configuration
- name: Access-SW-01
host: 10.1.1.10
managed: true
# Device with group membership and variables
- name: Core-SW-01
host: 10.1.1.1
device_groups:
- CORE_SWITCHES
- OSPF_AREA_0
variables:
router_id: "1.1.1.1"
mgmt_vlan: 100
# Device with specific configuration override
- name: Edge-SW-01
host: 10.1.1.20
device_groups: [ACCESS_SWITCHES]
configuration:
system:
hostname: EDGE-SWITCH-01
interfaces:
ethernets:
- type: GigabitEthernet
id: "1/0/1"
description: "Uplink to Core"
# Device temporarily excluded from management
- name: Maint-SW-01
host: 10.1.1.99
managed: false # Skip during maintenance