Initial commit
This commit is contained in:
commit
5044249e1f
79 changed files with 6513 additions and 0 deletions
.github/workflows
.gitignoreCOPYINGREADME.mdgalaxy.ymlplugins
action
cliconf
doc_fragments
module_utils
modules
terminal
tests
.gitignore
integration/targets
sanity
unit
__init__.py
compat
mock
modules
__init__.pyconftest.py
requirements.txtnetwork
__init__.py
utils.pydellos9
__init__.pydellos9_module.py
fixtures
dellos9_config_config.cfgdellos9_config_src.cfgshow_file-systemsshow_interfacesshow_inventoryshow_ipv6_interfaceshow_lldp_neighbors_detailshow_memory__except_Processorshow_running-configshow_running-config__grep_hostnameshow_version
test_dellos9_command.pytest_dellos9_config.pytest_dellos9_facts.py
308
.github/workflows/collection-continuous-integration.yml
vendored
Normal file
308
.github/workflows/collection-continuous-integration.yml
vendored
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
name: Collection test suite
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: 3 0 * * * # Run daily at 0:03 UTC
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-collection-artifact:
|
||||||
|
name: Build collection
|
||||||
|
runs-on: ${{ matrix.runner-os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runner-os:
|
||||||
|
- ubuntu-latest
|
||||||
|
ansible-version:
|
||||||
|
- git+https://github.com/ansible/ansible.git@devel
|
||||||
|
runner-python-version:
|
||||||
|
- 3.8
|
||||||
|
steps:
|
||||||
|
- name: Check out ${{ github.repository }} on disk
|
||||||
|
uses: actions/checkout@master
|
||||||
|
- name: Set up Python ${{ matrix.runner-python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.runner-python-version }}
|
||||||
|
- name: Set up pip cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('tests/sanity/requirements.txt') }}-${{ hashFiles('tests/unit/requirements.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
- name: Install Ansible ${{ matrix.ansible-version }}
|
||||||
|
run: >-
|
||||||
|
python -m
|
||||||
|
pip
|
||||||
|
install
|
||||||
|
--user
|
||||||
|
${{ matrix.ansible-version }}
|
||||||
|
- name: Build a collection tarball
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-galaxy
|
||||||
|
collection
|
||||||
|
build
|
||||||
|
--output-path
|
||||||
|
"${GITHUB_WORKSPACE}/.cache/collection-tarballs"
|
||||||
|
- name: Store migrated collection artifacts
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: >-
|
||||||
|
collection
|
||||||
|
path: .cache/collection-tarballs
|
||||||
|
|
||||||
|
sanity-test-collection-via-vms:
|
||||||
|
name: Sanity in VM ${{ matrix.os.vm || 'ubuntu-latest' }}
|
||||||
|
needs:
|
||||||
|
- build-collection-artifact
|
||||||
|
runs-on: ${{ matrix.os.vm || 'ubuntu-latest' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
ansible-version:
|
||||||
|
- git+https://github.com/ansible/ansible.git@devel
|
||||||
|
os:
|
||||||
|
- vm: ubuntu-latest
|
||||||
|
- vm: ubuntu-16.04
|
||||||
|
- vm: macos-latest
|
||||||
|
python-version:
|
||||||
|
- 3.8
|
||||||
|
- 3.7
|
||||||
|
- 3.6
|
||||||
|
- 3.5
|
||||||
|
- 2.7
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Set up pip cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ github.ref }}-sanity-VMs
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
- name: Install Ansible ${{ matrix.ansible-version }}
|
||||||
|
run: >-
|
||||||
|
python -m
|
||||||
|
pip
|
||||||
|
install
|
||||||
|
--user
|
||||||
|
${{ matrix.ansible-version }}
|
||||||
|
- name: Download migrated collection artifacts
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: >-
|
||||||
|
collection
|
||||||
|
path: .cache/collection-tarballs
|
||||||
|
- name: Install the collection tarball
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-galaxy
|
||||||
|
collection
|
||||||
|
install
|
||||||
|
.cache/collection-tarballs/*.tar.gz
|
||||||
|
- name: Run collection sanity tests
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-test
|
||||||
|
sanity
|
||||||
|
--color
|
||||||
|
--requirements
|
||||||
|
--venv
|
||||||
|
--python
|
||||||
|
"${{ matrix.python-version }}"
|
||||||
|
-vvv
|
||||||
|
working-directory: >-
|
||||||
|
/${{ runner.os == 'Linux' && 'home' || 'Users' }}/runner/.ansible/collections/ansible_collections/dellemc_networking/os9
|
||||||
|
|
||||||
|
sanity-test-collection-via-containers:
|
||||||
|
name: Sanity in container via Python ${{ matrix.python-version }}
|
||||||
|
needs:
|
||||||
|
- build-collection-artifact
|
||||||
|
runs-on: ${{ matrix.runner-os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
runner-os:
|
||||||
|
- ubuntu-latest
|
||||||
|
runner-python-version:
|
||||||
|
- 3.8
|
||||||
|
ansible-version:
|
||||||
|
- git+https://github.com/ansible/ansible.git@devel
|
||||||
|
python-version:
|
||||||
|
- 3.8
|
||||||
|
- 2.7
|
||||||
|
- 3.7
|
||||||
|
- 3.6
|
||||||
|
- 3.5
|
||||||
|
- 2.6
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.runner-python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.runner-python-version }}
|
||||||
|
- name: Set up pip cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ github.ref }}-sanity-containers
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
- name: Install Ansible ${{ matrix.ansible-version }}
|
||||||
|
run: >-
|
||||||
|
python -m
|
||||||
|
pip
|
||||||
|
install
|
||||||
|
--user
|
||||||
|
${{ matrix.ansible-version }}
|
||||||
|
- name: Download migrated collection artifacts
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: >-
|
||||||
|
collection
|
||||||
|
path: .cache/collection-tarballs
|
||||||
|
- name: Install the collection tarball
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-galaxy
|
||||||
|
collection
|
||||||
|
install
|
||||||
|
.cache/collection-tarballs/*.tar.gz
|
||||||
|
- name: Run collection sanity tests
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-test
|
||||||
|
sanity
|
||||||
|
--color
|
||||||
|
--requirements
|
||||||
|
--docker
|
||||||
|
--python
|
||||||
|
"${{ matrix.python-version }}"
|
||||||
|
-vvv
|
||||||
|
working-directory: >-
|
||||||
|
/home/runner/.ansible/collections/ansible_collections/dellemc_networking/os9
|
||||||
|
|
||||||
|
unit-test-collection-via-vms:
|
||||||
|
name: Units in VM ${{ matrix.os.vm || 'ubuntu-latest' }}
|
||||||
|
needs:
|
||||||
|
- build-collection-artifact
|
||||||
|
runs-on: ${{ matrix.os.vm || 'ubuntu-latest' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
ansible-version:
|
||||||
|
- git+https://github.com/ansible/ansible.git@devel
|
||||||
|
os:
|
||||||
|
- vm: ubuntu-latest
|
||||||
|
- vm: ubuntu-16.04
|
||||||
|
- vm: macos-latest
|
||||||
|
python-version:
|
||||||
|
- 3.8
|
||||||
|
- 3.7
|
||||||
|
- 3.6
|
||||||
|
- 3.5
|
||||||
|
- 2.7
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Set up pip cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ github.ref }}-units-VMs
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
- name: Install Ansible ${{ matrix.ansible-version }}
|
||||||
|
run: >-
|
||||||
|
python -m
|
||||||
|
pip
|
||||||
|
install
|
||||||
|
--user
|
||||||
|
${{ matrix.ansible-version }}
|
||||||
|
- name: Download migrated collection artifacts
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: >-
|
||||||
|
collection
|
||||||
|
path: .cache/collection-tarballs
|
||||||
|
- name: Install the collection tarball
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-galaxy
|
||||||
|
collection
|
||||||
|
install
|
||||||
|
.cache/collection-tarballs/*.tar.gz
|
||||||
|
- name: Run collection unit tests
|
||||||
|
run: |
|
||||||
|
[[ ! -d 'tests/unit' ]] && echo This collection does not have unit tests. Skipping... || \
|
||||||
|
~/.local/bin/ansible-test units --color --coverage --requirements --venv --python "${{ matrix.python-version }}" -vvv
|
||||||
|
working-directory: >-
|
||||||
|
/${{ runner.os == 'Linux' && 'home' || 'Users' }}/runner/.ansible/collections/ansible_collections/dellemc_networking/os9
|
||||||
|
|
||||||
|
unit-test-collection-via-containers:
|
||||||
|
name: Units in container ${{ matrix.container-image }}
|
||||||
|
needs:
|
||||||
|
- build-collection-artifact
|
||||||
|
runs-on: ${{ matrix.runner-os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
runner-os:
|
||||||
|
- ubuntu-latest
|
||||||
|
runner-python-version:
|
||||||
|
- 3.8
|
||||||
|
ansible-version:
|
||||||
|
- git+https://github.com/ansible/ansible.git@devel
|
||||||
|
container-image:
|
||||||
|
- fedora31
|
||||||
|
- ubuntu1804
|
||||||
|
- centos8
|
||||||
|
- opensuse15
|
||||||
|
- fedora30
|
||||||
|
- centos7
|
||||||
|
- opensuse15py2
|
||||||
|
- ubuntu1604
|
||||||
|
- centos6
|
||||||
|
steps:
|
||||||
|
- name: Set up Python ${{ matrix.runner-python-version }}
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.runner-python-version }}
|
||||||
|
- name: Set up pip cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ github.ref }}-units-containers
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
- name: Install Ansible ${{ matrix.ansible-version }}
|
||||||
|
run: >-
|
||||||
|
python -m
|
||||||
|
pip
|
||||||
|
install
|
||||||
|
--user
|
||||||
|
${{ matrix.ansible-version }}
|
||||||
|
- name: Download migrated collection artifacts
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: >-
|
||||||
|
collection
|
||||||
|
path: .cache/collection-tarballs
|
||||||
|
- name: Install the collection tarball
|
||||||
|
run: >-
|
||||||
|
~/.local/bin/ansible-galaxy
|
||||||
|
collection
|
||||||
|
install
|
||||||
|
.cache/collection-tarballs/*.tar.gz
|
||||||
|
- name: Run collection unit tests
|
||||||
|
run: |
|
||||||
|
[[ ! -d 'tests/unit' ]] && echo This collection does not have unit tests. Skipping... || \
|
||||||
|
~/.local/bin/ansible-test units --color --coverage --requirements --docker "${{ matrix.container-image }}" -vvv
|
||||||
|
working-directory: >-
|
||||||
|
/home/runner/.ansible/collections/ansible_collections/dellemc_networking/os9
|
387
.gitignore
vendored
Normal file
387
.gitignore
vendored
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||||
|
# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||||
|
|
||||||
|
### dotenv ###
|
||||||
|
.env
|
||||||
|
|
||||||
|
### Emacs ###
|
||||||
|
# -*- mode: gitignore; -*-
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Org-mode
|
||||||
|
.org-id-locations
|
||||||
|
*_archive
|
||||||
|
|
||||||
|
# flymake-mode
|
||||||
|
*_flymake.*
|
||||||
|
|
||||||
|
# eshell files
|
||||||
|
/eshell/history
|
||||||
|
/eshell/lastdir
|
||||||
|
|
||||||
|
# elpa packages
|
||||||
|
/elpa/
|
||||||
|
|
||||||
|
# reftex files
|
||||||
|
*.rel
|
||||||
|
|
||||||
|
# AUCTeX auto folder
|
||||||
|
/auto/
|
||||||
|
|
||||||
|
# cask packages
|
||||||
|
.cask/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flycheck
|
||||||
|
flycheck_*.el
|
||||||
|
|
||||||
|
# server auth directory
|
||||||
|
/server/
|
||||||
|
|
||||||
|
# projectiles files
|
||||||
|
.projectile
|
||||||
|
|
||||||
|
# directory configuration
|
||||||
|
.dir-locals.el
|
||||||
|
|
||||||
|
# network security
|
||||||
|
/network-security.data
|
||||||
|
|
||||||
|
|
||||||
|
### Git ###
|
||||||
|
# Created by git for backups. To disable backups in Git:
|
||||||
|
# $ git config --global mergetool.keepBackup false
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Created by git when using merge tools for conflicts
|
||||||
|
*.BACKUP.*
|
||||||
|
*.BASE.*
|
||||||
|
*.LOCAL.*
|
||||||
|
*.REMOTE.*
|
||||||
|
*_BACKUP_*.txt
|
||||||
|
*_BASE_*.txt
|
||||||
|
*_LOCAL_*.txt
|
||||||
|
*_REMOTE_*.txt
|
||||||
|
|
||||||
|
#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!#
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### PyCharm+all ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### PyCharm+all Patch ###
|
||||||
|
# Ignores the whole .idea folder and all .iml files
|
||||||
|
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/sonarlint
|
||||||
|
|
||||||
|
### pydev ###
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
|
### Python ###
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
### Vim ###
|
||||||
|
# Swap
|
||||||
|
[._]*.s[a-v][a-z]
|
||||||
|
[._]*.sw[a-p]
|
||||||
|
[._]s[a-rt-v][a-z]
|
||||||
|
[._]ss[a-gi-z]
|
||||||
|
[._]sw[a-p]
|
||||||
|
|
||||||
|
# Session
|
||||||
|
Session.vim
|
||||||
|
Sessionx.vim
|
||||||
|
|
||||||
|
# Temporary
|
||||||
|
.netrwhist
|
||||||
|
# Auto-generated tag files
|
||||||
|
tags
|
||||||
|
# Persistent undo
|
||||||
|
[._]*.un~
|
||||||
|
|
||||||
|
### WebStorm ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
|
||||||
|
### WebStorm Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
675
COPYING
Normal file
675
COPYING
Normal file
|
@ -0,0 +1,675 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[](https://github.com/ansible-collection-migration/dellemc_networking.os9/actions?query=workflow%3A%22Collection%20test%20suite%22)
|
||||||
|
|
||||||
|
Ansible Collection: dellemc_networking.os9
|
||||||
|
=================================================
|
15
galaxy.yml
Normal file
15
galaxy.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace: dellemc_networking
|
||||||
|
name: os9
|
||||||
|
version: 0.1.0
|
||||||
|
readme: README.md
|
||||||
|
authors: null
|
||||||
|
description: null
|
||||||
|
license: GPL-3.0-or-later
|
||||||
|
license_file: COPYING
|
||||||
|
tags: null
|
||||||
|
dependencies:
|
||||||
|
ansible.netcommon: '>=0.1.0'
|
||||||
|
repository: git@github.com:ansible-collection-migration/dellemc_networking.os9.git
|
||||||
|
documentation: https://github.com/ansible-collection-migration/dellemc_networking.os9/tree/master/docs
|
||||||
|
homepage: https://github.com/ansible-collection-migration/dellemc_networking.os9
|
||||||
|
issues: https://github.com/ansible-collection-migration/dellemc_networking.os9/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
|
0
plugins/action/__init__.py
Normal file
0
plugins/action/__init__.py
Normal file
81
plugins/action/dellos9.py
Normal file
81
plugins/action/dellos9.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
#
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2017 Dell Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ansible import constants as C
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import load_provider
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_provider_spec
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionNetworkModule):
|
||||||
|
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
del tmp # tmp no longer has any effect
|
||||||
|
|
||||||
|
module_name = self._task.action.split('.')[-1]
|
||||||
|
self._config_module = True if module_name == 'dellos9_config' else False
|
||||||
|
socket_path = None
|
||||||
|
persistent_connection = self._play_context.connection.split('.')[-1]
|
||||||
|
|
||||||
|
if persistent_connection == 'network_cli':
|
||||||
|
provider = self._task.args.get('provider', {})
|
||||||
|
if any(provider.values()):
|
||||||
|
display.warning('provider is unnecessary when using network_cli and will be ignored')
|
||||||
|
del self._task.args['provider']
|
||||||
|
elif self._play_context.connection == 'local':
|
||||||
|
provider = load_provider(dellos9_provider_spec, self._task.args)
|
||||||
|
pc = copy.deepcopy(self._play_context)
|
||||||
|
pc.connection = 'network_cli'
|
||||||
|
pc.network_os = 'dellos9'
|
||||||
|
pc.remote_addr = provider['host'] or self._play_context.remote_addr
|
||||||
|
pc.port = int(provider['port'] or self._play_context.port or 22)
|
||||||
|
pc.remote_user = provider['username'] or self._play_context.connection_user
|
||||||
|
pc.password = provider['password'] or self._play_context.password
|
||||||
|
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
|
||||||
|
command_timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
|
||||||
|
pc.become = provider['authorize'] or False
|
||||||
|
if pc.become:
|
||||||
|
pc.become_method = 'enable'
|
||||||
|
pc.become_pass = provider['auth_pass']
|
||||||
|
|
||||||
|
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
|
||||||
|
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin, task_uuid=self._task._uuid)
|
||||||
|
connection.set_options(direct={'persistent_command_timeout': command_timeout})
|
||||||
|
|
||||||
|
socket_path = connection.run()
|
||||||
|
display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
|
||||||
|
if not socket_path:
|
||||||
|
return {'failed': True,
|
||||||
|
'msg': 'unable to open shell. Please see: ' +
|
||||||
|
'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
|
||||||
|
|
||||||
|
task_vars['ansible_socket'] = socket_path
|
||||||
|
|
||||||
|
result = super(ActionModule, self).run(task_vars=task_vars)
|
||||||
|
return result
|
0
plugins/cliconf/__init__.py
Normal file
0
plugins/cliconf/__init__.py
Normal file
122
plugins/cliconf/dellos9.py
Normal file
122
plugins/cliconf/dellos9.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
#
|
||||||
|
# (c) 2017 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# (c) 2017 Dell EMC.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
cliconf: dellos9
|
||||||
|
short_description: Use dellos9 cliconf to run command on Dell OS9 platform
|
||||||
|
description:
|
||||||
|
- This dellos9 plugin provides low level abstraction apis for
|
||||||
|
sending and receiving CLI commands from Dell OS9 network devices.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
from ansible.module_utils.common._collections_compat import Mapping
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
|
||||||
|
from ansible.plugins.cliconf import CliconfBase, enable_mode
|
||||||
|
|
||||||
|
|
||||||
|
class Cliconf(CliconfBase):
|
||||||
|
|
||||||
|
def get_device_info(self):
|
||||||
|
device_info = {}
|
||||||
|
|
||||||
|
device_info['network_os'] = 'dellos9'
|
||||||
|
reply = self.get('show version')
|
||||||
|
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
|
||||||
|
match = re.search(r'Software Version (\S+)', data)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_version'] = match.group(1)
|
||||||
|
|
||||||
|
match = re.search(r'System Type (\S+)', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_model'] = match.group(1)
|
||||||
|
|
||||||
|
reply = self.get('show running-config | grep hostname')
|
||||||
|
data = to_text(reply, errors='surrogate_or_strict').strip()
|
||||||
|
match = re.search(r'^hostname (.+)', data, re.M)
|
||||||
|
if match:
|
||||||
|
device_info['network_os_hostname'] = match.group(1)
|
||||||
|
|
||||||
|
return device_info
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def get_config(self, source='running', format='text', flags=None):
|
||||||
|
if source not in ('running', 'startup'):
|
||||||
|
return self.invalid_params("fetching configuration from %s is not supported" % source)
|
||||||
|
# if source == 'running':
|
||||||
|
# cmd = 'show running-config all'
|
||||||
|
else:
|
||||||
|
cmd = 'show startup-config'
|
||||||
|
return self.send_command(cmd)
|
||||||
|
|
||||||
|
@enable_mode
|
||||||
|
def edit_config(self, command):
|
||||||
|
for cmd in chain(['configure terminal'], to_list(command), ['end']):
|
||||||
|
self.send_command(cmd)
|
||||||
|
|
||||||
|
def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False):
|
||||||
|
return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
|
||||||
|
|
||||||
|
def get_capabilities(self):
|
||||||
|
result = super(Cliconf, self).get_capabilities()
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
def run_commands(self, commands=None, check_rc=True):
|
||||||
|
if commands is None:
|
||||||
|
raise ValueError("'commands' value is required")
|
||||||
|
|
||||||
|
responses = list()
|
||||||
|
for cmd in to_list(commands):
|
||||||
|
if not isinstance(cmd, Mapping):
|
||||||
|
cmd = {'command': cmd}
|
||||||
|
|
||||||
|
output = cmd.pop('output', None)
|
||||||
|
if output:
|
||||||
|
raise ValueError("'output' value %s is not supported for run_commands" % output)
|
||||||
|
|
||||||
|
try:
|
||||||
|
out = self.send_command(**cmd)
|
||||||
|
except AnsibleConnectionFailure as e:
|
||||||
|
if check_rc:
|
||||||
|
raise
|
||||||
|
out = getattr(e, 'err', to_text(e))
|
||||||
|
|
||||||
|
responses.append(out)
|
||||||
|
|
||||||
|
return responses
|
||||||
|
|
||||||
|
def set_cli_prompt_context(self):
|
||||||
|
"""
|
||||||
|
Make sure we are in the operational cli mode
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if self._connection.connected:
|
||||||
|
self._update_cli_prompt_context(config_context=')#')
|
0
plugins/doc_fragments/__init__.py
Normal file
0
plugins/doc_fragments/__init__.py
Normal file
58
plugins/doc_fragments/dellos9.py
Normal file
58
plugins/doc_fragments/dellos9.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
|
||||||
|
# Copyright: (c) 2016, Dell Inc.
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleDocFragment(object):
|
||||||
|
|
||||||
|
# Standard files documentation fragment
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
options:
|
||||||
|
provider:
|
||||||
|
description:
|
||||||
|
- A dict object containing connection details.
|
||||||
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
host:
|
||||||
|
description:
|
||||||
|
- Specifies the DNS host name or address for connecting to the remote
|
||||||
|
device over the specified transport. The value of host is used as
|
||||||
|
the destination address for the transport.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
port:
|
||||||
|
description:
|
||||||
|
- Specifies the port to use when building the connection to the remote
|
||||||
|
device.
|
||||||
|
type: int
|
||||||
|
default: 22
|
||||||
|
username:
|
||||||
|
description:
|
||||||
|
- User to authenticate the SSH session to the remote device. If the
|
||||||
|
value is not specified in the task, the value of environment variable
|
||||||
|
C(ANSIBLE_NET_USERNAME) will be used instead.
|
||||||
|
type: str
|
||||||
|
password:
|
||||||
|
description:
|
||||||
|
- Password to authenticate the SSH session to the remote device. If the
|
||||||
|
value is not specified in the task, the value of environment variable
|
||||||
|
C(ANSIBLE_NET_PASSWORD) will be used instead.
|
||||||
|
type: str
|
||||||
|
ssh_keyfile:
|
||||||
|
description:
|
||||||
|
- Path to an ssh key used to authenticate the SSH session to the remote
|
||||||
|
device. If the value is not specified in the task, the value of
|
||||||
|
environment variable C(ANSIBLE_NET_SSH_KEYFILE) will be used instead.
|
||||||
|
type: path
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Specifies idle timeout (in seconds) for the connection. Useful if the
|
||||||
|
console freezes before continuing. For example when saving
|
||||||
|
configurations.
|
||||||
|
type: int
|
||||||
|
default: 10
|
||||||
|
notes:
|
||||||
|
- For more information on using Ansible to manage Dell EMC Network devices see U(https://www.ansible.com/ansible-dell-networking).
|
||||||
|
'''
|
0
plugins/module_utils/__init__.py
Normal file
0
plugins/module_utils/__init__.py
Normal file
166
plugins/module_utils/network/dellos9/dellos9.py
Normal file
166
plugins/module_utils/network/dellos9/dellos9.py
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
#
|
||||||
|
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
||||||
|
# (c) 2017 Red Hat, Inc
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Dell Inc.
|
||||||
|
#
|
||||||
|
# This code is part of Ansible, but is an independent component.
|
||||||
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||||
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||||
|
# still belong to the author of the module, and may assign their own license
|
||||||
|
# to the complete work.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.basic import env_fallback
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list, ComplexList
|
||||||
|
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, ConfigLine
|
||||||
|
|
||||||
|
_DEVICE_CONFIGS = {}
|
||||||
|
|
||||||
|
WARNING_PROMPTS_RE = [
|
||||||
|
r"[\r\n]?\[confirm yes/no\]:\s?$",
|
||||||
|
r"[\r\n]?\[y/n\]:\s?$",
|
||||||
|
r"[\r\n]?\[yes/no\]:\s?$"
|
||||||
|
]
|
||||||
|
|
||||||
|
dellos9_provider_spec = {
|
||||||
|
'host': dict(),
|
||||||
|
'port': dict(type='int'),
|
||||||
|
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
|
||||||
|
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
|
||||||
|
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
|
||||||
|
'authorize': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTHORIZE']), type='bool'),
|
||||||
|
'auth_pass': dict(fallback=(env_fallback, ['ANSIBLE_NET_AUTH_PASS']), no_log=True),
|
||||||
|
'timeout': dict(type='int'),
|
||||||
|
}
|
||||||
|
dellos9_argument_spec = {
|
||||||
|
'provider': dict(type='dict', options=dellos9_provider_spec),
|
||||||
|
}
|
||||||
|
dellos9_top_spec = {
|
||||||
|
'host': dict(removed_in_version=2.9),
|
||||||
|
'port': dict(removed_in_version=2.9, type='int'),
|
||||||
|
'username': dict(removed_in_version=2.9),
|
||||||
|
'password': dict(removed_in_version=2.9, no_log=True),
|
||||||
|
'ssh_keyfile': dict(removed_in_version=2.9, type='path'),
|
||||||
|
'authorize': dict(removed_in_version=2.9, type='bool'),
|
||||||
|
'auth_pass': dict(removed_in_version=2.9, no_log=True),
|
||||||
|
'timeout': dict(removed_in_version=2.9, type='int'),
|
||||||
|
}
|
||||||
|
dellos9_argument_spec.update(dellos9_top_spec)
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider_argspec():
|
||||||
|
return dellos9_provider_spec
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection(module):
|
||||||
|
if hasattr(module, '_dellos9_connection'):
|
||||||
|
return module._dellos9_connection
|
||||||
|
|
||||||
|
capabilities = get_capabilities(module)
|
||||||
|
network_api = capabilities.get('network_api')
|
||||||
|
if network_api == 'cliconf':
|
||||||
|
module._dellos9_connection = Connection(module._socket_path)
|
||||||
|
else:
|
||||||
|
module.fail_json(msg='Invalid connection type %s' % network_api)
|
||||||
|
|
||||||
|
return module._dellos9_connection
|
||||||
|
|
||||||
|
|
||||||
|
def get_capabilities(module):
|
||||||
|
if hasattr(module, '_dellos9_capabilities'):
|
||||||
|
return module._dellos9_capabilities
|
||||||
|
try:
|
||||||
|
capabilities = Connection(module._socket_path).get_capabilities()
|
||||||
|
except ConnectionError as exc:
|
||||||
|
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'))
|
||||||
|
module._dellos9_capabilities = json.loads(capabilities)
|
||||||
|
return module._dellos9_capabilities
|
||||||
|
|
||||||
|
|
||||||
|
def check_args(module, warnings):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(module, flags=None):
|
||||||
|
flags = [] if flags is None else flags
|
||||||
|
|
||||||
|
cmd = 'show running-config '
|
||||||
|
cmd += ' '.join(flags)
|
||||||
|
cmd = cmd.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return _DEVICE_CONFIGS[cmd]
|
||||||
|
except KeyError:
|
||||||
|
rc, out, err = exec_command(module, cmd)
|
||||||
|
if rc != 0:
|
||||||
|
module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict'))
|
||||||
|
cfg = to_text(out, errors='surrogate_or_strict').strip()
|
||||||
|
_DEVICE_CONFIGS[cmd] = cfg
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def run_commands(module, commands, check_rc=True):
|
||||||
|
connection = get_connection(module)
|
||||||
|
try:
|
||||||
|
return connection.run_commands(commands=commands, check_rc=check_rc)
|
||||||
|
except ConnectionError as exc:
|
||||||
|
module.fail_json(msg=to_text(exc))
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(module, commands):
|
||||||
|
rc, out, err = exec_command(module, 'configure terminal')
|
||||||
|
if rc != 0:
|
||||||
|
module.fail_json(msg='unable to enter configuration mode', err=to_text(err, errors='surrogate_or_strict'))
|
||||||
|
|
||||||
|
for command in to_list(commands):
|
||||||
|
if command == 'end':
|
||||||
|
continue
|
||||||
|
rc, out, err = exec_command(module, command)
|
||||||
|
if rc != 0:
|
||||||
|
module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), command=command, rc=rc)
|
||||||
|
|
||||||
|
exec_command(module, 'end')
|
||||||
|
|
||||||
|
|
||||||
|
def get_sublevel_config(running_config, module):
|
||||||
|
contents = list()
|
||||||
|
current_config_contents = list()
|
||||||
|
running_config = NetworkConfig(contents=running_config, indent=1)
|
||||||
|
obj = running_config.get_object(module.params['parents'])
|
||||||
|
if obj:
|
||||||
|
contents = obj.children
|
||||||
|
contents[:0] = module.params['parents']
|
||||||
|
|
||||||
|
indent = 0
|
||||||
|
for c in contents:
|
||||||
|
if isinstance(c, str):
|
||||||
|
current_config_contents.append(c.rjust(len(c) + indent, ' '))
|
||||||
|
if isinstance(c, ConfigLine):
|
||||||
|
current_config_contents.append(c.raw)
|
||||||
|
indent = 1
|
||||||
|
sublevel_config = '\n'.join(current_config_contents)
|
||||||
|
|
||||||
|
return sublevel_config
|
0
plugins/modules/__init__.py
Normal file
0
plugins/modules/__init__.py
Normal file
239
plugins/modules/dellos9_command.py
Normal file
239
plugins/modules/dellos9_command.py
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
|
||||||
|
# Copyright: (c) 2016, Dell Inc.
|
||||||
|
# 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': 'community'}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: dellos9_command
|
||||||
|
author: "Dhivya P (@dhivyap)"
|
||||||
|
short_description: Run commands on remote devices running Dell OS9
|
||||||
|
description:
|
||||||
|
- Sends arbitrary commands to a Dell OS9 node and returns the results
|
||||||
|
read from the device. This module includes an
|
||||||
|
argument that will cause the module to wait for a specific condition
|
||||||
|
before returning or timing out if the condition is not met.
|
||||||
|
- This module does not support running commands in configuration mode.
|
||||||
|
Please use M(dellos9_config) to configure Dell OS9 devices.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- dellemc_networking.os9.dellos9
|
||||||
|
|
||||||
|
options:
|
||||||
|
commands:
|
||||||
|
description:
|
||||||
|
- List of commands to send to the remote dellos9 device over the
|
||||||
|
configured provider. The resulting output from the command
|
||||||
|
is returned. If the I(wait_for) argument is provided, the
|
||||||
|
module is not returned until the condition is satisfied or
|
||||||
|
the number of retries has expired. If a command sent to the
|
||||||
|
device requires answering a prompt, it is possible to pass
|
||||||
|
a dict containing I(command), I(answer) and I(prompt).
|
||||||
|
Common answers are 'yes' or "\r" (carriage return, must be
|
||||||
|
double quotes). See examples.
|
||||||
|
type: list
|
||||||
|
required: true
|
||||||
|
wait_for:
|
||||||
|
description:
|
||||||
|
- List of conditions to evaluate against the output of the
|
||||||
|
command. The task will wait for each condition to be true
|
||||||
|
before moving forward. If the conditional is not true
|
||||||
|
within the configured number of retries, the task fails.
|
||||||
|
See examples.
|
||||||
|
type: list
|
||||||
|
match:
|
||||||
|
description:
|
||||||
|
- The I(match) argument is used in conjunction with the
|
||||||
|
I(wait_for) argument to specify the match policy. Valid
|
||||||
|
values are C(all) or C(any). If the value is set to C(all)
|
||||||
|
then all conditionals in the wait_for must be satisfied. If
|
||||||
|
the value is set to C(any) then only one of the values must be
|
||||||
|
satisfied.
|
||||||
|
type: str
|
||||||
|
default: all
|
||||||
|
choices: [ 'all', 'any' ]
|
||||||
|
retries:
|
||||||
|
description:
|
||||||
|
- Specifies the number of retries a command should be tried
|
||||||
|
before it is considered failed. The command is run on the
|
||||||
|
target device every retry and evaluated against the
|
||||||
|
I(wait_for) conditions.
|
||||||
|
type: int
|
||||||
|
default: 10
|
||||||
|
interval:
|
||||||
|
description:
|
||||||
|
- Configures the interval in seconds to wait between retries
|
||||||
|
of the command. If the command does not pass the specified
|
||||||
|
conditions, the interval indicates how long to wait before
|
||||||
|
trying the command again.
|
||||||
|
type: int
|
||||||
|
default: 1
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- This module requires Dell OS9 version 9.10.0.1P13 or above.
|
||||||
|
|
||||||
|
- This module requires to increase the ssh connection rate limit.
|
||||||
|
Use the following command I(ip ssh connection-rate-limit 60)
|
||||||
|
to configure the same. This can be done via M(dellos9_config) module
|
||||||
|
as well.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
tasks:
|
||||||
|
- name: run show version on remote devices
|
||||||
|
dellos9_command:
|
||||||
|
commands: show version
|
||||||
|
|
||||||
|
- name: run show version and check to see if output contains OS9
|
||||||
|
dellos9_command:
|
||||||
|
commands: show version
|
||||||
|
wait_for: result[0] contains OS9
|
||||||
|
|
||||||
|
- name: run multiple commands on remote nodes
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show interfaces
|
||||||
|
|
||||||
|
- name: run multiple commands and evaluate the output
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show interfaces
|
||||||
|
wait_for:
|
||||||
|
- result[0] contains OS9
|
||||||
|
- result[1] contains Loopback
|
||||||
|
|
||||||
|
- name: run commands that require answering a prompt
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- command: 'copy running-config startup-config'
|
||||||
|
prompt: '[confirm yes/no]: ?$'
|
||||||
|
answer: 'yes'
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
stdout:
|
||||||
|
description: The set of responses from the commands
|
||||||
|
returned: always apart from low level errors (such as action plugin)
|
||||||
|
type: list
|
||||||
|
sample: ['...', '...']
|
||||||
|
stdout_lines:
|
||||||
|
description: The value of stdout split into a list
|
||||||
|
returned: always apart from low level errors (such as action plugin)
|
||||||
|
type: list
|
||||||
|
sample: [['...', '...'], ['...'], ['...']]
|
||||||
|
failed_conditions:
|
||||||
|
description: The list of conditionals that have failed
|
||||||
|
returned: failed
|
||||||
|
type: list
|
||||||
|
sample: ['...', '...']
|
||||||
|
warnings:
|
||||||
|
description: The list of warnings (if any) generated by module based on arguments
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['...', '...']
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import Conditional
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import transform_commands, to_lines
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import run_commands
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args
|
||||||
|
|
||||||
|
|
||||||
|
def parse_commands(module, warnings):
|
||||||
|
commands = transform_commands(module)
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
for item in list(commands):
|
||||||
|
if not item['command'].startswith('show'):
|
||||||
|
warnings.append(
|
||||||
|
'Only show commands are supported when using check mode, not '
|
||||||
|
'executing %s' % item['command']
|
||||||
|
)
|
||||||
|
commands.remove(item)
|
||||||
|
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""main entry point for module execution
|
||||||
|
"""
|
||||||
|
argument_spec = dict(
|
||||||
|
# { command: <str>, prompt: <str>, response: <str> }
|
||||||
|
commands=dict(type='list', required=True),
|
||||||
|
|
||||||
|
wait_for=dict(type='list'),
|
||||||
|
match=dict(default='all', choices=['all', 'any']),
|
||||||
|
|
||||||
|
retries=dict(default=10, type='int'),
|
||||||
|
interval=dict(default=1, type='int')
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(dellos9_argument_spec)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
result = {'changed': False}
|
||||||
|
|
||||||
|
warnings = list()
|
||||||
|
check_args(module, warnings)
|
||||||
|
commands = parse_commands(module, warnings)
|
||||||
|
result['warnings'] = warnings
|
||||||
|
|
||||||
|
wait_for = module.params['wait_for'] or list()
|
||||||
|
|
||||||
|
try:
|
||||||
|
conditionals = [Conditional(c) for c in wait_for]
|
||||||
|
except AttributeError as exc:
|
||||||
|
module.fail_json(msg=to_text(exc))
|
||||||
|
retries = module.params['retries']
|
||||||
|
interval = module.params['interval']
|
||||||
|
match = module.params['match']
|
||||||
|
|
||||||
|
while retries > 0:
|
||||||
|
responses = run_commands(module, commands)
|
||||||
|
|
||||||
|
for item in list(conditionals):
|
||||||
|
if item(responses):
|
||||||
|
if match == 'any':
|
||||||
|
conditionals = list()
|
||||||
|
break
|
||||||
|
conditionals.remove(item)
|
||||||
|
|
||||||
|
if not conditionals:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(interval)
|
||||||
|
retries -= 1
|
||||||
|
|
||||||
|
if conditionals:
|
||||||
|
failed_conditions = [item.raw for item in conditionals]
|
||||||
|
msg = 'One or more conditional statements have not been satisfied'
|
||||||
|
module.fail_json(msg=msg, failed_conditions=failed_conditions)
|
||||||
|
|
||||||
|
result.update({
|
||||||
|
'stdout': responses,
|
||||||
|
'stdout_lines': list(to_lines(responses))
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
346
plugins/modules/dellos9_config.py
Normal file
346
plugins/modules/dellos9_config.py
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
||||||
|
# Copyright (c) 2016 Dell Inc.
|
||||||
|
# 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': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: dellos9_config
|
||||||
|
author: "Dhivya P (@dhivyap)"
|
||||||
|
short_description: Manage Dell EMC Networking OS9 configuration sections
|
||||||
|
description:
|
||||||
|
- OS9 configurations use a simple block indent file syntax
|
||||||
|
for segmenting configuration into sections. This module provides
|
||||||
|
an implementation for working with OS9 configuration sections in
|
||||||
|
a deterministic way.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- dellemc_networking.os9.dellos9
|
||||||
|
|
||||||
|
options:
|
||||||
|
lines:
|
||||||
|
description:
|
||||||
|
- The ordered set of commands that should be configured in the
|
||||||
|
section. The commands must be the exact same commands as found
|
||||||
|
in the device running-config. Be sure to note the configuration
|
||||||
|
command syntax as some commands are automatically modified by the
|
||||||
|
device config parser. This argument is mutually exclusive with I(src).
|
||||||
|
aliases: ['commands']
|
||||||
|
parents:
|
||||||
|
description:
|
||||||
|
- The ordered set of parents that uniquely identify the section or hierarchy
|
||||||
|
the commands should be checked against. If the parents argument
|
||||||
|
is omitted, the commands are checked against the set of top
|
||||||
|
level or global commands.
|
||||||
|
src:
|
||||||
|
description:
|
||||||
|
- Specifies the source path to the file that contains the configuration
|
||||||
|
or configuration template to load. The path to the source file can
|
||||||
|
either be the full path on the Ansible control host or a relative
|
||||||
|
path from the playbook or role root directory. This argument is
|
||||||
|
mutually exclusive with I(lines).
|
||||||
|
before:
|
||||||
|
description:
|
||||||
|
- The ordered set of commands to push on to the command stack if
|
||||||
|
a change needs to be made. This allows the playbook designer
|
||||||
|
the opportunity to perform configuration commands prior to pushing
|
||||||
|
any changes without affecting how the set of commands are matched
|
||||||
|
against the system.
|
||||||
|
after:
|
||||||
|
description:
|
||||||
|
- The ordered set of commands to append to the end of the command
|
||||||
|
stack if a change needs to be made. Just like with I(before) this
|
||||||
|
allows the playbook designer to append a set of commands to be
|
||||||
|
executed after the command set.
|
||||||
|
match:
|
||||||
|
description:
|
||||||
|
- Instructs the module on the way to perform the matching of
|
||||||
|
the set of commands against the current device config. If
|
||||||
|
match is set to I(line), commands are matched line by line. If
|
||||||
|
match is set to I(strict), command lines are matched with respect
|
||||||
|
to position. If match is set to I(exact), command lines
|
||||||
|
must be an equal match. Finally, if match is set to I(none), the
|
||||||
|
module will not attempt to compare the source configuration with
|
||||||
|
the running configuration on the remote device.
|
||||||
|
default: line
|
||||||
|
choices: ['line', 'strict', 'exact', 'none']
|
||||||
|
replace:
|
||||||
|
description:
|
||||||
|
- Instructs the module on the way to perform the configuration
|
||||||
|
on the device. If the replace argument is set to I(line) then
|
||||||
|
the modified lines are pushed to the device in configuration
|
||||||
|
mode. If the replace argument is set to I(block) then the entire
|
||||||
|
command block is pushed to the device in configuration mode if any
|
||||||
|
line is not correct.
|
||||||
|
default: line
|
||||||
|
choices: ['line', 'block']
|
||||||
|
update:
|
||||||
|
description:
|
||||||
|
- The I(update) argument controls how the configuration statements
|
||||||
|
are processed on the remote device. Valid choices for the I(update)
|
||||||
|
argument are I(merge) and I(check). When you set this argument to
|
||||||
|
I(merge), the configuration changes merge with the current
|
||||||
|
device running configuration. When you set this argument to I(check)
|
||||||
|
the configuration updates are determined but not actually configured
|
||||||
|
on the remote device.
|
||||||
|
default: merge
|
||||||
|
choices: ['merge', 'check']
|
||||||
|
save:
|
||||||
|
description:
|
||||||
|
- The C(save) argument instructs the module to save the running-
|
||||||
|
config to the startup-config at the conclusion of the module
|
||||||
|
running. If check mode is specified, this argument is ignored.
|
||||||
|
type: bool
|
||||||
|
default: no
|
||||||
|
config:
|
||||||
|
description:
|
||||||
|
- The module, by default, will connect to the remote device and
|
||||||
|
retrieve the current running-config to use as a base for comparing
|
||||||
|
against the contents of source. There are times when it is not
|
||||||
|
desirable to have the task get the current running-config for
|
||||||
|
every task in a playbook. The I(config) argument allows the
|
||||||
|
implementer to pass in the configuration to use as the base
|
||||||
|
config for comparison.
|
||||||
|
backup:
|
||||||
|
description:
|
||||||
|
- This argument will cause the module to create a full backup of
|
||||||
|
the current C(running-config) from the remote device before any
|
||||||
|
changes are made. If the C(backup_options) value is not given,
|
||||||
|
the backup file is written to the C(backup) folder in the playbook
|
||||||
|
root directory. If the directory does not exist, it is created.
|
||||||
|
type: bool
|
||||||
|
default: 'no'
|
||||||
|
backup_options:
|
||||||
|
description:
|
||||||
|
- This is a dict object containing configurable options related to backup file path.
|
||||||
|
The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
|
||||||
|
to I(no) this option will be silently ignored.
|
||||||
|
suboptions:
|
||||||
|
filename:
|
||||||
|
description:
|
||||||
|
- The filename to be used to store the backup configuration. If the filename
|
||||||
|
is not given it will be generated based on the hostname, current time and date
|
||||||
|
in format defined by <hostname>_config.<current-date>@<current-time>
|
||||||
|
dir_path:
|
||||||
|
description:
|
||||||
|
- This option provides the path ending with directory name in which the backup
|
||||||
|
configuration file will be stored. If the directory does not exist it will be first
|
||||||
|
created and the filename is either the value of C(filename) or default filename
|
||||||
|
as described in C(filename) options description. If the path value is not given
|
||||||
|
in that case a I(backup) directory will be created in the current working directory
|
||||||
|
and backup configuration will be copied in C(filename) within I(backup) directory.
|
||||||
|
type: path
|
||||||
|
type: dict
|
||||||
|
notes:
|
||||||
|
- This module requires Dell OS9 version 9.10.0.1P13 or above.
|
||||||
|
|
||||||
|
- This module requires to increase the ssh connection rate limit.
|
||||||
|
Use the following command I(ip ssh connection-rate-limit 60)
|
||||||
|
to configure the same. This can also be done with the
|
||||||
|
M(dellos9_config) module.
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
|
- dellos9_config:
|
||||||
|
lines:
|
||||||
|
- 10 permit ip host 1.1.1.1 any log
|
||||||
|
- 20 permit ip host 2.2.2.2 any log
|
||||||
|
- 30 permit ip host 3.3.3.3 any log
|
||||||
|
- 40 permit ip host 4.4.4.4 any log
|
||||||
|
- 50 permit ip host 5.5.5.5 any log
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
before: ['no ip access-list extended test']
|
||||||
|
match: exact
|
||||||
|
|
||||||
|
- dellos9_config:
|
||||||
|
lines:
|
||||||
|
- 10 permit ip host 1.1.1.1 any log
|
||||||
|
- 20 permit ip host 2.2.2.2 any log
|
||||||
|
- 30 permit ip host 3.3.3.3 any log
|
||||||
|
- 40 permit ip host 4.4.4.4 any log
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
before: ['no ip access-list extended test']
|
||||||
|
replace: block
|
||||||
|
|
||||||
|
- dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
backup: yes
|
||||||
|
backup_options:
|
||||||
|
filename: backup.cfg
|
||||||
|
dir_path: /home/user
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
updates:
|
||||||
|
description: The set of commands that will be pushed to the remote device.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1']
|
||||||
|
commands:
|
||||||
|
description: The set of commands that will be pushed to the remote device
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1']
|
||||||
|
saved:
|
||||||
|
description: Returns whether the configuration is saved to the startup
|
||||||
|
configuration or not.
|
||||||
|
returned: When not check_mode.
|
||||||
|
type: bool
|
||||||
|
sample: True
|
||||||
|
backup_path:
|
||||||
|
description: The full path to the backup file
|
||||||
|
returned: when backup is yes
|
||||||
|
type: str
|
||||||
|
sample: /playbooks/ansible/backup/dellos9_config.2016-07-16@22:28:34
|
||||||
|
"""
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import get_config, get_sublevel_config
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import load_config, run_commands
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import WARNING_PROMPTS_RE
|
||||||
|
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps
|
||||||
|
|
||||||
|
|
||||||
|
def get_candidate(module):
|
||||||
|
candidate = NetworkConfig(indent=1)
|
||||||
|
if module.params['src']:
|
||||||
|
candidate.load(module.params['src'])
|
||||||
|
elif module.params['lines']:
|
||||||
|
parents = module.params['parents'] or list()
|
||||||
|
commands = module.params['lines'][0]
|
||||||
|
if (isinstance(commands, dict)) and (isinstance(commands['command'], list)):
|
||||||
|
candidate.add(commands['command'], parents=parents)
|
||||||
|
elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)):
|
||||||
|
candidate.add([commands['command']], parents=parents)
|
||||||
|
else:
|
||||||
|
candidate.add(module.params['lines'], parents=parents)
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
def get_running_config(module):
|
||||||
|
contents = module.params['config']
|
||||||
|
if not contents:
|
||||||
|
contents = get_config(module)
|
||||||
|
return contents
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
backup_spec = dict(
|
||||||
|
filename=dict(),
|
||||||
|
dir_path=dict(type='path')
|
||||||
|
)
|
||||||
|
argument_spec = dict(
|
||||||
|
lines=dict(aliases=['commands'], type='list'),
|
||||||
|
parents=dict(type='list'),
|
||||||
|
|
||||||
|
src=dict(type='path'),
|
||||||
|
|
||||||
|
before=dict(type='list'),
|
||||||
|
after=dict(type='list'),
|
||||||
|
|
||||||
|
match=dict(default='line',
|
||||||
|
choices=['line', 'strict', 'exact', 'none']),
|
||||||
|
replace=dict(default='line', choices=['line', 'block']),
|
||||||
|
|
||||||
|
update=dict(choices=['merge', 'check'], default='merge'),
|
||||||
|
save=dict(type='bool', default=False),
|
||||||
|
config=dict(),
|
||||||
|
backup=dict(type='bool', default=False),
|
||||||
|
backup_options=dict(type='dict', options=backup_spec)
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(dellos9_argument_spec)
|
||||||
|
|
||||||
|
mutually_exclusive = [('lines', 'src'),
|
||||||
|
('parents', 'src')]
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
mutually_exclusive=mutually_exclusive,
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
parents = module.params['parents'] or list()
|
||||||
|
|
||||||
|
match = module.params['match']
|
||||||
|
replace = module.params['replace']
|
||||||
|
|
||||||
|
warnings = list()
|
||||||
|
check_args(module, warnings)
|
||||||
|
|
||||||
|
result = dict(changed=False, saved=False, warnings=warnings)
|
||||||
|
|
||||||
|
candidate = get_candidate(module)
|
||||||
|
|
||||||
|
if module.params['backup']:
|
||||||
|
if not module.check_mode:
|
||||||
|
result['__backup__'] = get_config(module)
|
||||||
|
commands = list()
|
||||||
|
|
||||||
|
if any((module.params['lines'], module.params['src'])):
|
||||||
|
if match != 'none':
|
||||||
|
config = get_running_config(module)
|
||||||
|
if parents:
|
||||||
|
contents = get_sublevel_config(config, module)
|
||||||
|
config = NetworkConfig(contents=contents, indent=1)
|
||||||
|
else:
|
||||||
|
config = NetworkConfig(contents=config, indent=1)
|
||||||
|
configobjs = candidate.difference(config, match=match, replace=replace)
|
||||||
|
else:
|
||||||
|
configobjs = candidate.items
|
||||||
|
|
||||||
|
if configobjs:
|
||||||
|
commands = dumps(configobjs, 'commands')
|
||||||
|
if ((isinstance(module.params['lines'], list)) and
|
||||||
|
(isinstance(module.params['lines'][0], dict)) and
|
||||||
|
set(['prompt', 'answer']).issubset(module.params['lines'][0])):
|
||||||
|
|
||||||
|
cmd = {'command': commands,
|
||||||
|
'prompt': module.params['lines'][0]['prompt'],
|
||||||
|
'answer': module.params['lines'][0]['answer']}
|
||||||
|
commands = [module.jsonify(cmd)]
|
||||||
|
else:
|
||||||
|
commands = commands.split('\n')
|
||||||
|
|
||||||
|
if module.params['before']:
|
||||||
|
commands[:0] = module.params['before']
|
||||||
|
|
||||||
|
if module.params['after']:
|
||||||
|
commands.extend(module.params['after'])
|
||||||
|
|
||||||
|
if not module.check_mode and module.params['update'] == 'merge':
|
||||||
|
load_config(module, commands)
|
||||||
|
|
||||||
|
result['changed'] = True
|
||||||
|
result['commands'] = commands
|
||||||
|
result['updates'] = commands
|
||||||
|
|
||||||
|
if module.params['save']:
|
||||||
|
result['changed'] = True
|
||||||
|
if not module.check_mode:
|
||||||
|
cmd = {'command': 'copy running-config startup-config',
|
||||||
|
'prompt': r'\[confirm yes/no\]:\s?$', 'answer': 'yes'}
|
||||||
|
run_commands(module, [cmd])
|
||||||
|
result['saved'] = True
|
||||||
|
else:
|
||||||
|
module.warn('Skipping command `copy running-config startup-config`'
|
||||||
|
'due to check_mode. Configuration not copied to '
|
||||||
|
'non-volatile storage')
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
575
plugins/modules/dellos9_facts.py
Normal file
575
plugins/modules/dellos9_facts.py
Normal file
|
@ -0,0 +1,575 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
||||||
|
# Copyright (c) 2016 Dell Inc.
|
||||||
|
# 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': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: dellos9_facts
|
||||||
|
author: "Dhivya P (@dhivyap)"
|
||||||
|
short_description: Collect facts from remote devices running Dell EMC Networking OS9
|
||||||
|
description:
|
||||||
|
- Collects a base set of device facts from a remote device that
|
||||||
|
is running OS9. This module prepends all of the
|
||||||
|
base network fact keys with C(ansible_net_<fact>). The facts
|
||||||
|
module will always collect a base set of facts from the device
|
||||||
|
and can enable or disable collection of additional facts.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- dellemc_networking.os9.dellos9
|
||||||
|
|
||||||
|
options:
|
||||||
|
gather_subset:
|
||||||
|
description:
|
||||||
|
- When supplied, this argument will restrict the facts collected
|
||||||
|
to a given subset. Possible values for this argument include
|
||||||
|
all, hardware, config, and interfaces. Can specify a list of
|
||||||
|
values to include a larger subset. Values can also be used
|
||||||
|
with an initial C(M(!)) to specify that a specific subset should
|
||||||
|
not be collected.
|
||||||
|
default: [ '!config' ]
|
||||||
|
notes:
|
||||||
|
- This module requires OS9 version 9.10.0.1P13 or above.
|
||||||
|
|
||||||
|
- This module requires an increase of the SSH connection rate limit.
|
||||||
|
Use the following command I(ip ssh connection-rate-limit 60)
|
||||||
|
to configure the same. This can be also be done with the M(dellos9_config) module.
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
# Collect all facts from the device
|
||||||
|
- dellos9_facts:
|
||||||
|
gather_subset: all
|
||||||
|
|
||||||
|
# Collect only the config and default facts
|
||||||
|
- dellos9_facts:
|
||||||
|
gather_subset:
|
||||||
|
- config
|
||||||
|
|
||||||
|
# Do not collect hardware facts
|
||||||
|
- dellos9_facts:
|
||||||
|
gather_subset:
|
||||||
|
- "!hardware"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
ansible_net_gather_subset:
|
||||||
|
description: The list of fact subsets collected from the device
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
|
||||||
|
# default
|
||||||
|
ansible_net_model:
|
||||||
|
description: The model name returned from the device
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
ansible_net_serialnum:
|
||||||
|
description: The serial number of the remote device
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
ansible_net_version:
|
||||||
|
description: The operating system version running on the remote device
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
ansible_net_hostname:
|
||||||
|
description: The configured hostname of the device
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
ansible_net_image:
|
||||||
|
description: The image file the device is running
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
|
||||||
|
# hardware
|
||||||
|
ansible_net_filesystems:
|
||||||
|
description: All file system names available on the device
|
||||||
|
returned: when hardware is configured
|
||||||
|
type: list
|
||||||
|
ansible_net_memfree_mb:
|
||||||
|
description: The available free memory on the remote device in Mb
|
||||||
|
returned: when hardware is configured
|
||||||
|
type: int
|
||||||
|
ansible_net_memtotal_mb:
|
||||||
|
description: The total memory on the remote device in Mb
|
||||||
|
returned: when hardware is configured
|
||||||
|
type: int
|
||||||
|
|
||||||
|
# config
|
||||||
|
ansible_net_config:
|
||||||
|
description: The current active config from the device
|
||||||
|
returned: when config is configured
|
||||||
|
type: str
|
||||||
|
|
||||||
|
# interfaces
|
||||||
|
ansible_net_all_ipv4_addresses:
|
||||||
|
description: All IPv4 addresses configured on the device
|
||||||
|
returned: when interfaces is configured
|
||||||
|
type: list
|
||||||
|
ansible_net_all_ipv6_addresses:
|
||||||
|
description: All IPv6 addresses configured on the device
|
||||||
|
returned: when interfaces is configured
|
||||||
|
type: list
|
||||||
|
ansible_net_interfaces:
|
||||||
|
description: A hash of all interfaces running on the system
|
||||||
|
returned: when interfaces is configured
|
||||||
|
type: dict
|
||||||
|
ansible_net_neighbors:
|
||||||
|
description: The list of LLDP neighbors from the remote device
|
||||||
|
returned: when interfaces is configured
|
||||||
|
type: dict
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
from itertools import izip
|
||||||
|
except ImportError:
|
||||||
|
izip = zip
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import run_commands
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args
|
||||||
|
from ansible.module_utils.six import iteritems
|
||||||
|
|
||||||
|
|
||||||
|
class FactsBase(object):
|
||||||
|
|
||||||
|
COMMANDS = list()
|
||||||
|
|
||||||
|
def __init__(self, module):
|
||||||
|
self.module = module
|
||||||
|
self.facts = dict()
|
||||||
|
self.responses = None
|
||||||
|
|
||||||
|
def populate(self):
|
||||||
|
self.responses = run_commands(self.module, self.COMMANDS, check_rc=False)
|
||||||
|
|
||||||
|
def run(self, cmd):
|
||||||
|
return run_commands(self.module, cmd, check_rc=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Default(FactsBase):
|
||||||
|
|
||||||
|
COMMANDS = [
|
||||||
|
'show version',
|
||||||
|
'show inventory',
|
||||||
|
'show running-config | grep hostname'
|
||||||
|
]
|
||||||
|
|
||||||
|
def populate(self):
|
||||||
|
super(Default, self).populate()
|
||||||
|
data = self.responses[0]
|
||||||
|
self.facts['version'] = self.parse_version(data)
|
||||||
|
self.facts['model'] = self.parse_model(data)
|
||||||
|
self.facts['image'] = self.parse_image(data)
|
||||||
|
|
||||||
|
data = self.responses[1]
|
||||||
|
self.facts['serialnum'] = self.parse_serialnum(data)
|
||||||
|
|
||||||
|
data = self.responses[2]
|
||||||
|
self.facts['hostname'] = self.parse_hostname(data)
|
||||||
|
|
||||||
|
def parse_version(self, data):
|
||||||
|
match = re.search(r'Software Version:\s*(.+)', data)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_hostname(self, data):
|
||||||
|
match = re.search(r'^hostname (.+)', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_model(self, data):
|
||||||
|
match = re.search(r'^System Type:\s*(.+)', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_image(self, data):
|
||||||
|
match = re.search(r'image file is "(.+)"', data)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_serialnum(self, data):
|
||||||
|
for line in data.split('\n'):
|
||||||
|
if line.startswith('*'):
|
||||||
|
match = re.search(
|
||||||
|
r'\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)', line, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(3)
|
||||||
|
|
||||||
|
|
||||||
|
class Hardware(FactsBase):
|
||||||
|
|
||||||
|
COMMANDS = [
|
||||||
|
'show file-systems',
|
||||||
|
'show memory | except Processor'
|
||||||
|
]
|
||||||
|
|
||||||
|
def populate(self):
|
||||||
|
super(Hardware, self).populate()
|
||||||
|
data = self.responses[0]
|
||||||
|
self.facts['filesystems'] = self.parse_filesystems(data)
|
||||||
|
|
||||||
|
data = self.responses[1]
|
||||||
|
match = re.findall(r'\s(\d+)\s', data)
|
||||||
|
if match:
|
||||||
|
self.facts['memtotal_mb'] = int(match[0]) // 1024
|
||||||
|
self.facts['memfree_mb'] = int(match[2]) // 1024
|
||||||
|
|
||||||
|
def parse_filesystems(self, data):
|
||||||
|
return re.findall(r'\s(\S+):$', data, re.M)
|
||||||
|
|
||||||
|
|
||||||
|
class Config(FactsBase):
|
||||||
|
|
||||||
|
COMMANDS = ['show running-config']
|
||||||
|
|
||||||
|
def populate(self):
|
||||||
|
super(Config, self).populate()
|
||||||
|
self.facts['config'] = self.responses[0]
|
||||||
|
|
||||||
|
|
||||||
|
class Interfaces(FactsBase):
|
||||||
|
|
||||||
|
COMMANDS = [
|
||||||
|
'show interfaces',
|
||||||
|
'show ipv6 interface',
|
||||||
|
'show lldp neighbors detail',
|
||||||
|
'show inventory'
|
||||||
|
]
|
||||||
|
|
||||||
|
def populate(self):
|
||||||
|
super(Interfaces, self).populate()
|
||||||
|
self.facts['all_ipv4_addresses'] = list()
|
||||||
|
self.facts['all_ipv6_addresses'] = list()
|
||||||
|
|
||||||
|
data = self.responses[0]
|
||||||
|
interfaces = self.parse_interfaces(data)
|
||||||
|
|
||||||
|
for key in list(interfaces.keys()):
|
||||||
|
if "ManagementEthernet" in key:
|
||||||
|
temp_parsed = interfaces[key]
|
||||||
|
del interfaces[key]
|
||||||
|
interfaces.update(self.parse_mgmt_interfaces(temp_parsed))
|
||||||
|
|
||||||
|
for key in list(interfaces.keys()):
|
||||||
|
if "Vlan" in key:
|
||||||
|
temp_parsed = interfaces[key]
|
||||||
|
del interfaces[key]
|
||||||
|
interfaces.update(self.parse_vlan_interfaces(temp_parsed))
|
||||||
|
|
||||||
|
self.facts['interfaces'] = self.populate_interfaces(interfaces)
|
||||||
|
|
||||||
|
data = self.responses[1]
|
||||||
|
if len(data) > 0:
|
||||||
|
data = self.parse_ipv6_interfaces(data)
|
||||||
|
self.populate_ipv6_interfaces(data)
|
||||||
|
|
||||||
|
data = self.responses[3]
|
||||||
|
if 'LLDP' in self.get_protocol_list(data):
|
||||||
|
neighbors = self.responses[2]
|
||||||
|
self.facts['neighbors'] = self.parse_neighbors(neighbors)
|
||||||
|
|
||||||
|
def get_protocol_list(self, data):
|
||||||
|
start = False
|
||||||
|
protocol_list = list()
|
||||||
|
for line in data.split('\n'):
|
||||||
|
match = re.search(r'Software Protocol Configured\s*', line)
|
||||||
|
if match:
|
||||||
|
start = True
|
||||||
|
continue
|
||||||
|
if start:
|
||||||
|
line = line.strip()
|
||||||
|
if line.isalnum():
|
||||||
|
protocol_list.append(line)
|
||||||
|
return protocol_list
|
||||||
|
|
||||||
|
def populate_interfaces(self, interfaces):
|
||||||
|
facts = dict()
|
||||||
|
for key, value in interfaces.items():
|
||||||
|
intf = dict()
|
||||||
|
intf['description'] = self.parse_description(value)
|
||||||
|
intf['macaddress'] = self.parse_macaddress(value)
|
||||||
|
ipv4 = self.parse_ipv4(value)
|
||||||
|
intf['ipv4'] = self.parse_ipv4(value)
|
||||||
|
if ipv4:
|
||||||
|
self.add_ip_address(ipv4['address'], 'ipv4')
|
||||||
|
|
||||||
|
intf['mtu'] = self.parse_mtu(value)
|
||||||
|
intf['bandwidth'] = self.parse_bandwidth(value)
|
||||||
|
intf['mediatype'] = self.parse_mediatype(value)
|
||||||
|
intf['duplex'] = self.parse_duplex(value)
|
||||||
|
intf['lineprotocol'] = self.parse_lineprotocol(value)
|
||||||
|
intf['operstatus'] = self.parse_operstatus(value)
|
||||||
|
intf['type'] = self.parse_type(value)
|
||||||
|
|
||||||
|
facts[key] = intf
|
||||||
|
return facts
|
||||||
|
|
||||||
|
def populate_ipv6_interfaces(self, data):
|
||||||
|
for key, value in data.items():
|
||||||
|
if key in self.facts['interfaces']:
|
||||||
|
self.facts['interfaces'][key]['ipv6'] = list()
|
||||||
|
addresses = re.findall(r'\s+(.+), subnet', value, re.M)
|
||||||
|
subnets = re.findall(r', subnet is (\S+)', value, re.M)
|
||||||
|
for addr, subnet in izip(addresses, subnets):
|
||||||
|
ipv6 = dict(address=addr.strip(), subnet=subnet.strip())
|
||||||
|
self.add_ip_address(addr.strip(), 'ipv6')
|
||||||
|
self.facts['interfaces'][key]['ipv6'].append(ipv6)
|
||||||
|
|
||||||
|
def add_ip_address(self, address, family):
|
||||||
|
if family == 'ipv4':
|
||||||
|
self.facts['all_ipv4_addresses'].append(address)
|
||||||
|
else:
|
||||||
|
self.facts['all_ipv6_addresses'].append(address)
|
||||||
|
|
||||||
|
def parse_neighbors(self, neighbors):
|
||||||
|
facts = dict()
|
||||||
|
|
||||||
|
for entry in neighbors.split(
|
||||||
|
'========================================================================'):
|
||||||
|
if entry == '':
|
||||||
|
continue
|
||||||
|
|
||||||
|
intf = self.parse_lldp_intf(entry)
|
||||||
|
if intf not in facts:
|
||||||
|
facts[intf] = list()
|
||||||
|
fact = dict()
|
||||||
|
fact['host'] = self.parse_lldp_host(entry)
|
||||||
|
fact['port'] = self.parse_lldp_port(entry)
|
||||||
|
facts[intf].append(fact)
|
||||||
|
return facts
|
||||||
|
|
||||||
|
def parse_interfaces(self, data):
|
||||||
|
parsed = dict()
|
||||||
|
newline_count = 0
|
||||||
|
interface_start = True
|
||||||
|
|
||||||
|
for line in data.split('\n'):
|
||||||
|
if interface_start:
|
||||||
|
newline_count = 0
|
||||||
|
if len(line) == 0:
|
||||||
|
newline_count += 1
|
||||||
|
if newline_count == 2:
|
||||||
|
interface_start = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
match = re.match(r'^(\S+) (\S+)', line)
|
||||||
|
if match and interface_start:
|
||||||
|
interface_start = False
|
||||||
|
key = match.group(0)
|
||||||
|
parsed[key] = line
|
||||||
|
else:
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
def parse_mgmt_interfaces(self, data):
|
||||||
|
parsed = dict()
|
||||||
|
interface_start = True
|
||||||
|
for line in data.split('\n'):
|
||||||
|
match = re.match(r'^(\S+) (\S+)', line)
|
||||||
|
if "Time since" in line:
|
||||||
|
interface_start = True
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
continue
|
||||||
|
elif match and interface_start:
|
||||||
|
interface_start = False
|
||||||
|
key = match.group(0)
|
||||||
|
parsed[key] = line
|
||||||
|
else:
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
def parse_vlan_interfaces(self, data):
|
||||||
|
parsed = dict()
|
||||||
|
interface_start = True
|
||||||
|
line_before_end = False
|
||||||
|
for line in data.split('\n'):
|
||||||
|
match = re.match(r'^(\S+) (\S+)', line)
|
||||||
|
match_endline = re.match(r'^\s*\d+ packets, \d+ bytes$', line)
|
||||||
|
|
||||||
|
if "Output Statistics" in line:
|
||||||
|
line_before_end = True
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
elif match_endline and line_before_end:
|
||||||
|
line_before_end = False
|
||||||
|
interface_start = True
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
elif match and interface_start:
|
||||||
|
interface_start = False
|
||||||
|
key = match.group(0)
|
||||||
|
parsed[key] = line
|
||||||
|
else:
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
def parse_ipv6_interfaces(self, data):
|
||||||
|
parsed = dict()
|
||||||
|
for line in data.split('\n'):
|
||||||
|
if len(line) == 0:
|
||||||
|
continue
|
||||||
|
elif line[0] == ' ':
|
||||||
|
parsed[key] += '\n%s' % line
|
||||||
|
else:
|
||||||
|
match = re.match(r'^(\S+) (\S+)', line)
|
||||||
|
if match:
|
||||||
|
key = match.group(0)
|
||||||
|
parsed[key] = line
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
def parse_description(self, data):
|
||||||
|
match = re.search(r'Description: (.+)$', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_macaddress(self, data):
|
||||||
|
match = re.search(r'address is (\S+)', data)
|
||||||
|
if match:
|
||||||
|
if match.group(1) != "not":
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_ipv4(self, data):
|
||||||
|
match = re.search(r'Internet address is (\S+)', data)
|
||||||
|
if match:
|
||||||
|
if match.group(1) != "not":
|
||||||
|
addr, masklen = match.group(1).split('/')
|
||||||
|
return dict(address=addr, masklen=int(masklen))
|
||||||
|
|
||||||
|
def parse_mtu(self, data):
|
||||||
|
match = re.search(r'MTU (\d+)', data)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
|
||||||
|
def parse_bandwidth(self, data):
|
||||||
|
match = re.search(r'LineSpeed (\d+)', data)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
|
||||||
|
def parse_duplex(self, data):
|
||||||
|
match = re.search(r'(\w+) duplex', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_mediatype(self, data):
|
||||||
|
media = re.search(r'(.+) media present, (.+)', data, re.M)
|
||||||
|
if media:
|
||||||
|
match = re.search(r'type is (.+)$', media.group(0), re.M)
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_type(self, data):
|
||||||
|
match = re.search(r'Hardware is (.+),', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_lineprotocol(self, data):
|
||||||
|
match = re.search(r'line protocol is (\w+[ ]?\w*)\(?.*\)?$', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_operstatus(self, data):
|
||||||
|
match = re.search(r'^(?:.+) is (.+),', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_lldp_intf(self, data):
|
||||||
|
match = re.search(r'^\sLocal Interface (\S+\s\S+)', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_lldp_host(self, data):
|
||||||
|
match = re.search(r'Remote System Name: (.+)$', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def parse_lldp_port(self, data):
|
||||||
|
match = re.search(r'Remote Port ID: (.+)$', data, re.M)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
FACT_SUBSETS = dict(
|
||||||
|
default=Default,
|
||||||
|
hardware=Hardware,
|
||||||
|
interfaces=Interfaces,
|
||||||
|
config=Config,
|
||||||
|
)
|
||||||
|
|
||||||
|
VALID_SUBSETS = frozenset(FACT_SUBSETS.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""main entry point for module execution
|
||||||
|
"""
|
||||||
|
argument_spec = dict(
|
||||||
|
gather_subset=dict(default=['!config'], type='list')
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(dellos9_argument_spec)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
gather_subset = module.params['gather_subset']
|
||||||
|
|
||||||
|
runable_subsets = set()
|
||||||
|
exclude_subsets = set()
|
||||||
|
|
||||||
|
for subset in gather_subset:
|
||||||
|
if subset == 'all':
|
||||||
|
runable_subsets.update(VALID_SUBSETS)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if subset.startswith('!'):
|
||||||
|
subset = subset[1:]
|
||||||
|
if subset == 'all':
|
||||||
|
exclude_subsets.update(VALID_SUBSETS)
|
||||||
|
continue
|
||||||
|
exclude = True
|
||||||
|
else:
|
||||||
|
exclude = False
|
||||||
|
|
||||||
|
if subset not in VALID_SUBSETS:
|
||||||
|
module.fail_json(msg='Bad subset')
|
||||||
|
|
||||||
|
if exclude:
|
||||||
|
exclude_subsets.add(subset)
|
||||||
|
else:
|
||||||
|
runable_subsets.add(subset)
|
||||||
|
|
||||||
|
if not runable_subsets:
|
||||||
|
runable_subsets.update(VALID_SUBSETS)
|
||||||
|
|
||||||
|
runable_subsets.difference_update(exclude_subsets)
|
||||||
|
runable_subsets.add('default')
|
||||||
|
|
||||||
|
facts = dict()
|
||||||
|
facts['gather_subset'] = list(runable_subsets)
|
||||||
|
|
||||||
|
instances = list()
|
||||||
|
for key in runable_subsets:
|
||||||
|
instances.append(FACT_SUBSETS[key](module))
|
||||||
|
|
||||||
|
for inst in instances:
|
||||||
|
inst.populate()
|
||||||
|
facts.update(inst.facts)
|
||||||
|
|
||||||
|
ansible_facts = dict()
|
||||||
|
for key, value in iteritems(facts):
|
||||||
|
key = 'ansible_net_%s' % key
|
||||||
|
ansible_facts[key] = value
|
||||||
|
|
||||||
|
warnings = list()
|
||||||
|
check_args(module, warnings)
|
||||||
|
|
||||||
|
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
plugins/terminal/__init__.py
Normal file
0
plugins/terminal/__init__.py
Normal file
83
plugins/terminal/dellos9.py
Normal file
83
plugins/terminal/dellos9.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2017 Dell Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text, to_bytes
|
||||||
|
from ansible.plugins.terminal import TerminalBase
|
||||||
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalModule(TerminalBase):
|
||||||
|
|
||||||
|
terminal_stdout_re = [
|
||||||
|
re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
|
||||||
|
re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$")
|
||||||
|
]
|
||||||
|
|
||||||
|
terminal_stderr_re = [
|
||||||
|
re.compile(br"% ?Error: (?:(?!\bdoes not exist\b)(?!\balready exists\b)(?!\bHost not found\b)(?!\bnot active\b).)*\n"),
|
||||||
|
re.compile(br"% ?Bad secret"),
|
||||||
|
re.compile(br"invalid input", re.I),
|
||||||
|
re.compile(br"(?:incomplete|ambiguous) command", re.I),
|
||||||
|
re.compile(br"connection timed out", re.I),
|
||||||
|
re.compile(br"'[^']' +returned error code: ?\d+"),
|
||||||
|
]
|
||||||
|
|
||||||
|
terminal_initial_prompt = br"\[y/n\]:"
|
||||||
|
|
||||||
|
terminal_initial_answer = b"y"
|
||||||
|
|
||||||
|
def on_open_shell(self):
|
||||||
|
try:
|
||||||
|
self._exec_cli_command(b'terminal length 0')
|
||||||
|
except AnsibleConnectionFailure:
|
||||||
|
raise AnsibleConnectionFailure('unable to set terminal parameters')
|
||||||
|
|
||||||
|
def on_become(self, passwd=None):
|
||||||
|
if self._get_prompt().endswith(b'#'):
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = {u'command': u'enable'}
|
||||||
|
if passwd:
|
||||||
|
cmd[u'prompt'] = to_text(r"[\r\n]?password: $", errors='surrogate_or_strict')
|
||||||
|
cmd[u'answer'] = passwd
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict'))
|
||||||
|
except AnsibleConnectionFailure:
|
||||||
|
raise AnsibleConnectionFailure('unable to elevate privilege to enable mode')
|
||||||
|
|
||||||
|
def on_unbecome(self):
|
||||||
|
prompt = self._get_prompt()
|
||||||
|
if prompt is None:
|
||||||
|
# if prompt is None most likely the terminal is hung up at a prompt
|
||||||
|
return
|
||||||
|
|
||||||
|
if prompt.strip().endswith(b')#'):
|
||||||
|
self._exec_cli_command(b'end')
|
||||||
|
self._exec_cli_command(b'disable')
|
||||||
|
|
||||||
|
elif prompt.endswith(b'#'):
|
||||||
|
self._exec_cli_command(b'disable')
|
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
output/
|
0
tests/integration/targets/__init__.py
Normal file
0
tests/integration/targets/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
testcase: "*"
|
16
tests/integration/targets/dellos9_command/tasks/cli.yaml
Normal file
16
tests/integration/targets/dellos9_command/tasks/cli.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
- name: collect all cli test cases
|
||||||
|
find:
|
||||||
|
paths: "{{ role_path }}/tests/cli"
|
||||||
|
patterns: "{{ testcase }}.yaml"
|
||||||
|
register: test_cases
|
||||||
|
|
||||||
|
- name: set test_items
|
||||||
|
set_fact:
|
||||||
|
test_items: "{{ test_cases.files | map(attribute='path') | list }}"
|
||||||
|
|
||||||
|
- name: run test case
|
||||||
|
include: "{{ test_case_to_run }}"
|
||||||
|
with_items: "{{ test_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: test_case_to_run
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- { include: cli.yaml, tags: ['cli'] }
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/bad_operator.yaml"
|
||||||
|
|
||||||
|
- name: test bad operator
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show interfaces TenGigabitEthernet 0/0
|
||||||
|
wait_for:
|
||||||
|
- "result[0] contains 'Description : blah'"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == true"
|
||||||
|
- "result.msg is defined"
|
||||||
|
|
||||||
|
- debug: msg="END cli/bad_operator.yaml"
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/contains.yaml"
|
||||||
|
|
||||||
|
- name: test contains operator
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show interface TenGigabitEthernet 0/0
|
||||||
|
wait_for:
|
||||||
|
- "result[0] contains 2.0"
|
||||||
|
- "result[1] contains TenGigabitEthernet "
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.stdout is defined"
|
||||||
|
|
||||||
|
- debug: msg="END cli/contains.yaml"
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/invalid.yaml"
|
||||||
|
|
||||||
|
- name: run invalid command
|
||||||
|
dellos9_command:
|
||||||
|
commands: ['show foo']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed"
|
||||||
|
|
||||||
|
- name: run commands that include invalid command
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show foo
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed"
|
||||||
|
|
||||||
|
- debug: msg="END cli/invalid.yaml"
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/output.yaml"
|
||||||
|
|
||||||
|
- name: get output for single command
|
||||||
|
dellos9_command:
|
||||||
|
commands: ['show version']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.stdout is defined"
|
||||||
|
|
||||||
|
- name: get output for multiple commands
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
- show interfaces
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.stdout is defined"
|
||||||
|
- "result.stdout | length == 2"
|
||||||
|
|
||||||
|
- debug: msg="END cli/output.yaml"
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/timeout.yaml"
|
||||||
|
|
||||||
|
- name: test bad condition
|
||||||
|
dellos9_command:
|
||||||
|
commands:
|
||||||
|
- show version
|
||||||
|
wait_for:
|
||||||
|
- "result[0] contains bad_value_string"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == true"
|
||||||
|
- "result.msg is defined"
|
||||||
|
|
||||||
|
- debug: msg="END cli/timeout.yaml"
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
testcase: "*"
|
15
tests/integration/targets/dellos9_config/tasks/cli.yaml
Normal file
15
tests/integration/targets/dellos9_config/tasks/cli.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
- name: collect all cli test cases
|
||||||
|
find:
|
||||||
|
paths: "{{ role_path }}/tests/cli"
|
||||||
|
patterns: "{{ testcase }}.yaml"
|
||||||
|
register: test_cases
|
||||||
|
|
||||||
|
- name: set test_items
|
||||||
|
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||||
|
|
||||||
|
- name: run test case
|
||||||
|
include: "{{ test_case_to_run }}"
|
||||||
|
with_items: "{{ test_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: test_case_to_run
|
2
tests/integration/targets/dellos9_config/tasks/main.yaml
Normal file
2
tests/integration/targets/dellos9_config/tasks/main.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- { include: cli.yaml, tags: ['cli'] }
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/sublevel.yaml"
|
||||||
|
|
||||||
|
- name: setup test
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- 'no ip access-list extended test'
|
||||||
|
- 'no ip access-list standard test'
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure sub level command
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['seq 5 permit ip any any log threshold-in-msgs 10 interval 5']
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'ip access-list extended test' in result.updates"
|
||||||
|
- "'seq 5 permit ip any any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
|
||||||
|
- name: configure sub level command idempotent check
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['seq 5 permit ip any any log threshold-in-msgs 10 interval 5']
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- 'no ip access-list extended test'
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/sublevel.yaml"
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/sublevel_block.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
before: ['no ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure sub level command using block replace
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
replace: block
|
||||||
|
after: ['exit']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'ip access-list extended test' in result.updates"
|
||||||
|
- "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
|
||||||
|
- name: check sub level command using block replace
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
replace: block
|
||||||
|
after: ['exit']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- no ip access-list extended test
|
||||||
|
match: none
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
|
||||||
|
- debug: msg="END cli/sublevel_block.yaml"
|
|
@ -0,0 +1,66 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/sublevel_exact.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
before: ['no ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure sub level command using exact match
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
match: exact
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'ip access-list extended test' in result.updates"
|
||||||
|
- "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5' not in result.updates"
|
||||||
|
|
||||||
|
- name: check sub level command using exact match
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
match: exact
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- no ip access-list extended test
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/sublevel_exact.yaml"
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/sublevel_strict.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
before: ['no ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure sub level command using strict match
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
match: strict
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: check sub level command using strict match
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 10 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5
|
||||||
|
- seq 15 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5
|
||||||
|
parents: ['ip access-list extended test']
|
||||||
|
after: ['exit']
|
||||||
|
match: strict
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'ip access-list extended test' in result.updates"
|
||||||
|
- "'seq 5 permit ip host 192.0.2.1 any log threshold-in-msgs 10 interval 5' not in result.updates"
|
||||||
|
- "'seq 15 permit ip host 192.0.2.2 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 10 permit ip host 192.0.2.3 any log threshold-in-msgs 10 interval 5' in result.updates"
|
||||||
|
- "'seq 20 permit ip host 192.0.2.4 any log threshold-in-msgs 10 interval 5' not in result.updates"
|
||||||
|
- "'seq 25 permit ip host 192.0.2.5 any log threshold-in-msgs 10 interval 5' not in result.updates"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- no ip access-list extended test
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/sublevel_strict.yaml"
|
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/toplevel.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname_short }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure top level command
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'hostname foo' in result.updates"
|
||||||
|
|
||||||
|
- name: configure top level command idempotent check
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname_short }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/toplevel.yaml"
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/toplevel_after.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- "snmp-server contact ansible"
|
||||||
|
- "hostname {{ inventory_hostname_short }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure top level command with after
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
after: ['snmp-server contact bar']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'hostname foo' in result.updates"
|
||||||
|
- "'snmp-server contact bar' in result.updates"
|
||||||
|
|
||||||
|
- name: configure top level command with after idempotent check
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
after: ['snmp-server contact bar']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- "no snmp-server contact"
|
||||||
|
- "hostname {{ inventory_hostname_short }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/toplevel_after.yaml"
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/toplevel_before.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- "snmp-server contact ansible"
|
||||||
|
- "hostname {{ inventory_hostname_short }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure top level command with before
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
before: ['snmp-server contact bar']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'hostname foo' in result.updates"
|
||||||
|
- "'snmp-server contact bar' in result.updates"
|
||||||
|
|
||||||
|
- name: configure top level command with before idempotent check
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
before: ['snmp-server contact bar']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines:
|
||||||
|
- "no snmp-server contact"
|
||||||
|
- "hostname {{ inventory_hostname_short }}"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/toplevel_before.yaml"
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/toplevel_nonidempotent.yaml"
|
||||||
|
|
||||||
|
- name: setup
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname_short }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- name: configure top level command
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: strict
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
- "'hostname foo' in result.updates"
|
||||||
|
|
||||||
|
- name: configure top level command idempotent check
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname foo']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: strict
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == true"
|
||||||
|
|
||||||
|
- name: teardown
|
||||||
|
dellos9_config:
|
||||||
|
lines: ['hostname {{ inventory_hostname_short }}']
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
match: none
|
||||||
|
|
||||||
|
- debug: msg="END cli/toplevel_nonidempotent.yaml"
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
testcase: "*"
|
16
tests/integration/targets/dellos9_facts/tasks/cli.yaml
Normal file
16
tests/integration/targets/dellos9_facts/tasks/cli.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
- name: collect all cli test cases
|
||||||
|
find:
|
||||||
|
paths: "{{ role_path }}/tests/cli"
|
||||||
|
patterns: "{{ testcase }}.yaml"
|
||||||
|
register: test_cases
|
||||||
|
|
||||||
|
- name: set test_items
|
||||||
|
set_fact:
|
||||||
|
test_items: "{{ test_cases.files | map(attribute='path') | list }}"
|
||||||
|
|
||||||
|
- name: run test case
|
||||||
|
include: "{{ test_case_to_run }}"
|
||||||
|
with_items: "{{ test_items }}"
|
||||||
|
loop_control:
|
||||||
|
loop_var: test_case_to_run
|
2
tests/integration/targets/dellos9_facts/tasks/main.yaml
Normal file
2
tests/integration/targets/dellos9_facts/tasks/main.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- { include: cli.yaml, tags: ['cli'] }
|
42
tests/integration/targets/dellos9_facts/tests/cli/facts.yaml
Normal file
42
tests/integration/targets/dellos9_facts/tests/cli/facts.yaml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
- debug: msg="START cli/facts.yaml"
|
||||||
|
|
||||||
|
- name: test all facts
|
||||||
|
dellos9_facts:
|
||||||
|
gather_subset:
|
||||||
|
- all
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.ansible_facts is defined"
|
||||||
|
|
||||||
|
- name: test all facts except hardware
|
||||||
|
dellos9_facts:
|
||||||
|
gather_subset:
|
||||||
|
- "!hardware"
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.ansible_facts.ansible_net_filesystems is not defined"
|
||||||
|
|
||||||
|
- name: test interface facts
|
||||||
|
dellos9_facts:
|
||||||
|
gather_subset:
|
||||||
|
- interfaces
|
||||||
|
provider: "{{ cli }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
- "result.ansible_facts.ansible_net_interfaces is defined"
|
||||||
|
- "result.ansible_facts.ansible_net_filesystems is not defined"
|
||||||
|
|
||||||
|
|
||||||
|
- debug: msg="END cli/facts.yaml"
|
31
tests/sanity/ignore-2.10.txt
Normal file
31
tests/sanity/ignore-2.10.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
plugins/module_utils/network/dellos9/dellos9.py future-import-boilerplate
|
||||||
|
plugins/module_utils/network/dellos9/dellos9.py metaclass-boilerplate
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:undocumented-parameter
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:undocumented-parameter
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:undocumented-parameter
|
||||||
|
plugins/action/dellos9.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
|
||||||
|
plugins/doc_fragments/dellos9.py future-import-boilerplate
|
||||||
|
plugins/doc_fragments/dellos9.py metaclass-boilerplate
|
||||||
|
tests/unit/mock/path.py future-import-boilerplate
|
||||||
|
tests/unit/mock/path.py metaclass-boilerplate
|
||||||
|
tests/unit/mock/yaml_helper.py future-import-boilerplate
|
||||||
|
tests/unit/mock/yaml_helper.py metaclass-boilerplate
|
||||||
|
tests/unit/modules/conftest.py future-import-boilerplate
|
||||||
|
tests/unit/modules/conftest.py metaclass-boilerplate
|
||||||
|
tests/unit/modules/utils.py future-import-boilerplate
|
||||||
|
tests/unit/modules/utils.py metaclass-boilerplate
|
31
tests/sanity/ignore-2.9.txt
Normal file
31
tests/sanity/ignore-2.9.txt
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
plugins/module_utils/network/dellos9/dellos9.py future-import-boilerplate
|
||||||
|
plugins/module_utils/network/dellos9/dellos9.py metaclass-boilerplate
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_command.py validate-modules:undocumented-parameter
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_config.py validate-modules:undocumented-parameter
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-default-does-not-match-spec
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-missing-type
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:doc-required-mismatch
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:parameter-list-no-elements
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:parameter-type-not-in-doc
|
||||||
|
plugins/modules/dellos9_facts.py validate-modules:undocumented-parameter
|
||||||
|
plugins/action/dellos9.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
|
||||||
|
plugins/doc_fragments/dellos9.py future-import-boilerplate
|
||||||
|
plugins/doc_fragments/dellos9.py metaclass-boilerplate
|
||||||
|
tests/unit/mock/path.py future-import-boilerplate
|
||||||
|
tests/unit/mock/path.py metaclass-boilerplate
|
||||||
|
tests/unit/mock/yaml_helper.py future-import-boilerplate
|
||||||
|
tests/unit/mock/yaml_helper.py metaclass-boilerplate
|
||||||
|
tests/unit/modules/conftest.py future-import-boilerplate
|
||||||
|
tests/unit/modules/conftest.py metaclass-boilerplate
|
||||||
|
tests/unit/modules/utils.py future-import-boilerplate
|
||||||
|
tests/unit/modules/utils.py metaclass-boilerplate
|
4
tests/sanity/requirements.txt
Normal file
4
tests/sanity/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
packaging # needed for update-bundled and changelog
|
||||||
|
sphinx ; python_version >= '3.5' # docs build requires python 3+
|
||||||
|
sphinx-notfound-page ; python_version >= '3.5' # docs build requires python 3+
|
||||||
|
straight.plugin ; python_version >= '3.5' # needed for hacking/build-ansible.py which will host changelog generation and requires python 3+
|
0
tests/unit/__init__.py
Normal file
0
tests/unit/__init__.py
Normal file
0
tests/unit/compat/__init__.py
Normal file
0
tests/unit/compat/__init__.py
Normal file
33
tests/unit/compat/builtins.py
Normal file
33
tests/unit/compat/builtins.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compat for python2.7
|
||||||
|
#
|
||||||
|
|
||||||
|
# One unittest needs to import builtins via __import__() so we need to have
|
||||||
|
# the string that represents it
|
||||||
|
try:
|
||||||
|
import __builtin__
|
||||||
|
except ImportError:
|
||||||
|
BUILTINS = 'builtins'
|
||||||
|
else:
|
||||||
|
BUILTINS = '__builtin__'
|
122
tests/unit/compat/mock.py
Normal file
122
tests/unit/compat/mock.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
'''
|
||||||
|
Compat module for Python3.x's unittest.mock module
|
||||||
|
'''
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Python 2.7
|
||||||
|
|
||||||
|
# Note: Could use the pypi mock library on python3.x as well as python2.x. It
|
||||||
|
# is the same as the python3 stdlib mock library
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Allow wildcard import because we really do want to import all of mock's
|
||||||
|
# symbols into this compat shim
|
||||||
|
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||||
|
from unittest.mock import *
|
||||||
|
except ImportError:
|
||||||
|
# Python 2
|
||||||
|
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||||
|
try:
|
||||||
|
from mock import *
|
||||||
|
except ImportError:
|
||||||
|
print('You need the mock library installed on python2.x to run tests')
|
||||||
|
|
||||||
|
|
||||||
|
# Prior to 3.4.4, mock_open cannot handle binary read_data
|
||||||
|
if sys.version_info >= (3,) and sys.version_info < (3, 4, 4):
|
||||||
|
file_spec = None
|
||||||
|
|
||||||
|
def _iterate_read_data(read_data):
|
||||||
|
# Helper for mock_open:
|
||||||
|
# Retrieve lines from read_data via a generator so that separate calls to
|
||||||
|
# readline, read, and readlines are properly interleaved
|
||||||
|
sep = b'\n' if isinstance(read_data, bytes) else '\n'
|
||||||
|
data_as_list = [l + sep for l in read_data.split(sep)]
|
||||||
|
|
||||||
|
if data_as_list[-1] == sep:
|
||||||
|
# If the last line ended in a newline, the list comprehension will have an
|
||||||
|
# extra entry that's just a newline. Remove this.
|
||||||
|
data_as_list = data_as_list[:-1]
|
||||||
|
else:
|
||||||
|
# If there wasn't an extra newline by itself, then the file being
|
||||||
|
# emulated doesn't have a newline to end the last line remove the
|
||||||
|
# newline that our naive format() added
|
||||||
|
data_as_list[-1] = data_as_list[-1][:-1]
|
||||||
|
|
||||||
|
for line in data_as_list:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
def mock_open(mock=None, read_data=''):
|
||||||
|
"""
|
||||||
|
A helper function to create a mock to replace the use of `open`. It works
|
||||||
|
for `open` called directly or used as a context manager.
|
||||||
|
|
||||||
|
The `mock` argument is the mock object to configure. If `None` (the
|
||||||
|
default) then a `MagicMock` will be created for you, with the API limited
|
||||||
|
to methods or attributes available on standard file handles.
|
||||||
|
|
||||||
|
`read_data` is a string for the `read` methoddline`, and `readlines` of the
|
||||||
|
file handle to return. This is an empty string by default.
|
||||||
|
"""
|
||||||
|
def _readlines_side_effect(*args, **kwargs):
|
||||||
|
if handle.readlines.return_value is not None:
|
||||||
|
return handle.readlines.return_value
|
||||||
|
return list(_data)
|
||||||
|
|
||||||
|
def _read_side_effect(*args, **kwargs):
|
||||||
|
if handle.read.return_value is not None:
|
||||||
|
return handle.read.return_value
|
||||||
|
return type(read_data)().join(_data)
|
||||||
|
|
||||||
|
def _readline_side_effect():
|
||||||
|
if handle.readline.return_value is not None:
|
||||||
|
while True:
|
||||||
|
yield handle.readline.return_value
|
||||||
|
for line in _data:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
global file_spec
|
||||||
|
if file_spec is None:
|
||||||
|
import _io
|
||||||
|
file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
|
||||||
|
|
||||||
|
if mock is None:
|
||||||
|
mock = MagicMock(name='open', spec=open)
|
||||||
|
|
||||||
|
handle = MagicMock(spec=file_spec)
|
||||||
|
handle.__enter__.return_value = handle
|
||||||
|
|
||||||
|
_data = _iterate_read_data(read_data)
|
||||||
|
|
||||||
|
handle.write.return_value = None
|
||||||
|
handle.read.return_value = None
|
||||||
|
handle.readline.return_value = None
|
||||||
|
handle.readlines.return_value = None
|
||||||
|
|
||||||
|
handle.read.side_effect = _read_side_effect
|
||||||
|
handle.readline.side_effect = _readline_side_effect()
|
||||||
|
handle.readlines.side_effect = _readlines_side_effect
|
||||||
|
|
||||||
|
mock.return_value = handle
|
||||||
|
return mock
|
38
tests/unit/compat/unittest.py
Normal file
38
tests/unit/compat/unittest.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
'''
|
||||||
|
Compat module for Python2.7's unittest module
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Allow wildcard import because we really do want to import all of
|
||||||
|
# unittests's symbols into this compat shim
|
||||||
|
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||||
|
if sys.version_info < (2, 7):
|
||||||
|
try:
|
||||||
|
# Need unittest2 on python2.6
|
||||||
|
from unittest2 import *
|
||||||
|
except ImportError:
|
||||||
|
print('You need unittest2 installed on python2.6.x to run tests')
|
||||||
|
else:
|
||||||
|
from unittest import *
|
0
tests/unit/mock/__init__.py
Normal file
0
tests/unit/mock/__init__.py
Normal file
116
tests/unit/mock/loader.py
Normal file
116
tests/unit/mock/loader.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleParserError
|
||||||
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.module_utils._text import to_bytes, to_text
|
||||||
|
|
||||||
|
|
||||||
|
class DictDataLoader(DataLoader):
|
||||||
|
|
||||||
|
def __init__(self, file_mapping=None):
|
||||||
|
file_mapping = {} if file_mapping is None else file_mapping
|
||||||
|
assert type(file_mapping) == dict
|
||||||
|
|
||||||
|
super(DictDataLoader, self).__init__()
|
||||||
|
|
||||||
|
self._file_mapping = file_mapping
|
||||||
|
self._build_known_directories()
|
||||||
|
self._vault_secrets = None
|
||||||
|
|
||||||
|
def load_from_file(self, path, cache=True, unsafe=False):
|
||||||
|
path = to_text(path)
|
||||||
|
if path in self._file_mapping:
|
||||||
|
return self.load(self._file_mapping[path], path)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# TODO: the real _get_file_contents returns a bytestring, so we actually convert the
|
||||||
|
# unicode/text it's created with to utf-8
|
||||||
|
def _get_file_contents(self, path):
|
||||||
|
path = to_text(path)
|
||||||
|
if path in self._file_mapping:
|
||||||
|
return (to_bytes(self._file_mapping[path]), False)
|
||||||
|
else:
|
||||||
|
raise AnsibleParserError("file not found: %s" % path)
|
||||||
|
|
||||||
|
def path_exists(self, path):
|
||||||
|
path = to_text(path)
|
||||||
|
return path in self._file_mapping or path in self._known_directories
|
||||||
|
|
||||||
|
def is_file(self, path):
|
||||||
|
path = to_text(path)
|
||||||
|
return path in self._file_mapping
|
||||||
|
|
||||||
|
def is_directory(self, path):
|
||||||
|
path = to_text(path)
|
||||||
|
return path in self._known_directories
|
||||||
|
|
||||||
|
def list_directory(self, path):
|
||||||
|
ret = []
|
||||||
|
path = to_text(path)
|
||||||
|
for x in (list(self._file_mapping.keys()) + self._known_directories):
|
||||||
|
if x.startswith(path):
|
||||||
|
if os.path.dirname(x) == path:
|
||||||
|
ret.append(os.path.basename(x))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def is_executable(self, path):
|
||||||
|
# FIXME: figure out a way to make paths return true for this
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _add_known_directory(self, directory):
|
||||||
|
if directory not in self._known_directories:
|
||||||
|
self._known_directories.append(directory)
|
||||||
|
|
||||||
|
def _build_known_directories(self):
|
||||||
|
self._known_directories = []
|
||||||
|
for path in self._file_mapping:
|
||||||
|
dirname = os.path.dirname(path)
|
||||||
|
while dirname not in ('/', ''):
|
||||||
|
self._add_known_directory(dirname)
|
||||||
|
dirname = os.path.dirname(dirname)
|
||||||
|
|
||||||
|
def push(self, path, content):
|
||||||
|
rebuild_dirs = False
|
||||||
|
if path not in self._file_mapping:
|
||||||
|
rebuild_dirs = True
|
||||||
|
|
||||||
|
self._file_mapping[path] = content
|
||||||
|
|
||||||
|
if rebuild_dirs:
|
||||||
|
self._build_known_directories()
|
||||||
|
|
||||||
|
def pop(self, path):
|
||||||
|
if path in self._file_mapping:
|
||||||
|
del self._file_mapping[path]
|
||||||
|
self._build_known_directories()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._file_mapping = dict()
|
||||||
|
self._known_directories = []
|
||||||
|
|
||||||
|
def get_basedir(self):
|
||||||
|
return os.getcwd()
|
||||||
|
|
||||||
|
def set_vault_secrets(self, vault_secrets):
|
||||||
|
self._vault_secrets = vault_secrets
|
5
tests/unit/mock/path.py
Normal file
5
tests/unit/mock/path.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import MagicMock
|
||||||
|
from ansible.utils.path import unfrackpath
|
||||||
|
|
||||||
|
|
||||||
|
mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x)
|
90
tests/unit/mock/procenv.py
Normal file
90
tests/unit/mock/procenv.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# (c) 2016, Matt Davis <mdavis@ansible.com>
|
||||||
|
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from io import BytesIO, StringIO
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat import unittest
|
||||||
|
from ansible.module_utils.six import PY3
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def swap_stdin_and_argv(stdin_data='', argv_data=tuple()):
|
||||||
|
"""
|
||||||
|
context manager that temporarily masks the test runner's values for stdin and argv
|
||||||
|
"""
|
||||||
|
real_stdin = sys.stdin
|
||||||
|
real_argv = sys.argv
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
fake_stream = StringIO(stdin_data)
|
||||||
|
fake_stream.buffer = BytesIO(to_bytes(stdin_data))
|
||||||
|
else:
|
||||||
|
fake_stream = BytesIO(to_bytes(stdin_data))
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdin = fake_stream
|
||||||
|
sys.argv = argv_data
|
||||||
|
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
sys.stdin = real_stdin
|
||||||
|
sys.argv = real_argv
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def swap_stdout():
|
||||||
|
"""
|
||||||
|
context manager that temporarily replaces stdout for tests that need to verify output
|
||||||
|
"""
|
||||||
|
old_stdout = sys.stdout
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
fake_stream = StringIO()
|
||||||
|
else:
|
||||||
|
fake_stream = BytesIO()
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdout = fake_stream
|
||||||
|
|
||||||
|
yield fake_stream
|
||||||
|
finally:
|
||||||
|
sys.stdout = old_stdout
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTestCase(unittest.TestCase):
|
||||||
|
def setUp(self, module_args=None):
|
||||||
|
if module_args is None:
|
||||||
|
module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False}
|
||||||
|
|
||||||
|
args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args))
|
||||||
|
|
||||||
|
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
|
||||||
|
self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
|
||||||
|
self.stdin_swap.__enter__()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually
|
||||||
|
self.stdin_swap.__exit__(None, None, None)
|
39
tests/unit/mock/vault_helper.py
Normal file
39
tests/unit/mock/vault_helper.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
from ansible.parsing.vault import VaultSecret
|
||||||
|
|
||||||
|
|
||||||
|
class TextVaultSecret(VaultSecret):
|
||||||
|
'''A secret piece of text. ie, a password. Tracks text encoding.
|
||||||
|
|
||||||
|
The text encoding of the text may not be the default text encoding so
|
||||||
|
we keep track of the encoding so we encode it to the same bytes.'''
|
||||||
|
|
||||||
|
def __init__(self, text, encoding=None, errors=None, _bytes=None):
|
||||||
|
super(TextVaultSecret, self).__init__()
|
||||||
|
self.text = text
|
||||||
|
self.encoding = encoding or 'utf-8'
|
||||||
|
self._bytes = _bytes
|
||||||
|
self.errors = errors or 'strict'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bytes(self):
|
||||||
|
'''The text encoded with encoding, unless we specifically set _bytes.'''
|
||||||
|
return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors)
|
121
tests/unit/mock/yaml_helper.py
Normal file
121
tests/unit/mock/yaml_helper.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import io
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from ansible.module_utils.six import PY3
|
||||||
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
|
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||||
|
|
||||||
|
|
||||||
|
class YamlTestUtils(object):
|
||||||
|
"""Mixin class to combine with a unittest.TestCase subclass."""
|
||||||
|
def _loader(self, stream):
|
||||||
|
"""Vault related tests will want to override this.
|
||||||
|
|
||||||
|
Vault cases should setup a AnsibleLoader that has the vault password."""
|
||||||
|
return AnsibleLoader(stream)
|
||||||
|
|
||||||
|
def _dump_stream(self, obj, stream, dumper=None):
|
||||||
|
"""Dump to a py2-unicode or py3-string stream."""
|
||||||
|
if PY3:
|
||||||
|
return yaml.dump(obj, stream, Dumper=dumper)
|
||||||
|
else:
|
||||||
|
return yaml.dump(obj, stream, Dumper=dumper, encoding=None)
|
||||||
|
|
||||||
|
def _dump_string(self, obj, dumper=None):
|
||||||
|
"""Dump to a py2-unicode or py3-string"""
|
||||||
|
if PY3:
|
||||||
|
return yaml.dump(obj, Dumper=dumper)
|
||||||
|
else:
|
||||||
|
return yaml.dump(obj, Dumper=dumper, encoding=None)
|
||||||
|
|
||||||
|
def _dump_load_cycle(self, obj):
|
||||||
|
# Each pass though a dump or load revs the 'generation'
|
||||||
|
# obj to yaml string
|
||||||
|
string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper)
|
||||||
|
|
||||||
|
# wrap a stream/file like StringIO around that yaml
|
||||||
|
stream_from_object_dump = io.StringIO(string_from_object_dump)
|
||||||
|
loader = self._loader(stream_from_object_dump)
|
||||||
|
# load the yaml stream to create a new instance of the object (gen 2)
|
||||||
|
obj_2 = loader.get_data()
|
||||||
|
|
||||||
|
# dump the gen 2 objects directory to strings
|
||||||
|
string_from_object_dump_2 = self._dump_string(obj_2,
|
||||||
|
dumper=AnsibleDumper)
|
||||||
|
|
||||||
|
# The gen 1 and gen 2 yaml strings
|
||||||
|
self.assertEqual(string_from_object_dump, string_from_object_dump_2)
|
||||||
|
# the gen 1 (orig) and gen 2 py object
|
||||||
|
self.assertEqual(obj, obj_2)
|
||||||
|
|
||||||
|
# again! gen 3... load strings into py objects
|
||||||
|
stream_3 = io.StringIO(string_from_object_dump_2)
|
||||||
|
loader_3 = self._loader(stream_3)
|
||||||
|
obj_3 = loader_3.get_data()
|
||||||
|
|
||||||
|
string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper)
|
||||||
|
|
||||||
|
self.assertEqual(obj, obj_3)
|
||||||
|
# should be transitive, but...
|
||||||
|
self.assertEqual(obj_2, obj_3)
|
||||||
|
self.assertEqual(string_from_object_dump, string_from_object_dump_3)
|
||||||
|
|
||||||
|
def _old_dump_load_cycle(self, obj):
|
||||||
|
'''Dump the passed in object to yaml, load it back up, dump again, compare.'''
|
||||||
|
stream = io.StringIO()
|
||||||
|
|
||||||
|
yaml_string = self._dump_string(obj, dumper=AnsibleDumper)
|
||||||
|
self._dump_stream(obj, stream, dumper=AnsibleDumper)
|
||||||
|
|
||||||
|
yaml_string_from_stream = stream.getvalue()
|
||||||
|
|
||||||
|
# reset stream
|
||||||
|
stream.seek(0)
|
||||||
|
|
||||||
|
loader = self._loader(stream)
|
||||||
|
# loader = AnsibleLoader(stream, vault_password=self.vault_password)
|
||||||
|
obj_from_stream = loader.get_data()
|
||||||
|
|
||||||
|
stream_from_string = io.StringIO(yaml_string)
|
||||||
|
loader2 = self._loader(stream_from_string)
|
||||||
|
# loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password)
|
||||||
|
obj_from_string = loader2.get_data()
|
||||||
|
|
||||||
|
stream_obj_from_stream = io.StringIO()
|
||||||
|
stream_obj_from_string = io.StringIO()
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
|
||||||
|
yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
|
||||||
|
else:
|
||||||
|
yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None)
|
||||||
|
yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None)
|
||||||
|
|
||||||
|
yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue()
|
||||||
|
yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue()
|
||||||
|
|
||||||
|
stream_obj_from_stream.seek(0)
|
||||||
|
stream_obj_from_string.seek(0)
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
|
||||||
|
yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
|
||||||
|
else:
|
||||||
|
yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None)
|
||||||
|
yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None)
|
||||||
|
|
||||||
|
assert yaml_string == yaml_string_obj_from_stream
|
||||||
|
assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
|
||||||
|
assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream ==
|
||||||
|
yaml_string_stream_obj_from_string)
|
||||||
|
assert obj == obj_from_stream
|
||||||
|
assert obj == obj_from_string
|
||||||
|
assert obj == yaml_string_obj_from_stream
|
||||||
|
assert obj == yaml_string_obj_from_string
|
||||||
|
assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
|
||||||
|
return {'obj': obj,
|
||||||
|
'yaml_string': yaml_string,
|
||||||
|
'yaml_string_from_stream': yaml_string_from_stream,
|
||||||
|
'obj_from_stream': obj_from_stream,
|
||||||
|
'obj_from_string': obj_from_string,
|
||||||
|
'yaml_string_obj_from_string': yaml_string_obj_from_string}
|
0
tests/unit/modules/__init__.py
Normal file
0
tests/unit/modules/__init__.py
Normal file
28
tests/unit/modules/conftest.py
Normal file
28
tests/unit/modules/conftest.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
from ansible.module_utils.common._collections_compat import MutableMapping
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def patch_ansible_module(request, mocker):
|
||||||
|
if isinstance(request.param, string_types):
|
||||||
|
args = request.param
|
||||||
|
elif isinstance(request.param, MutableMapping):
|
||||||
|
if 'ANSIBLE_MODULE_ARGS' not in request.param:
|
||||||
|
request.param = {'ANSIBLE_MODULE_ARGS': request.param}
|
||||||
|
if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']:
|
||||||
|
request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp'
|
||||||
|
if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']:
|
||||||
|
request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False
|
||||||
|
args = json.dumps(request.param)
|
||||||
|
else:
|
||||||
|
raise Exception('Malformed data to the patch_ansible_module pytest fixture')
|
||||||
|
|
||||||
|
mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args))
|
0
tests/unit/modules/network/__init__.py
Normal file
0
tests/unit/modules/network/__init__.py
Normal file
0
tests/unit/modules/network/dellos9/__init__.py
Normal file
0
tests/unit/modules/network/dellos9/__init__.py
Normal file
90
tests/unit/modules/network/dellos9/dellos9_module.py
Normal file
90
tests/unit/modules/network/dellos9/dellos9_module.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# (c) 2017 Dell EMC.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
|
||||||
|
fixture_data = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_fixture(name):
|
||||||
|
path = os.path.join(fixture_path, name)
|
||||||
|
|
||||||
|
if path in fixture_data:
|
||||||
|
return fixture_data[path]
|
||||||
|
|
||||||
|
with open(path) as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fixture_data[path] = data
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class TestDellos9Module(ModuleTestCase):
|
||||||
|
|
||||||
|
def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False):
|
||||||
|
|
||||||
|
self.load_fixtures(commands)
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
result = self.failed()
|
||||||
|
self.assertTrue(result['failed'], result)
|
||||||
|
else:
|
||||||
|
result = self.changed(changed)
|
||||||
|
self.assertEqual(result['changed'], changed, result)
|
||||||
|
|
||||||
|
if commands is not None:
|
||||||
|
if sort:
|
||||||
|
self.assertEqual(sorted(commands), sorted(result['updates']), result['updates'])
|
||||||
|
else:
|
||||||
|
self.assertEqual(commands, result['updates'], result['updates'])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def failed(self):
|
||||||
|
with self.assertRaises(AnsibleFailJson) as exc:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
result = exc.exception.args[0]
|
||||||
|
self.assertTrue(result['failed'], result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def changed(self, changed=False):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exc:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
result = exc.exception.args[0]
|
||||||
|
self.assertEqual(result['changed'], changed, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
pass
|
|
@ -0,0 +1,13 @@
|
||||||
|
!
|
||||||
|
hostname router
|
||||||
|
!
|
||||||
|
interface fortyGigE 1/6
|
||||||
|
ip address 1.2.3.4/24
|
||||||
|
description test string
|
||||||
|
!
|
||||||
|
interface fortyGigE 1/7
|
||||||
|
ip address 6.7.8.9/24
|
||||||
|
description test string
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
!
|
||||||
|
hostname foo
|
||||||
|
!
|
||||||
|
interface fortyGigE 1/6
|
||||||
|
no ip address
|
||||||
|
!
|
||||||
|
interface fortyGigE 1/7
|
||||||
|
ip address 6.7.8.9/24
|
||||||
|
description test string
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
Size(b) Free(b) Feature Type Flags Prefixes
|
||||||
|
6429872128 5582319616 FAT32 USERFLASH rw flash:
|
||||||
|
- - unformatted USERFLASH rw fcmfs:
|
||||||
|
241172480 91893760 Unknown NFSMOUNT rw nfsmount:
|
||||||
|
- - - network rw ftp:
|
||||||
|
- - - network rw tftp:
|
||||||
|
- - - network rw scp:
|
||||||
|
- - - network rw http:
|
||||||
|
- - - network rw https:
|
||||||
|
|
1259
tests/unit/modules/network/dellos9/fixtures/show_interfaces
Normal file
1259
tests/unit/modules/network/dellos9/fixtures/show_interfaces
Normal file
File diff suppressed because it is too large
Load diff
19
tests/unit/modules/network/dellos9/fixtures/show_inventory
Normal file
19
tests/unit/modules/network/dellos9/fixtures/show_inventory
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
System Type : S6000
|
||||||
|
System Mode : 1.0
|
||||||
|
Software Version : 9.12(0.0)
|
||||||
|
|
||||||
|
Unit Type Serial Number Part Number Rev Piece Part ID Rev Svc Tag Exprs Svc Code
|
||||||
|
--------------------------------------------------------------------------------------------------------------
|
||||||
|
* 0 S6000-01-FE-32T NA 08YWFG A00 CN-08YWFG-28298-3AG-0031 A00 6BJ8VS1 137 581 490 89
|
||||||
|
0 S6000-PWR-AC NA 0T9FNW A00 CN-0T9FNW-28298-3AG-0119 A00 NA NA
|
||||||
|
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0094 A00 NA NA
|
||||||
|
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0096 A00 NA NA
|
||||||
|
0 S6000-FAN NA 0MGDH8 A00 CN-0MGDH8-28298-3AG-0095 A00 NA NA
|
||||||
|
|
||||||
|
* - Management Unit
|
||||||
|
|
||||||
|
|
||||||
|
Software Protocol Configured
|
||||||
|
--------------------------------------------------------------
|
||||||
|
LLDP
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
fortyGigE 0/16 is down, line protocol is down
|
||||||
|
IPV6 is enabled
|
||||||
|
Link Local address: fe80::92b1:1cff:fef4:a28f
|
||||||
|
Global Unicast address(es):
|
||||||
|
2001:4898:5808:ffa2::5, subnet is 2001:4898:5808:ffa2::4/126 (MANUAL)
|
||||||
|
Remaining lifetime: infinite
|
||||||
|
Global Anycast address(es):
|
||||||
|
Joined Group address(es):
|
||||||
|
ff02::1
|
||||||
|
ff02::2
|
||||||
|
ff02::1:ff00:5
|
||||||
|
ff02::1:fff4:a28f
|
||||||
|
IP MTU is 1500 bytes
|
||||||
|
ND MTU is 0
|
||||||
|
ICMP redirects are not sent
|
||||||
|
DAD is enabled, number of DAD attempts: 3
|
||||||
|
ND reachable time is 35780 milliseconds
|
||||||
|
ND base reachable time is 30000 milliseconds
|
||||||
|
ND advertised reachable time is 0 milliseconds
|
||||||
|
ND advertised retransmit interval is 0 milliseconds
|
||||||
|
ND router advertisements are sent every 198 to 600 seconds
|
||||||
|
ND router advertisements live for 1800 seconds
|
||||||
|
ND advertised hop limit is 64
|
||||||
|
IPv6 hop limit for originated packets is 64
|
||||||
|
IPv6 unicast RPF check is not supported
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
========================================================================
|
||||||
|
Local Interface Ma 0/0 has 1 neighbor
|
||||||
|
Total Frames Out: 1747
|
||||||
|
Total Frames In: 10333
|
||||||
|
Total Neighbor information Age outs: 0
|
||||||
|
Total Multiple Neighbors Detected: 0
|
||||||
|
Total Frames Discarded: 0
|
||||||
|
Total In Error Frames: 0
|
||||||
|
Total Unrecognized TLVs: 0
|
||||||
|
Total TLVs Discarded: 0
|
||||||
|
Next packet will be sent after 17 seconds
|
||||||
|
The neighbors are given below:
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Remote Chassis ID Subtype: Mac address (4)
|
||||||
|
Remote Chassis ID: 90:b1:1c:f4:2f:6d
|
||||||
|
Remote Port Subtype: Interface name (5)
|
||||||
|
Remote Port ID: TenGigabitEthernet 0/33
|
||||||
|
Remote Port Description: TenGigabitEthernet 0/33
|
||||||
|
Local Port ID: ManagementEthernet 0/0
|
||||||
|
Locally assigned remote Neighbor Index: 1
|
||||||
|
Remote TTL: 20
|
||||||
|
Information valid for next 17 seconds
|
||||||
|
Time since last information change of this neighbor: 14:54:48
|
||||||
|
Remote System Name: swlab1-maa-tor-A2
|
||||||
|
Remote System Desc: Dell Real Time Operating System Software. Dell
|
||||||
|
Operating System Version: 2.0. Dell Application Software Version:
|
||||||
|
9.11(2.0) Copyright (c) 1999-2017Dell Inc. All Rights Reserved.Build
|
||||||
|
Time: Tue Apr 25 21:22:59 2017
|
||||||
|
Existing System Capabilities: Repeater Bridge Router
|
||||||
|
Enabled System Capabilities: Repeater Bridge Router
|
||||||
|
Remote Port Vlan ID: 148
|
||||||
|
Port and Protocol Vlan ID: 148, Capability: Supported, Status: Enabled
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
===========================
|
||||||
|
Total(b) Used(b) Free(b) Lowest(b) Largest(b)
|
||||||
|
3203911680 3172120 3200739560 3200673304 3200739560
|
||||||
|
|
238
tests/unit/modules/network/dellos9/fixtures/show_running-config
Normal file
238
tests/unit/modules/network/dellos9/fixtures/show_running-config
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
Current Configuration ...
|
||||||
|
! Version 9.12(0.0)
|
||||||
|
! Last configuration change at Thu Jan 11 06:53:29 2018 by admin
|
||||||
|
!
|
||||||
|
!
|
||||||
|
logging coredump stack-unit 0
|
||||||
|
logging coredump stack-unit 1
|
||||||
|
logging coredump stack-unit 2
|
||||||
|
logging coredump stack-unit 3
|
||||||
|
logging coredump stack-unit 4
|
||||||
|
logging coredump stack-unit 5
|
||||||
|
!
|
||||||
|
hostname Dell
|
||||||
|
!
|
||||||
|
protocol lldp
|
||||||
|
!
|
||||||
|
redundancy auto-synchronize full
|
||||||
|
!
|
||||||
|
enable password 7 b125455cf679b208e79b910e85789edf
|
||||||
|
!
|
||||||
|
username admin password 7 1d28e9f33f99cf5c
|
||||||
|
!
|
||||||
|
stack-unit 0 quad-port-profile 0,8,16,24,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,100,108,116,124
|
||||||
|
!
|
||||||
|
stack-unit 0 provision S6000
|
||||||
|
!
|
||||||
|
stack-unit 0 port 0 portmode quad
|
||||||
|
!
|
||||||
|
interface TenGigabitEthernet 0/0
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface TenGigabitEthernet 0/1
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface TenGigabitEthernet 0/2
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface TenGigabitEthernet 0/3
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/4
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/8
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/12
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/16
|
||||||
|
no ip address
|
||||||
|
ipv6 address 2001:4898:5808:ffa2::5/126
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/20
|
||||||
|
no ip address
|
||||||
|
switchport
|
||||||
|
ip access-group ipv6-ssh-only in
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/24
|
||||||
|
no ip address
|
||||||
|
switchport
|
||||||
|
mac access-group ssh-only-mac in
|
||||||
|
mac access-group ssh-only-mac out
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/28
|
||||||
|
no ip address
|
||||||
|
switchport
|
||||||
|
mac access-group ssh-only-mac in
|
||||||
|
mac access-group ssh-only-mac out
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/32
|
||||||
|
no ip address
|
||||||
|
switchport
|
||||||
|
ip access-group ipv6-ssh-only out
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/36
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/40
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/44
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/48
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/52
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/56
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/60
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/64
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/68
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/72
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/76
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/80
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/84
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/88
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/92
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/96
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/100
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/104
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/108
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/112
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/116
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/120
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface fortyGigE 0/124
|
||||||
|
no ip address
|
||||||
|
shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 0/0
|
||||||
|
ip address 10.16.148.71/16
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 1/0
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 2/0
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 3/0
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 4/0
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface ManagementEthernet 5/0
|
||||||
|
no shutdown
|
||||||
|
!
|
||||||
|
interface Vlan 1
|
||||||
|
!untagged fortyGigE 0/20-32
|
||||||
|
!
|
||||||
|
ipv6 access-list ipv6-ssh-only
|
||||||
|
description ipv6acl
|
||||||
|
remark 1 ipv6
|
||||||
|
seq 10 permit ipv6 2001:4898::/32 any
|
||||||
|
seq 20 permit tcp any eq 2 2404:f801::/32
|
||||||
|
seq 30 permit tcp any 2a01:110::/31 ack
|
||||||
|
seq 40 permit tcp any any
|
||||||
|
!
|
||||||
|
mac access-list extended ssh-only-mac
|
||||||
|
description macacl
|
||||||
|
remark 1 mac
|
||||||
|
seq 5 permit any any count
|
||||||
|
seq 6 deny any any
|
||||||
|
!
|
||||||
|
ip ssh server enable
|
||||||
|
!
|
||||||
|
line console 0
|
||||||
|
line vty 0
|
||||||
|
line vty 1
|
||||||
|
access-class ipv6-ssh-only ipv6
|
||||||
|
line vty 2
|
||||||
|
access-class ipv6-ssh-only ipv6
|
||||||
|
line vty 3
|
||||||
|
access-class ipv6-ssh-only ipv6
|
||||||
|
line vty 4
|
||||||
|
line vty 5
|
||||||
|
line vty 6
|
||||||
|
line vty 7
|
||||||
|
line vty 8
|
||||||
|
line vty 9
|
||||||
|
!
|
||||||
|
reload-type
|
||||||
|
boot-type normal-reload
|
||||||
|
config-scr-download enable
|
||||||
|
!
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
hostname dellos9_sw1
|
18
tests/unit/modules/network/dellos9/fixtures/show_version
Normal file
18
tests/unit/modules/network/dellos9/fixtures/show_version
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Dell Real Time Operating System Software
|
||||||
|
Dell Operating System Version: 2.0
|
||||||
|
Dell Application Software Version: 9.10(0.1P13)
|
||||||
|
Copyright (c) 1999-2016 by Dell Inc. All Rights Reserved.
|
||||||
|
Build Time: Wed Sep 7 23:48:35 2016
|
||||||
|
Build Path: /sites/eqx/work/swbuild01_1/build01/E9-10-0/SW/SRC
|
||||||
|
Dell Networking OS uptime is 12 week(s), 6 day(s), 9 hour(s), 20 minute(s)
|
||||||
|
|
||||||
|
System image file is "system://A"
|
||||||
|
|
||||||
|
System Type: S6000-ON
|
||||||
|
Control Processor: Intel Centerton with 3 Gbytes (3203911680 bytes) of memory, core(s) 2.
|
||||||
|
|
||||||
|
16G bytes of boot flash memory.
|
||||||
|
|
||||||
|
1 32-port TE/FG (SI-ON)
|
||||||
|
32 Forty GigabitEthernet/IEEE 802.3 interface(s)
|
||||||
|
|
110
tests/unit/modules/network/dellos9/test_dellos9_command.py
Normal file
110
tests/unit/modules/network/dellos9/test_dellos9_command.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# (c) 2017 Dell EMC.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_command
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args
|
||||||
|
from ..dellos9_module import TestDellos9Module, load_fixture
|
||||||
|
|
||||||
|
|
||||||
|
class TestDellos9CommandModule(TestDellos9Module):
|
||||||
|
|
||||||
|
module = dellos9_command
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDellos9CommandModule, self).setUp()
|
||||||
|
|
||||||
|
self.mock_run_commands = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_command.run_commands')
|
||||||
|
self.run_commands = self.mock_run_commands.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestDellos9CommandModule, self).tearDown()
|
||||||
|
self.mock_run_commands.stop()
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
|
||||||
|
def load_from_file(*args, **kwargs):
|
||||||
|
module, commands = args
|
||||||
|
output = list()
|
||||||
|
|
||||||
|
for item in commands:
|
||||||
|
try:
|
||||||
|
obj = json.loads(item['command'])
|
||||||
|
command = obj['command']
|
||||||
|
except ValueError:
|
||||||
|
command = item['command']
|
||||||
|
filename = str(command).replace(' ', '_')
|
||||||
|
output.append(load_fixture(filename))
|
||||||
|
return output
|
||||||
|
|
||||||
|
self.run_commands.side_effect = load_from_file
|
||||||
|
|
||||||
|
def test_dellos9_command_simple(self):
|
||||||
|
set_module_args(dict(commands=['show version']))
|
||||||
|
result = self.execute_module()
|
||||||
|
self.assertEqual(len(result['stdout']), 1)
|
||||||
|
self.assertTrue(result['stdout'][0].startswith('Dell Real Time'))
|
||||||
|
|
||||||
|
def test_dellos9_command_multiple(self):
|
||||||
|
set_module_args(dict(commands=['show version', 'show version']))
|
||||||
|
result = self.execute_module()
|
||||||
|
self.assertEqual(len(result['stdout']), 2)
|
||||||
|
self.assertTrue(result['stdout'][0].startswith('Dell Real Time'))
|
||||||
|
|
||||||
|
def test_dellos9_command_wait_for(self):
|
||||||
|
wait_for = 'result[0] contains "Dell Real"'
|
||||||
|
set_module_args(dict(commands=['show version'], wait_for=wait_for))
|
||||||
|
self.execute_module()
|
||||||
|
|
||||||
|
def test_dellos9_command_wait_for_fails(self):
|
||||||
|
wait_for = 'result[0] contains "test string"'
|
||||||
|
set_module_args(dict(commands=['show version'], wait_for=wait_for))
|
||||||
|
self.execute_module(failed=True)
|
||||||
|
self.assertEqual(self.run_commands.call_count, 10)
|
||||||
|
|
||||||
|
def test_dellos9_command_retries(self):
|
||||||
|
wait_for = 'result[0] contains "test string"'
|
||||||
|
set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2))
|
||||||
|
self.execute_module(failed=True)
|
||||||
|
self.assertEqual(self.run_commands.call_count, 2)
|
||||||
|
|
||||||
|
def test_dellos9_command_match_any(self):
|
||||||
|
wait_for = ['result[0] contains "Dell Real"',
|
||||||
|
'result[0] contains "test string"']
|
||||||
|
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='any'))
|
||||||
|
self.execute_module()
|
||||||
|
|
||||||
|
def test_dellos9_command_match_all(self):
|
||||||
|
wait_for = ['result[0] contains "Dell Real"',
|
||||||
|
'result[0] contains "Operating System"']
|
||||||
|
set_module_args(dict(commands=['show version'], wait_for=wait_for, match='all'))
|
||||||
|
self.execute_module()
|
||||||
|
|
||||||
|
def test_dellos9_command_match_all_failure(self):
|
||||||
|
wait_for = ['result[0] contains "Dell Real"',
|
||||||
|
'result[0] contains "test string"']
|
||||||
|
commands = ['show version', 'show version']
|
||||||
|
set_module_args(dict(commands=commands, wait_for=wait_for, match='all'))
|
||||||
|
self.execute_module(failed=True)
|
150
tests/unit/modules/network/dellos9/test_dellos9_config.py
Normal file
150
tests/unit/modules/network/dellos9/test_dellos9_config.py
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
#
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# (c) 2017 Dell EMC.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_config
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args
|
||||||
|
from ..dellos9_module import TestDellos9Module, load_fixture
|
||||||
|
|
||||||
|
|
||||||
|
class TestDellos9ConfigModule(TestDellos9Module):
|
||||||
|
|
||||||
|
module = dellos9_config
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDellos9ConfigModule, self).setUp()
|
||||||
|
|
||||||
|
self.mock_get_config = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.get_config')
|
||||||
|
self.get_config = self.mock_get_config.start()
|
||||||
|
|
||||||
|
self.mock_load_config = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.load_config')
|
||||||
|
self.load_config = self.mock_load_config.start()
|
||||||
|
|
||||||
|
self.mock_run_commands = patch('ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_config.run_commands')
|
||||||
|
self.run_commands = self.mock_run_commands.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestDellos9ConfigModule, self).tearDown()
|
||||||
|
self.mock_get_config.stop()
|
||||||
|
self.mock_load_config.stop()
|
||||||
|
self.mock_run_commands.stop()
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
config_file = 'dellos9_config_config.cfg'
|
||||||
|
self.get_config.return_value = load_fixture(config_file)
|
||||||
|
self.load_config.return_value = None
|
||||||
|
|
||||||
|
def test_dellos9_config_unchanged(self):
|
||||||
|
src = load_fixture('dellos9_config_config.cfg')
|
||||||
|
set_module_args(dict(src=src))
|
||||||
|
self.execute_module()
|
||||||
|
|
||||||
|
def test_dellos9_config_src(self):
|
||||||
|
src = load_fixture('dellos9_config_src.cfg')
|
||||||
|
set_module_args(dict(src=src))
|
||||||
|
commands = ['hostname foo', 'interface fortyGigE 1/6',
|
||||||
|
'no ip address']
|
||||||
|
self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_dellos9_config_backup(self):
|
||||||
|
set_module_args(dict(backup=True))
|
||||||
|
result = self.execute_module()
|
||||||
|
self.assertIn('__backup__', result)
|
||||||
|
|
||||||
|
def test_dellos9_config_save(self):
|
||||||
|
set_module_args(dict(save=True))
|
||||||
|
self.execute_module(changed=True)
|
||||||
|
self.assertEqual(self.run_commands.call_count, 1)
|
||||||
|
self.assertEqual(self.get_config.call_count, 0)
|
||||||
|
self.assertEqual(self.load_config.call_count, 0)
|
||||||
|
args = self.run_commands.call_args[0][1]
|
||||||
|
self.assertDictContainsSubset({'command': 'copy running-config startup-config'}, args[0])
|
||||||
|
# self.assertIn('copy running-config startup-config\r', args)
|
||||||
|
|
||||||
|
def test_dellos9_config_lines_wo_parents(self):
|
||||||
|
set_module_args(dict(lines=['hostname foo']))
|
||||||
|
commands = ['hostname foo']
|
||||||
|
self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_dellos9_config_lines_w_parents(self):
|
||||||
|
set_module_args(dict(lines=['shutdown'], parents=['interface fortyGigE 1/6']))
|
||||||
|
commands = ['interface fortyGigE 1/6', 'shutdown']
|
||||||
|
self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_dellos9_config_before(self):
|
||||||
|
set_module_args(dict(lines=['hostname foo'], before=['snmp-server contact bar']))
|
||||||
|
commands = ['snmp-server contact bar', 'hostname foo']
|
||||||
|
self.execute_module(changed=True, commands=commands, sort=False)
|
||||||
|
|
||||||
|
def test_dellos9_config_after(self):
|
||||||
|
set_module_args(dict(lines=['hostname foo'], after=['snmp-server contact bar']))
|
||||||
|
commands = ['hostname foo', 'snmp-server contact bar']
|
||||||
|
self.execute_module(changed=True, commands=commands, sort=False)
|
||||||
|
|
||||||
|
def test_dellos9_config_before_after_no_change(self):
|
||||||
|
set_module_args(dict(lines=['hostname router'],
|
||||||
|
before=['snmp-server contact bar'],
|
||||||
|
after=['snmp-server location chennai']))
|
||||||
|
self.execute_module()
|
||||||
|
|
||||||
|
def test_dellos9_config_config(self):
|
||||||
|
config = 'hostname localhost'
|
||||||
|
set_module_args(dict(lines=['hostname router'], config=config))
|
||||||
|
commands = ['hostname router']
|
||||||
|
self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_dellos9_config_replace_block(self):
|
||||||
|
lines = ['description test string', 'test string']
|
||||||
|
parents = ['interface fortyGigE 1/6']
|
||||||
|
set_module_args(dict(lines=lines, replace='block', parents=parents))
|
||||||
|
commands = parents + lines
|
||||||
|
self.execute_module(changed=True, commands=commands)
|
||||||
|
|
||||||
|
def test_dellos9_config_match_none(self):
|
||||||
|
lines = ['hostname router']
|
||||||
|
set_module_args(dict(lines=lines, match='none'))
|
||||||
|
self.execute_module(changed=True, commands=lines)
|
||||||
|
|
||||||
|
def test_dellos9_config_match_none(self):
|
||||||
|
lines = ['ip address 1.2.3.4/24', 'description test string']
|
||||||
|
parents = ['interface fortyGigE 1/6']
|
||||||
|
set_module_args(dict(lines=lines, parents=parents, match='none'))
|
||||||
|
commands = parents + lines
|
||||||
|
self.execute_module(changed=True, commands=commands, sort=False)
|
||||||
|
|
||||||
|
def test_dellos9_config_match_strict(self):
|
||||||
|
lines = ['ip address 1.2.3.4/24', 'description test string',
|
||||||
|
'shutdown']
|
||||||
|
parents = ['interface fortyGigE 1/6']
|
||||||
|
set_module_args(dict(lines=lines, parents=parents, match='strict'))
|
||||||
|
commands = parents + ['shutdown']
|
||||||
|
self.execute_module(changed=True, commands=commands, sort=False)
|
||||||
|
|
||||||
|
def test_dellos9_config_match_exact(self):
|
||||||
|
lines = ['ip address 1.2.3.4/24', 'description test string',
|
||||||
|
'shutdown']
|
||||||
|
parents = ['interface fortyGigE 1/6']
|
||||||
|
set_module_args(dict(lines=lines, parents=parents, match='exact'))
|
||||||
|
commands = parents + lines
|
||||||
|
self.execute_module(changed=True, commands=commands, sort=False)
|
106
tests/unit/modules/network/dellos9/test_dellos9_facts.py
Normal file
106
tests/unit/modules/network/dellos9/test_dellos9_facts.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# (c) 2016 Red Hat Inc.
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Make coding more python3-ish
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.modules.utils import set_module_args
|
||||||
|
from ..dellos9_module import TestDellos9Module, load_fixture
|
||||||
|
from ansible_collections.dellemc_networking.os9.plugins.modules import dellos9_facts
|
||||||
|
|
||||||
|
|
||||||
|
class TestDellos9Facts(TestDellos9Module):
|
||||||
|
|
||||||
|
module = dellos9_facts
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDellos9Facts, self).setUp()
|
||||||
|
|
||||||
|
self.mock_run_command = patch(
|
||||||
|
'ansible_collections.dellemc_networking.os9.plugins.modules.dellos9_facts.run_commands')
|
||||||
|
self.run_command = self.mock_run_command.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestDellos9Facts, self).tearDown()
|
||||||
|
|
||||||
|
self.mock_run_command.stop()
|
||||||
|
|
||||||
|
def load_fixtures(self, commands=None):
|
||||||
|
|
||||||
|
def load_from_file(*args, **kwargs):
|
||||||
|
module, commands = args
|
||||||
|
output = list()
|
||||||
|
|
||||||
|
for item in commands:
|
||||||
|
try:
|
||||||
|
obj = json.loads(item)
|
||||||
|
command = obj['command']
|
||||||
|
except ValueError:
|
||||||
|
command = item
|
||||||
|
if '|' in command:
|
||||||
|
command = str(command).replace('|', '')
|
||||||
|
filename = str(command).replace(' ', '_')
|
||||||
|
filename = filename.replace('/', '7')
|
||||||
|
output.append(load_fixture(filename))
|
||||||
|
return output
|
||||||
|
|
||||||
|
self.run_command.side_effect = load_from_file
|
||||||
|
|
||||||
|
def test_dellos9_facts_gather_subset_default(self):
|
||||||
|
set_module_args(dict())
|
||||||
|
result = self.execute_module()
|
||||||
|
ansible_facts = result['ansible_facts']
|
||||||
|
self.assertIn('hardware', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertEqual('dellos9_sw1', ansible_facts['ansible_net_hostname'])
|
||||||
|
self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys())
|
||||||
|
self.assertEqual(3128820, ansible_facts['ansible_net_memtotal_mb'])
|
||||||
|
self.assertEqual(3125722, ansible_facts['ansible_net_memfree_mb'])
|
||||||
|
|
||||||
|
def test_dellos9_facts_gather_subset_config(self):
|
||||||
|
set_module_args({'gather_subset': 'config'})
|
||||||
|
result = self.execute_module()
|
||||||
|
ansible_facts = result['ansible_facts']
|
||||||
|
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('config', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertEqual('dellos9_sw1', ansible_facts['ansible_net_hostname'])
|
||||||
|
self.assertIn('ansible_net_config', ansible_facts)
|
||||||
|
|
||||||
|
def test_dellos9_facts_gather_subset_hardware(self):
|
||||||
|
set_module_args({'gather_subset': 'hardware'})
|
||||||
|
result = self.execute_module()
|
||||||
|
ansible_facts = result['ansible_facts']
|
||||||
|
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('hardware', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertEqual(['flash', 'fcmfs', 'nfsmount', 'ftp', 'tftp', 'scp', 'http', 'https'], ansible_facts['ansible_net_filesystems'])
|
||||||
|
self.assertEqual(3128820, ansible_facts['ansible_net_memtotal_mb'])
|
||||||
|
self.assertEqual(3125722, ansible_facts['ansible_net_memfree_mb'])
|
||||||
|
|
||||||
|
def test_dellos9_facts_gather_subset_interfaces(self):
|
||||||
|
set_module_args({'gather_subset': 'interfaces'})
|
||||||
|
result = self.execute_module()
|
||||||
|
ansible_facts = result['ansible_facts']
|
||||||
|
self.assertIn('default', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset'])
|
||||||
|
self.assertIn('fortyGigE 0/24', ansible_facts['ansible_net_interfaces'].keys())
|
||||||
|
self.assertEqual(['Ma 0/0'], list(ansible_facts['ansible_net_neighbors'].keys()))
|
||||||
|
self.assertIn('ansible_net_interfaces', ansible_facts)
|
47
tests/unit/modules/utils.py
Normal file
47
tests/unit/modules/utils.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat import unittest
|
||||||
|
from ansible_collections.dellemc_networking.os9.tests.unit.compat.mock import patch
|
||||||
|
from ansible.module_utils import basic
|
||||||
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def set_module_args(args):
|
||||||
|
if '_ansible_remote_tmp' not in args:
|
||||||
|
args['_ansible_remote_tmp'] = '/tmp'
|
||||||
|
if '_ansible_keep_remote_files' not in args:
|
||||||
|
args['_ansible_keep_remote_files'] = False
|
||||||
|
|
||||||
|
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
|
||||||
|
basic._ANSIBLE_ARGS = to_bytes(args)
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleExitJson(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleFailJson(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def exit_json(*args, **kwargs):
|
||||||
|
if 'changed' not in kwargs:
|
||||||
|
kwargs['changed'] = False
|
||||||
|
raise AnsibleExitJson(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def fail_json(*args, **kwargs):
|
||||||
|
kwargs['failed'] = True
|
||||||
|
raise AnsibleFailJson(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json)
|
||||||
|
self.mock_module.start()
|
||||||
|
self.mock_sleep = patch('time.sleep')
|
||||||
|
self.mock_sleep.start()
|
||||||
|
set_module_args({})
|
||||||
|
self.addCleanup(self.mock_module.stop)
|
||||||
|
self.addCleanup(self.mock_sleep.stop)
|
42
tests/unit/requirements.txt
Normal file
42
tests/unit/requirements.txt
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
boto3
|
||||||
|
placebo
|
||||||
|
pycrypto
|
||||||
|
passlib
|
||||||
|
pypsrp
|
||||||
|
python-memcached
|
||||||
|
pytz
|
||||||
|
pyvmomi
|
||||||
|
redis
|
||||||
|
requests
|
||||||
|
setuptools > 0.6 # pytest-xdist installed via requirements does not work with very old setuptools (sanity_ok)
|
||||||
|
unittest2 ; python_version < '2.7'
|
||||||
|
importlib ; python_version < '2.7'
|
||||||
|
netaddr
|
||||||
|
ipaddress
|
||||||
|
netapp-lib
|
||||||
|
solidfire-sdk-python
|
||||||
|
|
||||||
|
# requirements for F5 specific modules
|
||||||
|
f5-sdk ; python_version >= '2.7'
|
||||||
|
f5-icontrol-rest ; python_version >= '2.7'
|
||||||
|
deepdiff
|
||||||
|
|
||||||
|
# requirement for Fortinet specific modules
|
||||||
|
pyFMG
|
||||||
|
|
||||||
|
# requirement for aci_rest module
|
||||||
|
xmljson
|
||||||
|
|
||||||
|
# requirement for winrm connection plugin tests
|
||||||
|
pexpect
|
||||||
|
|
||||||
|
# requirement for the linode module
|
||||||
|
linode-python # APIv3
|
||||||
|
linode_api4 ; python_version > '2.6' # APIv4
|
||||||
|
|
||||||
|
# requirement for the gitlab module
|
||||||
|
python-gitlab
|
||||||
|
httmock
|
||||||
|
|
||||||
|
# requirment for kubevirt modules
|
||||||
|
openshift ; python_version >= '2.7'
|
Loading…
Reference in a new issue