1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2017, René Moser <mail@renemoser.net>
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
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15DOCUMENTATION = r'''
16---
17module: cs_vpn_connection
18short_description: Manages site-to-site VPN connections on Apache CloudStack based clouds.
19description:
20    - Create and remove VPN connections.
21version_added: '2.5'
22author: René Moser (@resmo)
23options:
24  vpc:
25    description:
26      - Name of the VPC the VPN connection is related to.
27    type: str
28    required: true
29  vpn_customer_gateway:
30    description:
31      - Name of the VPN customer gateway.
32    type: str
33    required: true
34  passive:
35    description:
36      - State of the VPN connection.
37      - Only considered when I(state=present).
38    default: no
39    type: bool
40  force:
41    description:
42      - Activate the VPN gateway if not already activated on I(state=present).
43      - Also see M(cs_vpn_gateway).
44    default: no
45    type: bool
46  state:
47    description:
48      - State of the VPN connection.
49    type: str
50    default: present
51    choices: [ present, absent ]
52  zone:
53    description:
54      - Name of the zone the VPC is related to.
55      - If not set, default zone is used.
56    type: str
57  domain:
58    description:
59      - Domain the VPN connection is related to.
60    type: str
61  account:
62    description:
63      - Account the VPN connection is related to.
64    type: str
65  project:
66    description:
67      - Name of the project the VPN connection is related to.
68    type: str
69  poll_async:
70    description:
71      - Poll async jobs until job has finished.
72    default: yes
73    type: bool
74extends_documentation_fragment: cloudstack
75'''
76
77EXAMPLES = r'''
78- name: Create a VPN connection with activated VPN gateway
79  cs_vpn_connection:
80    vpn_customer_gateway: my vpn connection
81    vpc: my vpc
82  delegate_to: localhost
83
84- name: Create a VPN connection and force VPN gateway activation
85  cs_vpn_connection:
86    vpn_customer_gateway: my vpn connection
87    vpc: my vpc
88    force: yes
89  delegate_to: localhost
90
91- name: Remove a vpn connection
92  cs_vpn_connection:
93    vpn_customer_gateway: my vpn connection
94    vpc: my vpc
95    state: absent
96  delegate_to: localhost
97'''
98
99RETURN = r'''
100---
101id:
102  description: UUID of the VPN connection.
103  returned: success
104  type: str
105  sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
106vpn_gateway_id:
107  description: UUID of the VPN gateway.
108  returned: success
109  type: str
110  sample: 04589590-ac63-93f5-4ffc-b698b8ac38b6
111domain:
112  description: Domain the VPN connection is related to.
113  returned: success
114  type: str
115  sample: example domain
116account:
117  description: Account the VPN connection is related to.
118  returned: success
119  type: str
120  sample: example account
121project:
122  description: Name of project the VPN connection is related to.
123  returned: success
124  type: str
125  sample: Production
126created:
127  description: Date the connection was created.
128  returned: success
129  type: str
130  sample: 2014-12-01T14:57:57+0100
131dpd:
132  description: Whether dead pear detection is enabled or not.
133  returned: success
134  type: bool
135  sample: true
136esp_lifetime:
137  description: Lifetime in seconds of phase 2 VPN connection.
138  returned: success
139  type: int
140  sample: 86400
141esp_policy:
142  description: IKE policy of the VPN connection.
143  returned: success
144  type: str
145  sample: aes256-sha1;modp1536
146force_encap:
147  description: Whether encapsulation for NAT traversal is enforced or not.
148  returned: success
149  type: bool
150  sample: true
151ike_lifetime:
152  description: Lifetime in seconds of phase 1 VPN connection.
153  returned: success
154  type: int
155  sample: 86400
156ike_policy:
157  description: ESP policy of the VPN connection.
158  returned: success
159  type: str
160  sample: aes256-sha1;modp1536
161cidrs:
162  description: List of CIDRs of the customer gateway.
163  returned: success
164  type: list
165  sample: [ 10.10.10.0/24 ]
166passive:
167  description: Whether the connection is passive or not.
168  returned: success
169  type: bool
170  sample: false
171public_ip:
172  description: IP address of the VPN gateway.
173  returned: success
174  type: str
175  sample: 10.100.212.10
176gateway:
177  description: IP address of the VPN customer gateway.
178  returned: success
179  type: str
180  sample: 10.101.214.10
181state:
182  description: State of the VPN connection.
183  returned: success
184  type: str
185  sample: Connected
186'''
187
188from ansible.module_utils.basic import AnsibleModule
189from ansible.module_utils.cloudstack import (
190    AnsibleCloudStack,
191    cs_argument_spec,
192    cs_required_together
193)
194
195
196class AnsibleCloudStackVpnConnection(AnsibleCloudStack):
197
198    def __init__(self, module):
199        super(AnsibleCloudStackVpnConnection, self).__init__(module)
200        self.returns = {
201            'dpd': 'dpd',
202            'esplifetime': 'esp_lifetime',
203            'esppolicy': 'esp_policy',
204            'gateway': 'gateway',
205            'ikepolicy': 'ike_policy',
206            'ikelifetime': 'ike_lifetime',
207            'publicip': 'public_ip',
208            'passive': 'passive',
209            's2svpngatewayid': 'vpn_gateway_id',
210        }
211        self.vpn_customer_gateway = None
212
213    def get_vpn_customer_gateway(self, key=None, identifier=None, refresh=False):
214        if not refresh and self.vpn_customer_gateway:
215            return self._get_by_key(key, self.vpn_customer_gateway)
216
217        args = {
218            'account': self.get_account(key='name'),
219            'domainid': self.get_domain(key='id'),
220            'projectid': self.get_project(key='id'),
221            'fetch_list': True,
222        }
223
224        vpn_customer_gateway = identifier or self.module.params.get('vpn_customer_gateway')
225        vcgws = self.query_api('listVpnCustomerGateways', **args)
226        if vcgws:
227            for vcgw in vcgws:
228                if vpn_customer_gateway.lower() in [vcgw['id'], vcgw['name'].lower()]:
229                    self.vpn_customer_gateway = vcgw
230                    return self._get_by_key(key, self.vpn_customer_gateway)
231        self.fail_json(msg="VPN customer gateway not found: %s" % vpn_customer_gateway)
232
233    def get_vpn_gateway(self, key=None):
234        args = {
235            'vpcid': self.get_vpc(key='id'),
236            'account': self.get_account(key='name'),
237            'domainid': self.get_domain(key='id'),
238            'projectid': self.get_project(key='id'),
239        }
240        vpn_gateways = self.query_api('listVpnGateways', **args)
241        if vpn_gateways:
242            return self._get_by_key(key, vpn_gateways['vpngateway'][0])
243
244        elif self.module.params.get('force'):
245            if self.module.check_mode:
246                return {}
247            res = self.query_api('createVpnGateway', **args)
248            vpn_gateway = self.poll_job(res, 'vpngateway')
249            return self._get_by_key(key, vpn_gateway)
250
251        self.fail_json(msg="VPN gateway not found and not forced to create one")
252
253    def get_vpn_connection(self):
254        args = {
255            'vpcid': self.get_vpc(key='id'),
256            'account': self.get_account(key='name'),
257            'domainid': self.get_domain(key='id'),
258            'projectid': self.get_project(key='id'),
259        }
260
261        vpn_conns = self.query_api('listVpnConnections', **args)
262        if vpn_conns:
263            for vpn_conn in vpn_conns['vpnconnection']:
264                if self.get_vpn_customer_gateway(key='id') == vpn_conn['s2scustomergatewayid']:
265                    return vpn_conn
266
267    def present_vpn_connection(self):
268        vpn_conn = self.get_vpn_connection()
269
270        args = {
271            's2scustomergatewayid': self.get_vpn_customer_gateway(key='id'),
272            's2svpngatewayid': self.get_vpn_gateway(key='id'),
273            'passive': self.module.params.get('passive'),
274        }
275
276        if not vpn_conn:
277            self.result['changed'] = True
278
279            if not self.module.check_mode:
280                res = self.query_api('createVpnConnection', **args)
281                poll_async = self.module.params.get('poll_async')
282                if poll_async:
283                    vpn_conn = self.poll_job(res, 'vpnconnection')
284
285        return vpn_conn
286
287    def absent_vpn_connection(self):
288        vpn_conn = self.get_vpn_connection()
289
290        if vpn_conn:
291            self.result['changed'] = True
292
293            args = {
294                'id': vpn_conn['id']
295            }
296
297            if not self.module.check_mode:
298                res = self.query_api('deleteVpnConnection', **args)
299                poll_async = self.module.params.get('poll_async')
300                if poll_async:
301                    self.poll_job(res, 'vpnconnection')
302
303        return vpn_conn
304
305    def get_result(self, vpn_conn):
306        super(AnsibleCloudStackVpnConnection, self).get_result(vpn_conn)
307        if vpn_conn:
308            if 'cidrlist' in vpn_conn:
309                self.result['cidrs'] = vpn_conn['cidrlist'].split(',') or [vpn_conn['cidrlist']]
310            # Ensure we return a bool
311            self.result['force_encap'] = True if vpn_conn.get('forceencap') else False
312            args = {
313                'key': 'name',
314                'identifier': vpn_conn['s2scustomergatewayid'],
315                'refresh': True,
316            }
317            self.result['vpn_customer_gateway'] = self.get_vpn_customer_gateway(**args)
318        return self.result
319
320
321def main():
322    argument_spec = cs_argument_spec()
323    argument_spec.update(dict(
324        vpn_customer_gateway=dict(required=True),
325        vpc=dict(required=True),
326        domain=dict(),
327        account=dict(),
328        project=dict(),
329        zone=dict(),
330        passive=dict(type='bool', default=False),
331        force=dict(type='bool', default=False),
332        state=dict(choices=['present', 'absent'], default='present'),
333        poll_async=dict(type='bool', default=True),
334    ))
335
336    module = AnsibleModule(
337        argument_spec=argument_spec,
338        required_together=cs_required_together(),
339        supports_check_mode=True
340    )
341
342    acs_vpn_conn = AnsibleCloudStackVpnConnection(module)
343
344    state = module.params.get('state')
345    if state == "absent":
346        vpn_conn = acs_vpn_conn.absent_vpn_connection()
347    else:
348        vpn_conn = acs_vpn_conn.present_vpn_connection()
349
350    result = acs_vpn_conn.get_result(vpn_conn)
351    module.exit_json(**result)
352
353
354if __name__ == '__main__':
355    main()
356