Skip to content

Commit

Permalink
Merge branch 'main' into issue-29
Browse files Browse the repository at this point in the history
  • Loading branch information
mtarking committed Apr 8, 2024
2 parents 0d033a1 + ac4e1cb commit c559e63
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 17 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Change Log

## v1.2.1 (2023-12-13)

### BUG FIXING
- added CHANGELOG.md

## v1.2.0 (2023-12-13)

### FEATURE ENHANCEMENT
- added PATty functionality
90 changes: 75 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,98 @@ Cisco Modeling Labs is an on-premise network simulation tool that runs on workst

## Inventory

To use the dynamic inventory plugin, the environmental variables must be set and a file (e.g. `cml.yml`) placed in the inventory specifying the plugin information:

```
plugin: cisco.cml.cml_inventory
```

The dynamic inventory script will then return information about the nodes in the
lab:

```
ok: [hq-host1] => {
ok: [hq-rtr1] => {
"cml_facts": {
"config": "#cloud-config\npassword: admin\nchpasswd: { expire: False }\nssh_pwauth: True\nhostname: hq-host1\nruncmd:\n - sudo ip address add 10.0.1.10/24 dev enp0s2\n - sudo ip link set dev enp0s2 up\n - sudo ip route add default via 10.0.1.1\n",
"cpus": null,
"config": "hostname hq-rtr1\nvrf definition Mgmt-intf\n!\naddress-family ipv4\nexit-address-family\n!\naddress-family ipv6\nexit-address-family\n!\nusername admin privilege 15 secret 0 admin\ncdp run\nno aaa new-model\nip domain-name mdd.cisco.com\n!\ninterface GigabitEthernet1\nvrf forwarding Mgmt-intf\nip address dhcp\nnegotiation auto\nno cdp enable\nno shutdown\n!\ninterface GigabitEthernet2\ncdp enable\n!\ninterface GigabitEthernet3\ncdp enable\n!\ninterface GigabitEthernet4\ncdp enable\n!\nip http server\nip http secure-server\nip http max-connections 2\n!\nip ssh time-out 60\nip ssh version 2\nip ssh server algorithm encryption aes128-ctr aes192-ctr aes256-ctr\nip ssh client algorithm encryption aes128-ctr aes192-ctr aes256-ctr\n!\nline vty 0 4\nexec-timeout 30 0\nabsolute-timeout 60\nsession-limit 16\nlogin local\ntransport input ssh\n!\nend",
"cpus": 1,
"data_volume": null,
"image_definition": "ubuntu-18-04",
"image_definition": null,
"interfaces": [
{
"ipv4_addresses": [],
"ipv4_addresses": null,
"ipv6_addresses": null,
"mac_address": null,
"name": "Loopback0",
"state": "STARTED"
},
{
"ipv4_addresses": [
"192.168.255.199"
],
"ipv6_addresses": [],
"mac_address": "52:54:00:13:1b:fb",
"name": "enp0s2",
"mac_address": "52:54:00:13:51:66",
"name": "GigabitEthernet1",
"state": "STARTED"
}
],
"node_definition": "ubuntu",
"ram": null,
"node_definition": "csr1000v",
"ram": 3072,
"state": "BOOTED"
}
}
```

The first IPv4 address found (in order of the interfaces) is used as `ansible_host` to enable the playbook to connect to the device.

To use the CML dynamic inventory plugin, the environmental variables must be set and a file (e.g. `cml.yml`) placed in the inventory specifying the plugin information:

```
plugin: cisco.cml.cml_inventory
group_tags: network, ios, nxos, router
```

Options:

`plugin:` Specfies the name of the inventory plugin
`group_tags:` The group tags that, if one or more are found in a CML device tags, will create an Ansible group of the same name

To create an Ansible group, specify a device tag in CML:

![CML Tag Example](cml_group_tag.png?raw=true "CML Tag Example")

When the CML dynamic inventory plugin runs, it will create a router group with all of the devices that have that tag:
```
mdd % ansible-playbook cisco.cml.inventory --limit=router
PLAY [cml_hosts] ******************************************************************************************************************************
TASK [debug] **********************************************************************************************************************************
ok: [hq-rtr1] => {
"msg": "Node: hq-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.199:22"
}
ok: [hq-rtr2] => {
"msg": "Node: hq-rtr2(csr1000v), State: BOOTED, Address: 192.168.255.53:22"
}
ok: [site1-rtr1] => {
"msg": "Node: site1-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.63:22"
}
ok: [site2-rtr1] => {
"msg": "Node: site2-rtr1(csr1000v), State: BOOTED, Address: 192.168.255.7:22"
}
PLAY RECAP ************************************************************************************************************************************
hq-rtr1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
hq-rtr2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
site1-rtr1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
site2-rtr1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
```

In addition to group tags, the CML dynamic inventory plugin will also parse tags to pass information from PATty and to create
generic inventory facts.

![PAT Tag Example](cml_pat_tags.png?raw=true "PAT Tag Example")

If a CML tag is specified that matches `^pat:(?:tcp|udp)?:?(\d+):(\d+)`, the CML server address (as opposed to the first IPv4 address found)
will be used for `ansible_host`. To change `ansible_port` to point to the translated SSH port, the tag `ansible:ansible_port=2020` can
be set. These two tags tell the Ansible playbook to connect to port 2020 of the CML server to automate the specified host in the topology.
The `ansible:` tag can also be used to specify other host facts. For example, the tag `ansible:nso_api_port=2021` can be used to tell the
playbook the port to use to reach the Cisco NSO API. Any arbitrary fact can be set in this way.


## Collection Playbooks

### `cisco.cml.build`
Expand Down
Binary file added cml_group_tag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cml_pat_tags.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace: cisco
name: cml

# The version of the collection. Must be compatible with semantic versioning
version: 1.1.0
version: 1.2.1

# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
Expand Down
1 change: 1 addition & 0 deletions playbooks/clean.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# yaml-language-server: $schema=https://json.schemastore.org/ansible-playbook.json
- hosts: localhost
gather_facts: no
connection: local
Expand Down
3 changes: 2 additions & 1 deletion playbooks/inventory.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# yaml-language-server: $schema=https://json.schemastore.org/ansible-playbook.json
- hosts: cml_hosts
connection: local
gather_facts: no
tasks:
- debug:
msg: "Node: {{ inventory_hostname }}({{ cml_facts.node_definition }}), State: {{ cml_facts.state }}, Address: {{ ansible_host }}"
msg: "Node: {{ inventory_hostname }}({{ cml_facts.node_definition }}), State: {{ cml_facts.state }}, Address: {{ ansible_host }}:{{ ansible_port | default('22') }}"
when: ansible_host is defined
# - debug:
# var: cml_facts
Expand Down
22 changes: 22 additions & 0 deletions plugins/inventory/cml_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

import os
import traceback
import re
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils._text import to_text
Expand Down Expand Up @@ -185,6 +186,25 @@ def parse(self, inventory, loader, path, cache=True):
}
interface_list = []
ansible_host = None
ansible_port = None
# pat_regex_list = [r"^pat:tcp:(\d+):22", r"^pat:(\d+):22"]
for tag in node.tags():
fact_match = re.search(r"^ansible:([^=]+)=(\d+)$", tag)
pat_match = re.search(r"^pat:(?:tcp|udp)?:?(\d+):(\d+)", tag)
if fact_match:
self.display.vvv("Add fact to node {0}: {1}={2}".format(node.label, fact_match.group(1), fact_match.group(2)))
self.inventory.set_variable(node.label, fact_match.group(1), fact_match.group(2))
# for regex_pattern in pat_regex_list:
# # Use re.search to find a match
elif pat_match:
self.display.vvv("Found PAT: outside_port={0}, inside_port={1}".format(pat_match.group(1), pat_match.group(2)))
# Extract values from capture groups
# outside_port = match.group(1)
ansible_host = self.host
# ansible_port = match.group(1)
# break # Exit the inner loop once a match is found
else:
continue # Continue with the next string if no match was found
for interface in node.interfaces():
if node.state == 'BOOTED':
# Fill out the oper data if the node is not fully booted
Expand All @@ -211,6 +231,8 @@ def parse(self, inventory, loader, path, cache=True):
cml.update({'interfaces': interface_list})
if ansible_host:
self.inventory.set_variable(node.label, 'ansible_host', ansible_host)
if ansible_port:
self.inventory.set_variable(node.label, 'ansible_port', ansible_port)
self.inventory.set_variable(node.label, 'cml_facts', cml)
self.display.vvv("Adding {0}({1}) to group {2}, state: {3}, ansible_host: {4}".format(
node.label, node.node_definition, self.group, node.state, ansible_host))
Expand Down

0 comments on commit c559e63

Please sign in to comment.