class MaasTagging(object): def __init__(self): """ Main execution path """ self.conn = None self.read_settings() self.run() def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read( os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) def run(self): """ Make calls to Maas to create tags and assign nodes to them """ self._connect() # Get system ids of nodes nodes = self.maas_client.get('/nodes/', 'list').read() system_ids = {} for node in json.loads(nodes): system_ids[node['hostname'].split('.')[0]] = node['system_id'] # Get existing tags tags = self.maas_client.get('/tags/', 'list').read() tags = [tag['name'] for tag in json.loads(tags)] # Create tags if not existing and apply them for tag in self.tags: if not tag in tags: params = {'name': tag} self.maas_client.post('/tags/', 'new', **params) nodes = self.tags[tag].split(',') for node in nodes: params = {'add': system_ids[node]} self.maas_client.post('/tags/%s/' % tag, 'update_nodes', **params)
class MaasTagging(object): def __init__(self): """ Main execution path """ self.conn = None self.read_settings() self.run() def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) def run(self): """ Make calls to Maas to create tags and assign nodes to them """ self._connect() # Get system ids of nodes nodes = self.maas_client.get('/nodes/', 'list').read() system_ids = {} for node in json.loads(nodes): system_ids[node['hostname'].split('.')[0]] = node['system_id'] # Get existing tags tags = self.maas_client.get('/tags/', 'list').read() tags = [tag['name'] for tag in json.loads(tags)] # Create tags if not existing and apply them for tag in self.tags: if not tag in tags: params = {'name': tag} self.maas_client.post('/tags/', 'new', **params) nodes = self.tags[tag].split(',') for node in nodes: params = {'add': system_ids[node]} self.maas_client.post('/tags/%s/' % tag, 'update_nodes', **params)
class FabricMAAS(object): CORD_TEST_HOST = '172.17.0.1' head_node = os.getenv('HEAD_NODE', CORD_TEST_HOST) maas_url = 'http://{}/MAAS/api/1.0/'.format(head_node) def __init__(self, api_key=None, url=maas_url): if api_key == None: self.api_key = self.get_api_key() else: self.api_key = api_key self.auth = MAASOAuth(*self.api_key.split(':')) self.url = url self.client = MAASClient(self.auth, MAASDispatcher(), self.url) @classmethod def get_api_key(cls): api_key = os.getenv('MAAS_API_KEY', None) if api_key: return api_key cmd = ['maas-region-admin', 'apikey', '--username=cord'] try: p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except: return 'UNKNOWN' out, err = p.communicate() if err: raise Exception('Cannot get api key for MAAS') return out.strip() def get_node_list(self): nodes = self.client.get(u'nodes/', 'list').read() node_list = json.loads(nodes) hosts = [self.head_node] + map(lambda n: n['hostname'], node_list) return hosts
class MaasInventory(object): def __init__(self): """ Main execution path """ self.conn = None self.inventory = dict() # A list of groups and the hosts in that group self.cache = dict() # Details about hosts in the inventory # Read settings and parse CLI arguments self.read_settings() self.parse_cli_args() # Cache if self.args.refresh_cache: self.update_cache() elif not self.is_cache_valid(): self.update_cache() else: self.load_inventory_from_cache() self.load_cache_from_cache() data_to_print = "" # Data to print if self.args.host: data_to_print = self.get_host_info() elif self.args.list: # Display list of instances for inventory data_to_print = self.json_format_dict(self.inventory, True) else: # default action with no options data_to_print = self.json_format_dict(self.inventory, True) print data_to_print def _connect(self): if not self.conn: auth = MAASOAuth(*self.maas_api_key.split(':')) dispatcher = MAASDispatcher() self.maas_client = MAASClient(auth, dispatcher, self.maas_url) self.conn = True def is_cache_valid(self): """ Determines if the cache files have expired, or if it is still valid """ if os.path.isfile(self.cache_path_cache): mod_time = os.path.getmtime(self.cache_path_cache) current_time = time() if (mod_time + self.cache_max_age) > current_time: if os.path.isfile(self.cache_path_inventory): return True return False def read_settings(self): """ Reads the settings from the maansible.ini file """ config = ConfigParser.SafeConfigParser() config.read(os.path.dirname(os.path.realpath(__file__)) + '/maansible.ini') self.maas_url = config.get('maas', 'url') self.maas_api_key = config.get('maas', 'api_key') self.tags = dict(config.items('tags')) family = dict(config.items('family')) groups = [] for parent in family: group = { 'name': parent, 'children': family[parent].split(','), 'vars': dict(config.items(parent+':vars')) } groups.append(group) self.ansible_groups = groups host_vars = dict(config.items('host:vars')) host_vars['devices'] = host_vars['devices'].split(',') host_vars['raw_journal_devices'] = host_vars['raw_journal_devices'].split(',') self.ansible_host_vars = host_vars # Cache related cache_path = config.get('maas', 'cache_path') self.cache_path_cache = cache_path + "/ansible-maas.cache" self.cache_path_inventory = cache_path + "/ansible-maas.index" self.cache_max_age = config.getint('maas', 'cache_max_age') def parse_cli_args(self): """ Command line argument processing """ parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Maas') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to Maas (default: False - use cache files)') self.args = parser.parse_args() def update_cache(self): """ Make calls to Maas and save the output in a cache """ self._connect() self.groups = dict() self.hosts = dict() hostvars = dict() for tag in self.tags: nodes = self.maas_client.get('/tags/' + tag, 'nodes').read() nodes = json.loads(nodes) hosts = [] for node in nodes: hostname = node['hostname'] ip_address = node['ip_addresses'][0] if not hostname in hostvars: hostvars[hostname] = dict() hostvars[hostname].update(self.ansible_host_vars) hostvars[hostname]['ansible_ssh_host'] = ip_address hosts.append(hostname) self.inventory[tag] = {'hosts': hosts} self.inventory['_meta'] = {'hostvars': hostvars} for group in self.ansible_groups: self.inventory[group['name']] = { 'children': group['children'], 'vars': group['vars'] } self.write_to_cache(self.cache, self.cache_path_cache) self.write_to_cache(self.inventory, self.cache_path_inventory) def get_host_info(self): """ Get variables about a specific host """ if not self.cache or len(self.cache) == 0: # Need to load index from cache self.load_cache_from_cache() if not self.args.host in self.cache: # try updating the cache self.update_cache() if not self.args.host in self.cache: # host might not exist anymore return self.json_format_dict({}, True) return self.json_format_dict(self.cache[self.args.host], True) def push(self, my_dict, key, element): """ Pushed an element onto an array that may not have been defined in the dict """ if key in my_dict: my_dict[key].append(element) else: my_dict[key] = [element] def load_inventory_from_cache(self): """ Reads the index from the cache file sets self.index """ cache = open(self.cache_path_inventory, 'r') json_inventory = cache.read() self.inventory = json.loads(json_inventory) def load_cache_from_cache(self): """ Reads the cache from the cache file sets self.cache """ cache = open(self.cache_path_cache, 'r') json_cache = cache.read() self.cache = json.loads(json_cache) def write_to_cache(self, data, filename): """ Writes data in JSON format to a file """ json_data = self.json_format_dict(data, True) cache = open(filename, 'w') cache.write(json_data) cache.close() def to_safe(self, word): """ Converts 'bad' characters in a string to underscores so they can be used as Ansible groups """ return re.sub("[^A-Za-z0-9\-]", "_", word) def json_format_dict(self, data, pretty=False): """ Converts a dict to a JSON object and dumps it as a formatted string """ if pretty: return json.dumps(data, sort_keys=True, indent=2) else: return json.dumps(data)