1# -*- coding: utf-8 -*-
2from __future__ import (absolute_import, division, print_function)
3__metaclass__ = type
4
5import json
6import re
7import sys
8
9from ansible.module_utils.basic import env_fallback
10from ansible.module_utils.urls import fetch_url
11from ansible.module_utils.six.moves.urllib.parse import urlencode
12
13
14def scaleway_argument_spec():
15    return dict(
16        api_token=dict(required=True, fallback=(env_fallback, ['SCW_TOKEN', 'SCW_API_KEY', 'SCW_OAUTH_TOKEN', 'SCW_API_TOKEN']),
17                       no_log=True, aliases=['oauth_token']),
18        api_url=dict(fallback=(env_fallback, ['SCW_API_URL']), default='https://api.scaleway.com', aliases=['base_url']),
19        api_timeout=dict(type='int', default=30, aliases=['timeout']),
20        query_parameters=dict(type='dict', default={}),
21        validate_certs=dict(default=True, type='bool'),
22    )
23
24
25def payload_from_object(scw_object):
26    return dict(
27        (k, v)
28        for k, v in scw_object.items()
29        if k != 'id' and v is not None
30    )
31
32
33class ScalewayException(Exception):
34
35    def __init__(self, message):
36        self.message = message
37
38
39# Specify a complete Link header, for validation purposes
40R_LINK_HEADER = r'''<[^>]+>;\srel="(first|previous|next|last)"
41    (,<[^>]+>;\srel="(first|previous|next|last)")*'''
42# Specify a single relation, for iteration and string extraction purposes
43R_RELATION = r'</?(?P<target_IRI>[^>]+)>; rel="(?P<relation>first|previous|next|last)"'
44
45
46def parse_pagination_link(header):
47    if not re.match(R_LINK_HEADER, header, re.VERBOSE):
48        raise ScalewayException('Scaleway API answered with an invalid Link pagination header')
49    else:
50        relations = header.split(',')
51        parsed_relations = {}
52        rc_relation = re.compile(R_RELATION)
53        for relation in relations:
54            match = rc_relation.match(relation)
55            if not match:
56                raise ScalewayException('Scaleway API answered with an invalid relation in the Link pagination header')
57            data = match.groupdict()
58            parsed_relations[data['relation']] = data['target_IRI']
59        return parsed_relations
60
61
62class Response(object):
63
64    def __init__(self, resp, info):
65        self.body = None
66        if resp:
67            self.body = resp.read()
68        self.info = info
69
70    @property
71    def json(self):
72        if not self.body:
73            if "body" in self.info:
74                return json.loads(self.info["body"])
75            return None
76        try:
77            return json.loads(self.body)
78        except ValueError:
79            return None
80
81    @property
82    def status_code(self):
83        return self.info["status"]
84
85    @property
86    def ok(self):
87        return self.status_code in (200, 201, 202, 204)
88
89
90class Scaleway(object):
91
92    def __init__(self, module):
93        self.module = module
94        self.headers = {
95            'X-Auth-Token': self.module.params.get('api_token'),
96            'User-Agent': self.get_user_agent_string(module),
97            'Content-Type': 'application/json',
98        }
99        self.name = None
100
101    def get_resources(self):
102        results = self.get('/%s' % self.name)
103
104        if not results.ok:
105            raise ScalewayException('Error fetching {0} ({1}) [{2}: {3}]'.format(
106                self.name, '%s/%s' % (self.module.params.get('api_url'), self.name),
107                results.status_code, results.json['message']
108            ))
109
110        return results.json.get(self.name)
111
112    def _url_builder(self, path, params):
113        d = self.module.params.get('query_parameters')
114        if params is not None:
115            d.update(params)
116        query_string = urlencode(d, doseq=True)
117
118        if path[0] == '/':
119            path = path[1:]
120        return '%s/%s?%s' % (self.module.params.get('api_url'), path, query_string)
121
122    def send(self, method, path, data=None, headers=None, params=None):
123        url = self._url_builder(path=path, params=params)
124        self.warn(url)
125
126        if headers is not None:
127            self.headers.update(headers)
128
129        if self.headers['Content-Type'] == "application/json":
130            data = self.module.jsonify(data)
131
132        resp, info = fetch_url(
133            self.module, url, data=data, headers=self.headers, method=method,
134            timeout=self.module.params.get('api_timeout')
135        )
136
137        # Exceptions in fetch_url may result in a status -1, the ensures a proper error to the user in all cases
138        if info['status'] == -1:
139            self.module.fail_json(msg=info['msg'])
140
141        return Response(resp, info)
142
143    @staticmethod
144    def get_user_agent_string(module):
145        return "ansible %s Python %s" % (module.ansible_version, sys.version.split(' ', 1)[0])
146
147    def get(self, path, data=None, headers=None, params=None):
148        return self.send(method='GET', path=path, data=data, headers=headers, params=params)
149
150    def put(self, path, data=None, headers=None, params=None):
151        return self.send(method='PUT', path=path, data=data, headers=headers, params=params)
152
153    def post(self, path, data=None, headers=None, params=None):
154        return self.send(method='POST', path=path, data=data, headers=headers, params=params)
155
156    def delete(self, path, data=None, headers=None, params=None):
157        return self.send(method='DELETE', path=path, data=data, headers=headers, params=params)
158
159    def patch(self, path, data=None, headers=None, params=None):
160        return self.send(method="PATCH", path=path, data=data, headers=headers, params=params)
161
162    def update(self, path, data=None, headers=None, params=None):
163        return self.send(method="UPDATE", path=path, data=data, headers=headers, params=params)
164
165    def warn(self, x):
166        self.module.warn(str(x))
167
168
169SCALEWAY_LOCATION = {
170    'par1': {'name': 'Paris 1', 'country': 'FR', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/fr-par-1'},
171    'EMEA-FR-PAR1': {'name': 'Paris 1', 'country': 'FR', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/fr-par-1'},
172
173    'par2': {'name': 'Paris 2', 'country': 'FR', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/fr-par-2'},
174    'EMEA-FR-PAR2': {'name': 'Paris 2', 'country': 'FR', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/fr-par-2'},
175
176    'ams1': {'name': 'Amsterdam 1', 'country': 'NL', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/nl-ams-1'},
177    'EMEA-NL-EVS': {'name': 'Amsterdam 1', 'country': 'NL', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/nl-ams-1'},
178
179    'waw1': {'name': 'Warsaw 1', 'country': 'PL', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/pl-waw-1'},
180    'EMEA-PL-WAW1': {'name': 'Warsaw 1', 'country': 'PL', "api_endpoint": 'https://api.scaleway.com/instance/v1/zones/pl-waw-1'},
181}
182
183SCALEWAY_ENDPOINT = "https://api.scaleway.com"
184
185SCALEWAY_REGIONS = [
186    "fr-par",
187    "nl-ams",
188    "pl-waw",
189]
190
191SCALEWAY_ZONES = [
192    "fr-par-1",
193    "fr-par-2",
194    "nl-ams-1",
195    "pl-waw-1",
196]
197