Coder Social home page Coder Social logo

ansible-aci's Introduction

ansible-aci

The ansible-aci project provides an Ansible collection for managing and automating your Cisco ACI environment. It consists of a set of modules and roles for performing tasks related to ACI.

This collection has been tested and supports ACI 3.2+. Modules supporting new features introduced in ACI API in specific ACI versions might not be supported in earlier ACI releases.

Note: This collection is not compatible with versions of Ansible before v2.8.

Requirements

Ansible v2.14 or newer

Install

Ansible must be installed

sudo pip install ansible

Install the collection

ansible-galaxy collection install cisco.aci

Use

Once the collection is installed, you can use it in a playbook by specifying the full namespace path to the module, plugin and/or role.

- hosts: aci
  gather_facts: no

  tasks:
  - name: Add a new EPG
    cisco.aci.aci_epg:
      hostname: apic
      username: admin
      password: SomeSecretPassword
      tenant: production
      ap: intranet
      epg: web_epg
      description: Web Intranet EPG
      bd: prod_bd
    delegate_to: localhost

Optimizing Playbooks

To find out more about optimizing playbook execution, please refer to the Optimizing Playbooks documentation.

Update

Getting the latest/nightly collection build

First Approach

Clone the ansible-aci repository.

git clone https://github.com/CiscoDevNet/ansible-aci.git

Go to the ansible-aci directory

cd ansible-aci

Pull the latest master on your aci

git pull origin master

Build and Install a collection from source

ansible-galaxy collection build --force
ansible-galaxy collection install cisco-aci-* --force

Second Approach

Go to ansible-aci Actions

Select the latest CI build

Under Artifacts download collection and unzip it using Terminal or Console.

Note: The collection file is a zip file containing a tar.gz file. We recommend using CLI because some GUI-based unarchiver might unarchive both nested archives in one go.

Install the unarchived tar.gz file

ansible-galaxy collection install cisco-aci-1.0.0.tar.gz —-force

See Also

Contributing to this collection

Ongoing development efforts and contributions to this collection are tracked as issues in this repository.

We welcome community contributions to this collection. If you find problems, need an enhancement or need a new module, please open an issue or create a PR against the Cisco ACI collection repository.

ansible-aci's People

Contributors

abrahammughal avatar akinross avatar alexkross avatar anvitha-jain avatar bert-jan avatar fraserhenshaw avatar gmicol avatar gustavos86 avatar jasonjuenger avatar jt252 avatar ksaegusa avatar kudtarkar1 avatar lhercot avatar lumean avatar maercu avatar manofcolombia avatar markh0338 avatar netgirard avatar nkatarmal-crest avatar olemyhre avatar sajagana avatar samiib avatar shrsr avatar tbachman avatar timcragg avatar tomvrugt avatar trenzy avatar tvarohohlavy avatar xinyuezhao avatar zjpeterson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ansible-aci's Issues

Feature Request & aci_rest Bug

In support of Multi-Site configurations I would like to be able to create the following objects:
Spine Interface Policy Groups
Spine Interface Profiles (And associated port selectors)
Spine Switch Profiles (And associated switch selectors)

In the absence of that, I have tried creating this configuration with the aci_rest module.
Everything works except creation of the Spine Interface Profiles.

Here is the post:
url: https://{{apic}}/api/node/mo/uni/infra/spaccportprof-Test.json
payload: {"infraSpAccPortP":{"attributes":{"dn":"uni/infra/spaccportprof-Test","name":"Test","rn":"spaccportprof-Test","status":""},"children":[]}}

In Postman this creates the object.
In Ansible the playbook gives a status of OK, but does not create the object with the exact same url & payload.

aci_rest: change not detected

ISSUE TYPE
Bug Report
maybe Enhancement request?

COMPONENT NAME
aci_rest

ANSIBLE VERSION

ansible 2.4.2.0
  config file = None
  configured module search path = [u'/home/dhamann/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.12 (default, Nov 20 2017, 18:23:56) [GCC 5.4.0 20160609]

CONFIGURATION
DEFAULT_DEBUG(env: ANSIBLE_DEBUG) = False
DEFAULT_STRATEGY(env: ANSIBLE_STRATEGY) = linear

OS / ENVIRONMENT
N/A

SUMMARY
aci_rest module does not always detect changed config, but reports it as unchanged ("ok:")

STEPS TO REPRODUCE
Depending on how you push the config to APIC the aci_rest module detects it as changed or not:

This example will always report as unchanged:

- name: Create CDP Policies
      tags:
        - create
        - network
      aci_rest:
        host: "{{ apic_hostname }}"
        username: "{{ apic_username }}"
        password: "{{ apic_password }}"
        validate_certs: false
        method: post
        path: "api/node/mo/uni/infra.xml?rsp-subtree=modified"
        content: |
          <infraInfra>
            <cdpIfPol adminSt="{{item.state}}"  name="{{item.name}}" descr="CDP set to {{item.state}}"/>
          </infraInfra>
      with_items:
        - "{{cdp_policies}}"

This playbook works as expected ("ok:" if nothing has changed and "changed:" otherwise)

- name: Create CDP Policies
      tags:
        - create
        - network
      aci_rest:
        host: "{{ apic_hostname }}"
        username: "{{ apic_username }}"
        password: "{{ apic_password }}"
        validate_certs: false
        method: post
        path: "api/node/mo/uni/infra/.xml?rsp-subtree=modified"
        content: |
            <cdpIfPol adminSt="{{item.state}}"  name="{{item.name}}" descr="CDP set to {{item.state}}"/>
      with_items:
        - "{{cdp_policies}}"

Note the difference in path: parameter (infra.xml vs. infra/.xml) as well as adjusted content.

EXPECTED RESULTS
In both cases changes to actual APIC config should result in "changed:" status

ACTUAL RESULTS
The reason why the first playbook does not behave as expected is that APIC does not populate the "status=" parameter as expected by the aci_rest module (it will only report change if this parameter is set to either of 'created', 'modified', 'deleted')
(see https://github.com/datacenter/aci-ansible/blob/e87db8f15e34ea7dc2e1b30fd445a4c59561bbc9/library/aci_rest.py#L270-L284)

ok: [localhost] => (item={u'state': u'enabled', u'name': u'cdp_on2'}) => {
    "changed": false, 
    "error_code": 0, 
    "error_text": "Success", 
    "imdata": [
        {
            "infraInfra": {
                "attributes": {
                    "childAction": "deleteNonPresent", 
                    "dn": "uni/infra", 
                    "lcOwn": "local", 
                    "modTs": "2017-08-17T01:40:52.471+01:00", 
                    "monPolDn": "uni/fabric/monfab-default", 
                    "name": "infra", 
                    "nameAlias": "", 
                    "ownerKey": "", 
                    "ownerTag": "", 
                    "rn": "", 
                    "status": "", 
                    "uid": "0"
                }, 
                "children": [
                    {
                        "cdpIfPol": {
                            "attributes": {
                                "adminSt": "enabled", 
                                "childAction": "deleteNonPresent", 
                                "descr": "", 
                                "extMngdBy": "", 
                                "lcOwn": "local", 
                                "modTs": "2018-01-17T09:45:05.902+01:00", 
                                "monPolDn": "", 
                                "name": "cdp_on2", 
                                "nameAlias": "", 
                                "ownerKey": "", 
                                "ownerTag": "", 
                                "rn": "cdpIfP-cdp_on2", 
                                "status": "", 
                                "uid": "17805"
                            }
                        }
                    }
                ]
            }
        }
    ], 
    "invocation": {
        "module_args": {
            "content": "<infraInfra>\n  <cdpIfPol adminSt=\"enabled\"  name=\"cdp_on2\" />\n</infraInfra>\n", 
            "host": "apic.ddsdnlab.at", 
            "hostname": "apic.ddsdnlab.at", 
            "method": "post", 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "path": "api/node/mo/uni/infra.xml?rsp-subtree=modified", 
            "protocol": "https", 
            "src": null, 
            "timeout": 30, 
            "use_proxy": true, 
            "use_ssl": true, 
            "username": "ansible", 
            "validate_certs": false
        }
    }, 
    "item": {
        "name": "cdp_on2", 
        "state": "enabled"
    }, 
    "response": "OK (544 bytes)", 
    "status": 200, 
    "totalCount": "1", 
    "url": "https://apic.ddsdnlab.at/api/node/mo/uni/infra.xml?rsp-subtree=modified"
}

On the other hand the second playbook runs as expected as returned data from APIC contains (status='modified'):

changed: [localhost] => (item={u'state': u'enabled', u'name': u'cdp_on'}) => {
    "changed": true, 
    "error_code": 0, 
    "error_text": "Success", 
    "imdata": [
        {
            "cdpIfPol": {
                "attributes": {
                    "adminSt": "enabled", 
                    "childAction": "deleteNonPresent", 
                    "descr": "CDP set to enabled", 
                    "dn": "uni/infra/cdpIfP-cdp_on", 
                    "extMngdBy": "", 
                    "lcOwn": "local", 
                    "modTs": "2018-01-18T05:26:34.072+01:00", 
                    "monPolDn": "uni/fabric/monfab-default", 
                    "name": "cdp_on", 
                    "nameAlias": "", 
                    "ownerKey": "", 
                    "ownerTag": "", 
                    "rn": "", 
                    "status": "modified", 
                    "uid": "15374"
                }
            }
        }
    ], 
    "invocation": {
        "module_args": {
            "content": "<cdpIfPol adminSt=\"enabled\"  name=\"cdp_on\" descr=\"CDP set to enabled\"/>\n", 
            "host": "apic.ddsdnlab.at", 
            "hostname": "apic.ddsdnlab.at", 
            "method": "post", 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "path": "api/node/mo/uni/infra/.xml?rsp-subtree=modified", 
            "protocol": "https", 
            "src": null, 
            "timeout": 30, 
            "use_proxy": true, 
            "use_ssl": true, 
            "username": "ansible", 
            "validate_certs": false
        }
    }, 
    "item": {
        "name": "cdp_on", 
        "state": "enabled"
    }, 
    "response": "OK (375 bytes)", 
    "status": 200, 
    "totalCount": "1", 
    "url": "https://apic.ddsdnlab.at/api/node/mo/uni/infra/.xml?rsp-subtree=modified"
}

Given this (weired?) behavior of APIC (I confirmed this behavior is the same on APIC versions 3.0, 3.1 and 2.1), I think the current implementation of aci_changed() might be insufficient.

Fix galaxy import warnings

Here is the current (as of v1.0.1) import warnings:

  • plugins/modules/aci_firmware_group_node.py:179:1: F401 'json' imported but unused
  • plugins/modules/aci_epg_to_contract_master.py:252:5: F841 local variable 'name_alias' is assigned to but never used
  • plugins/modules/aci_cloud_provider.py:165:5: F841 local variable 'state' is assigned to but never used
  • plugins/modules/aci_cloud_region.py:195:5: F841 local variable 'state' is assigned to but never used
  • plugins/modules/aci_system.py:182:5: F841 local variable 'state' is assigned to but never used
  • plugins/modules/aci_cloud_zone.py:203:5: F841 local variable 'state' is assigned to but never used
  • plugins/modules/aci_rest.py:249:1: F401 'ast' imported but unused
  • plugins/modules/aci_fabric_scheduler.py:242:1: F401 'json' imported but unused
  • plugins/module_utils/aci.py:44:1: F401 'ansible.module_utils.parsing.convert_bool.boolean' imported but unused
  • plugins/module_utils/aci.py:47:1: F401 'ansible.module_utils.six.moves.urllib.parse.parse_qsl' imported but unused
  • plugins/module_utils/aci.py:47:1: F401 'ansible.module_utils.six.moves.urllib.parse.urlsplit' imported but unused
  • plugins/module_utils/aci.py:144:5: F811 redefinition of unused 'boolean' from line 44
  • plugins/module_utils/aci.py:676:9: F841 local variable 'parent_class' is assigned to but never used
  • plugins/module_utils/aci.py:678:9: F841 local variable 'parent_filter' is assigned to but never used
  • plugins/module_utils/aci.py:711:9: F841 local variable 'root_class' is assigned to but never used
  • plugins/module_utils/aci.py:713:9: F841 local variable 'root_filter' is assigned to but never used
  • plugins/module_utils/aci.py:779:9: F841 local variable 'root_class' is assigned to but never used
  • plugins/module_utils/aci.py:781:9: F841 local variable 'root_filter' is assigned to but never used
  • plugins/module_utils/aci.py:783:9: F841 local variable 'sec_class' is assigned to but never used
  • plugins/module_utils/aci.py:785:9: F841 local variable 'sec_filter' is assigned to but never used
  • plugins/module_utils/aci.py:787:9: F841 local variable 'parent_class' is assigned to but never used
  • plugins/module_utils/aci.py:789:9: F841 local variable 'parent_filter' is assigned to but never used

ACI module utils: support cryptography for signing, and eventually deprecate pyOpenSSL

SUMMARY
As noticed in #59907, lib/ansible/module_utils/network/aci/aci.py is using pyOpenSSL to sign something with a private key (https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/network/aci/aci.py#L212-L248). Since pyOpenSSL is not (anymore) supposed to be used for anything than TLS/SSL connections, it would be good to get rid of this eventually.

The best way forward is probably to first support the cryptography library as well (and prefer it over pyOpenSSL if it is around), and eventually deprecate pyOpenSSL support and later remove it.

New versions of pyOpenSSL have cryptography as a requirement, so eventually there should be no platforms left with pyOpenSSL but without a decent cryptography version.

ISSUE OPENER felixfontein

ISSUE TYPE
Bug Report

COMPONENT NAME
lib/ansible/module_utils/network/aci/aci.py

ANSIBLE VERSION
2.9.0

question for "aci_access_port_block_to_access_port:" module

SUMMARY
There seem to be issue with aci_access_port_block_to_access_port: module. What is the parameter for "leaf_port_blk:" ?
using aci_rest the dn is
dn: "uni/infra/accportprof-{{item.name}}/hports-{{item.intSel}}-typ-range/portblk-block2"
but if i use leaf_port_blk: block2, the dn shown is correct, but i there is error.

ISSUE OPENER ewchuah

ISSUE LINK ansible/ansible#65824

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_access_port_block_to_access_port:

ANSIBLE VERSION

ansible 2.9.2
  config file = /home/chuahew/ansible_aci/ansible.cfg
  configured module search path = ['/home/chuahew/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/chuahew/environments/venv_toolkit_py36/lib64/python3.6/site-packages/ansible
  executable location = /home/chuahew/environments/venv_toolkit_py36/bin/ansible
  python version = 3.6.8 (default, Aug  7 2019, 17:28:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

CONFIGURATION

DEFAULT_STDOUT_CALLBACK(/home/chuahew/ansible_aci/ansible.cfg) = skippy
RETRY_FILES_ENABLED(/home/chuahew/ansible_aci/ansible.cfg) = False

OS / ENVIRONMENT
Linux dev-centos.localdomain 3.10.0-1062.1.1.el7.x86_64 #1 SMP Fri Sep 13 22:55:44 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
centos-release-7-7.1908.0.el7.centos.x86_64

STEPS TO REPRODUCE

- name: Create Interface Profile (conntype = acc)
        aci_access_port_block_to_access_port:
        <<: *aci_login
        leaf_interface_profile: "{{item.name}}"
        access_port_selector: "{{item.intSel}}"
        leaf_port_blk: block2
        from_port: 1
        to_port: 1
        state: present
        delegate_to: localhost
        with_items:
          - "{{IntProf}}"
        when: item.conntype == 'acc'

EXPECTED RESULTS
should be able to provision interface profile. Using aci_rest works fine.

ACTUAL RESULTS

failed: [10.68.1.21 -> localhost] (item={'conntype': 'acc', 'name': 'IPF_SS301', 'desc': '', 'intPolGrp': 'IPG_ACC_SVR_Auto', 'intSel': 'IST_E1:01', 'fromPort': '1', 'toPort': '1'}) => {
    "ansible_loop_var": "item",
    "changed": false,
    "error": {
        "code": "102",
        "text": "configured object ((Dn0)) not found Dn0=uni/infra/accportprof-IPF_SS301/hports-IST_E1:01-typ-range/portblk-block2, "
    },
    "invocation": {
        "module_args": {
            "access_port_selector": "IST_E1:01",
            "certificate_name": null,
            "from_card": null,
            "from_port": "1",
            "host": "10.68.1.21",
            "hostname": "10.68.1.21",
            "leaf_interface_profile": "IPF_SS301",
            "leaf_port_blk": "block2",
            "leaf_port_blk_description": null,
            "output_level": "normal",
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "port": null,
            "private_key": null,
            "protocol": "https",
            "state": "present",
            "timeout": 30,
            "to_card": null,
            "to_port": "1",
            "use_proxy": true,
            "use_ssl": true,
            "username": "admin",
            "validate_certs": false
        }
    },
    "item": {
        "conntype": "acc",
        "desc": "",
        "fromPort": "1",
        "intPolGrp": "IPG_ACC_SVR_Auto",
        "intSel": "IST_E1:01",
        "name": "IPF_SS301",
        "toPort": "1"
    },
    "msg": "APIC Error 102: configured object ((Dn0)) not found Dn0=uni/infra/accportprof-IPF_SS301/hports-IST_E1:01-typ-range/portblk-block2, "
}

Unable to provision new port selector for leaf interface profile

It is not currently possible, that I have seen, to provision a new port selector with an associated port block and policy group.
I have been using cisco.aci.aci_access_port_block_to_access_port to create additional port blocks in a pre-existing port selector, but now I am looking to automate vpc deployments. These vpcs all need a unique port selector that is likely not created at playbook runtime.

Error received:
"text": "configured object ((Dn0)) not found Dn0=uni/infra/accportprof-LEAF_201:202_BAREMETAL_VPC_INT_PROF/hports-TESTANSIBLE01_PORT_SEL-typ-range/portblk-TESTANSIBLE01_PORT_SEL, "

accportprof-LEAF_201:202_BAREMETAL_VPC_INT_PROF - exists and is already attached to a leaf switch profile

hports-TESTANSIBLE01_PORT_SEL-typ-range - does not exist and was expected to be created at runtime

portblk-TESTANSIBLE01_PORT_SEL - does not exist and is not created at runtime due to hports path not existing

Policy group for the interface selector is also not configurable and is expected to already exist and attach to hports.

Idempotency Issue with aci_aep

Playbook creates AAEPs no problem. On subsequent runs Ansible reports the status as "changed" instead of "ok"
ansible 2.9.6 & 2.11.0.dev0

Here's the config:

 - name: Add AAEPs
    aci_aep:
      <<: *aci_login
      aep: '{{ item.access_policy_aci_aep_name }}'
      description: '{{ item.access_policy_aci_aep_description }}'
      infra_vlan: '{{ item.access_policy_aci_aep_enable_infra_vlan }}'
      state: '{{ item.access_policy_aci_aep_state | default("present") }}'
    with_items: '{{ aci_model_data|aci_listify("access_policy","aci_aep") }}'

Topology file (snippet):

 - aci_aep:
        - description: 
          domains:
          - name: Phys-Dom
            type: phys
          enable_infra_vlan: false
          name: Phys-AAEP
        - description: 
          domains:
          - name: VMM-Dom
            type: vmm
            vm_provider: vmware
          enable_infra_vlan: false
          name: VMM-AAEP
        - description: 
          domains:
          - name: Phys-Dom
            type: phys
          - name: L3Out-Dom
            type: l3dom
          enable_infra_vlan: false
          name: External-AAEP

Result:

TASK [datacenter.aci-model : Add AAEPs] ***************************************************************************************************************
changed: [apic1] => (item={'access_policy_aci_aep_description': None, 'access_policy_aci_aep_enable_infra_vlan': False, 'access_policy_aci_aep_name': 'Phys-AAEP'})
changed: [apic1] => (item={'access_policy_aci_aep_description': None, 'access_policy_aci_aep_enable_infra_vlan': False, 'access_policy_aci_aep_name': 'VMM-AAEP'})
changed: [apic1] => (item={'access_policy_aci_aep_description': None, 'access_policy_aci_aep_enable_infra_vlan': False, 'access_policy_aci_aep_name': 'External-AAEP'})

aci_rest: Attributes are missing, tag 'attributes' must be specified first, before any other tag

From @dagwieers on October 25, 2017 12:38

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_

ANSIBLE VERSION
v2.5

SUMMARY
When you use the YAML or JSON payload format, ACI requires an attributes entry even when there are no attributes needed. It will complain with:

Attributes are missing, tag 'attributes' must be specified first, before any other tag
You can add an empty attributes entry by doing attributes: {} like the example below:

 - name: Create a switch profile
    aci_rest:
      <<: *aci_login
      path: /api/policymgr/mo/uni.json
      method: post
      content:
        infraInfra:
          attributes: {}
          children:
          - infraNodeP:
-cut-

We could make aci_rest add empty attributes-entries if they are missing.

Copied from original issue: datacenter/aci-ansible#167

aci_rest: An unknown error occurred: unhashable type

From @dagwieers on October 25, 2017 12:32

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_rest

ANSIBLE VERSION
v2.5

SUMMARY
If you use the YAML syntax with aci_rest, but use an XML-path (ending with .xml), the aci_rest module currently fails with:

An unknown error occurred: unhashable type

The workaround is to change .xml to .json, however the real fix is to support YAML content with XML paths by converting the YAML to XML payload. Currently xmljson cannot convert from JSON to XML for the Cobra convention.

Copied from original issue: datacenter/aci-ansible#166

aci_interface_policy_leaf_policy_group.py - Access Interface Issue

Following the output I get when I run the playbook. It works just fine when the lag_type is either node or link for PC or VPC but for Access it gives the below error. Tried it on the simulator as well as on a Hardware setup but the issue persists. Here is the output.

Rohits-MacBook-Pro:ansible-aci rohitborkar$ ansible-playbook playbooks/Access_Policies/10_interface_policy_groups-M.yml -vvvv
ansible-playbook 2.9.11
config file = /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/ansible.cfg
configured module search path = [u'/Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/plugins/modules']
ansible python module location = /Library/Python/2.7/site-packages/ansible
executable location = /usr/local/bin/ansible-playbook
python version = 2.7.16 (default, Jul 5 2020, 02:24:03) [GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.29.21) (-macos10.15-objc-
Using /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/ansible.cfg as config file
setting up inventory plugins
host_list declined parsing /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/dynamic_inventory.py as it did not pass its verify_file() method
Set default localhost to localhost
Parsed /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/dynamic_inventory.py inventory source with script plugin
[WARNING]: Found both group and host with same name: ungrouped
Loading callback plugin default of type stdout, v2.0 from /Library/Python/2.7/site-packages/ansible/plugins/callback/default.pyc

PLAYBOOK: 10_interface_policy_groups-M.yml *********************************************************************************
Positional arguments: playbooks/Access_Policies/10_interface_policy_groups-M.yml
become_method: sudo
inventory: (u'/Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/dynamic_inventory.py',)
forks: 5
tags: (u'all',)
verbosity: 4
connection: smart
timeout: 10
1 plays in playbooks/Access_Policies/10_interface_policy_groups-M.yml

PLAY [localhost] ***********************************************************************************************************
META: ran handlers

TASK [Create leaf policy groups] *******************************************************************************************
task path: /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/playbooks/Access_Policies/10_interface_policy_groups-M.yml:7
ESTABLISH LOCAL CONNECTION FOR USER: rohitborkar
EXEC /bin/sh -c 'echo ~rohitborkar && sleep 0'
EXEC /bin/sh -c '( umask 77 && mkdir -p "echo /Users/rohitborkar/.ansible/tmp"&& mkdir /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299 && echo ansible-tmp-1599693232.61-19918-236071587691299="echo /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299" ) && sleep 0'
Attempting python interpreter discovery
EXEC /bin/sh -c 'echo PLATFORM; uname; echo FOUND; command -v '"'"'/usr/bin/python'"'"'; command -v '"'"'python3.7'"'"'; command -v '"'"'python3.6'"'"'; command -v '"'"'python3.5'"'"'; command -v '"'"'python2.7'"'"'; command -v '"'"'python2.6'"'"'; command -v '"'"'/usr/libexec/platform-python'"'"'; command -v '"'"'/usr/bin/python3'"'"'; command -v '"'"'python'"'"'; echo ENDFOUND && sleep 0'
Python interpreter discovery fallback (unsupported platform for extended discovery: darwin)
Using module file /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/plugins/modules/aci_interface_policy_leaf_policy_group.py
PUT /Users/rohitborkar/.ansible/tmp/ansible-local-19911HsbS9B/tmpmRf1Ky TO /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299/AnsiballZ_aci_interface_policy_leaf_policy_group.py
EXEC /bin/sh -c 'chmod u+x /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299/ /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299/AnsiballZ_aci_interface_policy_leaf_policy_group.py && sleep 0'
EXEC /bin/sh -c '/usr/bin/python /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299/AnsiballZ_aci_interface_policy_leaf_policy_group.py && sleep 0'
EXEC /bin/sh -c 'rm -f -r /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693232.61-19918-236071587691299/ > /dev/null 2>&1 && sleep 0'
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python, but future
installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
failed: [localhost] (item={u'aep': u'Phys_AEP', u'pc_policy': None, u'interface_policy_group': u'ACC_test_laptop_1', u'cdp_policy': u'CDP-Enabled-IntPol', u'stp_policy': None, u'link_level_policy': u'LnkSpd-Auto-IntPol', u'mcp_policy': u'MCP_Enabled', u'lag_type': u'leaf', u'pc_member_name': None, u'lldp_policy': u'LLDP-TxRx-Enabled-IntPol'}) => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_loop_var": "item",
"changed": false,
"error": {
"code": "400",
"text": "Invalid request. Can not contain child 'rslacpPol' under parent 'uni/infra/funcprof/accportgrp-ACC_test_laptop_1'"
},
"invocation": {
"module_args": {
"aep": "Phys_AEP",
"annotation": null,
"cdp_policy": "CDP-Enabled-IntPol",
"certificate_name": null,
"description": null,
"egress_data_plane_policing_policy": null,
"fibre_channel_interface_policy": null,
"host": "10.16.254.222",
"hostname": "10.16.254.222",
"ingress_data_plane_policing_policy": null,
"l2_interface_policy": null,
"lag_type": "leaf",
"link_level_policy": "LnkSpd-Auto-IntPol",
"lldp_policy": "LLDP-TxRx-Enabled-IntPol",
"mcp_policy": "MCP_Enabled",
"monitoring_policy": null,
"name": "ACC_test_laptop_1",
"name_alias": null,
"output_level": "normal",
"output_path": null,
"owner_key": null,
"owner_tag": null,
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"policy_group": "ACC_test_laptop_1",
"port": null,
"port_channel_policy": "",
"port_security_policy": null,
"priority_flow_control_policy": null,
"private_key": null,
"protocol": "https",
"slow_drain_policy": null,
"state": "present",
"storm_control_interface_policy": null,
"stp_interface_policy": "",
"timeout": 30,
"use_proxy": true,
"use_ssl": true,
"username": "admin",
"validate_certs": false
}
},
"item": {
"aep": "Phys_AEP",
"cdp_policy": "CDP-Enabled-IntPol",
"interface_policy_group": "ACC_test_laptop_1",
"lag_type": "leaf",
"link_level_policy": "LnkSpd-Auto-IntPol",
"lldp_policy": "LLDP-TxRx-Enabled-IntPol",
"mcp_policy": "MCP_Enabled",
"pc_member_name": null,
"pc_policy": null,
"stp_policy": null
},
"msg": "APIC Error 400: Invalid request. Can not contain child 'rslacpPol' under parent 'uni/infra/funcprof/accportgrp-ACC_test_laptop_1'"
}
EXEC /bin/sh -c 'echo ~rohitborkar && sleep 0'
EXEC /bin/sh -c '( umask 77 && mkdir -p "echo /Users/rohitborkar/.ansible/tmp"&& mkdir /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594 && echo ansible-tmp-1599693234.66-19918-71586901218594="echo /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594" ) && sleep 0'
Using module file /Users/rohitborkar/Documents/Ansible_MAC/ansible-aci/plugins/modules/aci_interface_policy_leaf_policy_group.py
PUT /Users/rohitborkar/.ansible/tmp/ansible-local-19911HsbS9B/tmpypBnAl TO /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594/AnsiballZ_aci_interface_policy_leaf_policy_group.py
EXEC /bin/sh -c 'chmod u+x /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594/ /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594/AnsiballZ_aci_interface_policy_leaf_policy_group.py && sleep 0'
EXEC /bin/sh -c '/usr/bin/python /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594/AnsiballZ_aci_interface_policy_leaf_policy_group.py && sleep 0'
EXEC /bin/sh -c 'rm -f -r /Users/rohitborkar/.ansible/tmp/ansible-tmp-1599693234.66-19918-71586901218594/ > /dev/null 2>&1 && sleep 0'
failed: [localhost] (item={u'aep': u'Phys_AEP', u'pc_policy': None, u'interface_policy_group': u'ACC_test_laptop_2', u'cdp_policy': u'CDP-Enabled-IntPol', u'stp_policy': None, u'link_level_policy': u'LnkSpd-Auto-IntPol', u'mcp_policy': u'MCP_Enabled', u'lag_type': u'leaf', u'pc_member_name': None, u'lldp_policy': u'LLDP-TxRx-Enabled-IntPol'}) => {
"ansible_loop_var": "item",
"changed": false,
"error": {
"code": "400",
"text": "Invalid request. Can not contain child 'rslacpPol' under parent 'uni/infra/funcprof/accportgrp-ACC_test_laptop_2'"
},
"invocation": {
"module_args": {
"aep": "Phys_AEP",
"annotation": null,
"cdp_policy": "CDP-Enabled-IntPol",
"certificate_name": null,
"description": null,
"egress_data_plane_policing_policy": null,
"fibre_channel_interface_policy": null,
"host": "10.16.254.222",
"hostname": "10.16.254.222",
"ingress_data_plane_policing_policy": null,
"l2_interface_policy": null,
"lag_type": "leaf",
"link_level_policy": "LnkSpd-Auto-IntPol",
"lldp_policy": "LLDP-TxRx-Enabled-IntPol",
"mcp_policy": "MCP_Enabled",
"monitoring_policy": null,
"name": "ACC_test_laptop_2",
"name_alias": null,
"output_level": "normal",
"output_path": null,
"owner_key": null,
"owner_tag": null,
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"policy_group": "ACC_test_laptop_2",
"port": null,
"port_channel_policy": "",
"port_security_policy": null,
"priority_flow_control_policy": null,
"private_key": null,
"protocol": "https",
"slow_drain_policy": null,
"state": "present",
"storm_control_interface_policy": null,
"stp_interface_policy": "",
"timeout": 30,
"use_proxy": true,
"use_ssl": true,
"username": "admin",
"validate_certs": false
}
},
"item": {
"aep": "Phys_AEP",
"cdp_policy": "CDP-Enabled-IntPol",
"interface_policy_group": "ACC_test_laptop_2",
"lag_type": "leaf",
"link_level_policy": "LnkSpd-Auto-IntPol",
"lldp_policy": "LLDP-TxRx-Enabled-IntPol",
"mcp_policy": "MCP_Enabled",
"pc_member_name": null,
"pc_policy": null,
"stp_policy": null
},
"msg": "APIC Error 400: Invalid request. Can not contain child 'rslacpPol' under parent 'uni/infra/funcprof/accportgrp-ACC_test_laptop_2'"
}

PLAY RECAP *****************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

Rohits-MacBook-Pro:ansible-aci rohitborkar$

aci_epg: add parameter to have Microsegmented EPG

ISSUE TYPE
enhancement

COMPONENT NAME
aci_epg

ANSIBLE VERSION
v2.9

SUMMARY
To create microsegmented EPG, it will be good to add in the playload the following paramter:
"isAttrBasedEPg": "true"

For the module, the name of this paramter could be something like "useg" and the value is a boolean.

Feature Request: Expose 'ownerTag' and 'ownerKey' in ACI modules

SUMMARY
In the ACI API, there are values called 'ownerKey' and 'ownerTag'. With our automation, we use non-user service accounts to connect to the APICs, so the user that ran a script isn't immediately known. As a result, we use these values to identify the user that ran the script and some sort of documentation identifier - Ticket Number, Change Number, etc. It would be great to expose these values in modules as an option.

ISSUE TYPE
Feature Idea

COMPONENT NAME
Ideally, this would be implemented on each ACI module.

ANSIBLE VERSION
2.4+

aci_contract: No JSON object could be decoded error

SUMMARY

ISSUE TYPE
Informational

COMPONENT NAME
aci_contract

ANSIBLE VERSION
Observed in Ansible 2.5.1, 2.5.2 and 2.5.3, with Ansible Tower 3.2.4.

CONFIGURATION
DEFAULT_LOOKUP_PLUGIN_PATH(/etc/ansible/ansible.cfg) = [u'/usr/share/ansible/plu
DEFAULT_MODULE_PATH(/etc/ansible/ansible.cfg) = [u'/usr/share/ansible']

OS / ENVIRONMENT
N/A

STEPS TO REPRODUCE
Following APIC upgrade to Version: 3.1(2o), the ACI playbook demonstrated (https://youtu.be/Fs8STriMqTM?t=8m24s) began to intermittently fail when when adding contracts to the APIC. Running the same playbook against an APIC simulator on 3.0(1k) has not encountered errors, using the same playbook and the same instance of Ansible Tower. Only the target APIC was changed.

Enabling debug level 3 (-vvv) appears to reduce or eliminate the frequency of the problem, suggesting it may be load or timing related. I suspect the fault is with the APIC controller on release 3.1(2o). This is an informational issue only.

    - name: Add (or delete) the contract
      aci_contract:
        hostname: '{{ inventory_hostname }}'
        username: '{{ apic.username }}'
        password: '{{ apic.password }}'
        validate_certs: '{{ validate_certs }}'
        state: '{{ desired_state }}'
        #
        contract: '{{ item.contract_name }}'
        description: '{{ item.contract_descr }}'
        tenant: '{{ tenant }}'
        scope: '{{ scope }}'  
      with_items: '{{ tpe }}'
      tags: contract

EXPECTED RESULTS
The contract would be added to the APIC.

ACTUAL RESULTS

{
    "changed": false,
    "error": {
        "code": -1,
        "text": "Unable to parse output as JSON, see 'raw' output. No JSON object could be decoded"
    }
}
failed: [aci-demo.sandbox.wwtatc.local] (item={u'contract_name': u'QA_Bling_Enterprise-to-Prod_Qualys', u'dst_port_start': u'4445', u'dst_port_end': u'4445', u'ip_protocol': u'tcp', u'contract_subject_name': u'sub-QA_Bling_Enterprise-to-Prod_Qualys', u'contract_descr': u'WorkOrder6756', u'entry_name': u'tcp-port_4445', u'filter_name': u'tcp-4445', u'contract_subject_descr': u'WorkOrder6756', u'filter_descr': u'WorkOrder6756', u'filter_entry_descr': u'ent_descr', u'ether_type': u'ip'}) => {
    "changed": false,
    "error": {
        "code": -1,
        "text": "Unable to parse output as JSON, see 'raw' output. No JSON object could be decoded"
    },
    "item": {
        "contract_descr": "WorkOrder6756",
        "contract_name": "QA_Bling_Enterprise-to-Prod_Qualys",
        "contract_subject_descr": "WorkOrder6756",
        "contract_subject_name": "sub-QA_Bling_Enterprise-to-Prod_Qualys",
        "dst_port_end": "4445",
        "dst_port_start": "4445",
        "entry_name": "tcp-port_4445",
        "ether_type": "ip",
        "filter_descr": "WorkOrder6756",
        "filter_entry_descr": "ent_descr",
-snip-
}

Redundant prefix aci_ for modules name

Hello

as the modules belongs to cisco.aci it is quite redundant to have the prefix aci_ on the name of module, and now as this is necessary to call them with the FQCN that make module unnecessary long.

aci_config_rollback Attribute Error: NoneType object has no attribute read

SUMMARY
Trying to compare to Cisco APIC Snapshots using aci_config_rollback module

ISSUE TYPE
task path:

/data/tools/ansible/playbooks/roles/cisco.apic/tasks/compare_snapshots.yml:20
ESTABLISH LOCAL CONNECTION FOR USER: root
EXEC /bin/sh -c 'echo ~root && sleep 0'
EXEC /bin/sh -c '( umask 77 && mkdir -p "echo /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710" && echo ansible-tmp-1559753113.2-180977196103710="echo /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710" ) && sleep 0'
Using module file /usr/lib/python2.7/site-packages/ansible/modules/network/aci/aci_config_rollback.py
PUT /root/.ansible/tmp/ansible-local-4142tNAUms/tmpuI5x6D TO /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py
EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/ /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py && sleep 0'
EXEC /bin/sh -c '/usr/bin/python2 /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py && sleep 0'
EXEC /bin/sh -c 'rm -f -r /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 113, in
_ansiballz_main()
File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 105, in _ansiballz_main
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 48, in invoke_module
imp.load_module('main', mod, module, MOD_DESC)
File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 309, in
File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 273, in main
File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 292, in get_preview
AttributeError: 'NoneType' object has no attribute 'read'

fatal: [localhost -> localhost]: FAILED! => {
"changed": false,
"module_stderr": "Traceback (most recent call last):\n File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 113, in \n _ansiballz_main()\n File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 105, in _ansiballz_main\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 48, in invoke_module\n imp.load_module('main', mod, module, MOD_DESC)\n File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 309, in \n File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 273, in main\n File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/main.py", line 292, in get_preview\nAttributeError: 'NoneType' object has no attribute 'read'\n",
"module_stdout": "",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
"rc": 1
}
to retry, use: --limit @/data/tools/ansible/playbooks/roles/cisco.apic/tasks/compare_snapshots.retry

COMPONENT NAME
aci_config_rollback

Snippet from module page:

name: Compare Snapshot Files
aci_config_rollback:
host: apic
username: admin
password: SomeSecretPassword
export_policy: config_backup
snapshot: run-2017-08-28T06-24-01
compare_export_policy: config_backup
compare_snapshot: run-2017-08-27T23-43-56
state: preview
delegate_to: localhost

ANSIBLE VERSION
ansible 2.7.10
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Mar 26 2019, 22:13:06) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

CONFIGURATION
[root@localhost tasks]# ansible-config dump --only-changed
[root@localhost tasks]#

OS / ENVIRONMENT
Target is Cisco APIC

STEPS TO REPRODUCE
replace snapshot and compare_snapshot fields with your snapshot names

---
- name: Compare ACI Snapshots
  hosts: localhost
  gather_facts: true

  - name: Compare APIC Snapshot
    aci_config_rollback:
      host: x.x.x.x
      username: admin
      password: 
      export_policy: config_backup
      snapshot: run-2019-02-05T19-28-56
      compare_export_policy: config_backup
      compare_snapshot: run-2019-02-07T22-34-49
      state: preview
      validate_certs: no
    delegate_to: localhost
    register: snapshot_compare_result

  - name: Print Snapshot Compare Results
    debug:
      msg: "{{ snapshot_compare_result }}"

EXPECTED RESULTS
Expected to see snapshot comparison results

ACTUAL RESULTS
Error - MUDULE FAILURE

task path: /data/tools/ansible/playbooks/roles/cisco.apic/tasks/compare_snapshots.yml:20
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: root
<localhost> EXEC /bin/sh -c 'echo ~root && sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710 `" && echo ansible-tmp-1559753113.2-180977196103710="` echo /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710 `" ) && sleep 0'
Using module file /usr/lib/python2.7/site-packages/ansible/modules/network/aci/aci_config_rollback.py
<localhost> PUT /root/.ansible/tmp/ansible-local-4142tNAUms/tmpuI5x6D TO /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py
<localhost> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/ /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py && sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python2 /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py && sleep 0'
<localhost> EXEC /bin/sh -c 'rm -f -r /root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 113, in <module>
    _ansiballz_main()
  File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 105, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py", line 48, in invoke_module
    imp.load_module('__main__', mod, module, MOD_DESC)
  File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py", line 309, in <module>
  File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py", line 273, in main
  File "/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py", line 292, in get_preview
AttributeError: 'NoneType' object has no attribute 'read'

fatal: [localhost -> localhost]: FAILED! => {
    "changed": false, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py\", line 113, in <module>\n    _ansiballz_main()\n  File \"/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py\", line 105, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/root/.ansible/tmp/ansible-tmp-1559753113.2-180977196103710/AnsiballZ_aci_config_rollback.py\", line 48, in invoke_module\n    imp.load_module('__main__', mod, module, MOD_DESC)\n  File \"/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py\", line 309, in <module>\n  File \"/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py\", line 273, in main\n  File \"/tmp/ansible_aci_config_rollback_payload_ZNJN9z/__main__.py\", line 292, in get_preview\nAttributeError: 'NoneType' object has no attribute 'read'\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", 
    "rc": 1
}
	to retry, use: --limit @/data/tools/ansible/playbooks/roles/cisco.apic/tasks/compare_snapshots.retry

Linked to #8

Request failed: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)

Hi,

I have the following ssl error on Python 3.6.10 with ansible 2.9.12, using the cisco sandbox.

ansible-playbook
PLAY [aci] *********************************************************************************************

TASK [Add a new EPG] ***********************************************************************************
fatal: [sandboxapicdc.cisco.com]: FAILED! => {"changed": false, "msg": "Connection failed for https://sandboxapicdc.cisco.com/api/aaaLogin.json. Request failed: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)>"}
ERROR:ansible:fatal: [sandboxapicdc.cisco.com]: FAILED! => {"changed": false, "msg": "Connection failed for https://sandboxapicdc.cisco.com/api/aaaLogin.json. Request failed: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)>"}

PLAY RECAP *********************************************************************************************
sandboxapicdc.cisco.com    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

Add (basic) CI

Following on from #36, we should really have (at minimum) ansible-test sanity running against this repo.

If you put the following in .github/workflows/ansible-test.yml (and fix any resulting issues) this will give you a solid base:

name: CI
on:
- pull_request

jobs:
  sanity:
    runs-on: ubuntu-latest
    steps:

      - name: Check out code
        uses: actions/checkout@v1
        with:
          path: ansible_collections/cisco/aci

      - name: Set up Python 3.6
        uses: actions/setup-python@v1
        with:
          python-version: 3.6

      - name: Install ansible-base (devel)
        run: pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check

      - name: Run sanity tests
        run: ansible-test sanity --docker -v --color --python 3.6

aci_epg add the ability to set contract masters

aci_epg module is misisng the ability to set EPG contract master associations which limits the ability for fully automated EPG creation when using inherited contracts which are useful for common infrastructure contracts such as AD, backups, monitoring.

Inclusion of cisco.aci in Ansible 2.10

This collection will be included in Ansible 2.10 because it contains modules and/or plugins that were included in Ansible 2.9. Please review:

DEADLINE: 2020-08-18

The latest version of the collection available on August 18 will be included in Ansible 2.10.0, except possibly newer versions which differ only in the patch level. (For details, see the roadmap). Please release version 1.0.0 of your collection by this date! If 1.0.0 does not exist, the same 0.x.y version will be used in all of Ansible 2.10 without updates, and your 1.x.y release will not be included until Ansible 2.11 (unless you request an exception at a community working group meeting and go through a demanding manual process to vouch for backwards compatibility . . . you want to avoid this!).

Follow semantic versioning rules

Your collection versioning must follow all semver rules. This means:

  • Patch level releases can only contain bugfixes;
  • Minor releases can contain new features, new modules and plugins, and bugfixes, but must not break backwards compatibility;
  • Major releases can break backwards compatibility.

Changelogs and Porting Guide

Your collection should provide data for the Ansible 2.10 changelog and porting guide. The changelog and porting guide are automatically generated from ansible-base, and from the changelogs of the included collections. All changes from the breaking_changes, major_changes, removed_features and deprecated_features sections will appear in both the changelog and the porting guide. You have two options for providing changelog fragments to include:

  1. If possible, use the antsibull-changelog tool, which uses the same changelog fragment as the ansible/ansible repository (see the documentation).
  2. If you cannot use antsibull-changelog, you can provide the changelog in a machine-readable format as changelogs/changelog.yaml inside your collection (see the documentation of changelogs/changelog.yaml format).

If you cannot contribute to the integrated Ansible changelog using one of these methods, please provide a link to your collection's changelog by creating an issue in https://github.com/ansible-community/ansible-build-data/. If you do not provide changelogs/changelog.yml or a link, users will not be able to find out what changed in your collection from the Ansible changelog and porting guide.

Make sure your collection passes the sanity tests

Run ansible-test sanity --docker -v in the collection with the latest ansible-base or stable-2.10 ansible/ansible checkout.

Keep informed

Be sure you're subscribed to:

Questions and Feedback

If you have questions or want to provide feedback, please see the Feedback section in the collection requirements.

(Internal link to keep track of issues: ansible-collections/overview#102)

ansible.module_utils.network.aci.aci module is generating issues in custom module

SUMMARY
ansible.module_utils.network.aci.aci module is generating issues in custom module (uEPG) that is using this library. Issue appears to be in get_diff_children method:

def get_diff_children(self, aci_class):
proposed_children = self.proposed[aci_class].get('children')
if proposed_children:
child_updates = []
existing_children = self.existing[0][aci_class].get('children', []) """<<<<<<<< ISSUE IS HERE"""

This causes the line: aci.get_diff(aci_class='fvAEPg') from the uEPG module (listed below) to cause inconsistent results.

ISSUE TYPE
Bug Report

ISSUE OPENER iljdam

COMPONENT NAME
!component =lib/ansible/module_utils/network/aci/aci.py

Error seen in custom uEPG module based on native EPG module.

ANSIBLE VERSION
ansible 2.8.2

CONFIGURATION

OS / ENVIRONMENT
Test was done with ACI 3.2.6i, but error is ACI firmware agnostic

STEPS TO REPRODUCE
Run ansible_aci_send.yaml using the uEPG module and the host_data_created.json data set.

Problem appears in the first recurrence of the execution, in the case of delete uEPG using the following playbook to remove inherited EPGs from uEPG work from the second element of the ROLELIST (for every uEPG the first remove will fail and rest will be proceeded as expected):

      uEPG:
        host: 192.168.1.1
        username: "admin"
        password: "pass"
        validate_certs: no
        tenant: "TNT_TNT"
        ap: "APP_ANP"
        uepg: "{{ item.0.HOSTNAME }}"
        bd: "BD_BD"
        inherited_epg: "{{ item.1 }}"
        state: absent
        output_level: debug
      register: query_result
      with_subelements:
        - "{{ list_uEPG.host_list }}"
        - ROLELIST
      ignore_errors: yes
The playbook, module and data used:

ansible_aci_send.yaml - the playbook
---

- name: test of using uEPG module
  gather_facts: no
  hosts: localhost
  connection: local

  tasks:

    - name: include vars for templating
      include_vars:
        file: host_data_created.json
        name: list_uEPG

## add section

    - name: add EPGs from role list
      aci_epg:
        host: 192.168.1.1
        username: "admin"
        password: "pass"
        validate_certs: no
        name: "{{ item.1  }}"
        tenant: "TNT_TNT"
        ap: "APP_ANP"
        bd: "BD_BD"
        state: present
        output_level: debug
      with_subelements:
        - "{{ list_uEPG.host_list }}"
        - ROLELIST
      ignore_errors: yes

    - name: add inherit EPGs from uEPGs
      uEPG:
        host: 192.168.1.1
        username: "admin"
        password: "pass"
        validate_certs: no
        tenant: "TNT_TNT"
        ap: "APP_ANP"
        bd: "BD_BD"
        uepg: "{{ item.0.HOSTNAME }}"
        uepg_attribute_ip: "{{ item.0.IPADDRESS }}"
        inherited_epg: "{{ item.1 }}"
        state: present
        output_level: debug
      with_subelements:
        - "{{ list_uEPG.host_list }}"
        - ROLELIST
      ignore_errors: yes
      register: query_result_present


    - name: rm inherit EPGs from uEPGs
      uEPG:
        host: 192.168.1.1
        username: "admin"
        password: "pass"
        validate_certs: no
        tenant: "TNT_TNT"
        ap: "APP_ANP"
        bd: "BD_BD"
        uepg: "{{ item.0.HOSTNAME }}"
        uepg_attribute_ip: "{{ item.0.IPADDRESS }}"
        inherited_epg: "{{ item.1 }}"
        state: absent
        output_level: debug
      register: query_result_absent
      with_subelements:
        - "{{ list_uEPG.host_list }}"
        - ROLELIST
      ignore_errors: yes

    - copy: content="{{ query_result_present | to_nice_json  }}" dest=uepg_present.json
    - copy: content="{{ query_result_absent | to_nice_json }}" dest=uepg_absent.json
    
## end add section
uEPG.py - the implementation of the module
#!/usr/bin/python
# -*- coding: utf-8 -*-

# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'certified'}

DOCUMENTATION = r'''
---
TBD
'''

EXAMPLES = r'''
TBD
'''

RETURN = r'''
TBD
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec


def main():
    argument_spec = aci_argument_spec()
    argument_spec.update(
        uepg=dict(type='str', aliases=['epg_name', 'name']),  # Not required for querying all objects
        bd=dict(type='str', aliases=['bd_name', 'bridge_domain']),
        ap=dict(type='str', aliases=['app_profile', 'app_profile_name']),  # Not required for querying all objects
        tenant=dict(type='str', aliases=['tenant_name']),  # Not required for querying all objects
        description=dict(type='str', aliases=['descr']),
        priority=dict(type='str', choices=['level1', 'level2', 'level3', 'unspecified']),
        intra_epg_isolation=dict(choices=['enforced', 'unenforced']),
        fwd_control=dict(type='str', choices=['none', 'proxy-arp']),
        preferred_group=dict(type='bool'),
        state=dict(type='str', default='query', choices=['present', 'absent', 'query']),
        uepg_attribute_ip=dict(type='str'),
        inherited_epg=dict(type='str'),
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
        required_if=[
            ['state', 'absent', ['ap', 'uepg', 'inherited_epg', 'tenant']],
            ['state', 'present', ['ap', 'uepg', 'uepg_attribute_ip', 'inherited_epg', 'tenant']],
        ],
    )

    aci = ACIModule(module)

    uepg = module.params['uepg']
    bd = module.params['bd']
    description = module.params['description']
    priority = module.params['priority']
    intra_epg_isolation = module.params['intra_epg_isolation']
    fwd_control = module.params['fwd_control']
    preferred_group = aci.boolean(module.params['preferred_group'], 'include', 'exclude')
    state = module.params['state']
    tenant = module.params['tenant']
    ap = module.params['ap']
    uepg_attribute_ip = module.params['uepg_attribute_ip']
    inherited_epg = module.params['inherited_epg']
    inherited_epg_dn = 'uni/tn-{0}/ap-{1}/epg-{2}'.format(tenant, ap, inherited_epg)
    uepg_dn = 'uni/tn-{0}/ap-{1}/epg-{2}'.format(tenant, ap, uepg)
    to_del = 'deleted'


    aci.construct_url(
        root_class=dict(
            aci_class='fvTenant',
            aci_rn='tn-{0}'.format(tenant),
            module_object=tenant,
            target_filter={'name': tenant},
        ),
        subclass_1=dict(
            aci_class='fvAp',
            aci_rn='ap-{0}'.format(ap),
            module_object=ap,
            target_filter={'name': ap},
        ),
        subclass_2=dict(
            aci_class='fvAEPg',
            aci_rn='epg-{0}'.format(uepg),
            module_object=uepg,
            target_filter={'name': uepg},
        ),
        child_classes=['fvRsBd', 'fvCrtrn', 'fvRsSecInherited'],
    )


    aci.get_existing()

    if state == 'present':
        aci.payload(
            aci_class='fvAEPg',
            class_config=dict(
                name=uepg,
                descr=description,
                prio=priority,
                pcEnfPref=intra_epg_isolation,
                fwdCtrl=fwd_control,
                isAttrBasedEPg='true',
                prefGrMemb=preferred_group,
            ),
            child_configs=[
                dict(fvRsBd=dict(
                    attributes=dict(
                        #tnFvBDName=bd,
                        tnFvBDName=bd,
                        ),
                    ),
                ),
                dict(fvRsSecInherited=dict(
                    attributes=dict(
                        # tnFvBDName=bd,
                        tDn=inherited_epg_dn,
                        ),
                    ),
                ),

                dict(fvCrtrn=dict(
                    attributes=dict(
                        scope='scope-bd',
                        match="any",
                        name='default',
                    ),
                    children=[dict(
                        fvIpAttr=dict(
                            attributes=dict(
                                ip=uepg_attribute_ip,
                                name='0',
                                ),
                            ),
                        )],
                    ),
                )
            ],
        )

        aci.get_diff(aci_class='fvAEPg')
        aci.post_config()

    elif state == 'absent':
        aci.payload(
            aci_class='fvAEPg',
            class_config=dict(
                dn=uepg_dn,
                name=uepg,
                descr=description,
                prio=priority,
                pcEnfPref=intra_epg_isolation,
                fwdCtrl=fwd_control,
                isAttrBasedEPg='true',
                prefGrMemb=preferred_group,
            ),
            child_configs=[
                dict(fvRsSecInherited=dict(
                    attributes=dict(
                        annotation='',
                        tDn=inherited_epg_dn,
                        status=to_del,
                        ),
                    ),
                )
            ],
        )
        aci.get_diff(aci_class='fvAEPg')
        aci.post_config()

    aci.exit_json()


if __name__ == "__main__":
    main()
host_data_created.json - the data file used with info for the uEPGs
"host_list": [
{ "HOSTNAME": "host1","ENV": "os","IPADDRESS": "1.1.1.1","ROLELIST":[ "role_1", "type_1", "group_1", "role_12", "location_1", "domain_1"] },
{ "HOSTNAME": "host2","ENV": "os","IPADDRESS": "2.2.2.2","ROLELIST":[ "role_2", "type_2", "role_3", "sgroup_2", "location_3", "domain_3", "role_13"] },
{ "HOSTNAME": "host3","ENV": "os","IPADDRESS": "3.3.3.3","ROLELIST":[ "type_3", "sgroup_2", "location_1", "domain_4", "role_5"] },
{ "HOSTNAME": "host4": "os","IPADDRESS": "4.4.4.4","ROLELIST":[ "role_4", "type_5", "sgroup_3", "location_5", "domain_6", "role_6"] }
]
}

EXPECTED RESULTS
This is how it looks for a working element:

Key: {"fvAEPg": {"attributes": {"isAttrBasedEPg": "true"}, "children": [{"fvRsSecInherited": {"attributes": {"status": "deleted", "tDn": "uni/tn-sd/ap-APP_ANP/epg-role_1"}}}]}}
ACTUAL RESULTS
Run the playbook ansible_aci_send.yaml and you will see that one element from list named ROLELIST fails:

failed: [bdsol-aci16-apic1] (item=[{u'HOSTNAME': u'host1', u'IPADDRESS': u'1.1.1.1', u'ENV': os'}, u'role_1']) => {"ansible_loop_var": "item", "changed": false, "error": {"code": "822", "text": "naming property ('fvRsSecInherited.tDn') is not set."}, "filter_string": "?rsp-prop-include=config-only&rsp-subtree-class=fvCrtrn,fvRsBd,fvRsSecInherited&rsp-subtree=full", "imdata": [{"error": {"attributes": {"code": "822", "text": "naming property ('fvRsSecInherited.tDn') is not set."}}}]
Looking at what is being POSTed to APIC, we see that actually the Ansible is not sending the "tDn" element:

[Full request URI: http://bdsol-aci16-apic1/api/mo/uni/tn-sd/ap-APP_ANP/epg-role1.json]
Key: {"fvAEPg": {"attributes": {"isAttrBasedEPg": "true"}, "children": [{"fvRsSecInherited": {"attributes": {"status": "deleted"}}}]}}

aci_vrf: Add ability to enable preferred groups under the VRF

ISSUE TYPE
enhancement

COMPONENT NAME
aci_vrf

ANSIBLE VERSION
v2.10

SUMMARY
Update the aci_vrf module to enable preferred groups for the module. This has to be enabled under vzAny under the VRF. The aci_epg module already has support to add it to a preferred group so the aci_vrf module is missing the capability to add it.

The module should enable (or disable) preferred groups and also configure the match type (matchT) for vzAny contracts. Those vzAny contracts should be handled in a different module.

extra space was added to "epg" when constructed url for api call for fex port

extra space was added to "epg" when constructed url for api call for fex port

in the following example, epg name is "test". An extra space was added to the end as "test "

"epg": "test ",

The full traceback is:
File "/tmp/ansible_cisco.aci.aci_static_binding_to_epg_payload_z00_4esa/ansible_cisco.aci.aci_static_binding_to_epg_payload.zip/ansible_collections/cisco/aci/plugins/module_utils/aci.py", line 994, in get_existing
self.response_json(info['body'])
failed: [10.1.102.66] (item={'tenant': 'jackyu', 'ap': 'poc', 'epg': 'test', 'vlan': '3100', 'leaf': '1201', 'interface': '1/30', 'extpaths': '181'}) => {
"ansible_loop_var": "item",
"changed": false,
"invocation": {
"module_args": {
"annotation": null,
"ap": "poc",
"certificate_name": null,
"deploy_immediacy": "immediate",
"description": null,
"encap_id": 3100,
"epg": "test ",
"extpaths": 181,
"host": "10.1.102.66",
"interface": "1/30",
"interface_mode": "native",
"interface_type": "fex",
"leafs": [
"1201"
],
"output_level": "normal",
"output_path": null,
"owner_key": null,
"owner_tag": null,
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"pod_id": 1,
"port": null,
"primary_encap_id": null,
"private_key": null,
"protocol": "https",
"state": "present",
"tenant": "jackyu",
"timeout": 30,
"use_proxy": true,
"use_ssl": true,
"username": "admin",
"validate_certs": false
}
},
"item": {
"ap": "poc",
"epg": "test",
"extpaths": "181",
"interface": "1/30",
"leaf": "1201",
"tenant": "jackyu",
"vlan": "3100"
},
"msg": "Connection failed for https://10.1.102.66/api/mo/uni/tn-jackyu/ap-poc/epg-test /rspathAtt-[topology/pod-1/paths-1201/extpaths-181/pathep-[eth1/30]].json?rsp-prop-include=config-only. An unknown error occurred: URL can't contain control characters. '/api/mo/uni/tn-jackyu/ap-poc/epg-test /rspathAtt-[topology/pod-1/paths-1201/extpaths-181/pathep-[eth1/30]].json?rsp-prop-include=config-only' (found at least ' ')"

Fix warning from latest galaxy release

plugins/modules/aci_firmware_group.py:168:1: F401 'json' imported but unused
plugins/module_utils/aci.py:227:9: F841 local variable 'e' is assigned to but never used
plugins/module_utils/aci.py:236:17: F841 local variable 'err' is assigned to but never used
plugins/module_utils/aci.py:824:9: F841 local variable 'root_class' is assigned to but never used
plugins/module_utils/aci.py:826:9: F841 local variable 'root_filter' is assigned to but never used
plugins/module_utils/aci.py:828:9: F841 local variable 'ter_class' is assigned to but never used
plugins/module_utils/aci.py:830:9: F841 local variable 'ter_filter' is assigned to but never used
plugins/module_utils/aci.py:832:9: F841 local variable 'sec_class' is assigned to but never used
plugins/module_utils/aci.py:834:9: F841 local variable 'sec_filter' is assigned to but never used
plugins/module_utils/aci.py:836:9: F841 local variable 'parent_class' is assigned to but never used
plugins/module_utils/aci.py:838:9: F841 local variable 'parent_filter' is assigned to but never used
plugins/module_utils/aci.py:893:9: F841 local variable 'root_class' is assigned to but never used
plugins/module_utils/aci.py:895:9: F841 local variable 'root_filter' is assigned to but never used
plugins/module_utils/aci.py:897:9: F841 local variable 'quad_class' is assigned to but never used
plugins/module_utils/aci.py:899:9: F841 local variable 'quad_filter' is assigned to but never used
plugins/module_utils/aci.py:901:9: F841 local variable 'ter_class' is assigned to but never used
plugins/module_utils/aci.py:903:9: F841 local variable 'ter_filter' is assigned to but never used
plugins/module_utils/aci.py:905:9: F841 local variable 'sec_class' is assigned to but never used
plugins/module_utils/aci.py:907:9: F841 local variable 'sec_filter' is assigned to but never used
plugins/module_utils/aci.py:909:9: F841 local variable 'parent_class' is assigned to but never used
plugins/module_utils/aci.py:911:9: F841 local variable 'parent_filter' is assigned to but never used

New Feature: Cisco ACI External Routed Network Module (L3_Out)

SUMMARY
Would like to be to create and modify Layer 3 Out Route Profiles. Including being able to manage the networks subtree and and adding and removing contracts.

ISSUE TYPE
Feature Idea

COMPONENT NAME
aci_l3out

ANSIBLE VERSION
ansible 2.5.2

OS / ENVIRONMENT
CentOS

EXPECTED RESULTS
Be able to create Layer 3 Out profiles under External Routed Networks and modifying the subtrees below it.

aci_rest does not generate config dump

If the output_path parameter is configured any module should dump the config to file.
However this is not the case for the aci_rest

This config:

aci_rest:
     <<: *aci_login
     path: /api/node/mo/uni/tn-CICD/ap-3Tier_App.json
     method: post
     src: 3Tier_App.json
     output_path: 'config.json'

Should dump the content of 3Tier_App.json into config.json but this does not happens.

aci_rest: Using status="created" behaves differently than status="deleted"

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_rest

ANSIBLE VERSION
v2.4+

SUMMARY
If you use a REST payload with aci_rest using status="created" the action is not idempotent. The first time (when the object does not exist) it works (changed=True), but a subsequent call fails as the APIC returns an HTTP failure.

However, if you do the same for removing an object using status="deleted" the action is idempotent. So a subsequent call works (and does not report a change, changed=False) as one would expect.

This seems an implementation issue in the APIC.

PS If you would add status="created,modified" or simply status="modified" your task will be idempotent and it will not lead to failures on subsequent runs.

STEPS TO REPRODUCE
This will cause a failure on a subsequent create:

<aaaUserCert dn="uni/userext/user-admin/usercert-admin" data="-----BEGIN CERTIFICATE-----\r\nMII<snip>NGg\r\n-----END CERTIFICATE\r\n" name="admin" rn="usercert-admin" status="created"/>

This will not cause a failure on a subsequent removal:

<aaaUserCert dn="uni/userext/user-admin/usercert-admin" name="admin" rn="usercert-admin" status="deleted"/>

However using status="modified" will always work.

This report wants to highlight that creation and deletion have a different behaviour.

The vendor has been notified of this inconsistency.

aci_l3out_extsubnet - crash if scope parameter is not specified

Currently if the scope parameter is not specify the aci_l3out_extsubnet will crash.
The scope should either made mandatory or default to import-security.

Defaulting to import-security is better as is the most common use case and is also the default in APIC

Cisco ACI - EPG to Domain - Allow Promiscuous

#67957 ### SUMMARY

Add the choice between Accept / Reject for the "allow promiscuous" option

ISSUE OPENER: AntoinePorte31

ISSUE LINK ansible/ansible#68109

ISSUE TYPE
Feature Idea
COMPONENT NAME
aci_epg_to_domain – Bind EPGs to Domains (fv:RsDomAtt)

ADDITIONAL INFORMATION
Add in Parameter something like "promiscuous" and in the choices : Accept / Reject, in order to fully achieve the vmm domain association with ansible.

- name: Add a new physical domain to EPG binding
      aci_epg_to_domain:
        host: "{{ inventory_hostname }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: false
        tenant: Prod_XXX
        ap: Prod_XX
        epg: "{{ epgname }}_EPG"
        domain: dvSwitch_ACI_XX
        domain_type: vmm
        state: present
        deploy_immediacy: immediate
        promiscuous: accept OR reject   <----- NEW OPTION
      delegate_to: localhost

aci_interface_policy_port_security timeout parameter not working

SUMMARY
I was trying to use the aci_interface_policy_port_security module and I noticed that the timeout parameter is not working, I pass values different than 60 but the policy is always created with a timeout value of 60.

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_interface_policy_port_security

ANSIBLE VERSION
ansible 2.7.5

CONFIGURATION

DEFAULT_BECOME(/etc/ansible/ansible.cfg) = True
DEFAULT_BECOME_ASK_PASS(/etc/ansible/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/etc/ansible/ansible.cfg) = sudo
DEFAULT_BECOME_USER(/etc/ansible/ansible.cfg) = root
DEFAULT_ROLES_PATH(/etc/ansible/ansible.cfg) = [u'/opt/working/roles']
DEFAULT_VAULT_PASSWORD_FILE(/etc/ansible/ansible.cfg) = /opt/working/.ansible_vault
OS / ENVIRONMENT
CentOS Linux release 7.6.1810 (Core)
Target device: CISCO Application Policy Infrastructure Controller Version: 2.3(1f)
The playbook is launched with ansible-playbook command from a Rundeck job.

STEPS TO REPRODUCE

---

- hosts: localhost
  gather_facts: no
  vars_files:
   - vars/aci.yml 
   - vars/policy.yml"

  tasks:
   - name: DEBUG
     debug:
      msg: "{{ ps_timeout }}"

   - name: CREATE PORT SECURITY INTERFACE POLICY
     aci_interface_policy_port_security:
      hostname: "{{ aci_host }}"
      username: "{{ aci_user }}"
      password: "{{ aci_password }}"
      validate_certs: False
      port_security: "{{ ps_pol_name }}"
      timeout: "{{ ps_timeout }}"
      max_end_points: "{{ ps_max_end_points }}"
      description: "{{ ps_description }}"
      state: present
Contents of aci.yml is only the hostname, username and password for connection to the ACI device.
The contents of policy.yml are:

ps_pol_name: ansible_ps05
ps_timeout: 80
ps_max_end_points: 3
ps_description: test policy

EXPECTED RESULTS
To create an ACI interface port security policy with a timeout of 80 or any other value different than 60.

ACTUAL RESULTS
The ACI interface port security policy is always created with a timeout of 60, regardless of the value you choose/pass as parameter.

ansible-playbook 2.7.5
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/var/lib/rundeck/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible-playbook
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
Using /etc/ansible/ansible.cfg as config file
/etc/ansible/hosts did not meet host_list requirements, check plugin documentation if this is unexpected
/etc/ansible/hosts did not meet script requirements, check plugin documentation if this is unexpected
Parsed /etc/ansible/hosts inventory source with ini plugin
Read vars_file 'vars/aci.yml'
Read vars_file 'vars/policy.yml'

PLAYBOOK: aci_create_intf_policy_ps.yml ****************************************
1 plays in /opt/working/roles/aci/playbooks/aci_create_intf_policy_ps.yml
Read vars_file 'vars/aci.yml'
Read vars_file 'vars/policy.yml'
Read vars_file 'vars/aci.yml'
Read vars_file 'vars/policy.yml'

PLAY [localhost] ***************************************************************
META: ran handlers
Read vars_file 'vars/aci.yml'
Read vars_file 'vars/policy.yml'

TASK [DEBUG] *******************************************************************
task path: /opt/working/roles/aci/playbooks/aci_create_intf_policy_ps.yml:11
ok: [localhost] => {
    "msg": 80
}
Read vars_file 'vars/aci.yml'
Read vars_file 'vars/policyyml'

TASK [CREATE PORT SECURITY INTERFACE POLICY] ***********************************
task path: /opt/working/roles/aci/playbooks/aci_create_intf_policy_ps.yml:15
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 localhost '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<localhost> (0, '/var/lib/rundeck\n', '')
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 localhost '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602 `" && echo ansible-tmp-1548782932.26-63215767615602="` echo /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602 `" ) && sleep 0'"'"''
<localhost> (0, 'ansible-tmp-1548782932.26-63215767615602=/var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602\n', '')
Using module file /usr/lib/python2.7/site-packages/ansible/modules/network/aci/_aci_intf_policy_port_security.py
<localhost> PUT /var/lib/rundeck/.ansible/tmp/ansible-local-62668WFQnHO/tmp0xX5ic TO /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/AnsiballZ__aci_intf_policy_port_security.py
<localhost> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 '[localhost]'
<localhost> (0, 'sftp> put /var/lib/rundeck/.ansible/tmp/ansible-local-62668WFQnHO/tmp0xX5ic /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/AnsiballZ__aci_intf_policy_port_security.py\n', '')
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 localhost '/bin/sh -c '"'"'chmod u+x /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/ /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/AnsiballZ__aci_intf_policy_port_security.py && sleep 0'"'"''
<localhost> (0, '', '')
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 -tt localhost '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-copwlrdecthkuqtlfgtnfhhxkequmwjh; /usr/bin/python /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/AnsiballZ__aci_intf_policy_port_security.py'"'"'"'"'"'"'"'"' && sleep 0'"'"''
Escalation succeeded
<localhost> (0, '\r\n{"current": [{"l2PortSecurityPol": {"attributes": {"dn": "uni/infra/portsecurityP-ansible_ps05", "ownerKey": "", "name": "ansible_ps05", "descr": "test policy", "nameAlias": "", "violation": "protect", "maximum": "3", "timeout": "60", "ownerTag": ""}}}], "invocation": {"module_args": {"username": "ansible", "max_end_points": 3, "private_key": null, "protocol": "https", "use_proxy": true, "certificate_name": null, "hostname": "*****", "host": "*****", "output_level": "normal", "state": "present", "port_security": "ansible_ps05", "timeout": 80, "use_ssl": true, "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "validate_certs": false, "port": null, "description": "test policy"}}, "changed": true}\r\n', 'Shared connection to localhost closed.\r\n')
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/var/lib/rundeck/.ansible/cp/8a5a4c6a60 localhost '/bin/sh -c '"'"'rm -f -r /var/lib/rundeck/.ansible/tmp/ansible-tmp-1548782932.26-63215767615602/ > /dev/null 2>&1 && sleep 0'"'"''
<localhost> (0, '', '')
changed: [localhost] => {
    "changed": true,
    "current": [
        {
            "l2PortSecurityPol": {
                "attributes": {
                    "descr": "test policy",
                    "dn": "uni/infra/portsecurityP-ansible_ps05",
                    "maximum": "3",
                    "name": "ansible_ps05",
                    "nameAlias": "",
                    "ownerKey": "",
                    "ownerTag": "",
                    "timeout": "60",
                    "violation": "protect"
                }
            }
        }
    ],
    "invocation": {
        "module_args": {
            "certificate_name": null,
            "description": "test policy",
            "host": "*****",
            "hostname": "*****",
            "max_end_points": 3,
            "output_level": "normal",
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "port": null,
            "port_security": "ansible_ps05",
            "private_key": null,
            "protocol": "https",
            "state": "present",
            "timeout": 80,
            "use_proxy": true,
            "use_ssl": true,
            "username": "ansible",
            "validate_certs": false
        }
    }
}
META: ran handlers
META: ran handlers

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0

Tags / Global Alias / Alias

SUMMARY
The is a feature request to implement support for "Tags", "Global Alias" and Alias for all ACI modules where applicable, see for example aci_epg

ISSUE OPENER rsmeyers

ISSUE TYPE
Feature Idea

COMPONENT NAME
aci_epg, aci_tenant etc...

Add breakout cable support to Cisco ACI modules

SUMMARY
Would like the aci modules to support breakout cables. Breakout enables a 40 Gigabit (Gb) port to be split into four independent and logical 10Gb ports or a 100Gb port to be split into four independent and logical 25Gb ports. When using a breakout cable, you will need the ability to configure subports. The options for "fromSubPort" and "toSubPort" will need to be added when using the "aci_access_port_to_interface_policy_leaf_profile" module. We will also need the option to create a "leaf breakout port group" under the "leaf policy groups" menu. This is what tells the port that a breakout cable is being used how it will be divided (10g-x4 or 25g-x4). I believe a new module will need to be created for this as it will be pretty different from the "aci_interface_policy_leaf_policy_group" module.

ISSUE TYPE
Feature Idea

COMPONENT NAME
aci_access_port_to_interface_policy_leaf_profile
new module for creating "leaf breakout port group"
aci_interface_policy_leaf_policy_group

ADDITIONAL INFORMATION
This feature would allow you to configure access port selectors that include sub port ranges when breakout cables are being used. It will also allow you to create leaf breakout port groups so that they can be assigned as the "policy group" to the parent port. The actual leaf access/pc/vpc policy groups will be assigned to the sub ports.

Here is a cisco doc on breakout ports in ACI
https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/2-x/L2_config/b_Cisco_APIC_Layer_2_Configuration_Guide/b_Cisco_APIC_Layer_2_Configuration_Guide_chapter_0110.html

I have attached an example XML code for configuring an access port selector with sub ports. Also attached an xml sample of a breakout port group as well as some pictures of the menus in the GUI

[Access Selector with sub ports example.txt]
(https://github.com/ansible/ansible/files/2885548/Access.Selector.with.sub.ports.example.txt)

brkoutportgrp-10g-4x.txt

Inter-Tenant Contract Deployment (fvRsConsIf)

SUMMARY
A new Cisco ACI Module to create contracts between epgs in different Tenants. Inter-Tenant contract deployment

ISSUE TYPE
Feature Idea

COMPONENT NAME
aci_inter_tenant_contract

ADDITIONAL INFORMATION
This would allow you to provide access between epgs that are in different tenants

New module for managing ACI Link Level policy

SUMMARY
This is a new Ansible module for managing ACI Link Level policy.

ISSUE OPENER vasilyprokopov

ISSUE TYPE
New Module Pull Request

COMPONENT NAME
aci_interface_policy_link_level

output_path should generate config only for new objects

When ansible-playbook is run with --check the config should be added in the output_path dump file only for the objects that are not present on the ACI fabric.

This does not have any impact as NAE will simply ignore objects that are already present in ACI but is sub optimal.

aci_aaa_user: Setting user password is not idempotent

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_aaa_user

ANSIBLE VERSION
v2.5

SUMMARY
Due to an inconsistency in the APIC REST API, a task that sets the password of a locally-authenticated user is not idempotent. The APIC will complain with message Password history check: user dag should not use previous 5 passwords.

  aaaUser:
    attributes:
      name: dag
      pwd: S0me!Pwd
{
    "aaaUser": {
        "attributes": {
            "name": "dag",
            "pwd": "S0me!Pwd"
        }
    }
}

This seems to be a bug in the APIC as according to APIC REST API documentation:

Standard REST methods are supported on the API, which includes POST, GET, and DELETE operations through HTTP. The POST and DELETE methods are idempotent, meaning that there is no additional effect if they are called more than once with the same input parameters. The GET method is nullipotent, meaning that it can be called zero or more times without making any changes (or that it is a read-only operation).

The APIC in this case should really be testing whether the provided password is identical to the existing password, before testing it against the password history.

The vendor is notified of this inconsistency.

aci_firmware_source: Does not report change

ISSUE TYPE
Bug Report

COMPONENT NAME
aci_firmware_source

ANSIBLE VERSION
v2.5

SUMMARY
The aci_firmware_source module does not correctly report a change as one would expect. However the same aci_rest payload does correctly report a change since it relies on the return output status: created.

The following fails:

  - aci_firmware_source:
      source: aci-n9000-dk9.11.3.2h.bin
      url: foo.bar.cisco.com/01/Software/Cisco/ACI/aci-n9000-dk9.11.3.2h.bin
      url_protocol: http
While this works:

  - aci_rest:
      method: post
      path: /api/mo/uni/fazbric/fwrepop.json
      content:
        firmwareOSource:
          name: aci-n9000-dk9.11.3.2h.bin
          url: foo.bar.cisco.com/01/Software/Cisco/ACI/aci-n9000-dk9.11.3.2h.bin
          proto: http

subclass_4 support

SUMMARY
in some scenarios to construct_url() need subclass_4 support. MO l2extRsPathL2OutAtt which is 4th level from root.

ISSUE TYPE
optional arguments

ADDITIONAL INFORMATION:

Cisco ACI module support policy

SUMMARY

Is there a document that lists the Platform Series or Software version supported by the ACI/MSO module?(Or a description of the support policy)
https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/recommended-release/b_Recommended_Cisco_ACI_Releases.html

ISSUE TYPE
  • Documentation Report
ADDITIONAL INFORMATION

For example, the following document is an example of NXOS.

https://docs.ansible.com/ansible/latest/network/user_guide/platform_nxos.html

Cisco Nexus Platform Support Matrix

Am I correct in assuming that the N9K described in this matrix is the version supported by the ACI module?

thanks

Copied from original issue: ansible/ansible#68483

Fix aci examples

SUMMARY
Gets rid of the last Could not lex literal_block as "guess". Highlighting skipped. errors in the docs build.

Thanks to @mattclay for the code revisions to the example!

Related to #63217.

ISSUE OPENER acozine

ISSUE LINK ansible/ansible#68095

ISSUE TYPE
Docs Pull Request

COMPONENT NAME

ACI
docs.ansible.com

ACI: Adding an ACI connection plugin for communication

ISSUE TYPE
Feature Idea

COMPONENT NAME
ACI

ANSIBLE VERSION
v2.6

SUMMARY
The general idea is that the ACI modules would feel more native and better integrated with how Ansible works. This means that the information/credentials to connect to the APIC is stored in the inventory (using ansible_host, ansible_port, ansible_user and ansible_password) and the playbook tasks only take into account the parameters required for its specific use.

Other benefits of using an ACI connection plugin include:

It would manage the connection and could handle HTTP errors more gracefully
On connection problems it can rebuild the session transparantly
During maintenance or APIC cluster issues the connection plugin would switch between APICs (provides high-availability)
It would centralize connection information per node or per group, keeping credentials out of playbooks
It avoids too many consecutive auth API calls which may result in connection throttling and playbook failure
Currently we do:

- hosts: apic_cluster01
  tasks:
  - aci_tenant:
      hostname: 10.1.2.1
      username: admin
      password: SecretPassword
      tenant: customer-xyz
      description: Customer XYZ
      state: present

  - aci_vrf:
      hostname: 10.1.2.1
      username: admin
      password: SecretPassword
      tenant: customer-xyz
      vrf: lab
      description: Lab VRF
      policy_control_preference: enforced
      policy_control_direction: ingress

  - aci_bd:
      hostname: 10.1.2.1
      username: admin
      password: SecretPassword
      tenant: customer-xyz
      vrf: lab
      bd: app01
      enable_routing: yes

  - aci_bd_subnet:
      hostname: 10.1.2.1
      username: admin
      password: SecretPassword
      tenant: customer-xyz
      bd: app01
      gateway: 10.10.10.1
      mask: 24
      scope: private
...

A typical playbook would then look much more concise and readable:

- hosts: apic_cluster01
  tasks:
  - aci_tenant:
      tenant: customer-xyz
      description: Customer XYZ
      state: present

  - aci_vrf:
      tenant: customer-xyz
      vrf: lab
      description: Lab VRF
      policy_control_preference: enforced
      policy_control_direction: ingress

  - aci_bd:
      tenant: customer-xyz
      vrf: lab
      bd: app01
      enable_routing: yes

  - aci_bd_subnet:
      tenant: customer-xyz
      bd: app01
      gateway: 10.10.10.1
      mask: 24
      scope: private
...

The inventory for an ACI cluster would then look like:

all:
    apic_cluster01:
        ansible_host: [ 10.1.2.1, 10.1.2.2, 10.1.2.3 ]
        ansible_connection: aci
        ansible_user: admin
        ansible_password: SuperSecret
        proxy_env:
          http_proxy: http://proxy.example.com:8080

This relates to #33887

aci_domain needs support for VMM domain creation

ISSUE TYPE
Feature Idea

COMPONENT NAME
aci_domain

ANSIBLE VERSION

# ansible --version
ansible 2.5.0 (devel b107e397cb) last updated 2018/01/15 16:56:58 (GMT +200)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/nillo/software/ansible/lib/ansible
  executable location = /home/nillo/software/ansible/bin/ansible
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
#

CONFIGURATION

# ansible-config dump --only-changed
DEFAULT_ROLES_PATH(/etc/ansible/ansible.cfg) = [u'/etc/ansible/roles', u'/usr/share/ansible/roles']
#

OS / ENVIRONMENT
Ansible running on CentOS 7.3 and ACI running 3.1(1i).

SUMMARY
For VMM domains we need to be able to specify all required parameters. This is dependent on the domain type (vmware, redhat, microsoft). I believe it would be great to add to the module.

For VMware:

domain_name
vswitch_type: [vds, avs, ave]
access_mode: [ro, rw]
vcenter_name
vcenter_ip: [ip or FQDN]
vds_version: [5.1, 5.5, 6.0, 6.5, default]
stats_collection: [disabled, enabled]
datacenter_name
mgmt_epg
credential_name
port_channel_mode: [on, lacp-active,lacp-passive,mac-pining,mac-pinning-plus]
vswitch_policy: [cdp, lldp, none]

For RHV:

domain_name
rhvm_name
rhvm_ip: [ip or FQDN]
datacenter_name
mgmt_epg
credential_name

For Microsoft:

domain_name
scvmm_name
scvmm_ip: [ip or FQDN]
scvmm_cloud_name
port_channel_mode: [on, lacp-active,lacp-passive,mac-pining,mac-pinning-plus]

STEPS TO REPRODUCE
This is an enhancement request/idea.

support key generated by openssl_privatekey

The Key generated by the openssl_privatekey ansible modules starts with:
-----BEGIN RSA PRIVATE KEY-----

However aci.py check for this string to validate the key:
if private_key_content.startswith('-----BEGIN PRIVATE KEY-----'):

See:
https://github.com/CiscoDevNet/ansible-aci/blob/master/plugins/module_utils/aci.py#L224
https://github.com/CiscoDevNet/ansible-aci/blob/master/plugins/module_utils/aci.py#L244

We should allow using also RSA PRIVATE KEY as they work just fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.