From 4b0768c53425bd8e0e6e392b7fbc3127d454f065 Mon Sep 17 00:00:00 2001 From: Ronny Abraham Date: Thu, 15 Sep 2016 20:39:22 +0300 Subject: [PATCH] adding means to deploy and test deployment of meta files new file: bin/deploy_meta.py new file: bin/test.py --- bin/deploy_meta.py | 478 +++++++++++++++++++++++++++++++++++++++++++++ bin/test.py | 90 +++++++++ 2 files changed, 568 insertions(+) create mode 100644 bin/deploy_meta.py create mode 100644 bin/test.py diff --git a/bin/deploy_meta.py b/bin/deploy_meta.py new file mode 100644 index 0000000..afa7c56 --- /dev/null +++ b/bin/deploy_meta.py @@ -0,0 +1,478 @@ +import sys +import yaml +import os +from PyQt4 import QtGui, QtCore + + +class DictQuery(dict): + # https://www.haykranen.nl/2016/02/13/handling-complex + # -nested-dicts-in-python/ + def get(self, path, default=None): + keys = path.split(".") + val = None + + for key in keys: + if val: + if isinstance(val, list): + val = [v.get(key, default) if v else None for v in val] + else: + val = val.get(key, default) + else: + val = dict.get(self, key, default) + + if not val: + break + + return val + + +def create_path(path_str, delimiter="/"): + """ + helper function to crate a path from a string + + Keword Arguments: + path_str -- a "/" separated path + delimiter -- you can use a different delimiter + """ + + path = '' + + for d in path_str.split(delimiter): + path = os.path.join(path, d) + + return path + + +def nested_path(dic, path, value, delimiter='.'): + """ + sets the value in a dictionary object given a string key + separated by '.' + + Keyword Arguments: + path -- the string key whose path is separated by '.' + value -- the value we wish to set + delimiter -- the delimeter value used to separate the path + """ + + keys = path.split('.') + return nested_set(dic, keys, value) + + +def nested_set(dic, keys, value): + """ + sets the value in a dictionary object given a list object + of keys + + Keyword Arguments: + dic -- the dictionary object + keys -- the list object of keys + value -- the value we wish to set + """ + + for key in keys[:-1]: + dic = dic.setdefault(key, {}) + dic[keys[-1]] = value + + +class DeployMeta(QtGui.QMainWindow): + + PATH_META = "../../../meta/configuration" + PATH_TEMPLATES = "../share/templates/meta" + + path_meta = None + path_templates = None + + default_store_file = "test.yml" + + currentbranch = None + config_data = None + + CONFIG_TYPES = { + 'development': 'development.yml', + 'staging': 'staging.yml', + 'production': 'staging.yml', + } + + widgets = None + + widgets_baseinfo = { + 'PROJECT_NAME': { + 'title': 'Project Name', + 'location': 'project.name', + }, + + 'PROJECT_IP': { + 'title': 'Project Host', + 'location': 'project.host', + }, + + 'BRANCH_NAME': { + 'title': 'Branch Name', + 'location': 'project.branch', + 'default': { + 'development': "development", + 'staging': "staging", + 'production': "production" + } + }, + + 'BRANCH_EXT': { + 'title': 'Branch Extension', + 'location': 'project.extension', + 'default': { + "development": "dev", + "staging": "stg", + "production": "prd" + } + }, + + 'BRANCH_USER': { + 'title': 'Branch User', + 'location': 'project.user', + 'default': { + "development": "ronny", + "staging": "website", + "production": "website" + } + }, + + 'BRANCH_GROUP': { + 'title': 'Branch Group', + 'location': 'project.group', + 'default': { + "development": "wheel", + "staging": "www-data", + "production": "www-data" + } + }, + + 'DATABASE_IP': { + 'title': 'Database IP', + 'location': 'database.host'}, + + 'DATABASE_PORT': { + 'title': 'Database Port', + 'location': 'database.port', + 'default': { + 'development': 'DOCKER_PORT', + "staging": "5432", + "production": "5432" + }, + }, + + 'DATABASE_NAME': { + 'title': 'Database Name', + 'location': 'database.name', + }, + + 'DATABASE_ADMIN_NAME': { + 'title': 'Database Admin Name', + 'location': 'database.users.admin.name', + 'default': { + "development": "admin", + "staging": "admin", + "production": "admin" + } + }, + + 'DATABASE_ADMIN_PASS': { + 'title': 'Database Admin Pass', + 'location': 'database.users.admin.pass', + 'default': { + 'development': "admin", + 'staging': "admin", + 'production': "admin" + } + }, + + 'DATABASE_USER_NAME': { + 'title': 'Database User Name', + 'location': 'database.users.default.name', + 'default': { + 'development': "ronny", + 'staging': "website", + 'production': "website" + } + }, + + 'DATABASE_USER_PASS': { + 'title': 'Database User Pass', + 'location': 'database.users.default.pass', + 'default': { + 'development': "admin", + 'staging': "admin", + 'production': "admin", + } + }, + + 'DJANGO_IP': { + 'title': 'Django IP Address', + 'location': 'django.host', + 'default': { + 'development': "127.0.0.1", + 'staging': None, + 'production': None + } + }, + + 'DJANGO_PORT': { + 'title': 'Django Port', + 'location': 'django.port', + 'default': { + 'development': None, + 'staging': "8000", + 'production': "8010", + }, + }, + + 'NGINX_PORT': { + 'title': 'Nginx Port', + 'location': 'nginx.port', + 'default': { + 'development': "8050", + 'staging': "80", + 'production': "80", + }, + } + } + + def __init__(self): + super(DeployMeta, self).__init__() + + # create the base path variables + self.path_meta = create_path(self.PATH_META) + self.path_templates = create_path(self.PATH_TEMPLATES) + + self.widgets = dict() + for key in self.widgets_baseinfo: + self.widgets[key] = dict() + self.widgets[key]['title'] = self.widgets_baseinfo[key]['title'] + self.widgets[key]['location'] = \ + self.widgets_baseinfo[key]['location'] + + if 'default' in self.widgets_baseinfo[key]: + self.widgets[key]['default'] = \ + self.widgets_baseinfo[key]['default'] + else: + self.widgets[key]['default'] = None + + self.widgets[key]['field'] = None + + self.initUI() + + def load_config(self, configname): + + if configname not in self.CONFIG_TYPES.keys(): + print "\nerror, load_config was called with parameter: {confname}," \ + "which is not a legitimate value in CONFIG TYPES." \ + "\nLegitimate values are {configvalues}".format( + confname=configname, + configvalues=self.CONFIG_TYPES.keys()) + sys.exit() + + path_config_full = os.path.join( + self.path_templates, self.CONFIG_TYPES[configname]) + + configuration_file = yaml.load(file(path_config_full, 'r')) + return configuration_file + + def store_config(self): + + for key in self.widgets: + configvalue = str(self.widgets[key]['field'].text().__str__()) + + if configvalue.isdigit(): + configvalue = int(configvalue) + + nested_path( + self.config_data, + self.widgets[key]['location'], + configvalue) + + dquery = DictQuery(self.config_data) + + database_name = "{projectname}_{branchext}".format( + projectname=dquery.get('project.name'), + branchext=dquery.get('project.extension')) + + nested_path( + self.config_data, 'database.name', database_name) + + nested_path( + self.config_data, 'virtualenv.name', dquery.get('project.name')) + + if self.currentbranch == 'development': + projectpath = "{projectname}.prj".format( + projectname=dquery.get('project.name')) + nested_path( + self.config_data, 'project.paths.home', projectpath) + + def add_widgetrow(self, key, row, grid): + + title = self.widgets[key]['title'] + + label = QtGui.QLabel(title) + field = QtGui.QLineEdit() + + grid.addWidget(label, row, 0) + grid.addWidget(field, row, 1) + + self.widgets[key]['field'] = field + + def create_action(self, key): + title = key.capitalize() + menuname = "&%s" % title + shortcut = "Ctrl+%s" % title[0] + + desc = "Load Configuration %s" % title + + loadAction = QtGui.QAction(menuname, self) + loadAction.setShortcut(shortcut) + loadAction.setStatusTip(desc) + loadAction.triggered.connect(self.action_loadconfig) + + variant = QtCore.QVariant(key) + loadAction.setData(variant) + + return loadAction + + def action_loadconfig(self): + sender = self.sender() + self.currentbranch = sender.data().toString().__str__() + + self.config_data = self.load_config(self.currentbranch) + + dquery = DictQuery(self.config_data) + for key in self.widgets: + configvalue = dquery.get(self.widgets[key]['location']) + + if self.widgets[key]['default']: + defaultvalue = self.widgets[key]['default'][self.currentbranch] + + if configvalue == key and defaultvalue is not None: + self.widgets[key]['field'].setText(defaultvalue) + else: + self.widgets[key]['field'].setText(str(configvalue)) + else: + self.widgets[key]['field'].setText(configvalue) + + def action_save_config(self): + + # first make sure we've loaded up a configuration + # file to save. currentbranch will be set if we + # loaded it up + + if self.currentbranch: + + self.store_config() + + # path_newconfig = os.path.join( + # self.path_meta, self.default_store_file) + + dialog_title = "Save {currentbranch} configuration".format( + currentbranch=self.currentbranch) + + # + # generate the default path to save to + + path_default_save = os.path.join( + self.path_meta, self.default_store_file) + + # + # get the path value from the dialog box + + path_newconfig = QtGui.QFileDialog.getSaveFileName( + self, dialog_title, + path_default_save, + selectedFilter='*.yml') + + # + # if the user hit 'cancel' path_newconfig will be empty + + if path_newconfig: + stream = file(path_newconfig, 'w') + yaml.dump(self.config_data, stream) + + else: + # + # display message warning no configuration has been loaded + + from PyQt4.QtGui import QMessageBox + msg = QMessageBox() + msg.setIcon(QMessageBox.Warning) + msg.setText('Save Error') + msg.setInformativeText( + "Cannot save modified configuration until you load one of the" + " default config types") + + retval = msg.exec_() + print "value of qmessagebox in action_save: %s" % retval + + def setupMenu(self): + menubar = self.menuBar() + menubar.setNativeMenuBar(False) + + exitAction = QtGui.QAction('&Exit', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip("Exit application") + exitAction.triggered.connect(QtGui.qApp.quit) + + saveAction = QtGui.QAction('&Gore', self) + saveAction.setShortcut('Ctrl+T') + saveAction.setStatusTip("Save Config File") + saveAction.triggered.connect(self.action_save_config) + + fileMenu = menubar.addMenu('&File') + fileMenu.addAction(exitAction) + fileMenu.addAction(saveAction) + + loadMenu = menubar.addMenu("&Load Configuration") + + for key in self.CONFIG_TYPES: + loadMenu.addAction(self.create_action(key)) + + def initUI(self): + + self.setupMenu() + + grid = QtGui.QGridLayout() + grid.setSpacing(10) + + self.add_widgetrow('PROJECT_NAME', 1, grid) + self.add_widgetrow('PROJECT_IP', 2, grid) + self.add_widgetrow('BRANCH_NAME', 4, grid) + self.add_widgetrow('BRANCH_EXT', 5, grid) + self.add_widgetrow('BRANCH_USER', 6, grid) + self.add_widgetrow('BRANCH_GROUP', 7, grid) + self.add_widgetrow('DATABASE_IP', 8, grid) + self.add_widgetrow('DATABASE_PORT', 9, grid) + self.add_widgetrow('DATABASE_NAME', 10, grid) + self.add_widgetrow('DATABASE_ADMIN_NAME', 11, grid) + self.add_widgetrow('DATABASE_ADMIN_PASS', 12, grid) + self.add_widgetrow('DATABASE_USER_NAME', 13, grid) + self.add_widgetrow('DATABASE_USER_PASS', 14, grid) + self.add_widgetrow('DJANGO_IP', 15, grid) + self.add_widgetrow('DJANGO_PORT', 16, grid) + self.add_widgetrow('NGINX_PORT', 17, grid) + + central = QtGui.QWidget() + central.setLayout(grid) + self.setCentralWidget(central) + + self.setGeometry(300, 300, 350, 300) + self.setWindowTitle("Deploy Meta Files") + self.show() + self.raise_() + + +def main(): + app = QtGui.QApplication(sys.argv) + app.setStyle("cleanlooks") + ex = DeployMeta() + sys.exit(app.exec_()) + + print ex + + +if __name__ == '__main__': + main() diff --git a/bin/test.py b/bin/test.py new file mode 100644 index 0000000..a118caa --- /dev/null +++ b/bin/test.py @@ -0,0 +1,90 @@ +import ruamel.yaml +import os +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('--file1', default='development.yml') +parser.add_argument('--file2', default='test.yml') + + +def findDiff(d1, d2, path=""): + for k in d1.keys(): + if not (k in d2): + print path, ":" + print k + " as key not in d2", "\n" + else: + if type(d1[k]) is dict: + if path == "": + path = k + else: + path = path + "->" + k + findDiff(d1[k], d2[k], path) + else: + if d1[k] != d2[k]: + print path, ":" + print " - ", k, " : ", d1[k] + print " + ", k, " : ", d2[k] + + +def compare_dictionaries(dict_1, dict_2, dict_1_name, dict_2_name, path=""): + """Compare two dictionaries recursively to find non mathcing elements + + Args: + dict_1: dictionary 1 + dict_2: dictionary 2 + + Returns: + + """ + err = '' + key_err = '' + value_err = '' + old_path = path + for k in dict_1.keys(): + path = old_path + "[%s]" % k + if not (k in dict_2): + key_err += "Key %s%s not in %s\n" % ( + dict_2_name, path, dict_2_name) + else: + if isinstance(dict_1[k], dict) and isinstance(dict_2[k], dict): + err += compare_dictionaries( + dict_1[k], dict_2[k], 'd1', 'd2', path) + else: + if dict_1[k] != dict_2[k]: + value_err += "Value of %s%s (%s) not same as %s%s (%s)\n"\ + % (dict_1_name, + path, dict_1[k], dict_2_name, path, dict_2[k]) + + for k in dict_2.keys(): + path = old_path + "[%s]" % k + if not (k in dict_1): + key_err += "Key %s%s not in %s\n" % ( + dict_2_name, path, dict_1_name) + + return key_err + value_err + err + + +def create_path(path_str, delimiter="/"): + path = '' + + for d in path_str.split(delimiter): + path = os.path.join(path, d) + + return path + + +def main(): + path_meta = create_path("../../meta/configuration") + + args = parser.parse_args() + + path_main = os.path.join(path_meta, args.file1) + path_test = os.path.join(path_meta, args.file2) + + yaml_main = ruamel.yaml.load(file(path_main, 'r')) + yaml_test = ruamel.yaml.load(file(path_test, 'r')) + + a = compare_dictionaries(yaml_main, yaml_test, 'main', 'test') + print a + +main()