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