Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add support for tagged RDS DBInstances #1061

Closed
wants to merge 9 commits into from
@gertjanol

These commits add support for listing, creating and removing tags to and from RDS-DBInstances (ListTagsForResource, AddTagsToResource and RemoveTagsFromResource API calls).

Amazon requires an ARN to reference the DBInstance on which the actions are performed, but doesn't supply this ARN, afaik. I added a property to the DBInstance-class, which uses IAM (for the account-id) to construct the ARN itself when asked for. It's not very clean, but I didn't see another way. If Amazon does supply the ARN in another way, or in the future, this could easily be removed while retaining backwards compatibility.

I'm hoping to add support for a TaggedRDSResource, similar to the TaggedEC2Resource.

I welcome your thought and suggestions!

PS: this is the same pull-request as #1050, but from it's own branch

[update 2012-11-06]
Implement classmethods for getting, adding and removing tags as methods from boto.rds.DBInstance. This is different than the implementation of EC2 tags. This is due to differences in the API: the EC2 API offers tags for more than just Instances, hence the superclass TaggedEC2Object. It also supplies all tags from a resource in each DescribeResource API call.

RDS only offers tags for DBInstances, and separate API calls are needed to retrieve them.

However, the interface for the user (dbinstance.tags, dbinstance.add_tag(key, value) and dbinstance.remove_tag(key)) is the same as for EC2 tags. If Amazon in the future does provide tags in the same way as EC2 tags, this could easily be refactored (and maybe even use the same superclass).

@sstoiana

Can we get this in?

@garnaat garnaat referenced this pull request
Closed

RDS tagging #1141

@znewman01

:+1: would love to see this merged in

@gertjanol

Me too :). But I haven't received feedback from Mitch, so I don't know what it's waiting for.

@J5
J5 commented

Thanks for this patch. I have it monkey patched into our code. One thing I would like to mention is that you are getting the account_id via an IAM call which we may not have access to since we are doing these calls as a third party on behalf of our clients using restricted IAM roles. Instead we grab the account id through the security groups API:

sg = ec2.get_all_security_groups()
account_id = sg[0].owner_id

@reversefold

I had previously implemented the same functionality and bowed to this one, but I've recently opened another pull request, #1641 which also has unit tests for everything.

@phspagiari

+1 This is already merged?

@gkope

+1

@bilalaslam

+1. This would be nice to have.

@worldofchris

+1. Could really do with being able to see the tags on RDS instances as well as on EC2.

@monkey1016

+1 Could definitely use this feature as well.

@bpollack

Would someone on the Boto team mind telling us what you'd want for this to get merged? It looks like #1641 has unit tests, which wasn't enough to get it merged, either. I'm happy to take a stab at whatever improvements you guys are looking for.

@toastdriven

@bpollack This PR wasn't merged because there aren't any tests on it. The rough consensus is that some combination of this & #1061 + tests would be best, but there hasn't been time to get back there.

We're still discussing what to do about RDS, given that we're lagging significantly behind on the module vs. the API. I'll update here when we have something to share.

@defionscode

+1 for sure

@danielgtaylor

Please note that boto now contains a new rds2 module with the latest Amazon RDS API support. It is a low-level module, but supports all the new features and is significantly easier for us to maintain.

http://boto.readthedocs.org/en/latest/ref/rds2.html
http://boto.readthedocs.org/en/latest/migrations/rds_v1_to_v2.html
https://github.com/boto/boto/tree/develop/boto/rds2

This module will be released soon in boto 2.26.0.

@defionscode

Is there a ETA on 2.26.0?

@wigsy

+1

@danielgtaylor

Boto-2.26.0 is now out! Closing this PR for the time being - please resubmit with tests if you wish to backport these features to boto.rds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
6 boto/iam/connection.py
@@ -25,6 +25,7 @@
from boto.resultset import ResultSet
from boto.iam.summarymap import SummaryMap
from boto.connection import AWSQueryConnection
+from boto.iam.user import User
ASSUME_ROLE_POLICY_DOCUMENT = json.dumps({
@@ -354,11 +355,14 @@ def get_user(self, user_name=None):
:type user_name: string
:param user_name: The name of the user to delete.
If not specified, defaults to user making request.
+
+ :rtype: :class:`boto.iam.user.User`
+ :return: The requested User instance
"""
params = {}
if user_name:
params['UserName'] = user_name
- return self.get_response('GetUser', params)
+ return self.get_object('GetUser', params, User)
def update_user(self, user_name, new_user_name=None, new_path=None):
"""
View
61 boto/iam/user.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2012 Byte BV, http://byte.nl
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class User(object):
+ """
+ Represents an IAM User
+
+ :ivar arn: The Amazon Resource Name (ARN) specifying the user
+ :ivar create_date: The date when the user was created
+ :ivar id: The stable and unique string identifying the user
+ :ivar name: The name identifying the user
+ :ivar path: Path to the user
+ """
+
+ def __init__(self, connection=None, name=None):
+ self.connection = connection
+ self.name = name
+ self.arn = None
+ self.create_date = None
+ self.id = None
+ self.path = None
+
+ def __repr__(self):
+ return 'User:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'Arn':
+ self.arn = value
+ elif name == 'Path':
+ self.path = value
+ elif name == 'UserId':
+ self.id = value
+ elif name == 'UserName':
+ self.name = value
+ elif name == 'CreateDate':
+ self.create_date = value
+ else:
+ setattr(self, name, value)
+
View
68 boto/rds/__init__.py
@@ -28,6 +28,7 @@
from boto.rds.dbsnapshot import DBSnapshot
from boto.rds.event import Event
from boto.rds.regioninfo import RDSRegionInfo
+from boto.rds.tag import RDSTag
def regions():
@@ -1182,3 +1183,70 @@ def get_all_events(self, source_identifier=None, source_type=None,
if marker:
params['Marker'] = marker
return self.get_list('DescribeEvents', params, [('Event', Event)])
+
+ def create_tags(self, dbinstance, tags):
+ """
+ Create new metadata tags for the specified RDS DBInstance.
+
+ At this moment an instance of :class:`boto.rds.dbinstance.DBInstance`
+ must be specified, since the AddTagsToResource API call needs an ARN.
+
+ :type dbinstance: :class:`boto.rds.dbinstance.DBInstance`
+ :param dbinstance: The dbinstance to get the tags for
+
+ :type tags: dict
+ :param tags: A dictionary containing the name/value pairs.
+ If you want to create only a tag name, the
+ value for that tag should be the empty string
+ (e.g. '').
+ """
+ assert isinstance(dbinstance, DBInstance)
+
+ params = {'ResourceName': dbinstance.arn}
+
+ for i, (key, value) in enumerate(tags.items()):
+ params['Tags.member.%d.Key' % (i + 1)] = key
+ params['Tags.member.%d.Value' % (i + 1)] = value
+
+ return self.get_status('AddTagsToResource', params, verb='POST')
+
+ def delete_tags(self, dbinstance, tags):
+ """
+ Delete metadata tags for the specified RDS DBInstance id.
+
+ At this moment an instance of :class:`boto.rds.dbinstance.DBInstance`
+ must be specified, since the AddTagsToResource API call needs an ARN.
+
+ :type dbinstance: :class:`boto.rds.dbinstance.DBInstance`
+ :param dbinstance: The dbinstance to get the tags for
+
+ :type tags: list
+ :param tags: A list containing the names of the tags to delete
+ """
+
+ assert isinstance(dbinstance, DBInstance)
+
+ params = {'ResourceName': dbinstance.arn}
+
+ for i, key in enumerate(tags):
+ params['TagKeys.member.%d' % (i + 1)] = key
+
+ return self.get_status('RemoveTagsFromResource', params, verb='POST')
+
+ def get_dbinstance_tags(self, dbinstance):
+ """
+ Get all metadata tags for the specified RDS DBInstance.
+
+ At this moment an instance of :class:`boto.rds.dbinstance.DBInstance`
+ must be specified, since the ListTagsForResource API call needs an ARN.
+
+ :type dbinstance: :class:`boto.rds.dbinstance.DBInstance`
+ :param dbinstance: The dbinstance to get the tags for
+ """
+ assert isinstance(dbinstance, DBInstance)
+
+ params = {'ResourceName': dbinstance.arn}
+
+ return self.get_list('ListTagsForResource', params,
+ [('Tag', RDSTag)])
+
View
103 boto/rds/dbinstance.py
@@ -66,6 +66,9 @@ class DBInstance(object):
:ivar pending_modified_values: Specifies that changes to the
DB Instance are pending. This element is only included when changes
are pending. Specific changes are identified by subelements.
+ :ivar arn: The Amazon Resource Name for this DBInstance. Retrieved by
+ querying IAM for the account-id of the current connection.
+ :ivar tags: A dict with tags associated with this DBInstance.
"""
def __init__(self, connection=None, id=None):
@@ -91,6 +94,8 @@ def __init__(self, connection=None, id=None):
self._in_endpoint = False
self._port = None
self._address = None
+ self._arn = None
+ self._tags = None
def __repr__(self):
return 'DBInstance:%s' % self.id
@@ -306,6 +311,104 @@ def modify(self, param_group=None, security_groups=None,
apply_immediately,
iops)
+ @property
+ def arn(self):
+
+ # Current RDS-API does not supply the ARN (Amazon Resource Name) for
+ # database instances. However, new functionality like tags for RDS
+ # resources, works with ARN's. This getter for DBInstance.arn gets
+ # the account-id from IAM, and creates an ARN for this DBInstance. If
+ # the Amazon-API does supply the ARN in the future, this code should
+ # be compatible (and could be removed, after renaming DBInstance._arn
+ # to DBInstance.arn
+
+ if self._arn is None:
+ import boto
+ iam = boto.connect_iam(
+ self.connection.aws_access_key_id,
+ self.connection.aws_secret_access_key)
+ if iam:
+ user = iam.get_user()
+ if user:
+ account_id = user.arn.split(':')[4]
+ self.arn = ('arn:aws:rds:%s:%s:db:%s' %
+ (self.connection.region.name, account_id, self.id))
+
+ return self._arn
+
+ @arn.setter
+ def arn(self, value):
+ self._arn = value
+
+ @property
+ def tags(self):
+
+ if self._tags is None:
+ mytags = {}
+ tag_rs = self.connection.get_dbinstance_tags(self)
+
+ # Translate the boto.ResultSet to a dict
+ #
+ for tag in tag_rs:
+ mytags[tag.name] = tag.value
+
+ self.tags = mytags
+
+ return self._tags
+
+ @tags.setter
+ def tags(self, value):
+ self._tags = value
+
+ def add_tag(self, key, value=''):
+ """
+ Add a tag to this object. The local tag-list (dbinstance.tags) will be
+ updated with the newly created or modified tag, without checking with
+ AWS. You could do that yourself with:
+
+ dbinstance.tags = None
+ tags = dbinstance.tags
+
+ This will query AWS for the current tags and update the local tags.
+
+ :type key: str
+ :param key: The key or name of the tag being stored.
+
+ :type value: str
+ :param value: An optional value that can be stored with the tag.
+ If you want only the tag name and no value, the
+ value should be the empty string.
+ """
+ self.connection.create_tags(self, {key: value})
+
+ # What to do: only update local tags with newly added tag, or update
+ # local tags with remote? Since it is uncertain if new tag is already
+ # added (AWS-API is asynchronous), we'll only update our local tags.
+ #
+ self.tags[key] = value
+
+ def remove_tag(self, key):
+ """
+ Remove a tag from this object. The tag will also be removed from the
+ local tag-list (dbinstance.tags), without checking with AWS. You could
+ do that yourself with:
+
+ dbinstance.tags = None
+ tags = dbinstance.tags
+
+ This will query AWS for the current tags and update the local tags.
+
+ Contrary to EC2-tags, RDS does not support removing tags with a key and
+ a matching value.
+
+ :type key: str
+ :param key: The key or name of the tag to remove.
+ """
+ self.connection.delete_tags(self, [key])
+
+ if key in self.tags:
+ del self.tags[key]
+
class PendingModifiedValues(dict):
View
46 boto/rds/tag.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
+# Copyright (c) 2010, Eucalyptus Systems, Inc.
+# Copyright (c) 2012, Byte BV, http://byte.nl
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+
+class RDSTag(object):
+
+ def __init__(self, connection=None, name=None, value=None):
+ self.connection = connection
+ self.name = name
+ self.value = value
+
+ def __repr__(self):
+ return 'Tag:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'Key':
+ self.name = value
+ elif name == 'Value':
+ self.value = value
+ else:
+ setattr(self, name, value)
+
+
Something went wrong with that request. Please try again.