blob: 10cdbe76cc9c00553f80f22bed6f69e1e1a21bd5 [file] [log] [blame]
David K. Bainbridge6e23ac82016-12-07 12:55:41 -08001#!/usr/bin/python
2
3DOCUMENTATION = '''
4---
5module: maas
6short_description: Manage MAAS server configuration
7options:
8 maas:
9 description:
10 - URL of MAAS server
11 default: http://localhost/MAAS/api/1.0/
12 key:
13 description:
14 - MAAS API key
15 required: yes
16 options:
17 description:
18 - list of config options to query, this is only used for query
19 enable_http_proxy: :
20 description:
21 - Enable the use of an APT and HTTP/HTTPS proxy.
22 upstream_dns: :
23 description:
24 - Upstream DNS used to resolve domains not managed by this MAAS (space-separated IP addresses).
25 default_storage_layout: :
26 description:
27 - Default storage layout.
28 choices:
29 - ['lvm', 'flat', 'bcache']
30 default_osystem: :
31 description:
32 - Default operating system used for deployment.
33 ports_archive: :
34 description:
35 - Ports archive.
36 http_proxy: :
37 description:
38 - Proxy for APT and HTTP/HTTPS.
39 boot_images_auto_import: :
40 description:
41 - Automatically import/refresh the boot images every 60 minutes.
42 enable_third_party_drivers: :
43 description:
44 - Enable the installation of proprietary drivers (i.e. HPVSA).
45 kernel_opts: :
46 description:
47 - Boot parameters to pass to the kernel by default.
48 main_archive: :
49 description:
50 - Main archive
51 maas_name: :
52 description:
53 - MAAS name.
54 curtin_verbose: :
55 description:
56 - Run the fast-path installer with higher verbosity. This provides more detail in the installation logs..
57 dnssec_validation: :
58 description:
59 - Enable DNSSEC validation of upstream zones.
60 commissioning_distro_series: :
61 description:
62 - Default Ubuntu release used for commissioning.
63 windows_kms_host: :
64 description:
65 - Windows KMS activation host.
66 enable_disk_erasing_on_release: :
67 description:
68 - Erase nodes' disks prior to releasing..
69 default_distro_series: :
70 description:
71 - Default OS release used for deployment.
72 ntp_server: :
73 description:
74 - Address of NTP server for nodes.
75 default_min_hwe_kernel: :
76 description:
77 - Default Minimum Kernel Version.
78 state:
79 description:
80 - possible states for the module
81 choices: ['present', 'query']
82 default: present
83
84requirements: [ipaddress, requests_oauthlib, maasclient]
85author: David Bainbridge
86'''
87
88EXAMPLES = '''
89examples:
90 maas:
91 maas: http://my.maas.server.com/MAAS/api/1.0/
92 key: 'xBvr9dx5k7S52myufC:fqBXV7hJgXegNZDw9c:K8hsmL47XjAppfQy2pDVW7G49p6PELgp'
93 options:
94 - upstream_dns
95 - ntp_servers
96 state: query
97
98 maas:
99 maas: http://my.maas.server.com/MAAS/api/1.0/
100 key: 'xBvr9dx5k7S52myufC:fqBXV7hJgXegNZDw9c:K8hsmL47XjAppfQy2pDVW7G49p6PELgp'
101 upstream_dns: 8.8.8.8 8.8.8.4
102 state: present
103'''
104
105import sys
106import json
107import ipaddress
108import requests
109from maasclient.auth import MaasAuth
110from maasclient import MaasClient
111
112# For some reason the maasclient doesn't provide a put method. So
113# we will add it here
114def put(client, url, params=None):
115 return requests.put(url=client.auth.api_url + url,
116 auth=client._oauth(), data=params)
117
118# Attempt to interpret the given value as a JSON object, if that fails
119# just return it as a string
120def string_or_object(val):
121 try:
122 return json.loads(val)
123 except:
124 return val
125
126# Return a copy of the given dictionary with any `null` valued entries
127# removed
128def remove_null(d_in):
129 d = d_in.copy()
130 to_remove = []
131 for k in d.keys():
132 if d[k] == None:
133 to_remove.append(k)
134 for k in to_remove:
135 del d[k]
136 return d
137
138# Deterine if two dictionaries are different
139def different(have, want):
140 have_keys = have.keys()
141 for key in want.keys():
142 if (key in have_keys and want[key] != have[key]) or key not in have_keys:
143 return True
144 return False
145
146# Get configuration options from MAAS
147def get_config(maas, desired):
148 config = {}
149 for o in desired.keys():
150 res = maas.get('/maas/', dict(name=o, op='get_config'))
151 if res.ok:
152 val = json.loads(res.text)
153 config[o] = val if val else ""
154 else:
155 config[o] = {'error': string_or_object(res.text)}
156 return config
157
158# Walk the list of options in the desired state setting those on MAAS
159def update_config(maas, have, want):
160 have_error = False
161 status = {}
162 for o in want.keys():
163 if want[o] != have[o]:
164 res = maas.post('/maas/', {'name': o, 'value': want[o], 'op': 'set_config'})
165 if res.ok:
166 status[o] = { 'error': False, 'status': want[o] }
167 else:
168 have_error = True
169 status[o] = { 'error': True, 'status': string_or_object(res.text) }
170 return {'error': have_error, 'status': status}
171
172def main():
173 module = AnsibleModule(
174 argument_spec = dict(
175 maas=dict(default='http://localhost/MAAS/api/1.0/'),
176 key=dict(required=True),
177 options=dict(required=False, type='list'),
178 enable_http_proxy=dict(required=False),
179 upstream_dns=dict(required=False),
180 default_storage_layout=dict(required=False),
181 default_osystem=dict(required=False),
182 ports_archive=dict(required=False),
183 http_proxy=dict(required=False),
184 boot_images_auto_import=dict(required=False),
185 enable_third_party_drivers=dict(required=False),
186 kernel_opts=dict(required=False),
187 main_archive=dict(required=False),
188 maas_name=dict(required=False),
189 curtin_verbose=dict(required=False),
190 dnssec_validation=dict(required=False),
191 commissioning_distro_series=dict(required=False),
192 windows_kms_host=dict(required=False),
193 enable_disk_erasing_on_release=dict(required=False),
194 default_distro_series=dict(required=False),
195 ntp_server=dict(required=False),
196 default_min_hwe_kernel=dict(required=False),
197 state=dict(default='present', choices=['present', 'query'])
198 ),
199 supports_check_mode = False
200 )
201
202 maas = module.params['maas']
203 key = module.params['key']
204 options = module.params['options']
205 state = module.params['state']
206
207 if state == 'query':
208 desired = {x:None for x in options}
209 else:
210 # Construct a sparsely populate desired state
211 desired = remove_null({
212 'enable_http_proxy': module.params['enable_http_proxy'],
213 'upstream_dns': module.params['upstream_dns'],
214 'default_storage_layout': module.params['default_storage_layout'],
215 'default_osystem': module.params['default_osystem'],
216 'ports_archive': module.params['ports_archive'],
217 'http_proxy': module.params['http_proxy'],
218 'boot_images_auto_import': module.params['boot_images_auto_import'],
219 'enable_third_party_drivers': module.params['enable_third_party_drivers'],
220 'kernel_opts': module.params['kernel_opts'],
221 'main_archive': module.params['main_archive'],
222 'maas_name': module.params['maas_name'],
223 'curtin_verbose': module.params['curtin_verbose'],
224 'dnssec_validation': module.params['dnssec_validation'],
225 'commissioning_distro_series': module.params['commissioning_distro_series'],
226 'windows_kms_host': module.params['windows_kms_host'],
227 'enable_disk_erasing_on_release': module.params['enable_disk_erasing_on_release'],
228 'default_distro_series': module.params['default_distro_series'],
229 'ntp_server': module.params['ntp_server'],
230 'default_min_hwe_kernel': module.params['default_min_hwe_kernel'],
231 })
232
233 # Authenticate into MAAS
234 auth = MaasAuth(maas, key)
235 maas = MaasClient(auth)
236
237 # Attempt to get the configuration from MAAS
238 config = get_config(maas, desired)
239
240 if state == 'query':
241 # If this is a query, return the options
242 module.exit_json(changed=False, found=True, maas=config)
243 elif state == 'present':
244 # If we want this to exists check to see if this is different and
245 # needs updated
246 if different(config, desired):
247 res = update_config(maas, config, desired)
248 if res['error']:
249 module.fail_json(msg=res['status'])
250 else:
251 module.exit_json(changed=True, maas=res['status'])
252 else:
253 # No differences, to nothing to change
254 module.exit_json(changed=False, maas=config)
255
256# this is magic, see lib/ansible/module_common.py
257#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
258if __name__ == '__main__':
259 main()