Refactoring maas functions to use config

Use config instead of args.
Adding
* process_fabrics
* process_subnets
* process_devices
* process_machines
* process_dhcp_snippets
* process_boot_resources
* process_package_repositories
diff --git a/_modules/maas.py b/_modules/maas.py
index 83e6b2a..b84b99f 100644
--- a/_modules/maas.py
+++ b/_modules/maas.py
@@ -13,8 +13,12 @@
 
 from __future__ import absolute_import
 
+import io
 import logging
-import os
+import os.path
+import subprocess
+import urllib2
+import hashlib
 
 import json
 
@@ -26,8 +30,7 @@
     from apiclient.maas_client import MAASClient, MAASDispatcher, MAASOAuth
     HAS_MASS = True
 except ImportError:
-    pass
-
+    LOG.exception('why??')
 
 def __virtual__():
     '''
@@ -38,166 +41,343 @@
         return 'maas'
     return False
 
-__opts__ = {}
+APIKEY_FILE = '/var/lib/maas/.maas_credentials'
+
+def _format_data(data):
+    class Lazy:
+        def __str__(self):
+            return ' '.join(['{0}={1}'.format(k, v)
+                             for k, v in data.iteritems()])
+
+    return Lazy()
 
 
-def _auth(**connection_args):
-    '''
-    Set up maas credentials
-
-    Only intended to be used within maas-enabled modules
-    '''
-
-    prefix = "maas."
-
-    # look in connection_args first, then default to config file
-    def get(key, default=None):
-        return connection_args.get('connection_' + key,
-            __salt__['config.get'](prefix + key, default))
-
-    api_token = get('token')
-    api_url = get('url', 'https://localhost/')
-
-    LOG.debug("MAAS url: " + api_url)
-    LOG.debug("MAAS token: " + api_token)
-    auth = MAASOAuth(*api_token.split(":"))
+def _create_maas_client():
+    global APIKEY_FILE
+    try:
+        api_token = file(APIKEY_FILE).read().strip().split(':')
+    except:
+        LOG.exception('token')
+    auth = MAASOAuth(*api_token)
+    api_url = 'http://localhost:5240/MAAS'
     dispatcher = MAASDispatcher()
-    client = MAASClient(auth, dispatcher, api_url)
+    return MAASClient(auth, dispatcher, api_url)
 
-    return client
+class MaasObject(object):
+    def __init__(self):
+        self._maas = _create_maas_client()
+        self._extra_data_urls = {}
+        self._extra_data = {}
+        self._update = False
+        self._element_key = 'name'
+        self._update_key = 'id'
+
+    def send(self, data):
+        LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
+        if self._update:
+            return self._maas.put(self._update_url.format(data[self._update_key]), **data).read()
+        return self._maas.post(self._create_url, None, **data).read()
+
+    def process(self):
+        config = __salt__['config.get']('maas')
+        for part in self._config_path.split('.'):
+            config = config.get(part, {})
+        extra = {}
+        for name, url_call in self._extra_data_urls.iteritems():
+            key = 'id'
+            if isinstance(url_call, tuple):
+                url_call, key = url_call[:]
+            extra[name] = {v['name']: v[key] for v in
+                            json.loads(self._maas.get(url_call).read())}
+        elements = self._maas.get(self._all_elements_url).read()
+        all_elements = {v[self._element_key]: v for v in json.loads(elements)}
+        ret = {
+            'success': [],
+            'errors': {},
+            'updated': [],
+        }
+        for name, config_data in config.iteritems():
+            try:
+                data = self.fill_data(name, config_data, **extra)
+                if name in all_elements:
+                    self._update = True
+                    LOG.error('%s DATA %s', all_elements[name], data)
+                    data = self.update(data, all_elements[name])
+                    self.send(data)
+                    ret['updated'].append(name)
+                else:
+                    self.send(data)
+                    ret['success'].append(name)
+            except urllib2.HTTPError as e:
+                etxt = e.read()
+                LOG.exception('Failed for object %s reason %s', name, etxt)
+                ret['errors'][name] = str(etxt)
+            except Exception as e:
+                LOG.exception('Failed for object %s reason %s', name, e)
+                ret['errors'][name] = str(e)
+        if ret['errors']:
+            raise Exception(ret)
+        return ret
 
 
-def cluster_get(cluster_name=None, **connection_args):
-    '''
-    Return a specific cluster
+class Fabric(MaasObject):
+    def __init__(self):
+        super(Fabric, self).__init__()
+        self._all_elements_url = u'api/2.0/fabrics/'
+        self._create_url = u'api/2.0/fabrics/'
+        self._update_url = u'api/2.0/fabrics/{0}/'
+        self._config_path = 'region.fabrics'
+#        self._update_keys = ['name', 'description', 'class_type', 'id']
 
-    CLI Example:
+    def fill_data(self, name, fabric):
+        data = {
+            'name': name,
+            'description': fabric.get('description', ''),
+        }
+        if 'class_type' in fabric:
+            data['class_type'] = fabric.get('class_type'),
+        return data
 
-    .. code-block:: bash
+    def update(self, new, old):
+        new['id'] = str(old['id'])
+        return new
 
-        salt '*' maas.cluster_get cluster
-    '''
-    maas = _auth(**connection_args)
+class Subnet(MaasObject):
+    def __init__(self):
+        super(Subnet, self).__init__()
+        self._all_elements_url = u'api/2.0/subnets/'
+        self._create_url = u'api/2.0/subnets/'
+        self._update_url = u'api/2.0/subnets/{0}/'
+        self._config_path = 'region.subnets'
+        self._extra_data_urls = {'fabrics':u'api/2.0/fabrics/'}
 
-    response = maas.get(u"nodegroups/", "list").read()
-    LOG.debug("Response: " + response)
+    def fill_data(self, name, subnet, fabrics):
+        data = {
+            'name': name,
+            'fabric': str(fabrics[subnet.get('fabric', '')]),
+            'cidr': subnet.get('cidr'),
+            'gateway_ip': subnet['gateway_ip'],
+        }
+        self._iprange = subnet['iprange']
+        return data
 
-    object_list = json.loads(response)
+    def update(self, new, old):
+        new['id'] = str(old['id'])
+        return new
 
+    def send(self, data):
+        response = super(Subnet, self).send(data)
+        res_json = json.loads(response)
+        self._process_iprange(res_json['id'])
+        return response
 
-    for cluster in object_list:
-        if cluster.get('cluster_name') == cluster_name:
-            return {cluster.get('cluster_name'): cluster}
-    return {'Error': 'Could not find specified cluster'}
-
-
-def cluster_list(**connection_args):
-    '''
-    Return a list of MAAS clusters
-
-    CLI Example:
-
-    .. code-block:: bash
-
-        salt '*' maas.cluster_list
-    '''
-    maas = _auth(**connection_args)
-    ret = {}
-
-    response = maas.get(u"nodegroups/", "list").read()
-
-    LOG.debug("Clusters in maas: " + response )
-
-    object_list = json.loads(response)
-
-    for cluster in object_list:
-        ret[cluster.get('cluster_name')] = cluster
-    return ret
-
-
-def cluster_create(cluster_name=None, **connection_args):
-    '''
-    Create MAAS cluster
-
-    CLI Examples:
-
-    .. code-block:: bash
-
-        salt '*' maas.cluster_create cluster
-    '''
-    maas = auth(**connection_args)
-    if project_name:
-        project = _get_project(maas, project_name)
-    else:
-        project = _get_project_by_id(maas, project_id)
-    if not project:
-        return {'Error': 'Unable to resolve project'}
-    create = True
-    for cluster in maas.getprojectclusters(project.get('id')):
-        if cluster.get('url') == cluster_url:
-            create = False
-    if create:
-        maas.addprojectcluster(project['id'], cluster_url)
-    return cluster_get(cluster_url, project_id=project['id'])
-
-
-def cluster_delete(cluster_name=None, **connection_args):
-    '''
-    Delete MAAS cluster
-
-    CLI Examples:
-
-    .. code-block:: bash
-
-        salt '*' maas.cluster_delete 'https://cluster.url/' project_id=300
-    '''
-    maas = _auth(**connection_args)
-    project = _get_project(maas, project_name)
-
-    for cluster in maas.getprojectclusters(project.get('id')):
-        if cluster.get('url') == cluster_url:
-            return maas.deleteprojectcluster(project['id'], cluster['id'])
-    return {'Error': 'Could not find cluster'}
-
-
-def cluster_update(cluster_id=None, old_cluster_name=None, new_cluster_name=None, domain=None, status=None, **connection_args):
-    '''
-    Update information in specific MAAS cluster
-
-    CLI Examples:
-
-    .. code-block:: bash
-
-        salt '*' maas.cluster_update cluster_id cluster_name dns_name status 
-    '''
-    maas = _auth(**connection_args)
-
-    cluster = {}
-
-    if not cluster_id and old_cluster_name:
-        cluster = cluster_get(old_cluster_name)
-        if cluster.get("Error"):
-            return cluster
+    def _process_iprange(self, subnet_id):
+        ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
+        LOG.warn('all %s ipranges %s', subnet_id, ipranges)
+        update = False
+        old_data = None
+        for iprange in ipranges:
+            if iprange['subnet']['id'] == subnet_id:
+                update = True
+                old_data = iprange
+                break
+        data = {
+            'start_ip': self._iprange.get('start'),
+            'end_ip': self._iprange.get('end'),
+            'subnet': str(subnet_id),
+            'type': self._iprange.get('type', 'dynamic')
+        }
+        LOG.warn('INFO: %s\n OLD: %s', data, old_data)
+        LOG.info('iprange %s', _format_data(data))
+        if update:
+            LOG.warn('UPDATING %s %s', data, old_data)
+            self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']), **data)
         else:
-            cluster_id = cluster_get(old_cluster_name).get(old_cluster_name).get("uuid")
+            self._maas.post(u'api/2.0/ipranges/', None, **data)
 
-    else:
-        return {'Error': 'No cluster id or name specified'}
+class DHCPSnippet(MaasObject):
+    def __init__(self):
+        super(DHCPSnippet, self).__init__()
+        self._all_elements_url = u'api/2.0/dhcp-snippets/'
+        self._create_url = u'api/2.0/dhcp-snippets/'
+        self._update_url = u'api/2.0/dhcp-snippets/{0}/'
+        self._config_path = 'region.dhcp_snippets'
+        self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
 
-    if new_cluster_name:
-        cluster["cluster_name"] = new_cluster_name
+    def fill_data(self, name, snippet, subnets):
+        data = {
+             'name': name,
+             'value': snippet['value'],
+             'description': snippet['description'],
+             'enabled': str(snippet['enabled'] and 1 or 0),
+             'subnet': str(subnets[snippet['subnet']]),
+        }
+        return data
 
-    if domain:
-        cluster["name"] = domain
+    def update(self, new, old):
+        new['id'] = str(old['id'])
+        return new
 
-    if status:
-	cluster["status"] = status
+class PacketRepository(MaasObject):
+    def __init__(self):
+        super(PacketRepository, self).__init__()
+        self._all_elements_url = u'api/2.0/package-repositories/'
+        self._create_url = u'api/2.0/package-repositories/'
+        self._update_url = u'api/2.0/package-repositories/{0}/'
+        self._config_path = 'region.package_repositories'
 
-    LOG.debug("Cluster id: " + cluster_id)
-    LOG.debug("New cluster info: " + str(cluster))
+    def fill_data(self, name, package_repository):
+        data = {
+            'name': name,
+            'url': package_repository['url'],
+            'distributions': package_repository['distributions'],
+            'components': package_repository['components'],
+            'arches': package_repository['arches'],
+            'key': package_repository['key'],
+            'enabled': str(package_repository['enabled'] and 1 or 0),
+        }
+        if 'disabled_pockets' in package_repository:
+            data['disabled_pockets'] = package_repository['disable_pockets'],
+        return data
 
-    response = maas.put(u"nodegroups/" + cluster_id + "/", **cluster)
+    def update(self, new, old):
+        new['id'] = str(old['id'])
+        return new
 
-    #TODO check response status
-    return {'Status': True}
+class Device(MaasObject):
+    def __init__(self):
+        super(Device, self).__init__()
+        self._all_elements_url = u'api/2.0/devices/'
+        self._create_url = u'api/2.0/devices/'
+        self._update_url = u'api/2.0/devices/{0}/'
+        self._config_path = 'region.devices'
+        self._element_key = 'hostname'
+        self._update_key = 'system_id'
 
+    def fill_data(self, name, device_data):
+        data = {
+            'mac_addresses': device_data['mac'],
+            'hostname': name,
+        }
+        self._interface = device_data['interface']
+        return data
+
+    def update(self, new, old):
+        new_macs = set(new['mac_addresses'])
+        old_macs = set(v['mac_address'] for v in old[interface_set])
+        if new_macs - old_macs:
+            self._update = False
+            self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
+        else:
+            new[self._update_key] = str(old[self._update_key])
+        return new
+
+    def send(self, data):
+        response = super(Device, self).send(data)
+        resp_json = json.loads(response)
+        system_id = resp_json['system_id']
+        iface_id = resp_json['interface_set'][0]['id']
+        self._link_interface(maas, system_id, iface_id)
+        return response
+
+    def _link_interface(self, system_id, interface_id):
+        data = {
+            'mode': self._interface.get('mode', 'STATIC'),
+            'subnet': self._interface.get('subnet'),
+            'ip_address': self._interface.get('ip_address'),
+        }
+        if 'default_gateway' in self._interface:
+            data['default_gateway'] = self._interface.get('default_gateway')
+        if self._update:
+            data['force'] = '1'
+        LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
+                _format_data(data))
+        self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
+                        .format(system_id, interface_id), 'link_subnet',
+                        **data)
+
+
+class Machine(MaasObject):
+    def __init__(self):
+        super(Machine, self).__init__()
+        self._all_elements_url = u'api/2.0/machines/'
+        self._create_url = u'api/2.0/machines/'
+        self._update_url = u'api/2.0/machines/{0}/'
+        self._config_path = 'region.machines'
+        self._element_key = 'hostname'
+        self._update_key = 'system_id'
+
+    def fill_data(self, name, machine_data):
+        main_interface = next(machine_data['interfaces'][0].iteritems())
+        interfaces = machine_data['interfaces'][1:]
+        power_data = machine_data['power_parameters']
+        data = {
+            'hostname': name,
+            'architecture': machine_data.get('architecture', 'amd64/generic'),
+            'mac_addresses': main_interface[1],
+            'power_type': machine_data.get('power_type', 'ipmi'),
+            'power_parameters_power_address': power_data['power_address'],
+        }
+        if 'power_user' in power_data:
+            data['power_parameters_power_user'] = power_data['power_user']
+        if 'power_password' in power_data:
+            data['power_parameters_power_pass'] = \
+                power_data['power_password']
+        return data
+
+    def update(self, new, old):
+        new_macs = set(new['mac_addresses'])
+        old_macs = set(v['mac_address'] for v in old[interface_set])
+        if new_macs - old_macs:
+            self._update = False
+            self._maas.delete(u'api/2.0/machiens/{0}/'.format(old['system_id']))
+        else:
+            new[self._update_key] = str(old[self._update_key])
+        return new
+
+class BootResource(MaasObject):
+    def __init__(self):
+        super(BootResource, self).__init__()
+        self._all_elements_url = u'api/2.0/boot-resources/'
+        self._create_url = u'api/2.0/boot-resources/'
+        self._update_url = u'api/2.0/boot-resources/{0}/'
+        self._config_path = 'region.boot_resources'
+
+    def fill_data(self, name, boot_data):
+        sha256 = hashlib.sha256()
+        sha256.update(file(boot_data['content']).read())
+        data = {
+            'name': name,
+            'title': boot_data['title'],
+            'architecture': boot_data['architecture'],
+            'filetype': boot_data['filetype'],
+            'size': str(os.path.getsize(boot_data['content'])),
+            'sha256': sha256.hexdigest(),
+            'content': io.open(boot_data['content']),
+        }
+        return data
+
+    def update(self, new, old):
+        self._update = False
+        return new
+
+def process_fabrics():
+    return Fabric().process()
+
+def process_subnets():
+    return Subnet().process()
+
+def process_dhcp_snippets():
+    return DHCPSnippet().process()
+
+def process_package_repositories():
+    return PacketRepository().process()
+
+def process_devices():
+    return Device().process()
+
+def process_machines():
+    return Machine().process()
+
+def process_boot_resources():
+    return BootResource().process()