From 543130927ba40a174e8674cca66ae442d5056d76 Mon Sep 17 00:00:00 2001 From: Jonas Bjurel Date: Sat, 3 Oct 2015 17:34:24 +0200 Subject: Moving tag arno.2015.2.0 from genesis to fuel/stable/arno Change-Id: I01b5f9f9125756d80d7ca666bb6d994f2b13d2a0 Signed-off-by: Jonas Bjurel --- opensteak/tools/opensteak/__init__.py | 18 ++ opensteak/tools/opensteak/argparser.py | 46 +++++ opensteak/tools/opensteak/conf.py | 72 ++++++++ opensteak/tools/opensteak/foreman.py | 60 +++++++ .../tools/opensteak/foreman_objects/__init__.py | 18 ++ opensteak/tools/opensteak/foreman_objects/api.py | 197 +++++++++++++++++++++ .../opensteak/foreman_objects/architectures.py | 49 +++++ .../opensteak/foreman_objects/compute_resources.py | 62 +++++++ .../tools/opensteak/foreman_objects/domains.py | 44 +++++ .../tools/opensteak/foreman_objects/freeip.py | 79 +++++++++ .../tools/opensteak/foreman_objects/hostgroups.py | 103 +++++++++++ opensteak/tools/opensteak/foreman_objects/hosts.py | 142 +++++++++++++++ opensteak/tools/opensteak/foreman_objects/item.py | 135 ++++++++++++++ .../tools/opensteak/foreman_objects/itemHost.py | 141 +++++++++++++++ .../opensteak/foreman_objects/itemHostsGroup.py | 50 ++++++ .../foreman_objects/itemOverrideValues.py | 61 +++++++ .../foreman_objects/itemSmartClassParameter.py | 62 +++++++ .../tools/opensteak/foreman_objects/objects.py | 136 ++++++++++++++ .../opensteak/foreman_objects/operatingsystems.py | 66 +++++++ .../opensteak/foreman_objects/puppetClasses.py | 46 +++++ .../opensteak/foreman_objects/smart_proxies.py | 36 ++++ .../tools/opensteak/foreman_objects/subnets.py | 67 +++++++ opensteak/tools/opensteak/printer.py | 141 +++++++++++++++ opensteak/tools/opensteak/templateparser.py | 34 ++++ opensteak/tools/opensteak/virsh.py | 174 ++++++++++++++++++ 25 files changed, 2039 insertions(+) create mode 100644 opensteak/tools/opensteak/__init__.py create mode 100644 opensteak/tools/opensteak/argparser.py create mode 100644 opensteak/tools/opensteak/conf.py create mode 100644 opensteak/tools/opensteak/foreman.py create mode 100644 opensteak/tools/opensteak/foreman_objects/__init__.py create mode 100644 opensteak/tools/opensteak/foreman_objects/api.py create mode 100644 opensteak/tools/opensteak/foreman_objects/architectures.py create mode 100644 opensteak/tools/opensteak/foreman_objects/compute_resources.py create mode 100644 opensteak/tools/opensteak/foreman_objects/domains.py create mode 100644 opensteak/tools/opensteak/foreman_objects/freeip.py create mode 100644 opensteak/tools/opensteak/foreman_objects/hostgroups.py create mode 100644 opensteak/tools/opensteak/foreman_objects/hosts.py create mode 100644 opensteak/tools/opensteak/foreman_objects/item.py create mode 100644 opensteak/tools/opensteak/foreman_objects/itemHost.py create mode 100644 opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py create mode 100644 opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py create mode 100644 opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py create mode 100644 opensteak/tools/opensteak/foreman_objects/objects.py create mode 100644 opensteak/tools/opensteak/foreman_objects/operatingsystems.py create mode 100644 opensteak/tools/opensteak/foreman_objects/puppetClasses.py create mode 100644 opensteak/tools/opensteak/foreman_objects/smart_proxies.py create mode 100644 opensteak/tools/opensteak/foreman_objects/subnets.py create mode 100644 opensteak/tools/opensteak/printer.py create mode 100644 opensteak/tools/opensteak/templateparser.py create mode 100644 opensteak/tools/opensteak/virsh.py (limited to 'opensteak/tools/opensteak') diff --git a/opensteak/tools/opensteak/__init__.py b/opensteak/tools/opensteak/__init__.py new file mode 100644 index 000000000..01f9c9a12 --- /dev/null +++ b/opensteak/tools/opensteak/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +# This directory is a Python package. diff --git a/opensteak/tools/opensteak/argparser.py b/opensteak/tools/opensteak/argparser.py new file mode 100644 index 000000000..de980b6b6 --- /dev/null +++ b/opensteak/tools/opensteak/argparser.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin +# @author: Pawel Chomicki + +""" +Parse arguments from CLI +""" + +import argparse + +class OpenSteakArgParser: + + def __init__(self): + """ + Parse the command line + """ + self.parser = argparse.ArgumentParser(description='This script will create config files for a VM in current folder.', usage='%(prog)s [options] name') + self.parser.add_argument('name', help='Set the name of the machine') + self.parser.add_argument('-i', '--ip', help='Set the ip address of the machine. (Default is 192.168.42.42)', default='192.168.42.42') + self.parser.add_argument('-n', '--netmask', help='Set the netmask in short format. (Default is 24)', default='24') + self.parser.add_argument('-g', '--gateway', help='Set the gateway to ping internet. (Default is 192.168.42.1)', default='192.168.42.1') + self.parser.add_argument('-p', '--password', help='Set the ssh password. Login is ubuntu. (Default password is moutarde)', default='moutarde') + self.parser.add_argument('-u', '--cpu', help='Set number of CPU for the VM. (Default is 2)', default='2') + self.parser.add_argument('-r', '--ram', help='Set quantity of RAM for the VM in kB. (Default is 2097152)', default='2097152') + self.parser.add_argument('-o', '--iso', help='Use this iso file. (Default is trusty-server-cloudimg-amd64-disk1.img)', default='trusty-server-cloudimg-amd64-disk1.img') + self.parser.add_argument('-d', '--disksize', help='Create a disk with that size. (Default is 5G)', default='5G') + self.parser.add_argument('-f', '--force', help='Force creation without asking questions. This is dangerous as it will delete old VM with same name.', default=False, action='store_true') + + def parse(self): + return self.parser.parse_args() + diff --git a/opensteak/tools/opensteak/conf.py b/opensteak/tools/opensteak/conf.py new file mode 100644 index 000000000..65eaf433b --- /dev/null +++ b/opensteak/tools/opensteak/conf.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from yaml import load, dump +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper + + +class OpenSteakConfig: + """OpenSteak config class + Use this object as a dict + """ + + def __init__(self, + config_file="/usr/local/opensteak/infra/config/common.yaml", + autosave=False): + """ Function __init__ + Load saved opensteak config. + + @param PARAM: DESCRIPTION + @return RETURN: DESCRIPTION + @param config_file: the yaml config file to read. + default is '/usr/local/opensteak/infra/config/common.yaml' + @param autosave: save automaticly the config at destroy + default is False + """ + self.config_file = config_file + self.autosave = autosave + with open(self.config_file, 'r') as stream: + self._data = load(stream, Loader=Loader) + + def __getitem__(self, index): + """Get an item of the configuration""" + return self._data[index] + + def __setitem__(self, index, value): + """Set an item of the configuration""" + self._data[index] = value + + def list(self): + """Set an item of the configuration""" + return self._data.keys() + + def dump(self): + """Dump the configuration""" + return dump(self._data, Dumper=Dumper) + + def save(self): + """Save the configuration to the file""" + with open(self.config_file, 'w') as f: + f.write(dump(self._data, Dumper=Dumper)) + + def __del__(self): + if self.autosave: + self.save() diff --git a/opensteak/tools/opensteak/foreman.py b/opensteak/tools/opensteak/foreman.py new file mode 100644 index 000000000..b7cbf42de --- /dev/null +++ b/opensteak/tools/opensteak/foreman.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.api import Api +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.domains import Domains +from opensteak.foreman_objects.smart_proxies import SmartProxies +from opensteak.foreman_objects.operatingsystems import OperatingSystems +from opensteak.foreman_objects.hostgroups import HostGroups +from opensteak.foreman_objects.hosts import Hosts +from opensteak.foreman_objects.architectures import Architectures +from opensteak.foreman_objects.subnets import Subnets +from opensteak.foreman_objects.puppetClasses import PuppetClasses +from opensteak.foreman_objects.compute_resources import ComputeResources + + +class OpenSteakForeman: + """ + HostGroup class + """ + def __init__(self, password, login='admin', ip='127.0.0.1'): + """ Function __init__ + Init the API with the connection params + @param password: authentication password + @param password: authentication login - default is admin + @param ip: api ip - default is localhost + @return RETURN: self + """ + self.api = Api(login=login, password=password, ip=ip, + printErrors=False) + self.domains = Domains(self.api) + self.smartProxies = SmartProxies(self.api) + self.puppetClasses = PuppetClasses(self.api) + self.operatingSystems = OperatingSystems(self.api) + self.architectures = Architectures(self.api) + self.subnets = Subnets(self.api) + self.hostgroups = HostGroups(self.api) + self.hosts = Hosts(self.api) + self.computeResources = ComputeResources(self.api) + self.environments = ForemanObjects(self.api, + 'environments', + 'environment') + self.smartClassParameters = ForemanObjects(self.api, + 'smart_class_parameters', + 'smart_class_parameter') diff --git a/opensteak/tools/opensteak/foreman_objects/__init__.py b/opensteak/tools/opensteak/foreman_objects/__init__.py new file mode 100644 index 000000000..01f9c9a12 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +# This directory is a Python package. diff --git a/opensteak/tools/opensteak/foreman_objects/api.py b/opensteak/tools/opensteak/foreman_objects/api.py new file mode 100644 index 000000000..dc9973484 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/api.py @@ -0,0 +1,197 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +import json +import requests +from requests_futures.sessions import FuturesSession +from pprint import pformat + + +class Api: + """ + Api class + Class to deal with the foreman API v2 + """ + def __init__(self, password, login='admin', ip='127.0.0.1', printErrors=False): + """ Function __init__ + Init the API with the connection params + + @param password: authentication password + @param password: authentication login - default is admin + @param ip: api ip - default is localhost + @return RETURN: self + """ + self.base_url = 'http://{}/api/v2/'.format(ip) + self.headers = {'Accept': 'version=2', + 'Content-Type': 'application/json; charset=UTF-8'} + self.auth = (login, password) + self.errorMsg = '' + self.printErrors = printErrors + + def list(self, obj, filter=False, only_id=False, limit=20): + """ Function list + Get the list of an object + + @param obj: object name ('hosts', 'puppetclasses'...) + @param filter: filter for objects + @param only_id: boolean to only return dict with name/id + @return RETURN: the list of the object + """ + self.url = '{}{}/?per_page={}'.format(self.base_url, obj, limit) + if filter: + self.url += '&search={}'.format(filter) + self.resp = requests.get(url=self.url, auth=self.auth, + headers=self.headers) + if only_id: + if self.__process_resp__(obj) is False: + return False + if type(self.res['results']) is list: + return dict((x['name'], x['id']) for x in self.res['results']) + elif type(self.res['results']) is dict: + r = {} + for v in self.res['results'].values(): + for vv in v: + r[vv['name']] = vv['id'] + return r + else: + return False + else: + return self.__process_resp__(obj) + + def get(self, obj, id, sub_object=None): + """ Function get + Get an object by id + + @param obj: object name ('hosts', 'puppetclasses'...) + @param id: the id of the object (name or id) + @return RETURN: the targeted object + """ + self.url = '{}{}/{}'.format(self.base_url, obj, id) + if sub_object: + self.url += '/' + sub_object + self.resp = requests.get(url=self.url, auth=self.auth, + headers=self.headers) + if self.__process_resp__(obj): + return self.res + return False + + def get_id_by_name(self, obj, name): + """ Function get_id_by_name + Get the id of an object + + @param obj: object name ('hosts', 'puppetclasses'...) + @param id: the id of the object (name or id) + @return RETURN: the targeted object + """ + list = self.list(obj, filter='name = "{}"'.format(name), + only_id=True, limit=1) + return list[name] if name in list.keys() else False + + def set(self, obj, id, payload, action='', async=False): + """ Function set + Set an object by id + + @param obj: object name ('hosts', 'puppetclasses'...) + @param id: the id of the object (name or id) + @param action: specific action of an object ('power'...) + @param payload: the dict of the payload + @param async: should this request be async, if true use + return.result() to get the response + @return RETURN: the server response + """ + self.url = '{}{}/{}'.format(self.base_url, obj, id) + if action: + self.url += '/{}'.format(action) + self.payload = json.dumps(payload) + if async: + session = FuturesSession() + return session.put(url=self.url, auth=self.auth, + headers=self.headers, data=self.payload) + else: + self.resp = requests.put(url=self.url, auth=self.auth, + headers=self.headers, data=self.payload) + if self.__process_resp__(obj): + return self.res + return False + + def create(self, obj, payload, async=False): + """ Function create + Create an new object + + @param obj: object name ('hosts', 'puppetclasses'...) + @param payload: the dict of the payload + @param async: should this request be async, if true use + return.result() to get the response + @return RETURN: the server response + """ + self.url = self.base_url + obj + self.payload = json.dumps(payload) + if async: + session = FuturesSession() + return session.post(url=self.url, auth=self.auth, + headers=self.headers, data=self.payload) + else: + self.resp = requests.post(url=self.url, auth=self.auth, + headers=self.headers, + data=self.payload) + return self.__process_resp__(obj) + + def delete(self, obj, id): + """ Function delete + Delete an object by id + + @param obj: object name ('hosts', 'puppetclasses'...) + @param id: the id of the object (name or id) + @return RETURN: the server response + """ + self.url = '{}{}/{}'.format(self.base_url, obj, id) + self.resp = requests.delete(url=self.url, + auth=self.auth, + headers=self.headers, ) + return self.__process_resp__(obj) + + def __process_resp__(self, obj): + """ Function __process_resp__ + Process the response sent by the server and store the result + + @param obj: object name ('hosts', 'puppetclasses'...) + @return RETURN: the server response + """ + self.last_obj = obj + if self.resp.status_code > 299: + self.errorMsg = ">> Error {} for object '{}'".format(self.resp.status_code, + self.last_obj) + try: + self.ret = json.loads(self.resp.text) + self.errorMsg += pformat(self.ret[list(self.ret.keys())[0]]) + except: + self.ret = self.resp.text + self.errorMsg += self.ret + if self.printErrors: + print(self.errorMsg) + return False + self.res = json.loads(self.resp.text) + if 'results' in self.res.keys(): + return self.res['results'] + return self.res + + def __str__(self): + ret = pformat(self.base_url) + "\n" + ret += pformat(self.headers) + "\n" + ret += pformat(self.auth) + "\n" + return ret diff --git a/opensteak/tools/opensteak/foreman_objects/architectures.py b/opensteak/tools/opensteak/foreman_objects/architectures.py new file mode 100644 index 000000000..5e4303e17 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/architectures.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects + + +class Architectures(ForemanObjects): + """ + Architectures class + """ + objName = 'architectures' + payloadObj = 'architecture' + + def checkAndCreate(self, key, payload, osIds): + """ Function checkAndCreate + Check if an architectures exists and create it if not + + @param key: The targeted architectures + @param payload: The targeted architectures description + @param osIds: The list of os ids liked with this architecture + @return RETURN: The id of the object + """ + if key not in self: + self[key] = payload + oid = self[key]['id'] + if not oid: + return False + #~ To be sure the OS list is good, we ensure our os are in the list + for os in self[key]['operatingsystems']: + osIds.add(os['id']) + self[key]["operatingsystem_ids"] = list(osIds) + if (len(self[key]['operatingsystems']) is not len(osIds)): + return False + return oid diff --git a/opensteak/tools/opensteak/foreman_objects/compute_resources.py b/opensteak/tools/opensteak/foreman_objects/compute_resources.py new file mode 100644 index 000000000..9ada9c481 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/compute_resources.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.item import ForemanItem + + +class ComputeResources(ForemanObjects): + """ + HostGroups class + """ + objName = 'compute_resources' + payloadObj = 'compute_resource' + + def list(self): + """ Function list + list the hostgroups + + @return RETURN: List of ForemanItemHostsGroup objects + """ + return list(map(lambda x: ForemanItem(self.api, x['id'], x), + self.api.list(self.objName))) + + def __getitem__(self, key): + """ Function __getitem__ + Get an hostgroup + + @param key: The hostgroup name or ID + @return RETURN: The ForemanItemHostsGroup object of an host + """ + # Because Hostgroup did not support get by name we need to do it by id + if type(key) is not int: + key = self.getId(key) + ret = self.api.get(self.objName, key) + return ForemanItem(self.api, key, ret) + + def __delitem__(self, key): + """ Function __delitem__ + Delete an hostgroup + + @param key: The hostgroup name or ID + @return RETURN: The API result + """ + # Because Hostgroup did not support get by name we need to do it by id + if type(key) is not int: + key = self.getId(key) + return self.api.delete(self.objName, key) diff --git a/opensteak/tools/opensteak/foreman_objects/domains.py b/opensteak/tools/opensteak/foreman_objects/domains.py new file mode 100644 index 000000000..753833fc5 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/domains.py @@ -0,0 +1,44 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects + + +class Domains(ForemanObjects): + """ + Domain class + """ + objName = 'domains' + payloadObj = 'domain' + + def load(self, id='0', name=''): + """ Function load + To be rewriten + + @param id: The Domain ID + @return RETURN: DESCRIPTION + """ + + if name: + id = self.__getIdByName__(name) + self.data = self.foreman.get('domains', id) + if 'parameters' in self.data: + self.params = self.data['parameters'] + else: + self.params = [] + self.name = self.data['name'] diff --git a/opensteak/tools/opensteak/foreman_objects/freeip.py b/opensteak/tools/opensteak/foreman_objects/freeip.py new file mode 100644 index 000000000..86c003fec --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/freeip.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +#~ from foreman.api import Api +import requests +from bs4 import BeautifulSoup +import sys +import json + +class FreeIP: + """ FreeIP return an available IP in the targeted network """ + + def __init__ (self, login, password): + """ Init: get authenticity token """ + with requests.session() as self.session: + try: + #~ 1/ Get login token and authentify + payload = {} + log_soup = BeautifulSoup(self.session.get('https://127.0.0.1/users/login', verify=False).text) + payload['utf8'] = log_soup.findAll('input',attrs={'name':'utf8'})[0].get('value') + payload['authenticity_token'] = log_soup.findAll('input',attrs={'name':'authenticity_token'})[0].get('value') + if payload['authenticity_token'] == None: + raise requests.exceptions.RequestException("Bad catch of authenticity_token") + payload['commit']='Login' + payload['login[login]'] = login + payload['login[password]'] = password + #~ 2/ Log in + r = self.session.post('https://127.0.0.1/users/login', verify=False, data=payload) + if r.status_code != 200: + raise requests.exceptions.RequestException("Bad login or password") + #~ Get token for host creation + log_soup = BeautifulSoup(self.session.get('https://127.0.0.1/hosts/new', verify=False).text) + self.authenticity_token = log_soup.findAll('input',attrs={'name':'authenticity_token'})[0].get('value') + if payload['authenticity_token'] == None: + raise requests.exceptions.RequestException("Bad catch of authenticity_token") + except requests.exceptions.RequestException as e: + print("Error connection Foreman to get a free ip") + print(e) + sys.exit(1) + pass + + def get(self, subnet, mac = ""): + payload = {"host_mac": mac, "subnet_id": subnet} + payload['authenticity_token'] = self.authenticity_token + try: + self.last_ip = json.loads(self.session.post('https://127.0.0.1/subnets/freeip', verify=False, data=payload).text)['ip'] + if payload['authenticity_token'] == None: + raise requests.exceptions.RequestException("Error getting free IP") + except requests.exceptions.RequestException as e: + print("Error connection Foreman to get a free ip") + print(e) + sys.exit(1) + return self.last_ip + + + +if __name__ == "__main__": + import pprint + import sys + if len(sys.argv) == 4: + f = FreeIP(sys.argv[1], sys.argv[2]) + print(f.get(sys.argv[3])) + else: + print('Error: Usage\npython {} foreman_user foreman_password subnet'.format(sys.argv[0])) diff --git a/opensteak/tools/opensteak/foreman_objects/hostgroups.py b/opensteak/tools/opensteak/foreman_objects/hostgroups.py new file mode 100644 index 000000000..55b8ba6b3 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/hostgroups.py @@ -0,0 +1,103 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.itemHostsGroup import ItemHostsGroup +from pprint import pprint as pp + + +class HostGroups(ForemanObjects): + """ + HostGroups class + """ + objName = 'hostgroups' + payloadObj = 'hostgroup' + + def list(self): + """ Function list + list the hostgroups + + @return RETURN: List of ItemHostsGroup objects + """ + return list(map(lambda x: ItemHostsGroup(self.api, x['id'], x), + self.api.list(self.objName))) + + def __getitem__(self, key): + """ Function __getitem__ + Get an hostgroup + + @param key: The hostgroup name or ID + @return RETURN: The ItemHostsGroup object of an host + """ + # Because Hostgroup did not support get by name we need to do it by id + if type(key) is not int: + key = self.getId(key) + ret = self.api.get(self.objName, key) + return ItemHostsGroup(self.api, key, ret) + + def __delitem__(self, key): + """ Function __delitem__ + Delete an hostgroup + + @param key: The hostgroup name or ID + @return RETURN: The API result + """ + # Because Hostgroup did not support get by name we need to do it by id + if type(key) is not int: + key = self.getId(key) + return self.api.delete(self.objName, key) + + def checkAndCreate(self, key, payload, + hostgroupConf, + hostgroupParent, + puppetClassesId): + """ Function checkAndCreate + check And Create procedure for an hostgroup + - check the hostgroup is not existing + - create the hostgroup + - Add puppet classes from puppetClassesId + - Add params from hostgroupConf + + @param key: The hostgroup name or ID + @param payload: The description of the hostgroup + @param hostgroupConf: The configuration of the host group from the + foreman.conf + @param hostgroupParent: The id of the parent hostgroup + @param puppetClassesId: The dict of puppet classes ids in foreman + @return RETURN: The ItemHostsGroup object of an host + """ + if key not in self: + self[key] = payload + oid = self[key]['id'] + if not oid: + return False + + # Create Hostgroup classes + hostgroupClassIds = self[key]['puppetclass_ids'] + if 'classes' in hostgroupConf.keys(): + if not self[key].checkAndCreateClasses(puppetClassesId.values()): + print("Failed in classes") + return False + + # Set params + if 'params' in hostgroupConf.keys(): + if not self[key].checkAndCreateParams(hostgroupConf['params']): + print("Failed in params") + return False + + return oid diff --git a/opensteak/tools/opensteak/foreman_objects/hosts.py b/opensteak/tools/opensteak/foreman_objects/hosts.py new file mode 100644 index 000000000..95d47af9d --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/hosts.py @@ -0,0 +1,142 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.itemHost import ItemHost +import time + + +class Hosts(ForemanObjects): + """ + Host sclass + """ + objName = 'hosts' + payloadObj = 'host' + + def list(self): + """ Function list + list the hosts + + @return RETURN: List of ItemHost objects + """ + return list(map(lambda x: ItemHost(self.api, x['id'], x), + self.api.list(self.objName))) + + def __getitem__(self, key): + """ Function __getitem__ + Get an host + + @param key: The host name or ID + @return RETURN: The ItemHost object of an host + """ + return ItemHost(self.api, key, self.api.get(self.objName, key)) + + def __printProgression__(self, status, msg, eol): + """ Function __printProgression__ + Print the creation progression or not + It uses the foreman.printer lib + + @param status: Status of the message + @param msg: Message + @param eol: End Of Line (to get a new line or not) + @return RETURN: None + """ + if self.printHostProgress: + self.__printProgression__(status, msg, eol=eol) + + def createVM(self, key, attributes, printHostProgress=False): + """ Function createVM + Create a Virtual Machine + + The creation of a VM with libVirt is a bit complexe. + We first create the element in foreman, the ask to start before + the result of the creation. + To do so, we make async calls to the API and check the results + + @param key: The host name or ID + @param attributes:The payload of the host creation + @param printHostProgress: The link to opensteak.printerlib + to print or not the + progression of the host creation + @return RETURN: The API result + """ + + self.printHostProgress = printHostProgress + self.async = True + # Create the VM in foreman + self.__printProgression__('In progress', + key + ' creation: push in Foreman', eol='\r') + future1 = self.api.create('hosts', attributes, async=True) + + # Wait before asking to power on the VM + sleep = 5 + for i in range(0, sleep): + time.sleep(1) + self.__printProgression__('In progress', + key + ' creation: start in {0}s' + .format(sleep - i), + eol='\r') + + # Power on the VM + self.__printProgression__('In progress', + key + ' creation: starting', eol='\r') + future2 = self[key].powerOn() + + # Show Power on result + if future2.result().status_code is 200: + self.__printProgression__('In progress', + key + ' creation: wait for end of boot', + eol='\r') + else: + self.__printProgression__(False, + key + ' creation: Error', + failed=str(future2.result().status_code)) + return False + # Show creation result + if future1.result().status_code is 200: + self.__printProgression__('In progress', + key + ' creation: created', + eol='\r') + else: + self.__printProgression__(False, + key + ' creation: Error', + failed=str(future1.result().status_code)) + return False + + # Wait for puppet catalog to be applied + loop_stop = False + while not loop_stop: + status = self[key].getStatus() + if status == 'No Changes' or status == 'Active': + self.__printProgression__(True, + key + ' creation: provisioning OK') + loop_stop = True + elif status == 'Error': + self.__printProgression__(False, + key + ' creation: Error', + failed="Error during provisioning") + loop_stop = True + return False + else: + self.__printProgression__('In progress', + key + ' creation: provisioning ({})' + .format(status), + eol='\r') + time.sleep(5) + + return True diff --git a/opensteak/tools/opensteak/foreman_objects/item.py b/opensteak/tools/opensteak/foreman_objects/item.py new file mode 100644 index 000000000..f418f8c11 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/item.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# David Blaisonneau +# Arnaud Morin + +from pprint import pprint as pp + + +class ForemanItem(dict): + """ + Item class + Represent the content of a foreman object as a dict + """ + + def __init__(self, api, key, + objName, payloadObj, + *args, **kwargs): + """ Function __init__ + Represent the content of a foreman object as a dict + + @param api: The foreman api + @param key: The object Key + @param *args, **kwargs: the dict representation + @return RETURN: Itself + """ + self.api = api + self.key = key + if objName: + self.objName = objName + if payloadObj: + self.payloadObj = payloadObj + self.store = dict() + if args[0]: + self.update(dict(*args, **kwargs)) + # We get the smart class parameters for the good items + if objName in ['hosts', 'hostgroups', + 'puppet_classes', 'environments']: + from opensteak.foreman_objects.itemSmartClassParameter\ + import ItemSmartClassParameter + scp_ids = map(lambda x: x['id'], + self.api.list('{}/{}/smart_class_parameters' + .format(self.objName, key))) + scp_items = list(map(lambda x: ItemSmartClassParameter(self.api, x, + self.api.get('smart_class_parameters', x)), + scp_ids)) + scp = {'{}::{}'.format(x['puppetclass']['name'], + x['parameter']): x + for x in scp_items} + self.update({'smart_class_parameters_dict': scp}) + + def __setitem__(self, key, attributes): + """ Function __setitem__ + Set a parameter of a foreman object as a dict + + @param key: The key to modify + @param attribute: The data + @return RETURN: The API result + """ + if key is 'puppetclass_ids': + payload = {"puppetclass_id": attributes, + self.payloadObj + "_class": + {"puppetclass_id": attributes}} + return self.api.create("{}/{}/{}" + .format(self.objName, + self.key, + "puppetclass_ids"), + payload) + elif key is 'parameters': + payload = {"parameter": attributes} + return self.api.create("{}/{}/{}" + .format(self.objName, + self.key, + "parameters"), + payload) + else: + payload = {self.payloadObj: {key: attributes}} + return self.api.set(self.objName, self.key, payload) + + def getParam(self, name=None): + """ Function getParam + Return a dict of parameters or a parameter value + + @param key: The parameter name + @return RETURN: dict of parameters or a parameter value + """ + if 'parameters' in self.keys(): + l = {x['name']: x['value'] for x in self['parameters']} + if name: + if name in l.keys(): + return l[name] + else: + return False + else: + return l + + def checkAndCreateClasses(self, classes): + """ Function checkAndCreateClasses + Check and add puppet classe + + @param key: The parameter name + @param classes: The classes ids list + @return RETURN: boolean + """ + actual_classes = self['puppetclass_ids'] + for v in classes: + if v not in actual_classes: + self['puppetclass_ids'] = v + return list(classes).sort() is list(self['puppetclass_ids']).sort() + + def checkAndCreateParams(self, params): + """ Function checkAndCreateParams + Check and add global parameters + + @param key: The parameter name + @param params: The params dict + @return RETURN: boolean + """ + actual_params = self['param_ids'] + for k, v in params.items(): + if k not in actual_params: + self['parameters'] = {"name": k, "value": v} + return self['param_ids'].sort() == list(params.values()).sort() diff --git a/opensteak/tools/opensteak/foreman_objects/itemHost.py b/opensteak/tools/opensteak/foreman_objects/itemHost.py new file mode 100644 index 000000000..c531e5cf4 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/itemHost.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +import base64 +from string import Template +from opensteak.foreman_objects.item import ForemanItem + + +class ItemHost(ForemanItem): + """ + ItemHostsGroup class + Represent the content of a foreman hostgroup as a dict + """ + + objName = 'hosts' + payloadObj = 'host' + + def __init__(self, api, key, *args, **kwargs): + """ Function __init__ + Represent the content of a foreman object as a dict + + @param api: The foreman api + @param key: The object Key + @param *args, **kwargs: the dict representation + @return RETURN: Itself + """ + ForemanItem.__init__(self, api, key, + self.objName, self.payloadObj, + *args, **kwargs) + self.update({'puppetclass_ids': + self.api.list('{}/{}/puppetclass_ids' + .format(self.objName, key))}) + self.update({'param_ids': + list(self.api.list('{}/{}/parameters' + .format(self.objName, key), + only_id=True) + .keys())}) + + + def getStatus(self): + """ Function getStatus + Get the status of an host + + @return RETURN: The host status + """ + return self.api.get('hosts', self.key, 'status')['status'] + + def powerOn(self): + """ Function powerOn + Power on a host + + @return RETURN: The API result + """ + return self.api.set('hosts', self.key, + {"power_action": "start"}, + 'power', async=self.async) + + def getParamFromEnv(self, var, default=''): + """ Function getParamFromEnv + Search a parameter in the host environment + + @param var: the var name + @param hostgroup: the hostgroup item linked to this host + @param default: default value + @return RETURN: the value + """ + if self.getParam(var): + return self.getParam(var) + if self.hostgroup: + if self.hostgroup.getParam(var): + return self.hostgroup.getParam(var) + if self.domain.getParam('password'): + return self.domain.getParam('password') + else: + return default + + def getUserData(self, + hostgroup, + domain, + defaultPwd='', + defaultSshKey='', + proxyHostname='', + tplFolder='templates_metadata/'): + """ Function getUserData + Generate a userdata script for metadata server from Foreman API + + @param domain: the domain item linked to this host + @param hostgroup: the hostgroup item linked to this host + @param defaultPwd: the default password if no password is specified + in the host>hostgroup>domain params + @param defaultSshKey: the default ssh key if no password is specified + in the host>hostgroup>domain params + @param proxyHostname: hostname of the smartproxy + @param tplFolder: the templates folder + @return RETURN: the user data + """ + if 'user-data' in self.keys(): + return self['user-data'] + else: + self.hostgroup = hostgroup + self.domain = domain + if proxyHostname == '': + proxyHostname = 'foreman.' + domain + password = self.getParamFromEnv('password', defaultPwd) + sshauthkeys = self.getParamFromEnv('global_sshkey', defaultSshKey) + with open(tplFolder+'puppet.conf', 'rb') as puppet_file: + p = MyTemplate(puppet_file.read()) + enc_puppet_file = base64.b64encode(p.substitute( + foremanHostname=proxyHostname)) + with open(tplFolder+'cloud-init.tpl', 'r') as content_file: + s = MyTemplate(content_file.read()) + if sshauthkeys: + sshauthkeys = ' - '+sshauthkeys + self.userdata = s.substitute( + password=password, + fqdn=self['name'], + sshauthkeys=sshauthkeys, + foremanurlbuilt="http://{}/unattended/built" + .format(proxyHostname), + puppet_conf_content=enc_puppet_file.decode('utf-8')) + return self.userdata + + +class MyTemplate(Template): + delimiter = '%' + idpattern = r'[a-z][_a-z0-9]*' diff --git a/opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py b/opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py new file mode 100644 index 000000000..d6a641c03 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/itemHostsGroup.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.item import ForemanItem + + +class ItemHostsGroup(ForemanItem): + """ + ItemHostsGroup class + Represent the content of a foreman hostgroup as a dict + """ + + objName = 'hostgroups' + payloadObj = 'hostgroup' + + def __init__(self, api, key, *args, **kwargs): + """ Function __init__ + Represent the content of a foreman object as a dict + + @param api: The foreman api + @param key: The object Key + @param *args, **kwargs: the dict representation + @return RETURN: Itself + """ + ForemanItem.__init__(self, api, key, + self.objName, self.payloadObj, + *args, **kwargs) + self.update({'puppetclass_ids': + self.api.list('{}/{}/puppetclass_ids' + .format(self.objName, key))}) + self.update({'param_ids': + list(self.api.list('{}/{}/parameters' + .format(self.objName, key), + only_id=True) + .keys())}) diff --git a/opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py b/opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py new file mode 100644 index 000000000..936185e98 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/itemOverrideValues.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + + +from opensteak.foreman_objects.item import ForemanItem +from pprint import pprint as pp + +class ItemOverrideValues(ForemanItem): + """ + ItemOverrideValues class + Represent the content of a foreman smart class parameter as a dict + """ + + objName = 'override_values' + payloadObj = 'override_value' + + def __init__(self, api, key, parentName, parentKey, *args, **kwargs): + """ Function __init__ + Represent the content of a foreman object as a dict + + @param api: The foreman api + @param key: The object Key + @param parentName: The object parent name (eg: smart_class_parameter) + @param parentKey: The object parent key + @param *args, **kwargs: the dict representation + @return RETURN: Itself + """ + self.parentName = parentName + self.parentKey = parentKey + ForemanItem.__init__(self, api, key, + self.objName, self.payloadObj, + *args, **kwargs) + + def __setitem__(self, key, attributes): + """ Function __setitem__ + Set a parameter of a foreman object as a dict + + @param key: The key to modify + @param attribute: The data + @return RETURN: The API result + """ + payload = {self.payloadObj: {key: attributes}} + return self.api.set('{}/{}/{}'.format(self.parentName, + self.parentKey, + self.objName), + self.key, payload) diff --git a/opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py b/opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py new file mode 100644 index 000000000..2d7ca2ab9 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/itemSmartClassParameter.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + + +from opensteak.foreman_objects.item import ForemanItem +from opensteak.foreman_objects.itemOverrideValues import ItemOverrideValues + + +class ItemSmartClassParameter(ForemanItem): + """ + ItemSmartClassParameter class + Represent the content of a foreman smart class parameter as a dict + """ + + objName = 'smart_class_parameters' + payloadObj = 'smart_class_parameter' + + def __init__(self, api, key, *args, **kwargs): + """ Function __init__ + Represent the content of a foreman object as a dict + + @param api: The foreman api + @param key: The object Key + @param *args, **kwargs: the dict representation + @return RETURN: Itself + """ + ForemanItem.__init__(self, api, key, + self.objName, self.payloadObj, + *args, **kwargs) + self.update({'override_values': + list(map(lambda x: ItemOverrideValues(self.api, + x['id'], + self.objName, + key, + x), + self['override_values']))}) + + def __setitem__(self, key, attributes): + """ Function __setitem__ + Set a parameter of a foreman object as a dict + + @param key: The key to modify + @param attribute: The data + @return RETURN: The API result + """ + payload = {self.payloadObj: {key: attributes}} + return self.api.set(self.objName, self.key, payload) diff --git a/opensteak/tools/opensteak/foreman_objects/objects.py b/opensteak/tools/opensteak/foreman_objects/objects.py new file mode 100644 index 000000000..c20c5a138 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/objects.py @@ -0,0 +1,136 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.item import ForemanItem + + +class ForemanObjects: + """ + ForemanObjects class + Parent class for Foreman Objects + """ + + def __init__(self, api, objName=None, payloadObj=None): + """ Function __init__ + Init the foreman object + + @param api: The foreman API + @param objName: The object name (linked with the Foreman API) + @param payloadObj: The object name inside the payload (in general + the singular of objName) + @return RETURN: Itself + """ + + self.api = api + if objName: + self.objName = objName + if payloadObj: + self.payloadObj = payloadObj + # For asynchronous creations + self.async = False + + def __iter__(self): + """ Function __iter__ + + @return RETURN: The iteration of objects list + """ + return iter(self.list()) + + def __getitem__(self, key): + """ Function __getitem__ + + @param key: The targeted object + @return RETURN: A ForemanItem + """ + return ForemanItem(self.api, + key, + self.objName, + self.payloadObj, + self.api.get(self.objName, key)) + + def __setitem__(self, key, attributes): + """ Function __setitem__ + + @param key: The targeted object + @param attributes: The attributes to apply to the object + @return RETURN: API result if the object was not present, or False + """ + if key not in self: + payload = {self.payloadObj: {'name': key}} + payload[self.payloadObj].update(attributes) + return self.api.create(self.objName, payload, async=self.async) + return False + + def __delitem__(self, key): + """ Function __delitem__ + + @return RETURN: API result + """ + return self.api.delete(self.objName, key) + + def __contains__(self, key): + """ Function __contains__ + + @param key: The targeted object + @return RETURN: True if the object exists + """ + return bool(key in self.listName().keys()) + + def getId(self, key): + """ Function getId + Get the id of an object + + @param key: The targeted object + @return RETURN: The ID + """ + return self.api.get_id_by_name(self.objName, key) + + def list(self, limit=20): + """ Function list + Get the list of all objects + + @param key: The targeted object + @param limit: The limit of items to return + @return RETURN: A ForemanItem list + """ + return list(map(lambda x: + ForemanItem(self.api, x['id'], + self.objName, self.payloadObj, + x), + self.api.list(self.objName, limit=limit))) + + def listName(self): + """ Function listName + Get the list of all objects name with Ids + + @param key: The targeted object + @return RETURN: A dict of obejct name:id + """ + return self.api.list(self.objName, limit=999999, only_id=True) + + def checkAndCreate(self, key, payload): + """ Function checkAndCreate + Check if an object exists and create it if not + + @param key: The targeted object + @param payload: The targeted object description + @return RETURN: The id of the object + """ + if key not in self: + self[key] = payload + return self[key]['id'] diff --git a/opensteak/tools/opensteak/foreman_objects/operatingsystems.py b/opensteak/tools/opensteak/foreman_objects/operatingsystems.py new file mode 100644 index 000000000..8cce606e6 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/operatingsystems.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.item import ForemanItem + + +class OperatingSystems(ForemanObjects): + """ + OperatingSystems class + """ + objName = 'operatingsystems' + payloadObj = 'operatingsystem' + + def __getitem__(self, key): + """ Function __getitem__ + + @param key: The operating system id/name + @return RETURN: The item + """ + ret = self.api.list(self.objName, + filter='title = "{}"'.format(key)) + if len(ret): + return ForemanItem(self.api, key, + self.objName, self.payloadObj, + ret[0]) + else: + return None + + def __setitem__(self, key, attributes): + """ Function __getitem__ + + @param key: The operating system id/name + @param attributes: The content of the operating system to create + @return RETURN: The API result + """ + if key not in self: + payload = {self.payloadObj: {'title': key}} + payload[self.payloadObj].update(attributes) + return self.api.create(self.objName, payload) + return False + + def listName(self): + """ Function listName + Get the list of all objects name with Ids + + @param key: The targeted object + @return RETURN: A dict of obejct name:id + """ + return { x['title']: x['id'] for x in self.api.list(self.objName, + limit=999999)} diff --git a/opensteak/tools/opensteak/foreman_objects/puppetClasses.py b/opensteak/tools/opensteak/foreman_objects/puppetClasses.py new file mode 100644 index 000000000..7f397f27a --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/puppetClasses.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects +from opensteak.foreman_objects.item import ForemanItem +from pprint import pprint as pp + + +class PuppetClasses(ForemanObjects): + """ + OperatingSystems class + """ + objName = 'puppetclasses' + payloadObj = 'puppetclass' + + def list(self, limit=20): + """ Function list + Get the list of all objects + + @param key: The targeted object + @param limit: The limit of items to return + @return RETURN: A ForemanItem list + """ + puppetClassList = list() + for v in self.api.list(self.objName, limit=limit).values(): + puppetClassList.extend(v) + return list(map(lambda x: + ForemanItem(self.api, x['id'], + self.objName, self.payloadObj, + x), + puppetClassList)) diff --git a/opensteak/tools/opensteak/foreman_objects/smart_proxies.py b/opensteak/tools/opensteak/foreman_objects/smart_proxies.py new file mode 100644 index 000000000..2d6518b48 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/smart_proxies.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects + + +class SmartProxies(ForemanObjects): + """ + Domain class + """ + objName = 'smart_proxies' + payloadObj = 'smart_proxy' + + def importPuppetClasses(self, smartProxyId): + """ Function importPuppetClasses + Force the reload of puppet classes + + @param smartProxyId: smartProxy Id + @return RETURN: the API result + """ + return self.api.create('smart_proxies/{}/import_puppetclasses'.format(smartProxyId), '{}') diff --git a/opensteak/tools/opensteak/foreman_objects/subnets.py b/opensteak/tools/opensteak/foreman_objects/subnets.py new file mode 100644 index 000000000..b1cac5445 --- /dev/null +++ b/opensteak/tools/opensteak/foreman_objects/subnets.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +from opensteak.foreman_objects.objects import ForemanObjects + + +class Subnets(ForemanObjects): + """ + Subnets class + """ + objName = 'subnets' + payloadObj = 'subnet' + + def checkAndCreate(self, key, payload, domainId): + """ Function checkAndCreate + Check if a subnet exists and create it if not + + @param key: The targeted subnet + @param payload: The targeted subnet description + @param domainId: The domainId to be attached wiuth the subnet + @return RETURN: The id of the subnet + """ + if key not in self: + self[key] = payload + oid = self[key]['id'] + if not oid: + return False + #~ Ensure subnet contains the domain + subnetDomainIds = [] + for domain in self[key]['domains']: + subnetDomainIds.append(domain['id']) + if domainId not in subnetDomainIds: + subnetDomainIds.append(domainId) + self[key]["domain_ids"] = subnetDomainIds + if len(self[key]["domains"]) is not len(subnetDomainIds): + return False + return oid + + def removeDomain(self, subnetId, domainId): + """ Function removeDomain + Delete a domain from a subnet + + @param subnetId: The subnet Id + @param domainId: The domainId to be attached wiuth the subnet + @return RETURN: boolean + """ + subnetDomainIds = [] + for domain in self[subnetId]['domains']: + subnetDomainIds.append(domain['id']) + subnetDomainIds.remove(domainId) + self[subnetId]["domain_ids"] = subnetDomainIds + return len(self[subnetId]["domains"]) is len(subnetDomainIds) diff --git a/opensteak/tools/opensteak/printer.py b/opensteak/tools/opensteak/printer.py new file mode 100644 index 000000000..98c5af54e --- /dev/null +++ b/opensteak/tools/opensteak/printer.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +import sys + + +class OpenSteakPrinter: + """ Just a nice message printer """ + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + TABSIZE = 4 + + def header(self, msg): + """ Function header + Print a header for a block + + @param msg: The message to print in the header (limited to 78 chars) + @return RETURN: None + """ + print(""" +# +# {} +# +""".format(msg[0:78])) + + def config(self, msg, name, value=None, indent=0): + """ Function config + Print a line with the value of a parameter + + @param msg: The message to print in the header (limited to 78 chars) + @param name: The name of the prameter + @param value: The value of the parameter + @param indent: Tab size at the beginning of the line + @return RETURN: None + """ + ind = ' ' * indent * self.TABSIZE + if value is None: + print('{} - {} = {}'.format(ind, msg, name)) + elif value is False: + print('{} [{}KO{}] {} > {} (NOT found)'. + format(ind, self.FAIL, self.ENDC, msg, name)) + else: + print('{} [{}OK{}] {} > {} = {}'. + format(ind, self.OKGREEN, self.ENDC, msg, name, str(value))) + + def list(self, msg, indent=0): + """ Function list + Print a list item + + @param msg: The message to print in the header (limited to 78 chars) + @param indent: Tab size at the beginning of the line + @return RETURN: None + """ + print(' ' * indent * self.TABSIZE, '-', msg) + + def list_id(self, dic, indent=0): + """ Function list_id + Print a list of dict items + + @param dic: The dict to print + @param indent: Tab size at the beginning of the line + @return RETURN: None + """ + for (k, v) in dic.items(): + self.list("{}: {}".format(k, v), indent=indent) + + def status(self, res, msg, failed="", eol="\n", quit=True, indent=0): + """ Function status + Print status message + - OK/KO if the result is a boolean + - Else the result text + + @param res: The status to show + @param msg: The message to show + @param eol: End of line + @param quit: Exit the system in case of failure + @param indent: Tab size at the beginning of the line + @return RETURN: None + """ + ind = ' ' * indent * self.TABSIZE + if res is True: + msg = '{} [{}OK{}] {}'.format(ind, self.OKGREEN, self.ENDC, msg) + elif res: + msg = '{} [{}{}{}] {}'.format(ind, self.OKBLUE, res, + self.ENDC, msg) + else: + msg = '{} [{}KO{}] {}'.format(ind, self.FAIL, self.ENDC, msg) + if failed: + msg += '\n > {}'.format(failed) + msg = msg.ljust(140) + eol + sys.stdout.write(msg) + if res is False and quit is True: + sys.exit(0) + + def ask_validation(self, prompt=None, resp=False): + """ Function ask_validation + Ask a validation message + + @param prompt: The question to ask ('Continue ?') if None + @param resp: The default value (Default is False) + @return RETURN: Trie or False + """ + if prompt is None: + prompt = 'Continue ?' + if resp: + prompt += ' [{}Y{}/n]: '.format(self.BOLD, self.ENDC) + else: + prompt += ' [y/{}N{}]: '.format(self.BOLD, self.ENDC) + while True: + ans = input(prompt) + if not ans: + ans = 'y' if resp else 'n' + if ans not in ['y', 'Y', 'n', 'N']: + print('please enter y or n.') + continue + if ans == 'y' or ans == 'Y': + return True + if ans == 'n' or ans == 'N': + sys.exit(0) diff --git a/opensteak/tools/opensteak/templateparser.py b/opensteak/tools/opensteak/templateparser.py new file mode 100644 index 000000000..720f008da --- /dev/null +++ b/opensteak/tools/opensteak/templateparser.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +""" +Template parser +""" + +from string import Template + +class OpenSteakTemplateParser: + + def __init__(self, filein, fileout, dictionary): + """ + Parse the files with the dictionary + """ + fin = open(filein) + fout = open(fileout,'w') + template = Template(fin.read()) + fout.write(template.substitute(dictionary)) diff --git a/opensteak/tools/opensteak/virsh.py b/opensteak/tools/opensteak/virsh.py new file mode 100644 index 000000000..594b84299 --- /dev/null +++ b/opensteak/tools/opensteak/virsh.py @@ -0,0 +1,174 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Authors: +# @author: David Blaisonneau +# @author: Arnaud Morin + +""" +Virsh library +""" + +import subprocess +import os + +class OpenSteakVirsh: + + virsh = "/usr/bin/virsh" + genisoimage = "/usr/bin/genisoimage" + environment = "" + + ### + # INIT + ### + def __init__(self): + self.environment = dict(os.environ) # Copy current environment + self.environment['LANG'] = 'en_US.UTF-8' + + + ### + # VOLUMES + ### + def volumeList(self, pool="default"): + """ + Return all volumes from a pool + """ + p = subprocess.Popen([self.virsh, "-q", "vol-list", pool], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + # Split lines + lines = stdout.splitlines() + + # Foreach line, split with space and construct a dictionnary + newLines = {} + for line in lines: + name, path = line.split(maxsplit=1) + newLines[name.strip()] = path.strip() + + return newLines + + def volumeDelete(self, path): + """ + Delete a volume + """ + p = subprocess.Popen([self.virsh, "-q", "vol-delete", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + def volumeClone(self, origin, name, pool="default"): + """ + Clone a volume + """ + p = subprocess.Popen([self.virsh, "-q", "vol-clone", "--pool", pool, origin, name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + def volumeResize(self, name, size, pool="default"): + """ + Resize a volume + """ + p = subprocess.Popen([self.virsh, "-q", "vol-resize", "--pool", pool, name, size], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + ### + # POOLS + ### + def poolRefresh(self, pool="default"): + """ + Refresh a pool + """ + p = subprocess.Popen([self.virsh, "-q", "pool-refresh", pool], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + ### + # DOMAINS + ### + def domainList(self): + """ + Return all domains (VM) + """ + p = subprocess.Popen([self.virsh, "-q", "list", "--all"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + # Split lines + lines = stdout.splitlines() + + # Foreach line, split with space and construct a dictionnary + newLines = {} + for line in lines: + id, name, status = line.split(maxsplit=2) + newLines[name.strip()] = status.strip() + + return newLines + + def domainDefine(self, xml): + """ + Define a domain (create a VM) + """ + p = subprocess.Popen([self.virsh, "-q", "define", xml], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + def domainUndefine(self, name): + """ + Undefine a domain (delete a VM) + """ + p = subprocess.Popen([self.virsh, "-q", "undefine", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + def domainStart(self, name): + """ + Define a domain (create a VM) + """ + p = subprocess.Popen([self.virsh, "-q", "start", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + def domainDestroy(self, name): + """ + Destroy a domain (stop a VM) + """ + p = subprocess.Popen([self.virsh, "-q", "destroy", name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + + ### + # ISO + ### + def generateConfiguration(self, name, files): + """ + Generate an ISO file + """ + + commandArray = [self.genisoimage, "-quiet", "-o", "/var/lib/libvirt/images/{0}-configuration.iso".format(name), "-volid", "cidata", "-joliet", "-rock"] + for k, f in files.items(): + commandArray.append(f) + + # Generate the iso file + p = subprocess.Popen(commandArray, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=self.environment) + stdout, stderr = p.communicate() + + return {"stdout":stdout, "stderr":stderr} + -- cgit 1.2.3-korg