New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apcon modules #57079
Apcon modules #57079
Changes from 4 commits
0b98fa3
c7ba2a0
73ac20d
474cc5e
2f05ef7
abe7194
20b5ff5
88e3449
bb1256c
056aac1
d71f5f6
bec9573
97ba48d
9318a5e
d241bd3
cac6df1
6f9337b
809e419
6321675
6cd1083
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# 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. | ||
# | ||
# Copyright (C) 2018 APCON, Inc. | ||
# All rights reserved. | ||
# | ||
# 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. | ||
# | ||
# Contains utility methods | ||
# APCON Networking | ||
|
||
import time | ||
import socket | ||
import re | ||
from distutils.cmd import Command | ||
from ansible.module_utils._text import to_text | ||
from ansible.module_utils.basic import env_fallback | ||
from ansible.module_utils.network.common.utils import to_list, EntityCollection | ||
from ansible.module_utils.connection import Connection, exec_command | ||
from ansible.module_utils.connection import ConnectionError | ||
|
||
_DEVICE_CONFIGS = {} | ||
_CONNECTION = None | ||
|
||
apconos_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'), | ||
'context': dict(), | ||
'passwords': dict() | ||
} | ||
|
||
apconos_argument_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'), | ||
'context': dict(), | ||
'passwords': dict(), | ||
'provider': dict(type='dict', options=apconos_provider_spec), | ||
} | ||
|
||
command_spec = { | ||
'command': dict(key=True), | ||
'prompt': dict(), | ||
'answer': dict(), | ||
'check_all': dict() | ||
} | ||
|
||
|
||
def get_provider_argspec(): | ||
return apconos_provider_spec | ||
|
||
|
||
def check_args(module, warnings): | ||
pass | ||
|
||
|
||
def get_connection(module): | ||
global _CONNECTION | ||
if _CONNECTION: | ||
return _CONNECTION | ||
_CONNECTION = Connection(module._socket_path) | ||
|
||
return _CONNECTION | ||
|
||
|
||
def get_config(module, flags=None): | ||
flags = [] if flags is None else flags | ||
|
||
cmd = ' '.join(flags).strip() | ||
|
||
try: | ||
return _DEVICE_CONFIGS[cmd] | ||
except KeyError: | ||
conn = get_connection(module) | ||
out = conn.get(cmd) | ||
cfg = to_text(out, errors='surrogate_then_replace').strip() | ||
_DEVICE_CONFIGS[cmd] = cfg | ||
return cfg | ||
|
||
|
||
def run_commands(module, commands, check_rc=True): | ||
connection = get_connection(module) | ||
transform = EntityCollection(module, command_spec) | ||
commands = transform(commands) | ||
|
||
responses = list() | ||
|
||
for cmd in commands: | ||
out = connection.get(**cmd) | ||
responses.append(to_text(out, errors='surrogate_then_replace')) | ||
|
||
return responses | ||
|
||
|
||
def load_config(module, config): | ||
try: | ||
conn = get_connection(module) | ||
conn.get('enable') | ||
conn.edit_config(config) | ||
except ConnectionError as exc: | ||
module.fail_json(msg=to_text(exc)) | ||
|
||
|
||
def get_defaults_flag(module): | ||
rc, out, err = exec_command(module, 'display running-config ?') | ||
out = to_text(out, errors='surrogate_then_replace') | ||
|
||
commands = set() | ||
for line in out.splitlines(): | ||
if line: | ||
commands.add(line.strip().split()[0]) | ||
|
||
if 'all' in commands: | ||
return 'all' | ||
else: | ||
return 'full' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
#!/usr/bin/python | ||
# | ||
# Copyright (C) 2018 Apcon. | ||
# | ||
# GNU General Public License v3.0+ | ||
# | ||
# 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. | ||
# | ||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# | ||
# Module to execute apconos Commands on Apcon Switches. | ||
# Apcon Networking | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: apconos_cert | ||
version_added: "2.9.0" | ||
author: "David Lee (@davidlee-ap)" | ||
short_description: install ssl ipv4 certificate on apcon network devices | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capitalize properly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll fix it. Thanks! |
||
description: | ||
- import and install ssl ipv4 certificate with specifying remote filename | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capitalize properly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll fix it. |
||
notes: | ||
- tested against apcon iis+ii | ||
options: | ||
command: | ||
description: | ||
- currently it is not being used in apconos_cert module. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it here then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanity test couldn't pass without it. It might be implemented in the future. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's not used by the module, remove it from the module spec instead of adding it to the docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll remove it. |
||
provider: | ||
description: | ||
- please use connection:network_cli. | ||
ipaddress: | ||
description: | ||
- specify a remote ip address at which tftp server resides. | ||
filename: | ||
description: | ||
- specify a remote file name which resides on a remote tftp server. | ||
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. | ||
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. | ||
default: all | ||
choices: ['any', 'all'] | ||
retries: | ||
description: | ||
- Specifies the number of retries a command should by tried | ||
before it is considered failed. The command is run on the | ||
target device every retry and evaluated against the | ||
I(wait_for) conditions. | ||
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. | ||
default: 1 | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: Install SSL Certificate | ||
apconos_cert: | ||
ipaddress: 10.0.0.100 | ||
filename: remoteSSLCert.pem | ||
""" | ||
|
||
RETURN = """ | ||
""" | ||
|
||
import re | ||
import time | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.network.common.parsing import Conditional | ||
from ansible.module_utils.network.common.utils import remove_default_spec | ||
from ansible.module_utils.network.apconos.apconos import load_config, run_commands | ||
from ansible.module_utils.network.apconos.apconos import apconos_argument_spec, check_args | ||
from ansible.module_utils.six import string_types | ||
|
||
|
||
def to_lines(stdout): | ||
for item in stdout: | ||
if isinstance(item, string_types): | ||
item = str(item).split('\n') | ||
yield item | ||
|
||
|
||
def construct_update_command(module): | ||
"""construct update command | ||
""" | ||
command = module.params['command'] | ||
ipaddress = module.params['ipaddress'] | ||
filename = module.params['filename'] | ||
command[0] = 'tftp put ssl ipv4 ' + ipaddress[0] + ' ' + filename[0] + time.strftime("%d_%b+%H:%M:%S", time.gmtime()) | ||
command.append('tftp get ssl ipv4 ' + ipaddress[0] + ' ' + filename[0]) | ||
|
||
return command | ||
|
||
|
||
def main(): | ||
""" main entry point for module execution | ||
""" | ||
spec = dict( | ||
wait_for=dict(type='list'), | ||
match=dict(default='all', choices=['all', 'any']), | ||
|
||
retries=dict(default=10, type='int'), | ||
interval=dict(default=1, type='int'), | ||
|
||
ipaddress=dict(type='list'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would this be a list instead of a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. str should be OK, but it is supposed to be a list for the application. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? How is this interfacing and what’s the application? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The module is only using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'll fix it. |
||
filename=dict(type='list'), | ||
command=dict(type='list')) | ||
|
||
spec.update(apconos_argument_spec) | ||
|
||
module = AnsibleModule(argument_spec=spec, supports_check_mode=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have anywhere in your code that's supporting check mode? |
||
result = {'changed': False} | ||
|
||
wait_for = module.params['wait_for'] or list() | ||
conditionals = [Conditional(c) for c in wait_for] | ||
|
||
retries = module.params['retries'] | ||
interval = module.params['interval'] | ||
match = module.params['match'] | ||
|
||
while retries > 0: | ||
responses = run_commands(module, construct_update_command(module)) | ||
|
||
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) | ||
|
||
for item in responses: | ||
if len(item) == 0: | ||
result.update({ | ||
'changed': True, | ||
'stdout': responses, | ||
'stdout_lines': list(to_lines(responses)) | ||
}) | ||
elif 'ERROR' in item: | ||
result.update({ | ||
'failed': True, | ||
'stdout': responses, | ||
'stdout_lines': list(to_lines(responses)) | ||
}) | ||
else: | ||
result.update({ | ||
'stdout': item, | ||
'stdout_lines': list(to_lines(responses)) | ||
}) | ||
|
||
module.exit_json(**result) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could also be 2.9 as no new modules are added on minor releases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix it!
Thanks!