We started to use phpIPAM in a customer project to use it as IP management system for the whole automation. Reasons to choose were
- provide a rice set an ipam functionality
- can be connected to foreman as an external ipam
- provide a API to manage entities
We quickly realize that there were no ready to use ansible modules. So We have started to use the API in conjunction of the ansible module “url”. But this was a mess because we have to implement the whole logic of CRUD operations in complex ansible tasks. So @cmeissner begun to develop an ansible collection with modules for most of the entities managable by API.
installation
The collection is hosted on galaxy, so you can simply use ansible-galaxy
to install it.
ansible-galaxy collection install codeaffen.phpipam
Alternatively you can install it from source.
git clone https://github.com/codeaffen/phpipam-ansible-modules.git
cd phpipam-ansible-modules
make dist
ansible-galaxy collection install codeaffen-phpipam-<version>.tar.gz
concept of names
Note: Relations between entities are managed via its entities ids in phpIPAM. These entities ids are not shown in UI so it’s difficult to get these ids.
To make the use of our modules as comfortable as possible we decided to implement an auto resolve mechanism to translate entities names to its entities ids. You will never need to use an id in our modules, you simply use its name.
connection parameters
To connect to the phpIPAM api via ansible you have to provide at least the parameters mentioned in the table below.
Parameter | Description | Default |
---|---|---|
server_url | URL of the phpIPAM server | |
username | Username to access phpIPAM server | |
password | Password of the user to access phpIPAM server | |
app_id | API app name | ansible |
As app_id
has a default it’s not absolutly nessecary but good to know what is the default. This app_id has to be created before running ansible against phpIPAM API to prevent any errors.
implicite actions
As described in concept of names this feature runs automatically or implicit. Another action which runs implicit is the API connection. If you define a task to create a phpIPAM entity you also define the connection parameters. If the tasks runs it creates a api connection and after that it lets magic happens.
a simple example
As ansible to most of the CRUD stuff under the hood we don’t show how to do each step separately. We want to show a simple example of managing a subnet. This example is part of our CRUD tests. The directory structure for the test looks should look like this:
tests/
├── inventory
│ └── hosts
└── test_playbooks
├── subnet.yml
├── tasks
│ ├── subnet.yml
└── vars
├── server.yml
└── subnet.yml
the task
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
---
- name: "Ensure state of subnet: {{ name }}"
subnet:
server_url: "{{ phpipam_server_url }}"
app_id: "{{ phpipam_app_id }}"
username: "{{ phpipam_username }}"
password: "{{ phpipam_password }}"
cidr: "{{ subnet.cidr | default(omit) }}"
subnet: "{{ subnet.subnet | default(omit) }}"
mask: "{{ subnet.mask | default(omit) }}"
description: "{{ subnet.description | default(omit) }}"
section: "{{ subnet.section | default(omit) }}"
linked_subnet: "{{ subnet.linked_subnet | default(omit) }}"
vlan_id: "{{ subnet.vlan_id | default(omit) }}"
vrf_id: "{{ subnet.vrf_id | default(omit) }}"
parent: "{{ subnet.parent | default(omit) }}"
nameserver: "{{ subnet.nameserver | default(omit) }}"
show_as_name: "{{ subnet.show_as_name | default(omit) }}"
permissions: "{{ subnet.permissions | default(omit) }}"
dns_recursive: "{{ subnet.dns_recursive | default(omit) }}"
dns_records: "{{ subnet.dns_records | default(omit) }}"
allow_requests: "{{ subnet.allow_requests | default(omit) }}"
scan_agent: "{{ subnet.scan_agent | default(omit) }}"
ping_subnet: "{{ subnet.ping_subnet | default(omit) }}"
discover_subnet: "{{ subnet.discover_subnet | default(omit) }}"
is_folder: "{{ subnet.is_folder | default(omit) }}"
is_full: "{{ subnet.is_full | default(omit) }}"
subnetr_state: "{{ subnet.subnet_state | default(omit) }}"
threshold: "{{ subnet.threshold | default(omit) }}"
location: "{{ subnet.location | default(omit) }}"
state: "{{ subnet.state | default('present') }}"
As you can see all parameters will be filled from variables. Most of the parameters will be omitted if they are undefined or empty.
the vars
To let provide some data to the task you can put your test data in a variable files like that.
subnet.yml
1
2
3
4
---
base_subnet_data:
cidr: 10.0.0.0/24
section: "Customers"
If you define your own data you have to guarantee that the section
you put in here already exists.
server.yml
1
2
3
4
5
---
phpipam_server_url: "https://ipam.example.com"
phpipam_app_id: ansible
phpipam_username: test
phpipam_password: "test123"
the play
After you had created the task and the vars file you can put all together in a playbook like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
- hosts: localhost
collections:
- codeaffen.phpipam
gather_facts: false
vars_files:
- vars/server.yml
- vars/subnet.yml
tasks:
- name: create subnet
include: tasks/subnet.yml
vars:
name: create subnet
subnet: "{{ base_subnet_data }}"
- name: create subnet again, no change
include: tasks/subnet.yml
vars:
name: create subnet again, no change
subnet: "{{ base_subnet_data }}"
- name: delete subnet
include: tasks/subnet.yml
vars:
name: delete subnet
override:
state: absent
subnet: "{{ base_subnet_data | combine(override) }}"
What does this playbook do?
- it sets the collection namespace to
codeaffen.phpipam
to use simply the module name without writing the fully qualified module name. - it loads server.yml and subnet.yml. There are the variables for the tasks defined.
- it creates a subnet.
- it run the create block again. Here should nothing be changed
- it updates data of the subnet.
- it deletes the test subnet.
If you run this playbook the recap should look like this:
$ ansible-playbook --inventory tests/inventory/hosts tests/test_playbooks/subnet.yml
PLAY [localhost] ************************************************************************************************************************************
TASK [Ensure state of subnet: create subnet] ********************************************************************************************************
changed: [localhost]
TASK [Ensure state of subnet: create subnet again, no change] ***************************************************************************************
ok: [localhost]
TASK [Ensure state of subnet: delete subnet] ********************************************************************************************************
changed: [localhost]
PLAY RECAP ******************************************************************************************************************************************
localhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
further resources
To get more information you can use the following sources: