Examples

Here are some examples that demonstrate how to use Ansible modules for common operations on FTD devices. Hopefully, they will make the process of creating your playbooks easier.

Make initial provisioning

Initially, the device is delivered in a locked state. To unlock the device and start using its API, the user needs to accept an end-user license agreement (EULA) and change the default password. Luckily, it can be done with Ansible.

The following tasks check the current state of device provisioning, displays the EULA text, and asks the user to accept it:

- name: Get provisioning info
  ftd_configuration:
    operation: getInitialProvision
    path_params:
      objId: default
    register_as: provisionInfo

- name: Show EULA text
  debug:
    msg: 'EULA details: {{ provisionInfo.eulaText }}'

- name: Confirm EULA acceptance
  pause:
    prompt: Please confirm you want to accept EULA. Press Return to continue. To abort,
      press Ctrl+C and then "A"

After getting familiar with the EULA details, this task sends a request to the device to reset the password and unlock it.

- name: Complete initial provisioning
  ftd_configuration:
    operation: addInitialProvision
    data:
      acceptEULA: true
      eulaText: '{{ provisionInfo.eulaText }}'
      type: initialprovision
      currentPassword: '{{ ansible_password }}'
      newPassword: '{{ ansible_password_temp }}'
  vars:
    ansible_command_timeout: 30

When the previous task is successfully accomplished, the device is unlocked and ready to be configured via API.

Create a NetworkObject

This example demonstrates how to create a simple entity representing a network - NetworkObject. The task creates a new object representing a fully qualified domain name (FQDN) network for Cisco DevNet website.

After creation, the network object is stored as an Ansible fact and can be accessed using networkobject_ciscodevnetnetwork variable.

- name: Create an FQDN network for Cisco DevNet
  ftd_configuration:
    operation: upsertNetworkObject
    data:
      name: CiscoDevNetNetwork
      subType: FQDN
      value: developer.cisco.com
      type: networkobject
      dnsResolution: IPV4_AND_IPV6

Create a NetworkObjectGroup

NetworkObjectGroup is a container object representing a group of networks.

The first task creates an auxiliary NetworkObject for Cisco FTD. Then, a NetworkObjectGroup, consisting of two previously created networks, is created in the second task.

Notice, that the second task, creating a NetworkObjectGroup object, references previously created NetworkObjects. It is possible because every created object is stored as an Ansible fact. By default, the fact name for a new object is constructed as $TYPE_$NAME, but it can be reset by adding a register_as parameter to the task that creates the object.

- name: Create an FQDN network for Cisco FTD
  ftd_configuration:
    operation: upsertNetworkObject
    data:
      name: CiscoFtdNetwork
      subType: FQDN
      value: ftd.cisco.com
      type: networkobject
      dnsResolution: IPV4_AND_IPV6

- name: Create a Cisco network group
  ftd_configuration:
    operation: upsertNetworkObjectGroup
    data:
      name: CiscoNetworkGroup
      type: networkobjectgroup
      objects:
      - '{{ networkobject_ciscodevnetnetwork }}'
      - '{{ networkobject_ciscoftdnetwork }}'

Create an AccessRule

An AccessRule permits or denies traffic based on different parameters, such as a protocol, a source or destination IP address, network, etc.

The example shows how to create an AccessRule allowing traffic from the previously created CiscoDevNetNetwork.

- name: Create an access rule allowing trafic from Cisco DevNet
  ftd_configuration:
    operation: upsertAccessRule
    data:
      name: AllowCiscoTraffic
      type: accessrule
      sourceNetworks:
      - '{{ networkobject_ciscodevnetnetwork }}'
      ruleAction: PERMIT
      eventLogAction: LOG_BOTH
    path_params:
      parentId: default

Make a deployment

Changes made on the FTD device are applied only after they get deployed. This example demonstrates how to start a deployment job and make sure that it succeeds.

The first two tasks fetch a list of pending changes that are not in the deployed state yet and make sure that there are at least some changes that need to be deployed. Otherwise, the playbook execution stops.

- name: Fetch pending changes
  ftd_configuration:
    operation: getBaseEntityDiffList
    register_as: pending_changes

- name: Complete playbook when nothing to deploy
  meta: end_play
  when: pending_changes | length == 0

Next, the playbook starts a new deployment job and registers returned DeploymentStatus as a deployment_job Ansible fact. The following task polls the DeploymentStatus every 3 seconds until the job is completed. The last part verifies that the deployment is completed successfully. Otherwise, it stops the playbook with an error message.

- name: Start deployment
  ftd_configuration:
    operation: addDeployment
    register_as: deployment_job

- name: Poll deployment status until the job is finished
  ftd_configuration:
    operation: getDeployment
    path_params:
      objId: '{{ deployment_job.id }}'
    register_as: deployment_status
  until: deployment_status.endTime != -1
  retries: 100
  delay: 3

- name: Stop the playbook if the deployment failed
  fail:
    msg: 'Deployment failed. Status: {{ deployment_status.statusMessages }}'
  when: deployment_status.state != 'DEPLOYED'

Configure interfaces

To enable a physical interface and update its IPv4 address with a static configuration, check the following upsert play. Notice, that it has a filters parameter to select an interface by hardware name. Usually, interfaces do not have names on fresh devices, while hardware names are always set and cannot be changed, so filtering by hardware name is preferred.

- name: Setup Inside Interface with static IP
  ftd_configuration:
    operation: upsertPhysicalInterface
    data:
      name: inside
      hardwareName: GigabitEthernet0/1
      monitorInterface: true
      ipv4:
        addressNull: false
        defaultRouteUsingDHCP: false
        dhcp: false
        ipAddress:
          ipAddress: 192.168.20.2
          netmask: 255.255.255.0
          standbyIpAddress: 192.168.20.3
          type: haipv4address
        ipType: STATIC
        type: interfaceipv4
      mtu: 1500
      enabled: true
      mode: ROUTED
      type: physicalinterface
    filters:
      hardwareName: GigabitEthernet0/1

Use host and group variables

Variables in Ansible allow making content of the playbook host- or group-specific. For example, to create a NetworkObject specific for each device, you can use the following task template with network_type and network_value variables:

- name: Create a NetworkObject based on host variables
  ftd_configuration:
    operation: addNetworkObject
    data:
      name: HostSpecificNetwork
      subType: '{{ network_type }}'
      value: '{{ network_value }}'
      isSystemDefined: false
      type: networkobject
      dnsResolution: IPV4_AND_IPV6

There are multiple ways to specify variables in Ansible. One option is to put them in the inventory file together with other host and group variables:

[vftd]
localhost ... network_value=127.0.0.1
10.89.21.213 ... network_value=130.51.181.225

[vftd:vars]
network_type=HOST

With the playbook and inventory shown above, Ansible will create a HOST network pointing to 127.0.0.1 on the localhost device, and another network for IP address 130.51.181.225 on device 10.89.21.213.

More information about variables in Ansible documentation.

More samples on GitHub

All playbook samples can be found on GitHub. We are constantly adding more examples there and highly appreciate all contributions too.