1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
5# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
6# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
7
8from __future__ import absolute_import, division, print_function
9__metaclass__ = type
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15DOCUMENTATION = r'''
16---
17module: mso_tenant_site
18short_description: Manage tenants with cloud sites.
19description:
20- Manage tenants with cloud sites on Cisco ACI Multi-Site.
21author:
22- Shreyas Srish (@shrsr)
23options:
24  tenant:
25    description:
26    - The name of the tenant.
27    type: str
28    required: yes
29  site:
30    description:
31    - The name of the site.
32    - This can either be cloud site or non-cloud site.
33    type: str
34    aliases: [ name ]
35  cloud_account:
36    description:
37    - Required for cloud site.
38    - Account id of AWS in the form '000000000000'.
39    - Account id of Azure in the form 'uni/tn-(tenant_name)/act-[(subscription_id)]-azure_vendor-azure'.
40    - Example values inside account id of Azure '(tenant_name)=tenant_test and (subscription_id)=10'.
41    type: str
42  security_domains:
43    description:
44    - List of security domains for cloud sites.
45    type: list
46    elements: str
47    default: []
48  aws_account_org:
49    description:
50    - AWS account for organization.
51    default: false
52    type: bool
53  aws_trusted:
54    description:
55    - AWS account's access in trusted mode. Credentials are required, when set to false.
56    type: bool
57  aws_access_key:
58    description:
59    - AWS account's access key id. This is required when aws_trusted is set to false.
60    type: str
61  azure_access_type:
62    description:
63    - Managed mode for Azure.
64    - Unmanaged mode for Azure.
65    - Shared mode if the attribute is not specified.
66    choices: [ managed, unmanaged, shared ]
67    default: shared
68    type: str
69  azure_active_directory_id:
70    description:
71    - Azure account's active directory id.
72    - This attribute is required when azure_access_type is in unmanaged mode.
73    type: str
74  azure_active_directory_name:
75    description:
76    - Azure account's active directory name. Example being 'CiscoINSBUAd' as active directory name.
77    - This attribute is required when azure_access_type is in unmanaged mode.
78    type: str
79  azure_subscription_id:
80    description:
81    - Azure account's subscription id.
82    - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
83    type: str
84  azure_application_id:
85    description:
86    - Azure account's application id.
87    - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
88    type: str
89  azure_credential_name:
90    description:
91    - Azure account's credential name.
92    - This attribute is required when azure_access_type is in unmanaged mode.
93    type: str
94  secret_key:
95    description:
96    - secret key of AWS for untrusted account. Required when aws_trusted is set to false.
97    - secret key of Azure account for unmanaged identity. Required in unmanaged mode of Azure account.
98    type: str
99  state:
100    description:
101    - Use C(present) or C(absent) for adding or removing.
102    - Use C(query) for listing an object or multiple objects.
103    type: str
104    choices: [ absent, present, query ]
105    default: present
106extends_documentation_fragment: cisco.mso.modules
107'''
108
109EXAMPLES = r'''
110- name: Associate a non-cloud site with a tenant
111  cisco.mso.mso_tenant_site:
112    host: mso_host
113    username: admin
114    password: SomeSecretPassword
115    tenant: tenant_name
116    site: site_name
117    state: present
118  delegate_to: localhost
119
120- name: Associate AWS site with a tenant, with aws_trusted set to true
121  cisco.mso.mso_tenant_site:
122    host: mso_host
123    username: admin
124    password: SomeSecretPassword
125    tenant: tenant_name
126    site: site_name
127    cloud_account: '000000000000'
128    aws_trusted: true
129    state: present
130  delegate_to: localhost
131
132- name: Associate AWS site with a tenant, with aws_trusted set to false
133  cisco.mso.mso_tenant_site:
134    host: mso_host
135    username: admin
136    password: SomeSecretPassword
137    tenant: tenant_name
138    site: AWS
139    cloud_account: '000000000000'
140    aws_trusted: false
141    aws_access_key: '1'
142    secret_key: '0'
143    aws_account_org: false
144    state: present
145  delegate_to: localhost
146
147- name: Associate Azure site in managed mode
148  mso.cisco.mso_tenant_site:
149    host: mso_host
150    username: admin
151    password: SomeSecretPassword
152    tenant: tenant_name
153    site: site_name
154    cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
155    azure_access_type: managed
156    azure_subscription_id: '9'
157    azure_application_id: '100'
158    state: present
159  delegate_to: localhost
160
161- name: Associate Azure site in unmanaged mode
162  mso.cisco.mso_tenant_site:
163    host: mso_host
164    username: admin
165    password: SomeSecretPassword
166    tenant: tenant_name
167    site: site_name
168    cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
169    azure_access_type: unmanaged
170    azure_subscription_id: '9'
171    azure_application_id: '100'
172    azure_credential_name: cApicApp
173    secret_key: iins
174    azure_active_directory_id: '32'
175    azure_active_directory_name: CiscoINSBUAd
176    state: present
177  delegate_to: localhost
178
179- name: Dissociate a site
180  cisco.mso.mso_tenant_site:
181    host: mso_host
182    username: admin
183    password: SomeSecretPassword
184    tenant: tenant_name
185    site: site_name
186    state: absent
187  delegate_to: localhost
188
189- name: Query a site
190  cisco.mso.mso_tenant_site:
191    host: mso_host
192    username: admin
193    password: SomeSecretPassword
194    tenant: tenant_name
195    site: site_name
196    state: query
197  delegate_to: localhost
198
199- name: Query all sites of a tenant
200  cisco.mso.mso_tenant_site:
201    host: mso_host
202    username: admin
203    password: SomeSecretPassword
204    tenant: tenant_name
205    state: query
206  delegate_to: localhost
207  register: query_result
208'''
209
210RETURN = r'''
211'''
212
213from ansible.module_utils.basic import AnsibleModule
214from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
215
216
217def main():
218    argument_spec = mso_argument_spec()
219    argument_spec.update(
220        tenant=dict(type='str', aliases=['name'], required=True),
221        site=dict(type='str', aliases=['name']),
222        cloud_account=dict(type='str'),
223        security_domains=dict(type='list', elements='str', default=[]),
224        aws_trusted=dict(type='bool'),
225        azure_access_type=dict(type='str', default='shared', choices=['managed', 'unmanaged', 'shared']),
226        azure_active_directory_id=dict(type='str'),
227        aws_access_key=dict(type='str'),
228        aws_account_org=dict(type='bool', default='false'),
229        azure_active_directory_name=dict(type='str'),
230        azure_subscription_id=dict(type='str'),
231        azure_application_id=dict(type='str'),
232        azure_credential_name=dict(type='str'),
233        secret_key=dict(type='str'),
234        state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
235    )
236
237    module = AnsibleModule(
238        argument_spec=argument_spec,
239        supports_check_mode=True,
240        required_if=[
241            ['state', 'absent', ['tenant', 'site']],
242            ['state', 'present', ['tenant', 'site']],
243        ],
244    )
245
246    state = module.params.get('state')
247    security_domains = module.params.get('security_domains')
248    cloud_account = module.params.get('cloud_account')
249    azure_access_type = module.params.get('azure_access_type')
250    azure_credential_name = module.params.get('azure_credential_name')
251    azure_application_id = module.params.get('azure_application_id')
252    azure_active_directory_id = module.params.get('azure_active_directory_id')
253    azure_active_directory_name = module.params.get('azure_active_directory_name')
254    azure_subscription_id = module.params.get('azure_subscription_id')
255    secret_key = module.params.get('secret_key')
256    aws_account_org = module.params.get('aws_account_org')
257    aws_access_key = module.params.get('aws_access_key')
258    aws_trusted = module.params.get('aws_trusted')
259
260    mso = MSOModule(module)
261
262    # Get tenant_id and site_id
263    tenant_id = mso.lookup_tenant(module.params.get('tenant'))
264    site_id = mso.lookup_site(module.params.get('site'))
265    tenants = [(t.get('id')) for t in mso.query_objs('tenants')]
266    tenant_idx = tenants.index((tenant_id))
267
268    # set tenent and port paths
269    tenant_path = 'tenants/{0}'.format(tenant_id)
270    ops = []
271    ports_path = '/siteAssociations/-'
272    port_path = '/siteAssociations/{0}'.format(site_id)
273
274    payload = dict(
275        siteId=site_id,
276        securityDomains=security_domains,
277        cloudAccount=cloud_account,
278    )
279
280    if cloud_account:
281        if 'azure' in cloud_account:
282            azure_account = dict(
283                accessType=azure_access_type,
284                securityDomains=security_domains,
285                vendor='azure',
286            )
287
288            payload['azureAccount'] = [azure_account]
289
290            cloudSubscription = dict(
291                cloudSubscriptionId=azure_subscription_id,
292                cloudApplicationId=azure_application_id,
293            )
294
295            payload['azureAccount'][0]['cloudSubscription'] = cloudSubscription
296
297            if azure_access_type == 'shared':
298                payload['azureAccount'] = []
299
300            if azure_access_type == 'managed':
301                if not azure_subscription_id:
302                    mso.fail_json(msg="azure_susbscription_id is required when in managed mode.")
303                if not azure_application_id:
304                    mso.fail_json(msg="azure_application_id is required when in managed mode.")
305                payload['azureAccount'][0]['cloudApplication'] = []
306                payload['azureAccount'][0]['cloudActiveDirectory'] = []
307
308            if azure_access_type == 'unmanaged':
309                if not azure_subscription_id:
310                    mso.fail_json(msg="azure_subscription_id is required when in unmanaged mode.")
311                if not azure_application_id:
312                    mso.fail_json(msg="azure_application_id is required when in unmanaged mode.")
313                if not secret_key:
314                    mso.fail_json(msg="secret_key is required when in unmanaged mode.")
315                if not azure_active_directory_id:
316                    mso.fail_json(msg="azure_active_directory_id is required when in unmanaged mode.")
317                if not azure_active_directory_name:
318                    mso.fail_json(msg="azure_active_directory_name is required when in unmanaged mode.")
319                if not azure_credential_name:
320                    mso.fail_json(msg="azure_credential_name is required when in unmanaged mode.")
321                azure_account.update(
322                    accessType='credentials',
323                )
324                cloudApplication = dict(
325                    cloudApplicationId=azure_application_id,
326                    cloudCredentialName=azure_credential_name,
327                    secretKey=secret_key,
328                    cloudActiveDirectoryId=azure_active_directory_id
329                )
330                cloudActiveDirectory = dict(
331                    cloudActiveDirectoryId=azure_active_directory_id,
332                    cloudActiveDirectoryName=azure_active_directory_name
333                )
334                payload['azureAccount'][0]['cloudApplication'] = [cloudApplication]
335                payload['azureAccount'][0]['cloudActiveDirectory'] = [cloudActiveDirectory]
336
337        else:
338            aws_account = dict(
339                accountId=cloud_account,
340                isTrusted=aws_trusted,
341                accessKeyId=aws_access_key,
342                secretKey=secret_key,
343                isAccountInOrg=aws_account_org,
344            )
345
346            if not aws_trusted:
347                if not aws_access_key:
348                    mso.fail_json(msg="aws_access_key is a required field in untrusted mode.")
349                if not secret_key:
350                    mso.fail_json(msg="secret_key is a required field in untrusted mode.")
351            payload['awsAccount'] = [aws_account]
352
353    sites = [(s.get('siteId')) for s in mso.query_objs('tenants')[tenant_idx]['siteAssociations']]
354
355    if site_id in sites:
356        site_idx = sites.index((site_id))
357        mso.existing = mso.query_objs('tenants')[tenant_idx]['siteAssociations'][site_idx]
358
359    if state == 'query':
360        if len(sites) == 0:
361            mso.fail_json(msg="No site associated with tenant Id {0}".format(tenant_id))
362        elif site_id not in sites and site_id is not None:
363            mso.fail_json(msg="Site Id {0} not associated with tenant Id {1}".format(site_id, tenant_id))
364        elif site_id is None:
365            mso.existing = mso.query_objs('tenants')[tenant_idx]['siteAssociations']
366        mso.exit_json()
367
368    mso.previous = mso.existing
369
370    if state == 'absent':
371        if mso.existing:
372            mso.sent = mso.existing = {}
373            ops.append(dict(op='remove', path=port_path))
374    if state == 'present':
375        mso.sanitize(payload, collate=True)
376
377        if mso.existing:
378            ops.append(dict(op='replace', path=port_path, value=mso.sent))
379        else:
380            ops.append(dict(op='add', path=ports_path, value=mso.sent))
381
382        mso.existing = mso.proposed
383
384    if not module.check_mode and mso.proposed != mso.previous:
385        mso.request(tenant_path, method='PATCH', data=ops)
386
387    mso.exit_json()
388
389
390if __name__ == "__main__":
391    main()
392