summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/network/cumulus/nclu.py
blob: b364fdb23ddc809d5a1407f50e8955d386caf257 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2016-2017, Cumulus Networks <ce-ceng@cumulusnetworks.com>
# 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: nclu
version_added: "2.3"
author: "Cumulus Networks"
short_description: Configure network interfaces using NCLU
description:
    - Interface to the Network Command Line Utility, developed to make it easier
      to configure operating systems running ifupdown2 and Quagga, such as
      Cumulus Linux. Command documentation is available at
      U(https://docs.cumulusnetworks.com/display/DOCS/Network+Command+Line+Utility)
options:
    commands:
        description:
            - A list of strings containing the net commands to run. Mutually
              exclusive with I(template).
    template:
        description:
            - A single, multi-line string with jinja2 formatting. This string
              will be broken by lines, and each line will be run through net.
              Mutually exclusive with I(commands).
    commit:
        description:
            - When true, performs a 'net commit' at the end of the block.
              Mutually exclusive with I(atomic).
        default: false
    abort:
        description:
            - Boolean. When true, perform a 'net abort' before the block.
              This cleans out any uncommitted changes in the buffer.
              Mutually exclusive with I(atomic).
        default: false
    atomic:
        description:
            - When true, equivalent to both I(commit) and I(abort) being true.
              Mutually exclusive with I(commit) and I(atomic).
        default: false
    description:
        description:
            - Commit description that will be recorded to the commit log if
              I(commit) or I(atomic) are true.
        default: "Ansible-originated commit"
'''

EXAMPLES = '''

- name: Add two interfaces without committing any changes
  nclu:
    commands:
        - add int swp1
        - add int swp2

- name: Add 48 interfaces and commit the change.
  nclu:
    template: |
        {% for iface in range(1,49) %}
        add int swp{{iface}}
        {% endfor %}
    commit: true
    description: "Ansible - add swps1-48"

- name: Fetch Details From All Interfaces In JSON Format
  nclu:
    commands:
        - show interface json
  register: output
- name: Print Interface Details
  debug:
    var: output["msg"]

- name: Atomically add an interface
  nclu:
    commands:
        - add int swp1
    atomic: true
    description: "Ansible - add swp1"

- name: Configure BGP AS and add 2 EBGP neighbors using BGP Unnumbered
  nclu:
    commands:
        - add bgp autonomous-system 65000
        - add bgp neighbor swp51 interface remote-as external
        - add bgp neighbor swp52 interface remote-as external
    commit: true

'''

RETURN = '''
changed:
    description: whether the interface was changed
    returned: changed
    type: bool
    sample: True
msg:
    description: human-readable report of success or failure
    returned: always
    type: string
    sample: "interface bond0 config updated"
'''

from ansible.module_utils.basic import AnsibleModule


def command_helper(module, command, errmsg=None):
    """Run a command, catch any nclu errors"""
    (_rc, output, _err) = module.run_command("/usr/bin/net %s" % command)
    if _rc or 'ERROR' in output or 'ERROR' in _err:
        module.fail_json(msg=errmsg or output)
    return str(output)


def check_pending(module):
    """Check the pending diff of the nclu buffer."""
    pending = command_helper(module, "pending", "Error in pending config. You may want to view `net pending` on this target.")

    delimeter1 = "net add/del commands since the last 'net commit'"
    color1 = '\x1b[94m'
    if delimeter1 in pending:
        pending = pending.split(delimeter1)[0]
        pending = pending.replace(color1, '')
    return pending.strip()


def run_nclu(module, command_list, command_string, commit, atomic, abort, description):
    _changed = False

    commands = []
    if command_list:
        commands = command_list
    elif command_string:
        commands = command_string.splitlines()

    do_commit = False
    do_abort = abort
    if commit or atomic:
        do_commit = True
        if atomic:
            do_abort = True

    if do_abort:
        command_helper(module, "abort")

    # First, look at the staged commands.
    before = check_pending(module)
    # Run all of the net commands
    output_lines = []
    for line in commands:
        output_lines += [command_helper(module, line.strip(), "Failed on line %s" % line)]
    output = "\n".join(output_lines)

    # If pending changes changed, report a change.
    after = check_pending(module)
    if before == after:
        _changed = False
    else:
        _changed = True

    # Do the commit.
    if do_commit:
        result = command_helper(module, "commit description '%s'" % description)
        if "commit ignored" in result:
            _changed = False
            command_helper(module, "abort")
        elif command_helper(module, "show commit last") == "":
            _changed = False

    return _changed, output


def main(testing=False):
    module = AnsibleModule(argument_spec=dict(
        commands=dict(required=False, type='list'),
        template=dict(required=False, type='str'),
        description=dict(required=False, type='str', default="Ansible-originated commit"),
        abort=dict(required=False, type='bool', default=False),
        commit=dict(required=False, type='bool', default=False),
        atomic=dict(required=False, type='bool', default=False)),
        mutually_exclusive=[('commands', 'template'),
                            ('commit', 'atomic'),
                            ('abort', 'atomic')]
    )
    command_list = module.params.get('commands', None)
    command_string = module.params.get('template', None)
    commit = module.params.get('commit')
    atomic = module.params.get('atomic')
    abort = module.params.get('abort')
    description = module.params.get('description')

    _changed, output = run_nclu(module, command_list, command_string, commit, atomic, abort, description)
    if not testing:
        module.exit_json(changed=_changed, msg=output)
    elif testing:
        return {"changed": _changed, "msg": output}


if __name__ == '__main__':
    main()