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