1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = r''' 15--- 16module: mso_schema_template_contract_filter 17short_description: Manage contract filters in schema templates 18description: 19- Manage contract filters in schema templates on Cisco ACI Multi-Site. 20author: 21- Dag Wieers (@dagwieers) 22version_added: '2.8' 23options: 24 schema: 25 description: 26 - The name of the schema. 27 type: str 28 required: yes 29 template: 30 description: 31 - The name of the template. 32 type: str 33 required: yes 34 contract: 35 description: 36 - The name of the contract to manage. 37 type: str 38 required: yes 39 contract_display_name: 40 description: 41 - The name as displayed on the MSO web interface. 42 - This defaults to the contract name when unset on creation. 43 type: str 44 contract_filter_type: 45 description: 46 - The type of filters defined in this contract. 47 - This defaults to C(both-way) when unset on creation. 48 type: str 49 choices: [ both-way, one-way ] 50 contract_scope: 51 description: 52 - The scope of the contract. 53 - This defaults to C(vrf) when unset on creation. 54 type: str 55 choices: [ application-profile, global, tenant, vrf ] 56 filter: 57 description: 58 - The filter to associate with this contract. 59 type: str 60 aliases: [ name ] 61 filter_template: 62 description: 63 - The template name in which the filter is located. 64 type: str 65 filter_schema: 66 description: 67 - The schema name in which the filter is located. 68 type: str 69 filter_type: 70 description: 71 - The type of filter to manage. 72 type: str 73 choices: [ both-way, consumer-to-provider, provider-to-consumer ] 74 default: both-way 75 aliases: [ type ] 76 filter_directives: 77 description: 78 - A list of filter directives. 79 type: list 80 choices: [ log, none ] 81 state: 82 description: 83 - Use C(present) or C(absent) for adding or removing. 84 - Use C(query) for listing an object or multiple objects. 85 type: str 86 choices: [ absent, present, query ] 87 default: present 88seealso: 89- module: mso_schema_template_filter_entry 90notes: 91- Due to restrictions of the MSO REST API this module creates contracts when needed, and removes them when the last filter has been removed. 92- Due to restrictions of the MSO REST API concurrent modifications to contract filters can be dangerous and corrupt data. 93extends_documentation_fragment: mso 94''' 95 96EXAMPLES = r''' 97- name: Add a new contract filter 98 mso_schema_template_contract_filter: 99 host: mso_host 100 username: admin 101 password: SomeSecretPassword 102 schema: Schema 1 103 template: Template 1 104 contract: Contract 1 105 contract_scope: global 106 filter: Filter 1 107 state: present 108 delegate_to: localhost 109 110- name: Remove a contract filter 111 mso_schema_template_contract_filter: 112 host: mso_host 113 username: admin 114 password: SomeSecretPassword 115 schema: Schema 1 116 template: Template 1 117 contract: Contract 1 118 filter: Filter 1 119 state: absent 120 delegate_to: localhost 121 122- name: Query a specific contract filter 123 mso_schema_template_contract_filter: 124 host: mso_host 125 username: admin 126 password: SomeSecretPassword 127 schema: Schema 1 128 template: Template 1 129 contract: Contract 1 130 filter: Filter 1 131 state: query 132 delegate_to: localhost 133 register: query_result 134 135- name: Query all contract filters 136 mso_schema_template_contract_filter: 137 host: mso_host 138 username: admin 139 password: SomeSecretPassword 140 schema: Schema 1 141 template: Template 1 142 contract: Contract 1 143 state: query 144 delegate_to: localhost 145 register: query_result 146''' 147 148RETURN = r''' 149''' 150 151from ansible.module_utils.basic import AnsibleModule 152from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec, mso_reference_spec, issubset 153 154FILTER_KEYS = { 155 'both-way': 'filterRelationships', 156 'consumer-to-provider': 'filterRelationshipsConsumerToProvider', 157 'provider-to-consumer': 'filterRelationshipsProviderToConsumer', 158} 159 160 161def main(): 162 argument_spec = mso_argument_spec() 163 argument_spec.update( 164 schema=dict(type='str', required=True), 165 template=dict(type='str', required=True), 166 contract=dict(type='str', required=True), 167 contract_display_name=dict(type='str'), 168 contract_scope=dict(type='str', choices=['application-profile', 'global', 'tenant', 'vrf']), 169 contract_filter_type=dict(type='str', choices=['both-way', 'one-way']), 170 filter=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects 171 filter_directives=dict(type='list', choices=['log', 'none']), 172 filter_template=dict(type='str'), 173 filter_schema=dict(type='str'), 174 filter_type=dict(type='str', default='both-way', choices=FILTER_KEYS.keys(), aliases=['type']), 175 state=dict(type='str', default='present', choices=['absent', 'present', 'query']), 176 ) 177 178 module = AnsibleModule( 179 argument_spec=argument_spec, 180 supports_check_mode=True, 181 required_if=[ 182 ['state', 'absent', ['filter']], 183 ['state', 'present', ['filter']], 184 ], 185 ) 186 187 schema = module.params['schema'] 188 template = module.params['template'] 189 contract = module.params['contract'] 190 contract_display_name = module.params['contract_display_name'] 191 contract_filter_type = module.params['contract_filter_type'] 192 contract_scope = module.params['contract_scope'] 193 filter_name = module.params['filter'] 194 filter_directives = module.params['filter_directives'] 195 filter_template = module.params['filter_template'] 196 filter_schema = module.params['filter_schema'] 197 filter_type = module.params['filter_type'] 198 state = module.params['state'] 199 200 contract_ftype = 'bothWay' if contract_filter_type == 'both-way' else 'oneWay' 201 202 if contract_filter_type == 'both-way' and filter_type != 'both-way': 203 module.warn("You are adding 'one-way' filters to a 'both-way' contract") 204 elif contract_filter_type != 'both-way' and filter_type == 'both-way': 205 module.warn("You are adding 'both-way' filters to a 'one-way' contract") 206 207 if filter_template is None: 208 filter_template = template 209 210 if filter_schema is None: 211 filter_schema = schema 212 213 filter_key = FILTER_KEYS[filter_type] 214 215 mso = MSOModule(module) 216 217 filter_schema_id = mso.lookup_schema(filter_schema) 218 219 # Get schema object 220 schema_obj = mso.get_obj('schemas', displayName=schema) 221 if schema_obj: 222 schema_id = schema_obj['id'] 223 else: 224 mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema)) 225 226 schema_path = 'schemas/{id}'.format(**schema_obj) 227 228 # Get template 229 templates = [t['name'] for t in schema_obj['templates']] 230 if template not in templates: 231 mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates))) 232 template_idx = templates.index(template) 233 234 # Get contracts 235 mso.existing = {} 236 contract_idx = None 237 filter_idx = None 238 contracts = [c['name'] for c in schema_obj['templates'][template_idx]['contracts']] 239 240 if contract in contracts: 241 contract_idx = contracts.index(contract) 242 243 filters = [f['filterRef'] for f in schema_obj['templates'][template_idx]['contracts'][contract_idx][filter_key]] 244 filter_ref = mso.filter_ref(schema_id=filter_schema_id, template=filter_template, filter=filter_name) 245 if filter_ref in filters: 246 filter_idx = filters.index(filter_ref) 247 filter_path = '/templates/{0}/contracts/{1}/{2}/{3}'.format(template, contract, filter_key, filter_name) 248 mso.existing = schema_obj['templates'][template_idx]['contracts'][contract_idx][filter_key][filter_idx] 249 250 if state == 'query': 251 if contract_idx is None: 252 mso.fail_json(msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(contract, ', '.join(contracts))) 253 254 if filter_name is None: 255 mso.existing = schema_obj['templates'][template_idx]['contracts'][contract_idx][filter_key] 256 elif not mso.existing: 257 mso.fail_json(msg="FilterRef '{filter_ref}' not found".format(filter_ref=filter_ref)) 258 mso.exit_json() 259 260 ops = [] 261 contract_path = '/templates/{0}/contracts/{1}'.format(template, contract) 262 filters_path = '/templates/{0}/contracts/{1}/{2}'.format(template, contract, filter_key) 263 264 mso.previous = mso.existing 265 if state == 'absent': 266 mso.proposed = mso.sent = {} 267 268 if contract_idx is None: 269 # There was no contract to begin with 270 pass 271 elif filter_idx is None: 272 # There was no filter to begin with 273 pass 274 elif len(filters) == 1: 275 # There is only one filter, remove contract 276 mso.existing = {} 277 ops.append(dict(op='remove', path=contract_path)) 278 else: 279 # Remove filter 280 mso.existing = {} 281 ops.append(dict(op='remove', path=filter_path)) 282 283 elif state == 'present': 284 if filter_directives is None: 285 filter_directives = ['none'] 286 287 payload = dict( 288 filterRef=dict( 289 filterName=filter_name, 290 templateName=filter_template, 291 schemaId=filter_schema_id, 292 ), 293 directives=filter_directives, 294 ) 295 296 mso.sanitize(payload, collate=True) 297 mso.existing = mso.sent 298 299 if contract_idx is None: 300 # Contract does not exist, so we have to create it 301 if contract_display_name is None: 302 contract_display_name = contract 303 if contract_filter_type is None: 304 contract_ftype = 'bothWay' 305 if contract_scope is None: 306 contract_scope = 'context' 307 308 payload = { 309 'name': contract, 310 'displayName': contract_display_name, 311 'filterType': contract_ftype, 312 'scope': contract_scope, 313 } 314 315 ops.append(dict(op='add', path='/templates/{0}/contracts/-'.format(template), value=payload)) 316 else: 317 # Contract exists, but may require an update 318 if contract_display_name is not None: 319 ops.append(dict(op='replace', path=contract_path + '/displayName', value=contract_display_name)) 320 if contract_filter_type is not None: 321 ops.append(dict(op='replace', path=contract_path + '/filterType', value=contract_ftype)) 322 if contract_scope is not None: 323 ops.append(dict(op='replace', path=contract_path + '/scope', value=contract_scope)) 324 325 if filter_idx is None: 326 # Filter does not exist, so we have to add it 327 ops.append(dict(op='add', path=filters_path + '/-', value=mso.sent)) 328 329 else: 330 # Filter exists, we have to update it 331 ops.append(dict(op='replace', path=filter_path, value=mso.sent)) 332 333 if not module.check_mode: 334 mso.request(schema_path, method='PATCH', data=ops) 335 336 mso.exit_json() 337 338 339if __name__ == "__main__": 340 main() 341