classAction:""" Abstract action base class """action_out=Nonedef__init__(self,args_dict):self.profile=args_dict['profile']self.input_dir=pl.Path(args_dict['input'])self.output_dir=pl.Path(args_dict['output'])self.debos=args_dict['debos']self.debos_output_dir=pl.Path(args_dict['debos_output'])/self.profileself.result={'architecture':'','chroot_default':False,'actions':[],'pre-actions':[],'post-actions':[],}@staticmethoddef_print(text,header=None,width=68):""" Print text wrapped """initial_indent=' 'ifheader:print(f' {header}')initial_indent=' 'wrapped_text=textwrap.wrap(text,width=width,initial_indent=initial_indent,subsequent_indent=' ',break_on_hyphens=False,)print('\n'.join(wrapped_text))def_action_inform(self,props):""" Inform on action start action status """ifaction_type:=props.get('action'):print(f' {action_type} action '.upper().center(70,'='))ifdescription:=props.get('description'):self._print(f'Description: {description}')forsrc_filein('recipe','preconf','source','command','script'):ifsrc:=props.get(src_file):self._print(f'{src_file.capitalize()}: {src}')def_read_substitute(self,filename,substitutions):""" Read string from file and perform jinja2 substitutions """input_file=self.input_dir/filenamewithopen(input_file,mode='r',encoding='utf-8')asfile:template=jinja2.Template(file.read())returntemplate.render(substitutions)def_write_action(self,string,extension,directory='profiles',no_duplicate=False):""" Append string to profile file """filename=self.profile+'.'+extensionoutput_file=self.output_dir/directory/filenameifno_duplicateandoutput_file.is_file():withopen(output_file,mode='r',encoding='utf-8')asfile:ifstringinfile.read():returnwithopen(output_file,mode='a',encoding='utf-8')asfile:file.write(string)defperform_action(self,props):""" Perform the action specific tasks and return result """raiseNotImplementedError('Action is an abstract base class!')defperform_debos_action(self,props):""" Process debos action specific tasks and return result """raiseNotImplementedError('Action is an abstract base class!')defexecute(self,props):""" Execute an action """self._action_inform(props)ifself.debos:result=self.perform_debos_action(props)else:result=self.perform_action(props)ifresult:ifnotself.action_out:self.action_out=props['action']self._write_action(result,self.action_out)defappend_result(self,new_result:dict,key='actions'):""" Append a new result to the result list """added_result=copy.deepcopy(new_result)self.result[key].append(added_result)defextend_result(self,new_result:dict,key='actions'):""" Append a new result to the result list """added_result=copy.deepcopy(new_result)self.result[key].extend(added_result)defprepend_result(self,new_result:dict,key='actions'):""" Prepend a new result to the result list """added_result=copy.deepcopy(new_result)self.result[key].insert(0,added_result)defcombine_results(self,result):""" Combine two result sets """forkeyin('actions','pre-actions','post-actions'):self.result[key].extend(result[key])foroptionin('architecture','chroot_default'):ifresult.get(option):self.result[option]=result[option]defunique_filename(self,base='script',ext='sh',description=None):""" Create a name from description or using uuid """name=base+'_'ifdescription:name+=description.lower()else:name+=str(uuid.uuid4().hex)return"".join([xifx.isalnum()else"_"forxinname])+'.'+ext