class Updates(object): """ Run through the working directories and sites updating them. """ def __init__(self): self.settings = Settings() self.install() self.utilities = Utils() self.working_dirs = self.settings.get('workingDir') self.single_site = '' self.alias_file = None if isinstance(self.working_dirs, str): self.working_dirs = [self.working_dirs] # by design, SingleSite setting only works with single working directory if len(self.working_dirs) == 1: self.single_site = self.settings.get('singleSite') def install(self): """ Basic Installation of Drupdates. """ base_dir = self.settings.get('baseDir') backup_dir = self.settings.get('backupDir') dirs = [backup_dir, base_dir] for directory in dirs: Utils.check_dir(directory) current_dir = os.path.dirname(os.path.realpath(__file__)) src = os.path.join(current_dir, "templates/settings.template") settings_file = os.path.join(Utils.check_dir(base_dir), 'settings.yaml') instructions_url = "http://drupdates.readthedocs.org/en/latest/setup/" if not os.path.isfile(settings_file): shutil.copy(src, settings_file) msg = "The Settings file {0} was created and needs updated.\n".format(settings_file) msg += "See {0} for instructions".format(instructions_url) print(msg) sys.exit(1) current_settings = open(settings_file, 'r') settings = yaml.load(current_settings) if 'repoDict' in settings and 'example' in settings['repoDict']['value']: msg = "The default Settings file, {0}, needs updated. \n ".format(settings_file) msg += "See {0} for instructions".format(instructions_url) print(msg) sys.exit(1) def run_updates(self): """ Drupdates main function. """ if self.settings.get('debug'): self.utilities.write_debug_file() report = {} for current_working_dir in self.working_dirs: try: current_working_dir = Utils.check_dir(current_working_dir) self.utilities.load_dir_settings(current_working_dir) update = self.update_sites(current_working_dir) report[current_working_dir] = update except DrupdatesError as update_error: report[current_working_dir] = update_error.msg if update_error.level >= 30: break else: continue try: reporting = Reports() except DrupdatesError as reports_error: print("Reporting error: \n {0}".format(reports_error.msg)) sys.exit(1) reporting.send(report) def update_sites(self, working_dir): """ Run updates for a working directory's sites. """ report = {} self.aliases(working_dir) blacklist = self.settings.get('blacklist') sites = Repos().get() if self.single_site: sites = {self.single_site : sites[self.single_site]} for site_name, ssh in sites.items(): if self.settings.get('verbose'): msg = "Drupdates is working on the site: {0} ...".format(site_name) print(msg) report[site_name] = {} if site_name in blacklist: continue self.utilities.load_dir_settings(working_dir) for phase in self.settings.get("drupdatesPhases"): mod = __import__('drupdates.' + phase['name'].lower(), fromlist=[phase]) class_ = getattr(mod, phase['name']) instance = class_(site_name, ssh, working_dir) result = '' try: call = getattr(instance, phase['method']) result = call() except DrupdatesError as error: result = error.msg if error.level < 30: break if error.level >= 30: msg = "Drupdates: fatal error\n Drupdates returned: {0}".format(result) raise DrupdatesError(error.level, msg) finally: report[site_name][phase['name']] = result self.settings.reset() self.delete_files() return report def aliases(self, working_dir): """ Build a Drush alias file in $HOME/.drush, with alises to be used later. Notes: The file name is controlled by the drushAliasFile settings All of the aliases will be prefixed with "drupdates" if the default file name is retained """ alias_file_name = self.settings.get('drushAliasFile') drush_folder = os.path.join(expanduser('~'), '.drush') self.alias_file = os.path.join(drush_folder, alias_file_name) if not os.path.isdir(drush_folder): try: os.makedirs(drush_folder) except OSError as error: msg = "Could not create ~/.drush folder \n Error: {0}".format(error.strerror) raise DrupdatesError(30, msg) current_dir = os.path.dirname(os.path.realpath(__file__)) # Symlink the Drush aliases file src = os.path.join(current_dir, "templates/aliases.template") doc = open(src) template = Template(doc.read()) doc.close() try: filepath = open(self.alias_file, 'w') except OSError as error: msg = "Could not create {0} file\n Error: {1}".format(self.alias_file, error.strerror) raise DrupdatesError(30, msg) webroot_dir = self.settings.get('webrootDir') filepath.write(template.safe_substitute(path=working_dir, webroot=webroot_dir)) filepath.close() def delete_files(self): """ Clean up files used by Drupdates. """ if os.path.isfile(self.alias_file): try: os.remove(self.alias_file) except OSError as error: msg = "Clean-up error, couldn't remove {0}\n".format(self.alias_file) msg += "Error: {1}".format(error.strerror) print(msg) return True
class Sitebuild(object): """ Build out the repository folder. """ def __init__(self, site_name, ssh, working_dir): self.settings = Settings() self._site_name = site_name self.site_dir = os.path.join(working_dir, self._site_name) self.ssh = ssh self.utilities = Utils() self.si_files = copy.copy(self.settings.get('drushSiFiles')) def build(self): """ Core build method. """ working_branch = self.settings.get('workingBranch') try: Utils.remove_dir(self.site_dir) except DrupdatesError as remove_error: raise DrupdatesBuildError(20, remove_error.msg) self.utilities.sys_commands(self, 'preBuildCmds') repository = Repo.init(self.site_dir) remote = git.Remote.create(repository, self._site_name, self.ssh) try: remote.fetch(working_branch, depth=1) except git.exc.GitCommandError as error: msg = "{0}: Could not checkout {1}. \n".format(self._site_name, working_branch) msg += "Error: {0}".format(error) raise DrupdatesBuildError(20, msg) git_repo = repository.git git_repo.checkout('FETCH_HEAD', b=working_branch) self.utilities.load_dir_settings(self.site_dir) self.standup_site() try: repo_status = Drush.call(['st'], self._site_name, True) except DrupdatesError as st_error: raise DrupdatesBuildError(20, st_error.msg) finally: self.file_cleanup() if not 'bootstrap' in repo_status: msg = "{0} failed to Stand-up properly after running drush qd".format(self._site_name) raise DrupdatesBuildError(20, msg) self.utilities.sys_commands(self, 'postBuildCmds') return "Site build for {0} successful".format(self._site_name) def standup_site(self): """ Using the drush core-quick-drupal (qd) command stand-up a Drupal site. This will: - Perform site install with sqlite. - If needed, build webroot from a make file. - Install any sub sites (ie multi-sites) - Ensure that all the files in the web root are writable. """ qd_settings = self.settings.get('qdCmds') qd_cmds = copy.copy(qd_settings) backup_dir = Utils.check_dir(self.settings.get('backupDir')) qd_cmds += ['--backup-dir=' + backup_dir] try: qd_cmds.remove('--no-backup') except ValueError: pass if self.settings.get('useMakeFile'): make_file = self.utilities.find_make_file(self._site_name, self.site_dir) if make_file: qd_cmds += ['--makefile=' + make_file] else: msg = "Can't find make file in {0} for {1}".format(self.site_dir, self._site_name) raise DrupdatesBuildError(20, msg) if self.settings.get('buildSource') == 'make': qd_cmds.remove('--use-existing') try: Drush.call(qd_cmds, self._site_name) sub_sites = Drush.get_sub_site_aliases(self._site_name) for alias, data in sub_sites.items(): Drush.call(qd_cmds, alias) # Add sub site settings.php to list of file_cleanup() files. sub_site_st = Drush.call(['st'], alias, True) self.si_files.append(sub_site_st['site'] + '/settings.php') self.si_files.append(sub_site_st['files'] + '/.htaccess') self.si_files.append(sub_site_st['site']) except DrupdatesError as standup_error: raise standup_error def file_cleanup(self): """ Drush sets the folder permissions for some file to be 0444, convert to 0777. """ drush_dd = Drush.call(['dd', '@drupdates.' + self._site_name]) site_webroot = drush_dd[0] for name in self.si_files: complete_name = os.path.join(site_webroot, name) if os.path.isfile(complete_name) or os.path.isdir(complete_name): try: os.chmod(complete_name, 0o777) except OSError: msg = "Couldn't change file permission for {0}".format(complete_name) raise DrupdatesBuildError(20, msg)