diff --git a/pym/cl_fill.py b/pym/cl_fill.py index 337b64f..76c6d11 100644 --- a/pym/cl_fill.py +++ b/pym/cl_fill.py @@ -261,7 +261,7 @@ class fillVars(glob_attr): def get_cl_template_clt_path(self): '''Пути к файлам ,clt''' if "CONFIG_PROTECT" in os.environ: - protectPaths = ["/etc"] + map(lambda x: x.strip(), + protectPaths = ["/etc"] + filter(lambda x: x.strip(), os.environ["CONFIG_PROTECT"].split(" ")) else: protectPaths = ["/etc", "/usr/share/X11/xkb", "var/lib/hsqldb", diff --git a/pym/cl_ldap.py b/pym/cl_ldap.py index e390625..16fefa1 100644 --- a/pym/cl_ldap.py +++ b/pym/cl_ldap.py @@ -144,7 +144,7 @@ class ldapUser(_error): return groupsDNs return False - def getUserLdapInfo(self, userName): + def getUserLdapInfo(self, userName, shadowAttr=False): """Выдаем информацию о пользователе из LDAP""" connectData = self.getBindConnectData() if not connectData: @@ -159,39 +159,59 @@ class ldapUser(_error): "uid=%s" %userName, None) if not searchUser: return False - uid = False - gid = False - fullName = "" - mail = "" - jid = "" - group = "" - if 'uidNumber' in searchUser[0][0][1]: - uid = searchUser[0][0][1]['uidNumber'][0] - if 'gidNumber' in searchUser[0][0][1]: - gid = searchUser[0][0][1]['gidNumber'][0] - for groupDN in groupsDNs: - searchGroup = self.ldapObj.ldapSearch(groupDN, + convertDict = {'uid':('user','uidNumber'), + 'gid':('user','gidNumber'), + 'fullName':('user','cn'), + 'mail':('user','mail'), + 'jid':('user','registeredAddress'), + 'home':('user','homeDirectory'), + 'group':('group','cn')} + if shadowAttr: + convertDict.update({'loginShell':('user','loginShell'), + 'shadowLastChange':('user','shadowLastChange'), + 'shadowMin':('user','shadowMin'), + 'shadowMax':('user','shadowMax'), + 'shadowWarning':('user','shadowWarning'), + 'shadowExpire':('user','shadowExpire'), + 'shadowFlag':('user','shadowFlag')}) + listUserAttr = map(lambda x: x[0], + filter(lambda x: x[1][0]=="user", + convertDict.items())) + listGroupAttr = map(lambda x: x[0], + filter(lambda x: x[1][0]=="group", + convertDict.items())) + uid = "" + gid = "" + dictOut = {} + for dictAttr in listUserAttr: + ldapAttr = convertDict[dictAttr][1] + if ldapAttr in searchUser[0][0][1]: + dictOut[dictAttr] = searchUser[0][0][1][ldapAttr][0] + else: + dictOut[dictAttr] = "" + if dictAttr == 'uid': + uid = dictOut[dictAttr] + if dictAttr == 'gid': + gid = dictOut[dictAttr] + if gid: + for dictAttr in listGroupAttr: + searchGroup = [] + ldapAttr = convertDict[dictAttr][1] + for groupDN in groupsDNs: + searchGroup = self.ldapObj.ldapSearch(groupDN, ldap.SCOPE_ONELEVEL, - "gidNumber=%s" %gid, ['cn']) + "gidNumber=%s" %gid, None) + if searchGroup: + break if searchGroup: - group = searchGroup[0][0][1]['cn'][0] - break - if 'cn' in searchUser[0][0][1]: - fullName = searchUser[0][0][1]['cn'][0] - if 'mail' in searchUser[0][0][1]: - mail = searchUser[0][0][1]['mail'][0] - if 'registeredAddress' in searchUser[0][0][1]: - jid = searchUser[0][0][1]['registeredAddress'][0] - if 'homeDirectory' in searchUser[0][0][1]: - home = searchUser[0][0][1]['homeDirectory'][0] + if ldapAttr in searchGroup[0][0][1]: + dictOut[dictAttr] = searchGroup[0][0][1][ldapAttr][0] + else: + dictOut[dictAttr] = "" + else: + dictOut[dictAttr] = "" if uid and gid: - return {"uid":uid, - "gid":gid, - "fullName":fullName, - "mail":mail, - "jid":jid, - "home":home, - "group":group} + return dictOut else: return {} diff --git a/pym/cl_template.py b/pym/cl_template.py index a290d33..2bf1ac5 100644 --- a/pym/cl_template.py +++ b/pym/cl_template.py @@ -301,13 +301,14 @@ class fileHeader(_terms): # Тип вставки шаблона typeAppend = "" # Возможные типы вставки шаблонов - _fileAppend = "join", "before", "after", "replace", "remove", "skip" + _fileAppend = "join", "before", "after", "replace", "remove", "skip",\ + "patch" # Интерпретатор (#!/bin/bash) (#!/usr/bin/python) execStr = "" # Символ комментария comment = False # Выражение для поиска строки интерпретатора - reExecStr = re.compile("^#!.+\s*",re.I) + reExecStr = re.compile("^#!.+\s*",re.M) # условные операторы terms = ('>', '<', '==', '!=', '>=', '<=') # параметры без значения @@ -323,7 +324,14 @@ class fileHeader(_terms): # Параметры описанные в заголовке файла шаблона self.params = {} # некорректные параметры - incorrectParams = set([]) + incorrectParams = [] + # Поиск строки запустка (#!/bin/bash и.т. д) + if comment or fileType!="bin": + reExecRes = self.reExecStr.search(self.body) + if reExecRes: + self.execStr = self.body[reExecRes.start():reExecRes.end()] + self.body = self.body[:reExecRes.start()] +\ + self.body[reExecRes.end():] # Удаление Заголовка Calculate if comment: titleFirst = _("Modified") @@ -332,25 +340,24 @@ class fileHeader(_terms): reCalcHeader =\ re.compile("\s*%s\s+%s.+\s+(.+\n)+%s\s?"\ %(comment[0], titleFirst, comment[1]),re.M|re.I) - reS = reCalcHeader.search(text) + reS = reCalcHeader.search(self.body) if reS: - self.body = text[:reS.start()]+text[reS.end():] + self.body = self.body[:reS.start()]+self.body[reS.end():] else: reCalcHeader = re.compile(\ "\s*%s\-+\s+%s\s+%s.+\s+(%s.+\s+)+%s\-+\s?"\ %(comment, comment, titleFirst ,comment,comment), re.M|re.I) - reS = reCalcHeader.search(text) + reS = reCalcHeader.search(self.body) if reS: - self.body = text[reS.end():] - + self.body = self.body[reS.end():] if fileType != False: if fileType=="bin": self.params["format"] = fileType self.fileType = self._getType() self.typeAppend = self._getAppend() else: - textLines = text.splitlines() + textLines = self.body.splitlines() if textLines: textLine = textLines[0] rePar = re.compile("\s*#\s*calculate\s+",re.I) @@ -358,17 +365,17 @@ class fileHeader(_terms): if reP: reL = False reLns = re.compile(r"\A([^\\\n]*\\\n)+[^\n]*\n*",re.M) - reLs = reLns.search(text) + reLs = reLns.search(self.body) if reLs: reL = reLs - paramLine = text[reP.end():reLs.end()] + paramLine = self.body[reP.end():reLs.end()] paramLine = paramLine.replace("\\"," ") else: reLn = re.compile("\n") - reL = reLn.search(text) + reL = reLn.search(self.body) paramLine = textLine[reP.end():] if reL: - self.body = text[reL.end():] + self.body = self.body[reL.end():] else: self.body = "" paramList = re.split("\s+",paramLine) @@ -405,12 +412,14 @@ class fileHeader(_terms): self.comment = self._getComment() self.fileType = self._getType() - self.typeAppend = self._getAppend() - reExecRes = self.reExecStr.search(self.body) - if reExecRes: - self.execStr = self.body[reExecRes.start():reExecRes.end()] - self.body = self.body[reExecRes.end():] - if not incorrectParams: + typeAppend = self._getAppend() + if typeAppend: + self.typeAppend = typeAppend + else: + self.headerTerm = False + self.setError(_("incorrect header parameters - '%s'")\ + %"append=%s"%self.params["append"]) + if not incorrectParams and self.params: incorrectParams = set(self.params.keys()) - set(self.allowParam) if incorrectParams: self.headerTerm = False @@ -419,19 +428,24 @@ class fileHeader(_terms): def _getType(self): """Выдать тип файла""" - if self.params.has_key("format"): + if "format" in self.params: return self.params["format"] else: return "raw" def _getAppend(self): """Выдать тип добавления файла""" - if self.params.has_key("append") and self.params["append"] in\ - self._fileAppend: - return self.params["append"] + if self.params.has_key("append"): + if self.params["append"] in self._fileAppend: + return self.params["append"] + else: + return False else: if self.fileType != "raw" and self.fileType != "bin" and\ self.fileType != "": + if "format" in self.params and self.params["format"] == "patch": + self.params["append"] = "patch" + return "patch" self.params["append"] = "join" return "join" self.params["append"] = "replace" @@ -532,7 +546,13 @@ class dirHeader(_terms): incorrectParams = set([i]) elif len(par) == 2: self.params[par[0]] = par[1] - self.typeAppend = self._getAppend() + typeAppend = self._getAppend() + if typeAppend: + self.typeAppend = typeAppend + else: + self.headerTerm = False + self.setError(_("incorrect header parameters - '%s'")\ + %"append=%s"%self.params["append"]) if not flagErrorBody: if not incorrectParams: incorrectParams = set(self.params.keys()) - set(self.allowParam) @@ -543,9 +563,11 @@ class dirHeader(_terms): def _getAppend(self): """Выдать тип добавления директории""" - if self.params.has_key("append") and self.params["append"] in\ - self._fileAppend: - return self.params["append"] + if self.params.has_key("append"): + if self.params["append"] in self._fileAppend: + return self.params["append"] + else: + return False else: return "join" @@ -3283,14 +3305,18 @@ re.M|re.S) def getTemplateType(self): """выдать тип шаблона (text, bin)""" - isBin = self.typeFileObj.isBinary(self.nameFileTemplate) + return self.getFileType(self.nameFileTemplate) + + def getFileType(self, fileName): + """выдать тип файла (text, bin)""" + isBin = self.typeFileObj.isBinary(fileName) typeTemplate = "bin" if isBin is True: typeTemplate = "bin" elif isBin is False: typeTemplate = "text" else: - self.setError(_("ERROR") + ": getTemplateType()") + self.setError(_("ERROR") + ": getFileType()") self.setError(isBin) return False return typeTemplate @@ -3567,10 +3593,9 @@ re.M|re.S) self.cltObj.dictProcessedTemplates = self.dictProcessedTemplates if self.cltFilter: # Шаблоны + .clt которые будут применены - self.cltObj.filterApplyTemplates=\ + self.cltObj.filterApplyTemplates +=\ map(lambda x: pathJoin('/', x.partition(self._baseDir)[2]), self.dictProcessedTemplates.keys()) - # Обрабатываем шаблоны clt if not self.cltObj.applyTemplates(): return False return (self.createdDirs, self.filesApply) @@ -4213,7 +4238,7 @@ re.M|re.S) return ([], False) if not objHeadNew.body.strip(): preReturn(pathProg) - return ([], False) + return (applyFiles, False) else: applyFiles = [pathOldFile] if pathProg: @@ -4333,9 +4358,48 @@ re.M|re.S) if objHeadNew.comment: objHeadOld = fileHeader(nameFileConfig, self.textConfig, objHeadNew.comment) + elif objHeadNew.fileType and\ + objHeadNew.typeAppend in ("before", "after"): + configFileType = self.getFileType(nameFileConfig) + objHeadOld = fileHeader(nameFileConfig, self.textConfig, + fileType=configFileType) + # Строка вызова скрипта (#!/bin/bash ...) + execStr = "" + if objHeadNew.execStr: + execStr = objHeadNew.execStr + elif objHeadOld and objHeadOld.execStr: + execStr = objHeadOld.execStr + if objHeadNew.fileType: formatTemplate = objHeadNew.fileType typeAppendTemplate = objHeadNew.typeAppend + if formatTemplate == "patch": + if typeAppendTemplate != "patch": + self.setError(\ + _("False option append=%(type)s in template %(file)s")\ + %{"type":typeAppendTemplate,"file":nameFileTemplate}) + return False + # создаем объект формата шаблона + objTempl = self.getFormatObj(formatTemplate, self.textTemplate) + if not objTempl: + self.setError(\ + _("Incorrect header parmeter format=%s in template")\ + %formatTemplate + " " + nameFileTemplate) + return False + if objHeadOld and objHeadOld.body: + self.textConfig = objHeadOld.body + # обработка конфигурационного файла + self.textTemplate = objTempl.processingFile(self.textConfig) + if objTempl.getError(): + self.setError(_("False template") + ": " +\ + nameFileTemplate) + return False + if execStr: + self.textConfig = execStr + title + self.textTemplate + else: + self.textConfig = title + self.textTemplate + self.saveConfFile() + return filesApply # Создаем объект в случае параметра format в заголовке if (typeAppendTemplate == "replace" or\ typeAppendTemplate == "before" or\ @@ -4397,7 +4461,7 @@ re.M|re.S) self.textConfig = title + self.textTemplate self.saveConfFile() return filesApply - # Впереди + # Вверху elif typeAppendTemplate == "before": if "xml_" in formatTemplate: self.setError(\ @@ -4410,16 +4474,14 @@ re.M|re.S) tmpTemplate = self.textTemplate + self.textConfig else: tmpTemplate = self.textTemplate + "\n" + self.textConfig - if objHeadNew.execStr: - self.textConfig = objHeadNew.execStr + title + tmpTemplate - elif objHeadOld and objHeadOld.execStr: - self.textConfig = objHeadOld.execStr + title + tmpTemplate + if execStr: + self.textConfig = execStr + title + tmpTemplate else: self.textConfig = title + tmpTemplate self.saveConfFile() return filesApply - # Cзади + # Внизу elif typeAppendTemplate == "after": if "xml_" in formatTemplate: self.setError(\ @@ -4432,10 +4494,8 @@ re.M|re.S) tmpTemplate = self.textConfig + self.textTemplate else: tmpTemplate = self.textConfig + "\n" + self.textTemplate - if objHeadNew.execStr: - self.textConfig = objHeadNew.execStr + title + tmpTemplate - elif objHeadOld and objHeadOld.execStr: - self.textConfig = objHeadOld.execStr + title + tmpTemplate + if execStr: + self.textConfig = execStr + title + tmpTemplate else: self.textConfig = title + tmpTemplate self.saveConfFile() @@ -4476,14 +4536,6 @@ re.M|re.S) if not self.textConfig or\ not reNoClean.search(self.textConfig): self.textConfig = "" - #if objHeadNew.execStr: - #self.textConfig = objHeadNew.execStr + \ - #title + objTemplNew.getConfig().encode("UTF-8") - #else: - #self.textConfig = title +\ - #objTemplNew.getConfig().encode("UTF-8") - #self.saveConfFile() - #return True objHeadOld = fileHeader(nameFileConfig, self.textConfig, objTemplNew._comment) @@ -4518,12 +4570,9 @@ re.M|re.S) data.insert(1,title) self.textConfig = "\n".join(data) else: - if objHeadNew.execStr: - self.textConfig = objHeadNew.execStr + title +\ - objTemplOld.getConfig().encode("UTF-8") - elif objHeadOld.execStr: - self.textConfig = objHeadOld.execStr + title +\ - objTemplOld.getConfig().encode("UTF-8") + if execStr: + self.textConfig = execStr + title +\ + objTemplOld.getConfig().encode("UTF-8") else: self.textConfig = title +\ objTemplOld.getConfig().encode("UTF-8") @@ -4588,6 +4637,9 @@ class templateClt(scanDirectoryClt, template): self.flagApplyTemplates = False if self.objVar.Get("cl_name") in applyPackages: self.flagApplyTemplates = True + # Базовая директория переноса шаблонов "/mnt/calculate" или "/" и.т.д + self._baseDir = pathJoin(self.objVar.Get("cl_chroot_path"), + self.objVar.Get("cl_root_path")) def applyTemplate(self, path): """Применение отдельного .clt шаблона""" @@ -4616,6 +4668,7 @@ class templateClt(scanDirectoryClt, template): else: nameFileConfig = path nameFileConfig = nameFileConfig[:-self.lenExtFileTemplate] + nameFileConfig = pathJoin(self._baseDir, nameFileConfig) # файл в системе без условий nameFileConfig = "/".join(map(lambda x:x.split("?")[0],\ nameFileConfig.split("/"))) @@ -4631,8 +4684,10 @@ class templateClt(scanDirectoryClt, template): if filesApl: self.filesApply += filesApl # Настоящее имя конфигурационного файла - nameFileConfig = filesApl[0] - return nameFileConfig + nameFileConfig = filesApl[0] + return nameFileConfig + else: + return True def countsNumberTemplates(self, dirsTemplates=[]): """Считаем количество шаблонов""" diff --git a/pym/format/patch.py b/pym/format/patch.py new file mode 100644 index 0000000..91dbde4 --- /dev/null +++ b/pym/format/patch.py @@ -0,0 +1,106 @@ +#-*- coding: utf-8 -*- + +# Copyright 2008-2010 Calculate Ltd. http://www.calculate-linux.org +# +# 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. + +import sys, re +import xml.dom.minidom +from cl_utils import _error +# Перевод cообщений модуля +from cl_lang import lang +tr = lang() +tr.setLocalDomain('cl_lib') +tr.setLanguage(sys.modules[__name__]) + + +class patch(_error): + """Класс для замены, добавления, удаления, строк в файле""" + # root нода + rootNode = False + # Документ + doc = False + # Текст шаблона + text = "" + + def __init__(self, text): + self.text = text + # Создаем XML документ + self.doc = self.textToXML() + if self.doc: + self.rootNode = self.doc.documentElement + + def textToXML(self): + """Создание из текста XML документа + Храним xml в своем формате + """ + if not self.text.strip(): + self.text = '' + text = '\n%s'\ + %self.text + try: + self.doc = xml.dom.minidom.parseString(text) + except: + return False + return self.doc + + def processingFile(self, textConfigFile): + """Обработка конфигурационного файла""" + if not self.doc: + self.setError(_("Can not convert text template in XML")) + return False + retTextConfigFile = textConfigFile + tags = ["reg", "text"] + dataList = [] + tagsIndex = 0 + for node in self.rootNode.childNodes: + if node.nodeType==node.ELEMENT_NODE: + if not node.tagName == tags[tagsIndex]: + self.setError(_("Incorrect text template")) + return False + if tagsIndex == 1: + tagsIndex = 0 + else: + tagsIndex += 1 + # регулярное выражение + if node.tagName == "reg": + if node.firstChild: + reText = node.firstChild.nodeValue + else: + self.setError(\ + _("Incorrect text template ''")) + return False + if not reText.strip(): + self.setError(\ + _("Incorrect text template '%s'")\ + %reText) + return False + try: + regex = re.compile(reText) + except: + self.setError(\ + _("Incorrect text template '%s'")\ + %reText) + + return False + elif node.tagName == "text" and regex: + if node.firstChild: + text = node.firstChild.nodeValue + else: + text = "" + dataList.append((regex, text)) + regex = False + for regex, text in dataList: + # Замены в тексте конфигурационного файла + retTextConfigFile = regex.sub(text, retTextConfigFile) + return retTextConfigFile diff --git a/pym/server/utils.py b/pym/server/utils.py index 8836912..d747c95 100644 --- a/pym/server/utils.py +++ b/pym/server/utils.py @@ -72,8 +72,9 @@ def dialogYesNo(message): """Вывод сообщения, ожидание набора Yes или No (в любом регистре) если Yes - True, если No - False""" - sys.stdout.write(message + ": ") - strIn=sys.stdin.readline().lower().strip() + #sys.stdout.write(message + ": ") + #strIn=sys.stdin.readline().lower().strip() + strIn = raw_input(message + ": ") sys.stdout.write("\n") if "yes" == strIn: return True diff --git a/pym/update_config/cl_update_config.py b/pym/update_config/cl_update_config.py index 3d20711..b70cbb2 100644 --- a/pym/update_config/cl_update_config.py +++ b/pym/update_config/cl_update_config.py @@ -22,7 +22,7 @@ from cl_log import log import cl_datavars import cl_template from cl_print import color_print as old_color_print -from cl_utils import runOsCommand +from cl_utils import runOsCommand, scanDirectory, pathJoin import cl_overriding import cl_lang @@ -189,7 +189,6 @@ class updateUserConfigs(shareUpdateConfigs): self.logger.warn(_("Not found templates")) return True - class updateSystemConfigs(shareUpdateConfigs): """Обновление системных конфигурационных файлов""" @@ -198,7 +197,7 @@ class updateSystemConfigs(shareUpdateConfigs): if not "CONFIG_PROTECT" in os.environ: self.printERROR(_("Missing environment variable CONFIG_PROTECT")) exit(1) - protectPaths = ["/etc"] + map(lambda x: x.strip(), + protectPaths = ["/etc"] + filter(lambda x: x.strip(), os.environ["CONFIG_PROTECT"].split(" ")) flagFoundProtect = False for pPath in protectPaths: @@ -210,6 +209,21 @@ class updateSystemConfigs(shareUpdateConfigs): return False return True + def scanProtectDirs(self, configPath): + configFiles = [] + scanObj = scanDirectory() + scanObj.processingFile = lambda path,prefix:configFiles.append(path) or\ + True + protectPaths = ["/etc"] + filter(lambda x: x.strip(), + os.environ["CONFIG_PROTECT"].split(" ")) + configPath = os.path.realpath(configPath) + for pPath in protectPaths: + realPath = pathJoin(configPath, pPath) + if os.path.exists(realPath): + scanObj.scanningDirectory(realPath) + configFiles = map(lambda x: x.partition(configPath)[2], configFiles) + return configFiles + def updateConfig(self, nameProgram, category, version, configPath): """Обновление системных конфигурационных файлов""" cleanVer = self.reCleanVer.search(version) @@ -235,9 +249,18 @@ class updateSystemConfigs(shareUpdateConfigs): clVars.Set("cl_root_path", configPath, True) clVars.Set("cl_belong_pkg", nameProgram, True) clVars.Set("cl_action", 'merge', True) - clTempl = cl_template.template(clVars) + configFiles = [] + nameProg = clVars.Get("cl_name") + if nameProg == "calculate-install": + configFiles = self.scanProtectDirs(configPath) + cltObject = cl_template.templateClt(clVars) + if configFiles: + cltObject.filterApplyTemplates = configFiles + else: + cltObject.filterApplyTemplates = [] + clTempl = cl_template.template(clVars, cltObj=cltObject) dirsFiles = clTempl.applyTemplates() - nameAndVerPkg = clVars.Get("cl_name")+"-"+clVars.Get("cl_ver") + nameAndVerPkg = nameProg + "-"+clVars.Get("cl_ver") if dirsFiles is False: self.printERROR(_("Error template in a package %s")\ %nameAndVerPkg)