def runCommand(self): self.parseArgs( _(""" Removes association between an existing adminstrative user and hardware or software profile. """)) swprofile = self.getArgs().swprofile hwprofile = self.getArgs().hwprofile admin_username = self.getArgs().adminUsername if swprofile: profile = swprofile api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) else: profile = hwprofile api = HardwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) api.deleteAdmin(profile, admin_username)
def runCommand(self): self.parseArgs(_('Create software profile')) if self.getArgs().name and self.getArgs().deprecated_name: self.getParser().error( 'argument name: not allowed with argument --name') name = self.getArgs().name \ if self.getArgs().name else self.getArgs().deprecated_name # load template if specified with '--template', otherwise build # template tmpl_dict = {} if not self.getArgs().jsonTemplatePath else \ self.load_software_profile_template( self.getArgs().jsonTemplatePath) if name: tmpl_dict['name'] = name if self.getArgs().description: tmpl_dict['description'] = self.getArgs().description if self.getArgs().profileType: tmpl_dict['type'] = self.getArgs().profileType elif 'type' not in tmpl_dict: tmpl_dict['type'] = 'compute' if self.getArgs().tags: tmpl_dict['tags'] = parse_tags(self.getArgs().tags) if hasattr(self.getArgs(), 'osInfo'): tmpl_dict['os'] = { 'name': getattr(self.getArgs(), 'osInfo').getName(), 'version': getattr(self.getArgs(), 'osInfo').getVersion(), 'arch': getattr(self.getArgs(), 'osInfo').getArch(), } if self.getArgs().dataRoot: tmpl_dict['dataRoot'] = self.getArgs().dataRoot if self.getArgs().dataRsync: tmpl_dict['dataRsync'] = self.getArgs().dataRsync sw_profile_spec = SoftwareProfile.getFromDict(tmpl_dict) api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) # Populate 'settings_dict' from command-line arguments settings_dict = { 'bOsMediaRequired': self.getArgs().bOsMediaRequired, 'unmanagedProfile': self.getArgs().unmanaged, } api.createSoftwareProfile(sw_profile_spec, settings_dict)
def runCommand(self): self.parseArgs(_('Copy an existing software profile')) api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) api.copySoftwareProfile(self.getArgs().srcSoftwareProfileName, self.getArgs().dstSoftwareProfileName)
def runCommand(self): self.parseArgs(_('Return list of software profiles configured in' ' the system')) api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) for sp in api.getSoftwareProfileList(tags=self.getArgs().tags): print('%s' % (sp))
def runCommand(self): self.parseArgs(usage=_('Displays software profile details')) if not self.getArgs().name and not self.getArgs().deprecated_name: self.getParser().error( 'the following arguments are required: NAME') if self.getArgs().name and self.getArgs().deprecated_name: self.getParser().error( 'argument name: not allowed with argument --name') name = self.getArgs().name \ if self.getArgs().name else self.getArgs().deprecated_name swprofileapi = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) optionDict = {} if self.getArgs().getNodes: optionDict['nodes'] = True if self.getArgs().getPartitions: optionDict['partitions'] = True if self.getArgs().getComponents: optionDict['components'] = True if self.getArgs().getAdmins: optionDict['admins'] = True if self.getArgs().getDataRoot: optionDict['dataRoot'] = True if self.getArgs().getDataRsync: optionDict['dataRsync'] = True swprofile = swprofileapi.getSoftwareProfile(name, optionDict) if self.getArgs().json: print( json.dumps({ 'softwareprofile': swprofile.getCleanDict(), }, sort_keys=True, indent=4, separators=(',', ': '))) elif self.getArgs().xml: print(swprofile.getXmlRep()) else: self.__console_output(swprofile)
def runCommand(self): self.parseArgs( _(""" Return list of idle software profiles configured on the system. """)) api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) for software_profile in api.getSoftwareProfileList(): if software_profile.getIsIdle(): print(software_profile.getName())
def runCommand(self): self.parseArgs(_(""" Return list of nodes that are using the specified software profile. """)) software_profile_name = self.getArgs().softwareProfile api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) for node in api.getNodeList(software_profile_name): print(str(node))
def runCommand(self): self.parseArgs(_(""" Adjust "software uses hardware" attribute on a software profile. """)) swprofile_name = self.getArgs().swprofile hwprofile_name = self.getArgs().hwprofile api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) api.deleteUsableHardwareProfileFromSoftwareProfile( hwprofile_name, swprofile_name)
def runCommand(self): self.parseArgs( _(""" Returns the list of nodes that have the given component enabled. The result set can be filtered by state and count. """)) state = self.getArgs().state if state: stateList = state.split(',') if self.getArgs().count is not None: numNodes = int(self.getArgs().count) if numNodes == 0: return else: numNodes = -1 api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) optionDict = {} optionDict['nodes'] = True for sp in api.getSoftwareProfileList(): comps = api.getEnabledComponentList(sp.getName()) for comp in comps: if comp.getName() == self.getArgs().componentName and \ comp.getKit().getName() == self.getArgs().kitName and \ comp.getKit().getVersion() == \ self.getArgs().kitVersion and \ comp.getKit().getIteration() == \ self.getArgs().kitIteration: softwareProfile = api.getSoftwareProfile( sp.getName(), optionDict) for node in softwareProfile.getNodes(): if state is None or node.getState() in stateList: print(node.getName()) numNodes = numNodes - 1 if numNodes == 0: return
def runCommand(self): self.parseArgs( _(""" Multiple software profiles can be mapped to a single hardware profile to accomodate a consistent software stack across mulitple resource adapters, for example. All profiles must be mapped in order to be used for active nodes. """)) api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) try: api.addUsableHardwareProfileToSoftwareProfile( self.getArgs().hwprofile, self.getArgs().swprofile) except SoftwareUsesHardwareAlreadyExists: pass
def runCommand(self): self.parseArgs(_('Removes software profile from system.')) if not self.getArgs().name and \ not self.getArgs().softwareProfileName: self.getParser().error( 'the following arguments are required: NAME') if self.getArgs().name and self.getArgs().softwareProfileName: self.getParser().error( 'argument name: not allowed with argument --name') name = self.getArgs().name \ if self.getArgs().name else self.getArgs().softwareProfileName api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) api.deleteSoftwareProfile(name)
def runCommand(self): self.parseArgs( _(""" Associates an existing adminstrative user with a hardware or software profile. """)) swprofile = self.getArgs().swprofile hwprofile = self.getArgs().hwprofile if swprofile and hwprofile: raise InvalidCliRequest( _('Only one of --software-profile and --hardware-profile' ' can be specified.')) if not swprofile and not hwprofile: raise InvalidCliRequest( _('Either --software-profile or --hardware-profile must' ' be specified.')) admin_username = self.getArgs().adminUsername if admin_username is None: raise InvalidCliRequest(_('Missing Admin Username')) if swprofile: profile = swprofile api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) else: profile = hwprofile api = HardwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) api.addAdmin(profile, admin_username)
def runCommand(self): self.parseArgs() comp_name = self.getArgs().component api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) results = {} for sw_profile in api.getSoftwareProfileList(): nodes = [] for component in sw_profile.components: if not self.getArgs().kitName or \ component.kit.name == self.getArgs().kitName: if comp_name and component.name == comp_name: nodes = [node.name for node in sw_profile.nodes] break results[sw_profile.name] = nodes print(yaml.safe_dump(results), end=' ')
def runCommand(self): self.parseArgs( _(""" Display list of components available for software profiles in the system. """)) softwareProfileName = self.__get_software_profile() if softwareProfileName: # Display all components enabled for software profile for c in SoftwareProfileWsApi( username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify).getEnabledComponentList( softwareProfileName): displayComponent(c, c.getKit()) return if self.getArgs().os: try: name, version, arch = self.getArgs().os.split('-', 3) except ValueError: self.getParser().error( 'Malformed argument to --os. Must be in form of' ' NAME-VERSION-ARCH') osinfo = getOsInfo(name, version, arch) else: osinfo = None # Display all components for kit in KitWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify).getKitList(): for c in kit.getComponentList(): if osinfo and osinfo not in c.getOsInfoList() and \ osinfo.getOsFamilyInfo() not in c.getOsFamilyInfoList(): # Exclude those components that cannot be enabled on the # specified operating system. continue displayComponent(c, kit)
def getSoftwareProfileApi(username=None, password=None): """ Creates an object that derives from KitApiInterface """ cm = ConfigManager() if username and password or not cm.isDbAvailable(): from tortuga.wsapi.softwareProfileWsApi import SoftwareProfileWsApi api = SoftwareProfileWsApi(username, password) else: from tortuga.softwareprofile.softwareProfileApi \ import SoftwareProfileApi api = SoftwareProfileApi() return api
def runCommand(self): self.parseArgs(usage=_(""" Updates software profile in the Tortuga system. """)) if not self.getArgs().name and \ not self.getArgs().softwareProfileName: self.getParser().error( 'the following arguments are required: NAME' ) if self.getArgs().name and self.getArgs().softwareProfileName: self.getParser().error( 'argument name: not allowed with argument --name' ) name = self.getArgs().name \ if self.getArgs().name else self.getArgs().softwareProfileName api = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) sp = api.getSoftwareProfile(name, UpdateSoftwareProfileCli.optionDict) if self.getArgs().new_name is not None: sp.setName(self.getArgs().new_name) if self.getArgs().description is not None: sp.setDescription(self.getArgs().description) if self.getArgs().kernel is not None: sp.setKernel(self.getArgs().kernel) if self.getArgs().kernelParameters is not None: sp.setKernelParams(self.getArgs().kernelParameters) if self.getArgs().initrd is not None: sp.setInitrd(self.getArgs().initrd) if self.getArgs().unlock: if self.getArgs().soft_locked is not None: raise InvalidCliRequest( '--soft-locked/--hard-locked arguments and --unlock' ' argument are mutually exclusive' ) sp.setLockedState('Unlocked') if self.getArgs().soft_locked is not None: sp.setLockedState( 'SoftLocked' if self.getArgs().soft_locked else 'HardLocked') if self.getArgs().min_nodes is not None: # update min_nodes value try: if self.getArgs().min_nodes.lower() == 'none': min_nodes = -1 else: min_nodes = int(self.getArgs().min_nodes) except ValueError: raise InvalidCliRequest( 'Invalid argument value for --min-nodes') sp.setMinNodes(min_nodes) else: min_nodes = sp.getMinNodes() if self.getArgs().max_nodes: try: max_nodes = -1 \ if self.getArgs().max_nodes.lower() == 'none' else \ int(self.getArgs().max_nodes) except ValueError: raise InvalidCliRequest( 'Invalid argument value for --max-nodes' ) # update maxNodes value if max_nodes < min_nodes: # do not allow max nodes to be less than min nodes raise InvalidCliRequest( 'Maximum number of allowed nodes must be greater or equal' ' to the mininum number of required nodes' ) sp.setMaxNodes(max_nodes) if self.getArgs().deletePartition is not None: out = TortugaObjectList() for p in sp.getPartitions(): for dp in self.getArgs().deletePartition: if dp == p.getName(): # Skip over this item..its getting deleted break else: # Not a partition we are deleting out.append(p) sp.setPartitions(out) partitionObject = None if self.getArgs().updatePartition: if self.getArgs().addPartition: raise InvalidCliRequest( _('Must provide only one of --update-partition and' ' --add-partition')) for p in sp.getPartitions(): if p.getName() == self.getArgs().updatePartition: partitionObject = p break else: raise InvalidCliRequest( _('Cannot update non-existent partition "%s"') % ( self.getArgs().updatePartition)) if self.getArgs().addPartition: from tortuga.objects.partition import Partition partitionObject = Partition() partitionObject.setName(self.getArgs().addPartition) sp.getPartitions().append(partitionObject) if self.getArgs().device is None or \ self.getArgs().fileSystem is None or \ self.getArgs().size is None: raise InvalidCliRequest( _('--device, --file-system, and --size options required' ' with --add-partition')) if self.getArgs().grow: if not partitionObject: raise InvalidCliRequest( _('The --grow/--no-grow options is only allowed with' ' --update-partition or --add-partition')) partitionObject.setGrow(self.getArgs().grow) if self.getArgs().maxsize: if not partitionObject: raise InvalidCliRequest( _('The --max-size option is only allowed with' ' --update-partition or --add-partition')) partitionObject.setMaxSize(self.getArgs().maxsize) if self.getArgs().device is not None: if partitionObject is None: raise InvalidCliRequest( _('The --device option is only allowed with' ' --update-partition or --add-partition')) partitionObject.setDevice(self.getArgs().device) if self.getArgs().mountPoint is not None: if partitionObject is None: raise InvalidCliRequest( _('--mount-point option only allowed with' ' --update-partition or --add-partition')) partitionObject.setMountPoint(self.getArgs().mountPoint) if self.getArgs().fileSystem is not None: if partitionObject is None: raise InvalidCliRequest( _('The --file-system option only allowed with' ' --update-partition or --add-partition')) partitionObject.setFsType(self.getArgs().fileSystem) if self.getArgs().size is not None: if partitionObject is None: raise InvalidCliRequest( _('--size option only allowed with --update-partition or' ' --add-partition')) partitionObject.setSize(self._parseDiskSize(self.getArgs().size)) if self.getArgs().options is not None: if partitionObject is None: raise InvalidCliRequest( _('--options option only allowed with --update-partition' ' or --add-partition')) partitionObject.setOptions(self.getArgs().options) if self.getArgs().directAttachment is not None: if partitionObject is None: raise InvalidCliRequest( _('--direct-attachment option only allowed with' ' --update-partition or --add-partition')) partitionObject.setDirectAttachment( self.getArgs().directAttachment) if self.getArgs().indirectAttachment is not None: if partitionObject is None: raise InvalidCliRequest( _('--indirect-attachment option only allowed with' ' --update-partition or --add-partition')) partitionObject.setIndirectAttachment( self.getArgs().indirectAttachment) if self.getArgs().diskSize is not None: if partitionObject is None: raise InvalidCliRequest( _('--disk-size option only allowed with' ' --update-partition or --add-partition')) try: partitionObject.setDiskSize( self._parseDiskSize(self.getArgs().diskSize)) except ValueError: raise InvalidCliRequest(_('Invalid --disk-size argument')) if self.getArgs().sanVolume is not None: if partitionObject is None: raise InvalidCliRequest( _('--san-volume option only allowed with' ' --update-partition or --add-partition')) partitionObject.setSanVolume(self.getArgs().sanVolume) if self.getArgs().preserve is None: if self.getArgs().addPartition is not None: raise InvalidCliRequest( _('--preserve or --no-preserve must be specified when' ' adding a new partition.')) else: if partitionObject is None: raise InvalidCliRequest( _('--preserve and --no-preserve options are only allowed' ' with --update-partition or --add-partition')) partitionObject.setPreserve(self.getArgs().preserve) if self.getArgs().bootLoader is None: if self.getArgs().addPartition is not None: raise InvalidCliRequest( _('--boot-loader or --no-boot-loader must be specified' ' when adding a new partition.')) else: if partitionObject is None: raise InvalidCliRequest( _('--boot-loader and --no-boot-loader options only' ' allowed with --update-partition or --add-partition')) partitionObject.setBootLoader(self.getArgs().bootLoader) if self.getArgs().tags: tags = sp.getTags() tags.update(parse_tags(self.getArgs().tags)) sp.setTags(tags) print(tags) # DEBUG if self.getArgs().remove_tags: tags = sp.getTags() for string in self.getArgs().remove_tags: for tag_name in string.split(','): if tag_name in tags.keys(): tags.pop(tag_name) sp.setTags(tags) print(tags) # DEBUG if self.getArgs().dataRoot is not None: sp.setDataRoot(self.getArgs().dataRoot) if self.getArgs().dataRsync is not None: sp.setDataRsync(self.getArgs().dataRsync) api.updateSoftwareProfile(sp)
def parseArgs(self, usage=None): excl_option_group = self.getParser().add_mutually_exclusive_group( required=True) excl_option_group.add_argument('--software-profile', dest='softwareProfileName', metavar='NAME', help=_('Software profile to act on')) excl_option_group.add_argument( '-p', dest='applyToInstaller', action='store_true', default=False, help=_('Perform action on Tortuga installer software profile')) self.addOption('--kit-name', dest='kitName', metavar='NAME', help=_('kit name')) self.addOption('--kit-version', dest='kitVersion', metavar='VERSION', help=_('kit version')) self.addOption('--kit-iteration', dest='kitIteration', metavar='ITERATION', help=_('kit iteration')) self.addOption('--comp-name', dest='compName', metavar='NAME', help=_('component name')) self.addOption('--comp-version', dest='compVersion', metavar='VERSION', help=_('component version')) self.addOption('--no-sync', dest='sync', action='store_false', default=True, help=argparse.SUPPRESS) self.addOption('args', nargs='*') super().parseArgs(usage=usage) if self.getArgs().args: if len(self.getArgs().args) == 1 and \ '-' not in self.getArgs().args[0]: # The first argument is assumed to be the component name. self.component_name = self.getArgs().args[0] self.kit_name = self.getArgs().kitName self.kit_version = self.getArgs().kitVersion self.kit_iteration = self.getArgs().kitIteration else: # Get given Kit information kit_name = None if len(self.getArgs().args) >= 1: kit_name = self.getArgs().args[0] # get installed kit name/version/iteration using name only self.kit_name, self.kit_version, self.kit_iteration = \ self.getKitNameVersionIteration(kit_name) # Get given Component information if len(self.getArgs().args) >= 2: vals = self.getArgs().args[1].rsplit('-', 1) self.component_name = vals[0] if not self.component_name: self.usage(_('Missing component name')) else: # copy args from command-line self.kit_name = self.getArgs().kitName self.kit_version = self.getArgs().kitVersion self.kit_iteration = self.getArgs().getIteration self.component_name = self.getArgs().compName # Get the given software profile information self.software_profile_name = self.__get_software_profile_name() self.software_profile_api = SoftwareProfileWsApi( username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify)
def runCommand(self): self.parseArgs() if not self.getArgs().name and \ not self.getArgs().hardwareProfileName: self.getParser().error( 'the following arguments are required: NAME') if self.getArgs().name and self.getArgs().hardwareProfileName: self.getParser().error( 'argument name: not allowed with argument --name') name = self.getArgs().name \ if self.getArgs().name else self.getArgs().hardwareProfileName api = HardwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) spApi = SoftwareProfileWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) nodeApi = NodeWsApi(username=self.getUsername(), password=self.getPassword(), baseurl=self.getUrl(), verify=self._verify) hp = api.getHardwareProfile(name, UpdateHardwareProfileCli.optionDict) if self.getArgs().newName is not None: hp.setName(self.getArgs().newName) if self.getArgs().description is not None: hp.setDescription(self.getArgs().description) if self.getArgs().nameFormat is not None: hp.setNameFormat(self.getArgs().nameFormat) if self.getArgs().kernel is not None: hp.setKernel(self.getArgs().kernel) if self.getArgs().kernelParameters is not None: hp.setKernelParams(self.getArgs().kernelParameters) if self.getArgs().initrd is not None: hp.setInitrd(self.getArgs().initrd) if self.getArgs().soAllowed is not None: if self.getArgs().soAllowed.lower() == _('true'): hp.setSoftwareOverrideAllowed(True) elif self.getArgs().soAllowed.lower() == _('false'): hp.setSoftwareOverrideAllowed(False) else: raise InvalidCliRequest( _('--software-override-allowed must be either "true" or' ' "false".')) if self.getArgs().idleProfile is not None and \ self.getArgs().bUnsetIdleProfile: raise InvalidCliRequest( _('Conflicting options --idle-software-profile and' ' --unset-idle-software-profile')) if self.getArgs().idleProfile is not None: sp = spApi.getSoftwareProfile(self.getArgs().idleProfile) hp.setIdleSoftwareProfileId(sp.getId()) if self.getArgs().bUnsetIdleProfile: hp.setIdleSoftwareProfileId(None) if self.getArgs().location is not None: hp.setLocation(self.getArgs().location) if self.getArgs().localBootParameters is not None: hp.setLocalBootParams(self.getArgs().localBootParameters) if self.getArgs().cost is not None: hp.setCost(self.getArgs().cost) if self.getArgs().resourceAdapter: resourceAdapter = ResourceAdapter( name=self.getArgs().resourceAdapter) hp.setResourceAdapter(resourceAdapter) if self.getArgs().default_adapter_config: hp.setDefaultResourceAdapterConfig( self.getArgs().default_adapter_config) if self.getArgs().deletePNic is not None: out = TortugaObjectList() for nic in hp.getProvisioningNics(): for dnic in self.getArgs().deletePNic: if dnic == nic.getIp(): # Skip over this item..its getting deleted break else: # Not a NIC we are deleting out.append(nic) hp.setProvisioningNics(out) if self.getArgs().addPNic is not None: for nicIp in self.getArgs().addPNic: nicsNode = nodeApi.getNodeByIp(nicIp) if nicsNode is not None: for nic in nicsNode.getNics(): if nic.getIp() == nicIp: hp.getProvisioningNics().append(nic) break if self.getArgs().deleteNetwork is not None: # Make sure we actually delete a network out = TortugaObjectList() out.extend(hp.getNetworks()) for netstring in self.getArgs().deleteNetwork: try: dnet, dmask, ddev = netstring.split('/') except ValueError: raise InvalidCliRequest( _('Incorrect input format for --delete-network' ' ("address/mask/device")')) for network in hp.getNetworks(): if dnet == network.getAddress() and \ dmask == network.getNetmask() and \ ddev == network.getNetworkDevice().getName(): # Skip over this item..its getting deleted for n in out: if n.getId() == network.getId(): out.remove(n) break break else: # Not a NIC we are deleting print('Ignoring deletion of non-existent network:' ' %s/%s/%s' % (dnet, dmask, ddev)) hp.setNetworks(out) if self.getArgs().addNetwork: for netstring in self.getArgs().addNetwork: try: anet, amask, adev = netstring.split('/') except ValueError: raise InvalidCliRequest( _('Incorrect input format for --add-network' ' ("address/mask/device")')) network = Network() networkDevice = NetworkDevice() networkDevice.setName(adev) network.setAddress(anet) network.setNetmask(amask) network.setNetworkDevice(networkDevice) hp.getNetworks().append(network) if self.getArgs().tags: tags = hp.getTags() tags.update(parse_tags(self.getArgs().tags)) hp.setTags(tags) if self.getArgs().remove_tags: tags = hp.getTags() for string in self.getArgs().remove_tags: for tag_name in string.split(','): if tag_name in tags.keys(): tags.pop(tag_name) hp.setTags(tags) api.updateHardwareProfile(hp)