diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2ee597045..3c15fe772 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -295,6 +295,7 @@ ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), ('tags:delete', 'SoftLayer.CLI.tags.delete:cli'), + ('tags:taggable', 'SoftLayer.CLI.tags.taggable:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index 5c6ccbc08..f72e4687f 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -1,30 +1,31 @@ -"""List Tags.""" +"""Delete Tags.""" # :license: MIT, see LICENSE for more details. import click -from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - -# pylint: disable=unnecessary-lambda - -from pprint import pprint as pp @click.command() -@click.option('-id', required=False, show_default=False, type=int, help='identifier') -@click.option('--name', required=False, default=False, type=str, show_default=False, help='tag name') +@click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') @environment.pass_env -def cli(env, id, name): - """delete Tag.""" +def cli(env, identifier, name): + """Delete a Tag. Tag names that contain spaces need to be encased in quotes""" tag_manager = TagManager(env.client) - - if not name and id is not None: - tag_name = tag_manager.get_tag(id) - tag_manager.delete_tag(tag_name['name']) - if name and id is None: - tag_manager.delete_tag(name) + tag_name = identifier + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: + tag = tag_manager.get_tag(tag_id) + tag_name = tag.get('name', None) + + + result = tag_manager.delete_tag(tag_name) + if result: + click.secho("Tag {} has been removed".format(tag_name), fg='green') + else: + click.secho("Failed to remove tag {}".format(tag_name), fg='red') \ No newline at end of file diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py index eb22bfada..7c397f431 100644 --- a/SoftLayer/CLI/tags/details.py +++ b/SoftLayer/CLI/tags/details.py @@ -10,13 +10,16 @@ @click.command() @click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') @environment.pass_env -def cli(env, identifier): - """Get details for a Tag.""" +def cli(env, identifier, name): + """Get details for a Tag. Identifier can be either a name or tag-id""" tag_manager = TagManager(env.client) - if str.isdigit(identifier): + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: tags = [tag_manager.get_tag(identifier)] else: tags = tag_manager.get_tag_by_name(identifier) diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py index aa0879c6e..e30137ea5 100644 --- a/SoftLayer/CLI/tags/set.py +++ b/SoftLayer/CLI/tags/set.py @@ -8,8 +8,10 @@ @click.command() -@click.option('--tags', '-t', type=click.STRING, required=True, help='List of tags e.g. "tag1, tag2"') -@click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE") +@click.option('--tags', '-t', type=click.STRING, required=True, + help='Comma seperated list of tags, enclosed in quotes. "tag1, tag2"') +@click.option('--key-name', '-k', type=click.STRING, required=True, + help="Key name of a tag type e.g. GUEST, HARDWARE. See slcli tags taggable output.") @click.option('--resource-id', '-r', type=click.INT, required=True, help="ID of the object being tagged") @environment.pass_env def cli(env, tags, key_name, resource_id): diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py new file mode 100644 index 000000000..2fd9ef2b1 --- /dev/null +++ b/SoftLayer/CLI/tags/taggable.py @@ -0,0 +1,44 @@ +"""List everything that could be tagged.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer.CLI import formatting +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment + +from pprint import pprint as pp +@click.command() +@environment.pass_env +def cli(env): + """List everything that could be tagged.""" + + tag_manager = TagManager(env.client) + tag_types = tag_manager.get_all_tag_types() + for tag_type in tag_types: + title = "{} ({})".format(tag_type['description'], tag_type['keyName']) + table = formatting.Table(['Id', 'Name'], title=title) + resources = tag_manager.taggable_by_type(tag_type['keyName']) + for resource in resources: + table.add_row([ + resource['resource']['id'], + get_resource_name(resource['resource'], tag_type['keyName']) + ]) + env.fout(table) + + +def get_resource_name(resource, tag_type): + """Returns a string that names a resource""" + if tag_type == 'NETWORK_VLAN_FIREWALL': + return resource.get('primaryIpAddress') + elif tag_type == 'NETWORK_VLAN': + return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) + elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + return resource.get('name') + elif tag_type == 'TICKET': + return resource.get('subjet') + elif tag_type == 'NETWORK_SUBNET': + return resource.get('networkIdentifier') + else: + return resource.get('fullyQualifiedDomainName') \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 8752f5257..19fd077ec 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -96,9 +96,75 @@ def reference_lookup(self, resource_table_id, tag_type): |Vlan |NETWORK_VLAN| |Dedicated Host |DEDICATED_HOST| """ + service = self.type_to_service(tag_type) + if service is None: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + # return {} + return self.client.call(service, 'getObject', id=resource_table_id) + + def delete_tag(self, name): + """Calls SoftLayer_Tag::deleteTag + + :param string name: tag name to delete + """ + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + + def set_tags(self, tags, key_name, resource_id): + """Calls SoftLayer_Tag::setTags() + + :param string tags: List of tags. + :param string key_name: Key name of a tag type. + :param int resource_id: ID of the object being tagged. + """ + return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) + def get_all_tag_types(self): + """Calls SoftLayer_Tag::getAllTagTypes()""" + types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') + useable_types = [] + for tag_type in types: + service = self.type_to_service(tag_type['keyName']) + # Mostly just to remove the types that are not user taggable. + if service is not None: + temp_type = tag_type + temp_type['service'] = service + useable_types.append(temp_type) + return useable_types + + def taggable_by_type(self, tag_type): + """Returns a list of resources that can be tagged, that are of the given type + + :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes + """ + service = self.type_to_service(tag_type) + search_term = "_objectType:SoftLayer_{}".format(service) + if tag_type == 'TICKET': + search_term = "{} status.name: open".format(search_term) + elif tag_type == 'IMAGE_TEMPLATE': + mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" + resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', + mask=mask, iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType':service, 'resource':resource}) + return to_return + elif tag_type == 'NETWORK_SUBNET': + resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType':service, 'resource':resource}) + return to_return + resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) + return resources + + @staticmethod + def type_to_service(tag_type): + """Returns the SoftLayer service for the given tag_type""" + service = None if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + return None if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': service = 'Network_Application_Delivery_Controller' @@ -106,24 +172,41 @@ def reference_lookup(self, resource_table_id, tag_type): service = 'Virtual_Guest' elif tag_type == 'DEDICATED_HOST': service = 'Virtual_DedicatedHost' + elif tag_type == 'IMAGE_TEMPLATE': + service = 'Virtual_Guest_Block_Device_Template_Group' else: tag_type = tag_type.lower() # Sets the First letter, and any letter preceeded by a '_' to uppercase # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) + return service + + # @staticmethod + # def type_to_datatype(tag_type): + # """Returns the SoftLayer datatye for the given tag_type""" + # datatye = None + # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + # return None + + # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + # datatye = 'adcLoadBalancers' + # elif tag_type == 'GUEST': + # datatye = 'virtualGuests' + # elif tag_type == 'DEDICATED_HOST': + # datatye = 'dedicatedHosts' + # elif tag_type == 'HARDWARE': + # datatye = 'hardware' + # elif tag_type == 'TICKET': + # datatye = 'openTickets' + # elif tag_type == 'NETWORK_SUBNET': + # datatye = 'subnets' + # elif tag_type == 'NETWORK_VLAN': + # datatye = 'networkVlans' + # elif tag_type == 'NETWORK_VLAN_FIREWALL': + # datatye = 'networkVlans' + # elif tag_type == 'IMAGE_TEMPLATE': + # datatye = 'blockDeviceTemplateGroups' + + # return datatye - # return {} - return self.client.call(service, 'getObject', id=resource_table_id) - - def delete_tag(self, name): - return self.client.call('SoftLayer_Tag', 'deleteTag', name) - - def set_tags(self, tags, key_name, resource_id): - """Calls SoftLayer_Tag::setTags() - - :param string tags: List of tags. - :param string key_name: Key name of a tag type. - :param int resource_id: ID of the object being tagged. - """ - return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id)