|
| 1 | +#!/usr/bin/python |
| 2 | +# |
| 3 | +# This file is part of Ansible |
| 4 | +# |
| 5 | +# Ansible is free software: you can redistribute it and/or modify |
| 6 | +# it under the terms of the GNU General Public License as published by |
| 7 | +# the Free Software Foundation, either version 3 of the License, or |
| 8 | +# (at your option) any later version. |
| 9 | +# |
| 10 | +# Ansible is distributed in the hope that it will be useful, |
| 11 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | +# GNU General Public License for more details. |
| 14 | +# |
| 15 | +# You should have received a copy of the GNU General Public License |
| 16 | +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. |
| 17 | +# |
| 18 | +DOCUMENTATION = """ |
| 19 | +--- |
| 20 | +module: junos_template |
| 21 | +version_added: "2.1" |
| 22 | +author: "Peter sprygada (@privateip)" |
| 23 | +short_description: Manage Juniper JUNOS device configurations |
| 24 | +description: |
| 25 | + - Manages network device configurations over SSH. This module |
| 26 | + allows implementors to work with the device configuration. It |
| 27 | + provides a way to push a set of commands onto a network device |
| 28 | + by evaluting the current configuration and only pushing |
| 29 | + commands that are not already configured. |
| 30 | +extends_documentation_fragment: junos |
| 31 | +options: |
| 32 | + src: |
| 33 | + description: |
| 34 | + - The path to the config source. The source can be either a |
| 35 | + file with config or a template that will be merged during |
| 36 | + runtime. By default the task will search for the source |
| 37 | + file in role or playbook root folder in templates directory. |
| 38 | + required: false |
| 39 | + default: null |
| 40 | + force: |
| 41 | + description: |
| 42 | + - The force argument instructs the module to not consider the |
| 43 | + current devices configuration. When set to true, this will |
| 44 | + cause the module to push the contents of I(src) into the device |
| 45 | + without first checking if already configured. |
| 46 | + required: false |
| 47 | + default: false |
| 48 | + choices: BOOLEANS |
| 49 | + backup: |
| 50 | + description: |
| 51 | + - When this argument is configured true, the module will backup |
| 52 | + the configuration from the node prior to making any changes. |
| 53 | + The backup file will be written to backup_{{ hostname }} in |
| 54 | + the root of the playbook directory. |
| 55 | + required: false |
| 56 | + default: false |
| 57 | + choices: BOOLEANS |
| 58 | + config: |
| 59 | + description: |
| 60 | + - The module, by default, will connect to the remote device and |
| 61 | + retrieve the current configuration to use as a base for comparing |
| 62 | + against the contents of source. There are times when it is not |
| 63 | + desirable to have the task get the current configuration for |
| 64 | + every task in a playbook. The I(config) argument allows the |
| 65 | + implementer to pass in the configuruation to use as the base |
| 66 | + config for comparision. |
| 67 | + required: false |
| 68 | + default: null |
| 69 | +""" |
| 70 | + |
| 71 | +EXAMPLES = """ |
| 72 | +
|
| 73 | +- name: push a configuration onto the device |
| 74 | + junos_template: |
| 75 | + src: config.j2 |
| 76 | +
|
| 77 | +- name: forceable push a configuration onto the device |
| 78 | + junos_template: |
| 79 | + src: config.j2 |
| 80 | + force: yes |
| 81 | +
|
| 82 | +- name: provide the base configuration for comparision |
| 83 | + junos_template: |
| 84 | + src: candidate_config.txt |
| 85 | + config: current_config.txt |
| 86 | +
|
| 87 | +""" |
| 88 | + |
| 89 | +RETURN = """ |
| 90 | +
|
| 91 | +commands: |
| 92 | + description: The set of commands that will be pushed to the remote device |
| 93 | + returned: always |
| 94 | + type: list |
| 95 | + sample: [...] |
| 96 | +
|
| 97 | +""" |
| 98 | + |
| 99 | +def compare(this, other): |
| 100 | + parents = [item.text for item in this.parents] |
| 101 | + for entry in other: |
| 102 | + if this == entry: |
| 103 | + return None |
| 104 | + return this |
| 105 | + |
| 106 | +def expand(obj, action='set'): |
| 107 | + cmd = [action] |
| 108 | + cmd.extend([p.text for p in obj.parents]) |
| 109 | + cmd.append(obj.text) |
| 110 | + return ' '.join(cmd) |
| 111 | + |
| 112 | +def flatten(data, obj): |
| 113 | + for k, v in data.items(): |
| 114 | + obj.append(k) |
| 115 | + flatten(v, obj) |
| 116 | + return obj |
| 117 | + |
| 118 | +def to_lines(config): |
| 119 | + lines = list() |
| 120 | + for item in config: |
| 121 | + if item.raw.endswith(';'): |
| 122 | + line = [p.text for p in item.parents] |
| 123 | + line.append(item.text) |
| 124 | + lines.append(' '.join(line)) |
| 125 | + return lines |
| 126 | + |
| 127 | +def get_config(module): |
| 128 | + config = module.params['config'] or list() |
| 129 | + if not config and not module.params['force']: |
| 130 | + config = module.config |
| 131 | + return config |
| 132 | + |
| 133 | +def main(): |
| 134 | + """ main entry point for module execution |
| 135 | + """ |
| 136 | + |
| 137 | + argument_spec = dict( |
| 138 | + src=dict(), |
| 139 | + force=dict(default=False, type='bool'), |
| 140 | + backup=dict(default=False, type='bool'), |
| 141 | + config=dict(), |
| 142 | + ) |
| 143 | + |
| 144 | + mutually_exclusive = [('config', 'backup'), ('config', 'force')] |
| 145 | + |
| 146 | + module = get_module(argument_spec=argument_spec, |
| 147 | + mutually_exclusive=mutually_exclusive, |
| 148 | + supports_check_mode=True) |
| 149 | + |
| 150 | + result = dict(changed=False) |
| 151 | + |
| 152 | + parsed = module.parse_config(module.params['src']) |
| 153 | + commands = to_lines(parsed) |
| 154 | + |
| 155 | + contents = get_config(module) |
| 156 | + result['_backup'] = module.config |
| 157 | + |
| 158 | + parsed = module.parse_config(contents) |
| 159 | + config = to_lines(parsed) |
| 160 | + |
| 161 | + candidate = list() |
| 162 | + for item in commands: |
| 163 | + if item not in config: |
| 164 | + candidate.append('set %s' % item) |
| 165 | + |
| 166 | + if candidate: |
| 167 | + if not module.check_mode: |
| 168 | + module.configure(candidate) |
| 169 | + result['changed'] = True |
| 170 | + |
| 171 | + result['updates'] = candidate |
| 172 | + return module.exit_json(**result) |
| 173 | + |
| 174 | + |
| 175 | +from ansible.module_utils.basic import * |
| 176 | +from ansible.module_utils.shell import * |
| 177 | +from ansible.module_utils.netcfg import * |
| 178 | +from ansible.module_utils.junos import * |
| 179 | +if __name__ == '__main__': |
| 180 | + main() |
| 181 | + |
0 commit comments