Merge pull request #2576 from gruebel/add-organizations-tags
Add organizations tags
This commit is contained in:
commit
97b0084668
@ -4850,12 +4850,12 @@
|
|||||||
- [X] list_policies
|
- [X] list_policies
|
||||||
- [X] list_policies_for_target
|
- [X] list_policies_for_target
|
||||||
- [X] list_roots
|
- [X] list_roots
|
||||||
- [ ] list_tags_for_resource
|
- [x] list_tags_for_resource
|
||||||
- [X] list_targets_for_policy
|
- [X] list_targets_for_policy
|
||||||
- [X] move_account
|
- [X] move_account
|
||||||
- [ ] remove_account_from_organization
|
- [ ] remove_account_from_organization
|
||||||
- [ ] tag_resource
|
- [x] tag_resource
|
||||||
- [ ] untag_resource
|
- [x] untag_resource
|
||||||
- [ ] update_organizational_unit
|
- [ ] update_organizational_unit
|
||||||
- [ ] update_policy
|
- [ ] update_policy
|
||||||
|
|
||||||
|
12
moto/organizations/exceptions.py
Normal file
12
moto/organizations/exceptions.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInputException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(InvalidInputException, self).__init__(
|
||||||
|
"InvalidInputException",
|
||||||
|
"You provided a value that does not match the required pattern.",
|
||||||
|
)
|
@ -8,6 +8,7 @@ from moto.core import BaseBackend, BaseModel
|
|||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
from moto.core.utils import unix_time
|
from moto.core.utils import unix_time
|
||||||
from moto.organizations import utils
|
from moto.organizations import utils
|
||||||
|
from moto.organizations.exceptions import InvalidInputException
|
||||||
|
|
||||||
|
|
||||||
class FakeOrganization(BaseModel):
|
class FakeOrganization(BaseModel):
|
||||||
@ -57,6 +58,7 @@ class FakeAccount(BaseModel):
|
|||||||
self.joined_method = "CREATED"
|
self.joined_method = "CREATED"
|
||||||
self.parent_id = organization.root_id
|
self.parent_id = organization.root_id
|
||||||
self.attached_policies = []
|
self.attached_policies = []
|
||||||
|
self.tags = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arn(self):
|
def arn(self):
|
||||||
@ -442,5 +444,32 @@ class OrganizationsBackend(BaseBackend):
|
|||||||
]
|
]
|
||||||
return dict(Targets=objects)
|
return dict(Targets=objects)
|
||||||
|
|
||||||
|
def tag_resource(self, **kwargs):
|
||||||
|
account = next((a for a in self.accounts if a.id == kwargs["ResourceId"]), None)
|
||||||
|
|
||||||
|
if account is None:
|
||||||
|
raise InvalidInputException
|
||||||
|
|
||||||
|
new_tags = {tag["Key"]: tag["Value"] for tag in kwargs["Tags"]}
|
||||||
|
account.tags.update(new_tags)
|
||||||
|
|
||||||
|
def list_tags_for_resource(self, **kwargs):
|
||||||
|
account = next((a for a in self.accounts if a.id == kwargs["ResourceId"]), None)
|
||||||
|
|
||||||
|
if account is None:
|
||||||
|
raise InvalidInputException
|
||||||
|
|
||||||
|
tags = [{"Key": key, "Value": value} for key, value in account.tags.items()]
|
||||||
|
return dict(Tags=tags)
|
||||||
|
|
||||||
|
def untag_resource(self, **kwargs):
|
||||||
|
account = next((a for a in self.accounts if a.id == kwargs["ResourceId"]), None)
|
||||||
|
|
||||||
|
if account is None:
|
||||||
|
raise InvalidInputException
|
||||||
|
|
||||||
|
for key in kwargs["TagKeys"]:
|
||||||
|
account.tags.pop(key, None)
|
||||||
|
|
||||||
|
|
||||||
organizations_backend = OrganizationsBackend()
|
organizations_backend = OrganizationsBackend()
|
||||||
|
@ -119,3 +119,18 @@ class OrganizationsResponse(BaseResponse):
|
|||||||
return json.dumps(
|
return json.dumps(
|
||||||
self.organizations_backend.list_targets_for_policy(**self.request_params)
|
self.organizations_backend.list_targets_for_policy(**self.request_params)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def tag_resource(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.tag_resource(**self.request_params)
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_tags_for_resource(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.list_tags_for_resource(**self.request_params)
|
||||||
|
)
|
||||||
|
|
||||||
|
def untag_resource(self):
|
||||||
|
return json.dumps(
|
||||||
|
self.organizations_backend.untag_resource(**self.request_params)
|
||||||
|
)
|
||||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import boto3
|
import boto3
|
||||||
import json
|
import json
|
||||||
import six
|
import six
|
||||||
|
import sure # noqa
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
@ -605,3 +606,110 @@ def test_list_targets_for_policy_exception():
|
|||||||
ex.operation_name.should.equal("ListTargetsForPolicy")
|
ex.operation_name.should.equal("ListTargetsForPolicy")
|
||||||
ex.response["Error"]["Code"].should.equal("400")
|
ex.response["Error"]["Code"].should.equal("400")
|
||||||
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
ex.response["Error"]["Message"].should.contain("InvalidInputException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_tag_resource():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
|
||||||
|
client.tag_resource(ResourceId=account_id, Tags=[{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
response["Tags"].should.equal([{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
# adding a tag with an existing key, will update the value
|
||||||
|
client.tag_resource(
|
||||||
|
ResourceId=account_id, Tags=[{"Key": "key", "Value": "new-value"}]
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
response["Tags"].should.equal([{"Key": "key", "Value": "new-value"}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_tag_resource_errors():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.tag_resource(
|
||||||
|
ResourceId="000000000000", Tags=[{"Key": "key", "Value": "value"},]
|
||||||
|
)
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("TagResource")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You provided a value that does not match the required pattern."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_tags_for_resource():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.tag_resource(ResourceId=account_id, Tags=[{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
|
||||||
|
response["Tags"].should.equal([{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_list_tags_for_resource_errors():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.list_tags_for_resource(ResourceId="000000000000")
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("ListTagsForResource")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You provided a value that does not match the required pattern."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_untag_resource():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
account_id = client.create_account(AccountName=mockname, Email=mockemail)[
|
||||||
|
"CreateAccountStatus"
|
||||||
|
]["AccountId"]
|
||||||
|
client.tag_resource(ResourceId=account_id, Tags=[{"Key": "key", "Value": "value"}])
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
response["Tags"].should.equal([{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
# removing a non existing tag should not raise any error
|
||||||
|
client.untag_resource(ResourceId=account_id, TagKeys=["not-existing"])
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
response["Tags"].should.equal([{"Key": "key", "Value": "value"}])
|
||||||
|
|
||||||
|
client.untag_resource(ResourceId=account_id, TagKeys=["key"])
|
||||||
|
response = client.list_tags_for_resource(ResourceId=account_id)
|
||||||
|
response["Tags"].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_organizations
|
||||||
|
def test_untag_resource_errors():
|
||||||
|
client = boto3.client("organizations", region_name="us-east-1")
|
||||||
|
client.create_organization(FeatureSet="ALL")
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as e:
|
||||||
|
client.untag_resource(ResourceId="000000000000", TagKeys=["key"])
|
||||||
|
ex = e.exception
|
||||||
|
ex.operation_name.should.equal("UntagResource")
|
||||||
|
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
|
ex.response["Error"]["Code"].should.contain("InvalidInputException")
|
||||||
|
ex.response["Error"]["Message"].should.equal(
|
||||||
|
"You provided a value that does not match the required pattern."
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user