def _rec_resolve_profile_include(self, profile): """ recursively resolve include of other profiles's: * dotfiles * actions """ this_profile = self.profiles[profile] # include dotfiles = this_profile.get(self.key_profile_dotfiles, []) actions = this_profile.get(self.key_profile_actions, []) includes = this_profile.get(self.key_profile_include, None) if not includes: # nothing to include return dotfiles, actions if self.debug: self.log.dbg('{} includes: {}'.format(profile, ','.join(includes))) self.log.dbg('{} dotfiles before include: {}'.format( profile, dotfiles)) self.log.dbg('{} actions before include: {}'.format( profile, actions)) seen = [] for i in uniq_list(includes): # ensure no include loop occurs if i in seen: raise YamlException('\"include loop\"') seen.append(i) # included profile even exists if i not in self.profiles.keys(): self.log.warn('include unknown profile: {}'.format(i)) continue # recursive resolve o_dfs, o_actions = self._rec_resolve_profile_include(i) # merge dotfile keys dotfiles.extend(o_dfs) this_profile[self.key_profile_dotfiles] = uniq_list(dotfiles) # merge actions keys actions.extend(o_actions) this_profile[self.key_profile_actions] = uniq_list(actions) dotfiles = this_profile.get(self.key_profile_dotfiles, []) actions = this_profile.get(self.key_profile_actions, []) if self.debug: self.log.dbg('{} dotfiles after include: {}'.format( profile, dotfiles)) self.log.dbg('{} actions after include: {}'.format( profile, actions)) # since dotfiles and actions are resolved here # and variables have been already done at the beginning # of the parsing, we can clear these include self.profiles[profile][self.key_profile_include] = None return dotfiles, actions
def _comp_dir(self, left, right, ignore): """compare a directory""" if self.debug: self.log.dbg('compare directory {} with {}'.format(left, right)) if not os.path.exists(right): return '' if must_ignore([left, right], ignore, debug=self.debug): if self.debug: self.log.dbg('ignoring diff {} and {}'.format(left, right)) return '' if not os.path.isdir(right): return '\"{}\" is a file\n'.format(right) if self.debug: self.log.dbg('compare {} and {}'.format(left, right)) ret = [] comp = filecmp.dircmp(left, right) # handle files only in deployed dir for i in comp.left_only: if must_ignore([os.path.join(left, i)], ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist on local\n'.format(i)) # handle files only in dotpath dir for i in comp.right_only: if must_ignore([os.path.join(right, i)], ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) # same left and right but different type funny = comp.common_funny for i in funny: lfile = os.path.join(left, i) rfile = os.path.join(right, i) if must_ignore([lfile, rfile], ignore, debug=self.debug): continue short = os.path.basename(lfile) # file vs dir ret.append('=> different type: \"{}\"\n'.format(short)) # content is different funny = comp.diff_files funny.extend(comp.funny_files) funny = uniq_list(funny) for i in funny: lfile = os.path.join(left, i) rfile = os.path.join(right, i) if must_ignore([lfile, rfile], ignore, debug=self.debug): continue diff = self._diff(lfile, rfile, header=True) ret.append(diff) # recursively compare subdirs for i in comp.common_dirs: subleft = os.path.join(left, i) subright = os.path.join(right, i) ret.extend(self._comp_dir(subleft, subright, ignore)) return ''.join(ret)
def _apply_args_update(self): """update specifics""" self.update_path = self.args['<path>'] self.update_iskey = self.args['--key'] self.update_ignore = self.args['--ignore'] self.update_ignore.extend(self.upignore) self.update_ignore.append('*{}'.format(self.install_backup_suffix)) self.update_ignore = uniq_list(self.update_ignore) self.update_showpatch = self.args['--show-patch']
def _apply_args_import(self): """import specifics""" self.import_path = self.args['<path>'] self.import_as = self.args['--as'] self.import_mode = self.args['--preserve-mode'] or self.chmod_on_import self.import_ignore = self.args['--ignore'] self.import_ignore.extend(self.impignore) self.import_ignore.append('*{}'.format(self.install_backup_suffix)) self.import_ignore = uniq_list(self.import_ignore)
def _apply_args_compare(self): """compare specifics""" self.compare_focus = self.args['--file'] self.compare_ignore = self.args['--ignore'] self.compare_ignore.extend(self.cmpignore) self.compare_ignore.append('*{}'.format(self.install_backup_suffix)) self.compare_ignore = uniq_list(self.compare_ignore) self.compare_fileonly = self.args['--file-only'] self.ignore_missing_in_dotdrop = self.ignore_missing_in_dotdrop or \ self.args['--ignore-missing']
def cmd_detail(o): """list details on all files for all dotfile entries""" if o.profile not in [p.key for p in o.profiles]: LOG.warn('unknown profile \"{}\"'.format(o.profile)) return dotfiles = o.dotfiles if o.detail_keys: # filtered dotfiles to install uniq = uniq_list(o.details_keys) dotfiles = [d for d in dotfiles if d.key in uniq] LOG.emph('dotfiles details for profile \"{}\":\n'.format(o.profile)) for d in dotfiles: _detail(o.dotpath, d) LOG.log('')
def _template_dotfiles_paths(self): """template dotfiles paths""" if self._debug: self._dbg('templating dotfiles paths') dotfiles = self.dotfiles.copy() # make sure no dotfiles path is None for dotfile in dotfiles.values(): src = dotfile[self.key_dotfile_src] if src is None: dotfile[self.key_dotfile_src] = '' dst = dotfile[self.key_dotfile_dst] if dst is None: dotfile[self.key_dotfile_dst] = '' # only keep dotfiles related to the selected profile pdfs = [] pro = self.profiles.get(self._profile, []) if pro: pdfs = list(pro.get(self.key_profile_dotfiles, [])) for addpro in self._inc_profiles: pro = self.profiles.get(addpro, []) if not pro: continue pdfsalt = pro.get(self.key_profile_dotfiles, []) pdfs.extend(pdfsalt) pdfs = uniq_list(pdfs) if self.key_all not in pdfs: # take a subset of the dotfiles newdotfiles = {} for k, v in dotfiles.items(): if k in pdfs: newdotfiles[k] = v dotfiles = newdotfiles for dotfile in dotfiles.values(): # src src = dotfile[self.key_dotfile_src] newsrc = self.resolve_dotfile_src(src, templater=self._tmpl) dotfile[self.key_dotfile_src] = newsrc # dst dst = dotfile[self.key_dotfile_dst] newdst = self.resolve_dotfile_dst(dst, templater=self._tmpl) dotfile[self.key_dotfile_dst] = newdst
def cmd_install(o): """install dotfiles for this profile""" dotfiles = o.dotfiles prof = o.conf.get_profile() adapt_workers(o, LOG) pro_pre_actions = prof.get_pre_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else [] if o.install_keys: # filtered dotfiles to install uniq = uniq_list(o.install_keys) dotfiles = [d for d in dotfiles if d.key in uniq] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return False # the installer tmpdir = None if o.install_temporary: tmpdir = get_tmpdir() installed = [] # execute profile pre-action if o.debug: LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) t = _get_templater(o) ret, err = action_executor(o, pro_pre_actions, [], t, post=False)() if not ret: return False # install each dotfile if o.workers > 1: # in parallel if o.debug: LOG.dbg('run with {} workers'.format(o.workers)) ex = futures.ThreadPoolExecutor(max_workers=o.workers) wait_for = [] for dotfile in dotfiles: j = ex.submit(_dotfile_install, o, dotfile, tmpdir=tmpdir) wait_for.append(j) # check result for f in futures.as_completed(wait_for): r, key, err = f.result() if r: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) else: # sequentially for dotfile in dotfiles: r, key, err = _dotfile_install(o, dotfile, tmpdir=tmpdir) # check result if r: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) # execute profile post-action if len(installed) > 0 or o.install_force_action: if o.debug: msg = 'run {} profile post actions' LOG.dbg(msg.format(len(pro_post_actions))) ret, err = action_executor(o, pro_post_actions, [], t, post=False)() if not ret: return False if o.debug: LOG.dbg('install done: installed \"{}\"'.format(','.join(installed))) if o.install_temporary: LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) return True
def _apply_args(self): """apply cli args as attribute""" # the commands self.cmd_profiles = self.args['profiles'] self.cmd_files = self.args['files'] self.cmd_install = self.args['install'] self.cmd_compare = self.args['compare'] self.cmd_import = self.args['import'] self.cmd_update = self.args['update'] self.cmd_detail = self.args['detail'] self.cmd_remove = self.args['remove'] # adapt attributes based on arguments self.safe = not self.args['--force'] # import link default value self.import_link = self.link_on_import if self.args['--link']: # overwrite default import link with cli switch link = self.args['--link'] if link not in OPT_LINK.keys(): self.log.err('bad option for --link: {}'.format(link)) sys.exit(USAGE) self.import_link = OPT_LINK[link] # "files" specifics self.files_templateonly = self.args['--template'] self.files_grepable = self.args['--grepable'] # "profiles" specifics self.profiles_grepable = self.args['--grepable'] # "install" specifics self.install_force_action = self.args['--force-actions'] self.install_temporary = self.args['--temp'] self.install_keys = self.args['<key>'] self.install_diff = not self.args['--nodiff'] self.install_showdiff = self.showdiff or self.args['--showdiff'] self.install_backup_suffix = BACKUP_SUFFIX self.install_default_actions_pre = [ a for a in self.default_actions if a.kind == Action.pre ] self.install_default_actions_post = [ a for a in self.default_actions if a.kind == Action.post ] self.install_ignore = self.instignore # "compare" specifics self.compare_focus = self.args['--file'] self.compare_ignore = self.args['--ignore'] self.compare_ignore.extend(self.cmpignore) self.compare_ignore.append('*{}'.format(self.install_backup_suffix)) self.compare_ignore = uniq_list(self.compare_ignore) # "import" specifics self.import_path = self.args['<path>'] self.import_as = self.args['--as'] # "update" specifics self.update_path = self.args['<path>'] self.update_iskey = self.args['--key'] self.update_ignore = self.args['--ignore'] self.update_ignore.extend(self.upignore) self.update_ignore.append('*{}'.format(self.install_backup_suffix)) self.update_ignore = uniq_list(self.update_ignore) self.update_showpatch = self.args['--show-patch'] # "detail" specifics self.detail_keys = self.args['<key>'] # "remove" specifics self.remove_path = self.args['<path>'] self.remove_iskey = self.args['--key']
def _comp_dir(self, local_path, deployed_path, ignore): """compare a directory""" if self.debug: self.log.dbg('compare directory {} with {}'.format( local_path, deployed_path, )) if not os.path.exists(deployed_path): return '' if (self.ignore_missing_in_dotdrop and not os.path.exists(local_path)) \ or must_ignore([local_path, deployed_path], ignore, debug=self.debug): if self.debug: self.log.dbg('ignoring diff {} and {}'.format( local_path, deployed_path, )) return '' if not os.path.isdir(deployed_path): return '\"{}\" is a file\n'.format(deployed_path) if self.debug: self.log.dbg('compare {} and {}'.format(local_path, deployed_path)) ret = [] comp = filecmp.dircmp(local_path, deployed_path) # handle files only in deployed dir for i in comp.left_only: if self.ignore_missing_in_dotdrop: continue if must_ignore([os.path.join(local_path, i)], ignore, debug=self.debug): continue ret.append('=> \"{}\" does not exist on destination\n'.format(i)) # handle files only in dotpath dir for i in comp.right_only: if must_ignore([os.path.join(deployed_path, i)], ignore, debug=self.debug): continue if not self.ignore_missing_in_dotdrop: ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i)) # same local_path and deployed_path but different type funny = comp.common_funny for i in funny: source_file = os.path.join(local_path, i) deployed_file = os.path.join(deployed_path, i) if self.ignore_missing_in_dotdrop and \ not os.path.exists(source_file): continue if must_ignore([source_file, deployed_file], ignore, debug=self.debug): continue short = os.path.basename(source_file) # file vs dir ret.append('=> different type: \"{}\"\n'.format(short)) # content is different funny = comp.diff_files funny.extend(comp.funny_files) funny = uniq_list(funny) for i in funny: source_file = os.path.join(local_path, i) deployed_file = os.path.join(deployed_path, i) if self.ignore_missing_in_dotdrop and \ not os.path.exists(source_file): continue if must_ignore([source_file, deployed_file], ignore, debug=self.debug): continue ret.append(self._diff(source_file, deployed_file, header=True)) # recursively compare subdirs for i in comp.common_dirs: sublocal_path = os.path.join(local_path, i) subdeployed_path = os.path.join(deployed_path, i) ret.extend(self._comp_dir(sublocal_path, subdeployed_path, ignore)) return ''.join(ret)
def _rec_resolve_profile_include(self, profile): """ recursively resolve include of other profiles's: * dotfiles * actions returns dotfiles, actions """ this_profile = self.profiles[profile] # considered profile content dotfiles = this_profile.get(self.key_profile_dotfiles, []) or [] actions = this_profile.get(self.key_profile_actions, []) or [] includes = this_profile.get(self.key_profile_include, []) or [] if not includes: # nothing to include return dotfiles, actions if self._debug: self._dbg('{} includes {}'.format(profile, ','.join(includes))) self._dbg('{} dotfiles before include: {}'.format( profile, dotfiles)) self._dbg('{} actions before include: {}'.format(profile, actions)) seen = [] for i in uniq_list(includes): if self._debug: self._dbg('resolving includes "{}" <- "{}"'.format(profile, i)) # ensure no include loop occurs if i in seen: raise YamlException('\"include loop\"') seen.append(i) # included profile even exists if i not in self.profiles.keys(): self._log.warn('include unknown profile: {}'.format(i)) continue # recursive resolve if self._debug: self._dbg( 'recursively resolving includes for profile "{}"'.format( i)) o_dfs, o_actions = self._rec_resolve_profile_include(i) # merge dotfile keys if self._debug: self._dbg('Merging dotfiles {} <- {}: {} <- {}'.format( profile, i, dotfiles, o_dfs)) dotfiles.extend(o_dfs) this_profile[self.key_profile_dotfiles] = uniq_list(dotfiles) # merge actions keys if self._debug: self._dbg('Merging actions {} <- {}: {} <- {}'.format( profile, i, actions, o_actions)) actions.extend(o_actions) this_profile[self.key_profile_actions] = uniq_list(actions) dotfiles = this_profile.get(self.key_profile_dotfiles, []) actions = this_profile.get(self.key_profile_actions, []) if self._debug: self._dbg('{} dotfiles after include: {}'.format( profile, dotfiles)) self._dbg('{} actions after include: {}'.format(profile, actions)) # since included items are resolved here # we can clear these include self.profiles[profile][self.key_profile_include] = [] return dotfiles, actions
def cmd_install(opts): """install dotfiles for this profile""" dotfiles = opts.dotfiles prof = opts.conf.get_profile() adapt_workers(opts, LOG) pro_pre_actions = prof.get_pre_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else [] if opts.install_keys: # filtered dotfiles to install uniq = uniq_list(opts.install_keys) dotfiles = [d for d in dotfiles if d.key in uniq] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(opts.profile)) return False LOG.dbg('dotfiles registered for install: {}'.format( [k.key for k in dotfiles])) # the installer tmpdir = None if opts.install_temporary: tmpdir = get_tmpdir() installed = [] # clear the workdir if opts.install_clear_workdir and not opts.dry: LOG.dbg('clearing the workdir under {}'.format(opts.workdir)) for root, _, files in os.walk(opts.workdir): for file in files: fpath = os.path.join(root, file) removepath(fpath, logger=LOG) # execute profile pre-action LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) templ = _get_templater(opts) ret, _ = action_executor(opts, pro_pre_actions, [], templ, post=False)() if not ret: return False # install each dotfile if opts.workers > 1: # in parallel LOG.dbg('run with {} workers'.format(opts.workers)) ex = futures.ThreadPoolExecutor(max_workers=opts.workers) wait_for = [] for dotfile in dotfiles: j = ex.submit(_dotfile_install, opts, dotfile, tmpdir=tmpdir) wait_for.append(j) # check result for fut in futures.as_completed(wait_for): tmpret, key, err = fut.result() # check result if tmpret: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) else: # sequentially for dotfile in dotfiles: tmpret, key, err = _dotfile_install(opts, dotfile, tmpdir=tmpdir) # check result if tmpret: installed.append(key) elif err: LOG.err('installing \"{}\" failed: {}'.format(key, err)) # execute profile post-action if len(installed) > 0 or opts.install_force_action: msg = 'run {} profile post actions' LOG.dbg(msg.format(len(pro_post_actions))) ret, _ = action_executor(opts, pro_post_actions, [], templ, post=False)() if not ret: return False LOG.dbg('install done: installed \"{}\"'.format(','.join(installed))) if opts.install_temporary: LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) return True
def cmd_install(o): """install dotfiles for this profile""" dotfiles = o.dotfiles prof = o.conf.get_profile() pro_pre_actions = prof.get_pre_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else [] if o.install_keys: # filtered dotfiles to install uniq = uniq_list(o.install_keys) dotfiles = [d for d in dotfiles if d.key in uniq] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return False t = Templategen(base=o.dotpath, variables=o.variables, func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) tmpdir = None if o.install_temporary: tmpdir = get_tmpdir() inst = Installer(create=o.create, backup=o.backup, dry=o.dry, safe=o.safe, base=o.dotpath, workdir=o.workdir, diff=o.install_diff, debug=o.debug, totemp=tmpdir, showdiff=o.install_showdiff, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) installed = 0 tvars = t.add_tmp_vars() # execute profile pre-action if o.debug: LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) ret, err = action_executor(o, pro_pre_actions, [], t, post=False)() if not ret: return False # install each dotfile for dotfile in dotfiles: # add dotfile variables t.restore_vars(tvars) newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) preactions = [] if not o.install_temporary: preactions.extend(dotfile.get_pre_actions()) defactions = o.install_default_actions_pre pre_actions_exec = action_executor(o, preactions, defactions, t, post=False) if o.debug: LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key)) LOG.dbg(dotfile.prt()) if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: r, err = inst.link(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec) elif hasattr(dotfile, 'link') and \ dotfile.link == LinkTypes.LINK_CHILDREN: r, err = inst.link_children(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec) else: src = dotfile.src tmp = None if dotfile.trans_r: tmp = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmp: continue src = tmp ignores = list(set(o.install_ignore + dotfile.instignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) r, err = inst.install(t, src, dotfile.dst, actionexec=pre_actions_exec, noempty=dotfile.noempty, ignore=ignores) if tmp: tmp = os.path.join(o.dotpath, tmp) if os.path.exists(tmp): remove(tmp) if r: # dotfile was installed if not o.install_temporary: defactions = o.install_default_actions_post postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() installed += 1 elif not r: # dotfile was NOT installed if o.install_force_action: # pre-actions if o.debug: LOG.dbg('force pre action execution ...') pre_actions_exec() # post-actions if o.debug: LOG.dbg('force post action execution ...') postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() if err: LOG.err('installing \"{}\" failed: {}'.format( dotfile.key, err)) # execute profile post-action if installed > 0 or o.install_force_action: if o.debug: msg = 'run {} profile post actions' LOG.dbg(msg.format(len(pro_post_actions))) ret, err = action_executor(o, pro_post_actions, [], t, post=False)() if not ret: return False if o.debug: LOG.dbg('install done') if o.install_temporary: LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(installed)) return True
def _rec_resolve_profile_include(self, profile): """ recursively resolve include of other profiles's: * dotfiles * actions * variables * dynvariables variables/dynvariables are directly merged with the global variables (self.variables) if these are included in the selected profile returns dotfiles, actions, variables, dynvariables """ this_profile = self.profiles[profile] # considered profile content dotfiles = this_profile.get(self.key_profile_dotfiles, []) or [] actions = this_profile.get(self.key_profile_actions, []) or [] includes = this_profile.get(self.key_profile_include, []) or [] pvars = this_profile.get(self.key_profile_variables, {}) or {} pdvars = this_profile.get(self.key_profile_dvariables, {}) or {} if not includes: # nothing to include return dotfiles, actions, pvars, pdvars if self.debug: self.log.dbg('{} includes {}'.format(profile, ','.join(includes))) self.log.dbg('{} dotfiles before include: {}'.format( profile, dotfiles)) self.log.dbg('{} actions before include: {}'.format( profile, actions)) self.log.dbg('{} variables before include: {}'.format( profile, pvars)) self.log.dbg('{} dynvariables before include: {}'.format( profile, pdvars)) seen = [] for i in uniq_list(includes): if self.debug: self.log.dbg('resolving includes "{}" <- "{}"'.format( profile, i)) # ensure no include loop occurs if i in seen: raise YamlException('\"include loop\"') seen.append(i) # included profile even exists if i not in self.profiles.keys(): self.log.warn('include unknown profile: {}'.format(i)) continue # recursive resolve if self.debug: self.log.dbg( 'recursively resolving includes for profile "{}"'.format( i)) o_dfs, o_actions, o_v, o_dv = self._rec_resolve_profile_include(i) # merge dotfile keys if self.debug: self.log.dbg('Merging dotfiles {} <- {}: {} <- {}'.format( profile, i, dotfiles, o_dfs)) dotfiles.extend(o_dfs) this_profile[self.key_profile_dotfiles] = uniq_list(dotfiles) # merge actions keys if self.debug: self.log.dbg('Merging actions {} <- {}: {} <- {}'.format( profile, i, actions, o_actions)) actions.extend(o_actions) this_profile[self.key_profile_actions] = uniq_list(actions) # merge variables if self.debug: self.log.dbg('Merging variables {} <- {}: {} <- {}'.format( profile, i, dict(pvars), dict(o_v))) pvars = self._merge_dict(o_v, pvars) this_profile[self.key_profile_variables] = pvars # merge dynvariables if self.debug: self.log.dbg( 'Merging dynamic variables {} <- {}: {} <- {}'.format( profile, i, dict(pdvars), dict(o_dv))) pdvars = self._merge_dict(o_dv, pdvars) this_profile[self.key_profile_dvariables] = pdvars dotfiles = this_profile.get(self.key_profile_dotfiles, []) actions = this_profile.get(self.key_profile_actions, []) pvars = this_profile.get(self.key_profile_variables, {}) or {} pdvars = this_profile.get(self.key_profile_dvariables, {}) or {} if self.debug: self.log.dbg('{} dotfiles after include: {}'.format( profile, dotfiles)) self.log.dbg('{} actions after include: {}'.format( profile, actions)) self.log.dbg('{} variables after include: {}'.format( profile, pvars)) self.log.dbg('{} dynvariables after include: {}'.format( profile, pdvars)) if profile == self.profile: # Only for the selected profile, we execute dynamic variables and # we merge variables/dynvariables into the global variables self._shell_exec_dvars(pdvars.keys(), pdvars) self.variables = self._merge_dict(pvars, self.variables) self.variables = self._merge_dict(pdvars, self.variables) # since included items are resolved here # we can clear these include self.profiles[profile][self.key_profile_include] = None return dotfiles, actions, pvars, pdvars