def cleanup(self): """remove instance's configuration and database""" source = self.config.system_source_config for msg, step, default in self._cleanup_steps(source): if ASK.confirm(msg, default_is_yes=default): try: step(source) except Exception as exc: print('ERROR', exc) if ASK.confirm('An error occurred. Continue anyway?', default_is_yes=False): continue raise ExecutionError(str(exc))
def _remote_dump(host, appid, output, sudo=False): # XXX generate unique/portable file name from datetime import date filename = '%s-%s.tgz' % (appid, date.today().strftime('%Y-%m-%d')) dmpcmd = 'cubicweb-ctl db-dump -o /tmp/%s %s' % (filename, appid) if sudo: dmpcmd = 'sudo %s' % (dmpcmd) dmpcmd = 'ssh -t %s "%s"' % (host, dmpcmd) print(dmpcmd) if os.system(dmpcmd): raise ExecutionError('Error while dumping the database') if output is None: output = filename cmd = 'scp %s:/tmp/%s %s' % (host, filename, output) print(cmd) if os.system(cmd): raise ExecutionError('Error while retrieving the dump at /tmp/%s' % filename) rmcmd = 'ssh -t %s "rm -f /tmp/%s"' % (host, filename) print(rmcmd) if os.system(rmcmd) and not ASK.confirm( 'An error occurred while deleting remote dump at /tmp/%s. ' 'Continue anyway?' % filename): raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename)
def run(self, args): check_options_consistency(self.config) print('\n' + underline_title('Initializing the system database')) from cubicweb.server import init_repository appid = args[0] config = ServerConfiguration.config_for(appid) try: system = config.system_source_config extra_args = system.get('db-extra-arguments') extra = extra_args and {'extra_args': extra_args} or {} get_connection(system['db-driver'], database=system['db-name'], host=system.get('db-host'), port=system.get('db-port'), user=system.get('db-user') or '', password=system.get('db-password') or '', schema=system.get('db-namespace'), **extra) except Exception as ex: raise ConfigurationError( 'You seem to have provided wrong connection information in ' 'the %s file. Resolve this first (error: %s).' % (config.sources_file(), str(ex).strip())) init_repository(config, drop=self.config.drop) if not self.config.automatic: while ASK.confirm('Enter another source ?', default_is_yes=False): CWCTL.run([ 'source-add', '--config-level', str(self.config.config_level), config.appid ])
def show_diffs(appl_file, ref_file, askconfirm=True): """interactivly replace the old file with the new file according to user decision """ import shutil pipe = subprocess.Popen(['diff', '-u', appl_file, ref_file], stdout=subprocess.PIPE) diffs = pipe.stdout.read().decode('utf-8') if diffs: if askconfirm: print() print(diffs) action = ASK.ask('Replace ?', ('Y', 'n', 'q'), 'Y').lower() else: action = 'y' if action == 'y': try: shutil.copyfile(ref_file, appl_file) except IOError: os.system('chmod a+w %s' % appl_file) shutil.copyfile(ref_file, appl_file) print('replaced') elif action == 'q': sys.exit(0) else: copy_file = appl_file + '.default' copy = open(copy_file, 'w') copy.write(open(ref_file).read()) copy.close() print('keep current version, the new file has been written to', copy_file) else: print('no diff between %s and %s' % (appl_file, ref_file))
def copy_skeleton(skeldir, targetdir, context, exclude=SKEL_EXCLUDE, askconfirm=False): import shutil from fnmatch import fnmatch skeldir = normpath(skeldir) targetdir = normpath(targetdir) for dirpath, dirnames, filenames in walk(skeldir): tdirpath = dirpath.replace(skeldir, targetdir) if 'cubicweb_CUBENAME' in tdirpath: tdirpath = tdirpath.replace('cubicweb_CUBENAME', 'cubicweb_' + context['cubename']) create_dir(tdirpath) for fname in filenames: if any(fnmatch(fname, pat) for pat in exclude): continue fpath = join(dirpath, fname) if 'CUBENAME' in fname: tfpath = join(tdirpath, fname.replace('CUBENAME', context['cubename'])) elif 'DISTNAME' in fname: tfpath = join(tdirpath, fname.replace('DISTNAME', context['distname'])) else: tfpath = join(tdirpath, fname) if fname.endswith('.tmpl'): tfpath = tfpath[:-5] if not askconfirm or not exists(tfpath) or \ ASK.confirm('%s exists, overwrite?' % tfpath): fill_templated_file(fpath, tfpath, context) print('[generate] %s <-- %s' % (tfpath, fpath)) elif exists(tfpath): show_diffs(tfpath, fpath, askconfirm) else: shutil.copyfile(fpath, tfpath) shutil.copymode(fpath, tfpath)
def bootstrap(self, cubes, automatic=False, inputlevel=0): """bootstrap this configuration""" if not automatic: print('\n' + underline_title('Generic web configuration')) config = self.config config.input_config('web', inputlevel) if ASK.confirm('Allow anonymous access ?', False): config.global_set_option('anonymous-user', 'anon') config.global_set_option('anonymous-password', 'anon')
def postcreate(self, automatic=False, inputlevel=0): if automatic: CWCTL.run(['db-create', '--automatic', self.config.appid]) elif ASK.confirm('Run db-create to create the system database ?'): CWCTL.run([ 'db-create', '--config-level', str(inputlevel), self.config.appid ]) else: print('-> nevermind, you can do it later with ' '"cubicweb-ctl db-create %s".' % self.config.appid)
def i18nupgrade(self, config): # handle i18n upgrade: # * install new languages # * recompile catalogs # XXX search available language in the first cube given from cubicweb import i18n templdir = cwcfg.cube_dir(config.cubes()[0]) langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] errors = config.i18ncompile(langs) if errors: print('\n'.join(errors)) if not ASK.confirm('Error while compiling message catalogs, ' 'continue anyway?'): print('-> migration not completed.') return False return True
def _ask_for_dependencies(self): from logilab.common.shellutils import ASK from logilab.common.textutils import splitstrip depcubes = [] for cube in ServerConfiguration.available_cubes(): answer = ASK.ask("Depends on cube %s? " % cube, ('N', 'y', 'skip', 'type'), 'N') if answer == 'y': depcubes.append(cube) if answer == 'type': depcubes = splitstrip(input('type dependencies: ')) break elif answer == 'skip': break return dict( ('cubicweb-' + cube, ServerConfiguration.cube_version(cube)) for cube in depcubes)
def confirm( self, question, # pylint: disable=E0202 shell=True, abort=True, retry=False, pdb=False, default='y', traceback=None): """ask for confirmation and return true on positive answer if `retry` is true the r[etry] answer may return 2 """ possibleanswers = ['y', 'n'] if abort: possibleanswers.append('abort') if pdb: possibleanswers.append('pdb') if shell: possibleanswers.append('shell') if retry: possibleanswers.append('retry') try: answer = ASK.ask(question, possibleanswers, default) except (EOFError, KeyboardInterrupt): answer = 'abort' if answer == 'n': return False if answer == 'retry': return 2 if answer == 'abort': raise SystemExit(1) if answer == 'shell': self.interactive_shell() return self.confirm(question, shell, abort, retry, pdb, default, traceback) if answer == 'pdb': pdb = utils.get_pdb() if traceback: pdb.post_mortem(traceback) else: pdb.set_trace() return self.confirm(question, shell, abort, retry, pdb, default, traceback) return True
def execscript_confirm(scriptpath): """asks for confirmation before executing a script and provides the ability to show the script's content """ while True: answer = ASK.ask('Execute %r ?' % scriptpath, ('Y', 'n', 'show', 'abort'), 'Y') if answer == 'abort': raise SystemExit(1) elif answer == 'n': return False elif answer == 'show': stream = open(scriptpath) scriptcontent = stream.read() stream.close() print() print(scriptcontent) print() else: return True
def generate_static_dir(self, config, dest=None, ask_clean=False, repo=None): if not dest: dest = config['staticdir-path'] if not dest: dest = osp.join(config.appdatahome, 'data') if osp.exists(dest): if (config.verbosity and (not ask_clean or not (config.verbosity and ASK.confirm( 'Remove existing data directory %s?' % dest)))): raise ExecutionError('Directory %s already exists. ' 'Remove it first.' % dest) rmtreecontent(dest) config.quick_start = True # notify this is not a regular start # list all resources (no matter their order) resources = set() for datadir in self._datadirs(config, repo=repo): for dirpath, dirnames, filenames in os.walk(datadir): rel_dirpath = dirpath[len(datadir) + 1:] resources.update(osp.join(rel_dirpath, f) for f in filenames) # locate resources and copy them to destination for resource in resources: dest_resource = osp.join(dest, resource) dirname = osp.dirname(dest_resource) if not osp.isdir(dirname): os.makedirs(dirname) resource_dir, resource_path = config.locate_resource(resource) copy(osp.join(resource_dir, resource_path), dest_resource) # handle md5 version subdirectory linkdir(dest, osp.join(dest, config.instance_md5_version())) # ensure generated files are owned by configured uid config.ensure_uid_directory(dest) print('You can use apache rewrite rule below :\n' 'RewriteRule ^/data/(.*) %s/$1 [L]' % dest)
commit() drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWAttribute') drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRelation') if applcubicwebversion < (3, 5, 0) and cubicwebversion >= (3, 5, 0): # check that migration is not doomed rset = rql( 'Any X,Y WHERE X transition_of E, Y transition_of E, ' 'X name N, Y name N, NOT X identity Y', ask_confirm=False) if rset: from logilab.common.shellutils import ASK if not ASK.confirm( 'Migration will fail because of transitions with the same name. ' 'Continue anyway ?'): import sys sys.exit(1) # proceed with migration add_entity_type('Workflow') add_entity_type('BaseTransition') add_entity_type('WorkflowTransition') add_entity_type('SubWorkflowExitPoint') # drop explicit 'State allowed_transition Transition' since it should be # infered due to yams inheritance. However we've to disable the schema # sync hook first to avoid to destroy existing data... try: from cubicweb.hooks import syncschema repo.vreg.unregister(syncschema.AfterDelRelationTypeHook) try:
def run(self, args): import re from logilab.common.shellutils import ASK cubename = args[0] if not re.match('[_A-Za-z][_A-Za-z0-9]*$', cubename): raise BadCommandUsage( 'cube name must be a valid python module name') verbose = self.get('verbose') destdir = self.get('directory') if not destdir: destdir = getcwd() elif not osp.isdir(destdir): print("-> creating cubes directory", destdir) try: mkdir(destdir) except OSError as err: self.fail("failed to create directory %r\n(%s)" % (destdir, err)) default_name = 'cubicweb-%s' % cubename.lower().replace('_', '-') if verbose: distname = input('Debian name for your cube ? [%s]): ' % default_name).strip() if not distname: distname = default_name elif not distname.startswith('cubicweb-'): if ASK.confirm('Do you mean cubicweb-%s ?' % distname): distname = 'cubicweb-' + distname else: distname = default_name if not re.match('[a-z][-a-z0-9]*$', distname): raise BadCommandUsage( 'cube distname should be a valid debian package name') cubedir = osp.join(destdir, distname) if osp.exists(cubedir): self.fail("%s already exists!" % cubedir) skeldir = osp.join(BASEDIR, 'skeleton') longdesc = shortdesc = self['short-description'] or input( 'Enter a short description for your cube: ') if verbose: longdesc = input( 'Enter a long description (leave empty to reuse the short one): ' ) dependencies = { 'cubicweb': '>= %s' % cubicwebversion, } if verbose: dependencies.update(self._ask_for_dependencies()) context = { 'cubename': cubename, 'distname': distname, 'shortdesc': shortdesc, 'longdesc': longdesc or shortdesc, 'dependencies': dependencies, 'version': cubicwebversion, 'year': str(date.today().year), 'author': self['author'], 'author-email': self['author-email'], 'rfc2822-date': datetime.now(tz=UTC).strftime('%a, %d %b %Y %T %z'), 'author-web-site': self['author-web-site'], 'license': self['license'], 'long-license': self.LICENSES[self['license']], } exclude = SKEL_EXCLUDE if self['layout'] == 'simple': exclude += ('sobjects.py*', 'precreate.py*', 'realdb_test*', 'cubes.*', 'uiprops.py*') copy_skeleton(skeldir, cubedir, context, exclude=exclude)
def run(self, args): """run the command with its specific arguments""" from logilab.common.textutils import splitstrip check_options_consistency(self.config) configname = self.config.config cubes, appid = args cubes = splitstrip(cubes) # get the configuration and helper config = cwcfg.config_for(appid, configname, creating=True) cubes = config.expand_cubes(cubes) config.init_cubes(cubes) helper = self.config_helper(config) # check the cube exists try: templdirs = [cwcfg.cube_dir(cube) for cube in cubes] except ConfigurationError as ex: print(ex) print('\navailable cubes:', end=' ') print(', '.join(available_cube_names(cwcfg))) return # create the registry directory for this instance print('\n' + underline_title('Creating the instance %s' % appid)) create_dir(config.apphome) # cubicweb-ctl configuration if not self.config.automatic: print('\n' + underline_title('Configuring the instance (%s.conf)' % configname)) config.input_config('main', self.config.config_level) # configuration'specific stuff print() helper.bootstrap(cubes, self.config.automatic, self.config.config_level) # input for cubes specific options if not self.config.automatic: sections = set(sect.lower() for sect, opt, odict in config.all_options() if 'type' in odict and odict.get('level', 0) <= self.config.config_level) for section in sections: if section not in ('main', 'email', 'web'): print('\n' + underline_title('%s options' % section)) config.input_config(section, self.config.config_level) # write down configuration config.save() print('-> generated config %s' % config.main_config_file()) # handle i18n files structure # in the first cube given from cubicweb import i18n langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] errors = config.i18ncompile(langs) if errors: print('\n'.join(errors)) if self.config.automatic \ or not ASK.confirm('error while compiling message catalogs, ' 'continue anyway ?'): print('creation not completed') return # create the additional data directory for this instance if config.appdatahome != config.apphome: # true in dev mode create_dir(config.appdatahome) create_dir(join(config.appdatahome, 'backup')) if config['uid']: from logilab.common.shellutils import chown # this directory should be owned by the uid of the server process print('set %s as owner of the data directory' % config['uid']) chown(config.appdatahome, config['uid']) print('\n-> creation done for %s\n' % repr(config.apphome)[1:-1]) if not self.config.no_db_create: helper.postcreate(self.config.automatic, self.config.config_level)
def run(self, args): """run the command with its specific arguments""" check_options_consistency(self.config) automatic = self.get('automatic') appid = args.pop() config = ServerConfiguration.config_for(appid) source = config.system_source_config dbname = source['db-name'] driver = source['db-driver'] helper = get_db_helper(driver) if driver == 'sqlite': if os.path.exists(dbname) and (automatic or ASK.confirm( 'Database %s already exists. Drop it?' % dbname)): os.unlink(dbname) elif self.config.create_db: print('\n' + underline_title('Creating the system database')) # connect on the dbms system base to create our base dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER', interactive=not automatic) cursor = dbcnx.cursor() try: if helper.users_support: user = source['db-user'] if not helper.user_exists(cursor, user) and ( automatic or ASK.confirm('Create db user %s ?' % user, default_is_yes=False)): helper.create_user(source['db-user'], source.get('db-password')) print('-> user %s created.' % user) if dbname in helper.list_databases(cursor): if automatic or ASK.confirm( 'Database %s already exists -- ' 'do you want to drop it ?' % dbname): cursor.execute('DROP DATABASE "%s"' % dbname) else: print('you may want to run "cubicweb-ctl db-init ' '--drop %s" manually to continue.' % config.appid) return createdb(helper, source, dbcnx, cursor) dbcnx.commit() print('-> database %s created.' % dbname) except BaseException: dbcnx.rollback() raise cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE/SCHEMA', interactive=not automatic) cursor = cnx.cursor() helper.init_fti_extensions(cursor) namespace = source.get('db-namespace') if namespace and ASK.confirm('Create schema %s in database %s ?' % (namespace, dbname)): helper.create_schema(cursor, namespace) cnx.commit() # postgres specific stuff if driver == 'postgres': # install plpythonu/plpgsql languages langs = ('plpythonu', 'plpgsql') for extlang in langs: if automatic or ASK.confirm('Create language %s ?' % extlang): try: helper.create_language(cursor, extlang) except Exception as exc: print('-> ERROR:', exc) print('-> could not create language %s, ' 'some stored procedures might be unusable' % extlang) cnx.rollback() else: cnx.commit() print( '-> database for instance %s created and necessary extensions installed.' % appid) print() if automatic: CWCTL.run([ 'db-init', '--automatic', '--config-level', '0', config.appid ]) elif ASK.confirm('Run db-init to initialize the system database ?'): CWCTL.run([ 'db-init', '--config-level', str(self.config.config_level), config.appid ]) else: print('-> nevermind, you can do it later with ' '"cubicweb-ctl db-init %s".' % config.appid)