[issue #1720] Add support for AWS Organizations

added exception handling in class OrganizationsBackend
This commit is contained in:
Ashley Gould 2018-07-20 13:54:53 -07:00
parent 40e422b74d
commit 05928b1497
4 changed files with 120 additions and 31 deletions

View File

@ -3147,7 +3147,7 @@
- [ ] update_server - [ ] update_server
- [ ] update_server_engine_attributes - [ ] update_server_engine_attributes
## organizations - 28% implemented ## organizations - 30% implemented
- [ ] accept_handshake - [ ] accept_handshake
- [ ] attach_policy - [ ] attach_policy
- [ ] cancel_handshake - [ ] cancel_handshake
@ -3176,7 +3176,7 @@
- [X] list_accounts - [X] list_accounts
- [X] list_accounts_for_parent - [X] list_accounts_for_parent
- [ ] list_aws_service_access_for_organization - [ ] list_aws_service_access_for_organization
- [ ] list_children - [X] list_children
- [ ] list_create_account_status - [ ] list_create_account_status
- [ ] list_handshakes_for_account - [ ] list_handshakes_for_account
- [ ] list_handshakes_for_organization - [ ] list_handshakes_for_organization

View File

@ -112,7 +112,7 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| KMS | @mock_kms | basic endpoints done | | KMS | @mock_kms | basic endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| Organizations | @mock_organizations | some endpoints done | | Organizations | @mock_organizations | some core endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| Polly | @mock_polly | all endpoints done | | Polly | @mock_polly | all endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|

View File

@ -4,6 +4,7 @@ import datetime
import re import re
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
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
@ -168,13 +169,31 @@ class OrganizationsBackend(BaseBackend):
self.ou.append(new_ou) self.ou.append(new_ou)
return new_ou.describe() return new_ou.describe()
def get_organizational_unit_by_id(self, ou_id):
ou = next((ou for ou in self.ou if ou.id == ou_id), None)
if ou is None:
raise RESTError(
'OrganizationalUnitNotFoundException',
"You specified an organizational unit that doesn't exist."
)
return ou
def validate_parent_id(self, parent_id):
try:
self.get_organizational_unit_by_id(parent_id)
except RESTError as e:
raise RESTError(
'ParentNotFoundException',
"You specified parent that doesn't exist."
)
return parent_id
def describe_organizational_unit(self, **kwargs): def describe_organizational_unit(self, **kwargs):
ou = [ ou = self.get_organizational_unit_by_id(kwargs['OrganizationalUnitId'])
ou for ou in self.ou if ou.id == kwargs['OrganizationalUnitId']
].pop(0)
return ou.describe() return ou.describe()
def list_organizational_units_for_parent(self, **kwargs): def list_organizational_units_for_parent(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
return dict( return dict(
OrganizationalUnits=[ OrganizationalUnits=[
{ {
@ -183,7 +202,7 @@ class OrganizationsBackend(BaseBackend):
'Name': ou.name, 'Name': ou.name,
} }
for ou in self.ou for ou in self.ou
if ou.parent_id == kwargs['ParentId'] if ou.parent_id == parent_id
] ]
) )
@ -192,11 +211,20 @@ class OrganizationsBackend(BaseBackend):
self.accounts.append(new_account) self.accounts.append(new_account)
return new_account.create_account_status return new_account.create_account_status
def describe_account(self, **kwargs): def get_account_by_id(self, account_id):
account = [ account = next((
account for account in self.accounts account for account in self.accounts
if account.id == kwargs['AccountId'] if account.id == account_id
].pop(0) ), None)
if account is None:
raise RESTError(
'AccountNotFoundException',
"You specified an account that doesn't exist."
)
return account
def describe_account(self, **kwargs):
account = self.get_account_by_id(kwargs['AccountId'])
return account.describe() return account.describe()
def list_accounts(self): def list_accounts(self):
@ -205,35 +233,27 @@ class OrganizationsBackend(BaseBackend):
) )
def list_accounts_for_parent(self, **kwargs): def list_accounts_for_parent(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
return dict( return dict(
Accounts=[ Accounts=[
account.describe()['Account'] account.describe()['Account']
for account in self.accounts for account in self.accounts
if account.parent_id == kwargs['ParentId'] if account.parent_id == parent_id
] ]
) )
def move_account(self, **kwargs): def move_account(self, **kwargs):
new_parent_id = kwargs['DestinationParentId'] new_parent_id = self.validate_parent_id(kwargs['DestinationParentId'])
all_parent_id = [parent.id for parent in self.ou] self.validate_parent_id(kwargs['SourceParentId'])
account = [ account = self.get_account_by_id(kwargs['AccountId'])
account for account in self.accounts
if account.id == kwargs['AccountId']
].pop(0)
assert new_parent_id in all_parent_id
assert account.parent_id == kwargs['SourceParentId']
index = self.accounts.index(account) index = self.accounts.index(account)
self.accounts[index].parent_id = new_parent_id self.accounts[index].parent_id = new_parent_id
def list_parents(self, **kwargs): def list_parents(self, **kwargs):
if re.compile(r'[0-9]{12}').match(kwargs['ChildId']): if re.compile(r'[0-9]{12}').match(kwargs['ChildId']):
obj_list = self.accounts child_object = self.get_account_by_id(kwargs['ChildId'])
else: else:
obj_list = self.ou child_object = self.get_organizational_unit_by_id(kwargs['ChildId'])
parent_id = [
obj.parent_id for obj in obj_list
if obj.id == kwargs['ChildId']
].pop(0)
return dict( return dict(
Parents=[ Parents=[
{ {
@ -241,17 +261,21 @@ class OrganizationsBackend(BaseBackend):
'Type': ou.type, 'Type': ou.type,
} }
for ou in self.ou for ou in self.ou
if ou.id == parent_id if ou.id == child_object.parent_id
] ]
) )
def list_children(self, **kwargs): def list_children(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
if kwargs['ChildType'] == 'ACCOUNT': if kwargs['ChildType'] == 'ACCOUNT':
obj_list = self.accounts obj_list = self.accounts
elif kwargs['ChildType'] == 'ORGANIZATIONAL_UNIT': elif kwargs['ChildType'] == 'ORGANIZATIONAL_UNIT':
obj_list = self.ou obj_list = self.ou
else: else:
raise ValueError raise RESTError(
'InvalidInputException',
'You specified an invalid value.'
)
return dict( return dict(
Children=[ Children=[
{ {
@ -259,7 +283,7 @@ class OrganizationsBackend(BaseBackend):
'Type': kwargs['ChildType'], 'Type': kwargs['ChildType'],
} }
for obj in obj_list for obj in obj_list
if obj.parent_id == kwargs['ParentId'] if obj.parent_id == parent_id
] ]
) )

View File

@ -2,9 +2,11 @@ from __future__ import unicode_literals
import boto3 import boto3
import sure # noqa import sure # noqa
import yaml from botocore.exceptions import ClientError
from nose.tools import assert_raises
from moto import mock_organizations from moto import mock_organizations
from moto.organizations import utils
from .organizations_test_utils import ( from .organizations_test_utils import (
validate_organization, validate_organization,
validate_roots, validate_roots,
@ -67,6 +69,20 @@ def test_describe_organizational_unit():
validate_organizational_unit(org, response) validate_organizational_unit(org, response)
@mock_organizations
def test_describe_organizational_unit_exception():
client = boto3.client('organizations', region_name='us-east-1')
org = client.create_organization(FeatureSet='ALL')['Organization']
with assert_raises(ClientError) as e:
response = client.describe_organizational_unit(
OrganizationalUnitId=utils.make_random_root_id()
)
ex = e.exception
ex.operation_name.should.equal('DescribeOrganizationalUnit')
ex.response['Error']['Code'].should.equal('400')
ex.response['Error']['Message'].should.contain('OrganizationalUnitNotFoundException')
@mock_organizations @mock_organizations
def test_list_organizational_units_for_parent(): def test_list_organizational_units_for_parent():
client = boto3.client('organizations', region_name='us-east-1') client = boto3.client('organizations', region_name='us-east-1')
@ -81,6 +97,19 @@ def test_list_organizational_units_for_parent():
validate_organizational_unit(org, dict(OrganizationalUnit=ou)) validate_organizational_unit(org, dict(OrganizationalUnit=ou))
@mock_organizations
def test_list_organizational_units_for_parent_exception():
client = boto3.client('organizations', region_name='us-east-1')
with assert_raises(ClientError) as e:
response = client.list_organizational_units_for_parent(
ParentId=utils.make_random_root_id()
)
ex = e.exception
ex.operation_name.should.equal('ListOrganizationalUnitsForParent')
ex.response['Error']['Code'].should.equal('400')
ex.response['Error']['Message'].should.contain('ParentNotFoundException')
# Accounts # Accounts
mockname = 'mock-account' mockname = 'mock-account'
mockdomain = 'moto-example.org' mockdomain = 'moto-example.org'
@ -111,6 +140,17 @@ def test_describe_account():
response['Account']['Email'].should.equal(mockemail) response['Account']['Email'].should.equal(mockemail)
@mock_organizations
def test_describe_account_exception():
client = boto3.client('organizations', region_name='us-east-1')
with assert_raises(ClientError) as e:
response = client.describe_account(AccountId=utils.make_random_account_id())
ex = e.exception
ex.operation_name.should.equal('DescribeAccount')
ex.response['Error']['Code'].should.equal('400')
ex.response['Error']['Message'].should.contain('AccountNotFoundException')
@mock_organizations @mock_organizations
def test_list_accounts(): def test_list_accounts():
client = boto3.client('organizations', region_name='us-east-1') client = boto3.client('organizations', region_name='us-east-1')
@ -211,7 +251,7 @@ def test_list_parents_for_accounts():
@mock_organizations @mock_organizations
def test_list_chidlren(): def test_list_children():
client = boto3.client('organizations', region_name='us-east-1') client = boto3.client('organizations', region_name='us-east-1')
org = client.create_organization(FeatureSet='ALL')['Organization'] org = client.create_organization(FeatureSet='ALL')['Organization']
root_id = client.list_roots()['Roots'][0]['Id'] root_id = client.list_roots()['Roots'][0]['Id']
@ -244,3 +284,28 @@ def test_list_chidlren():
response03['Children'][0]['Type'].should.equal('ACCOUNT') response03['Children'][0]['Type'].should.equal('ACCOUNT')
response04['Children'][0]['Id'].should.equal(ou02_id) response04['Children'][0]['Id'].should.equal(ou02_id)
response04['Children'][0]['Type'].should.equal('ORGANIZATIONAL_UNIT') response04['Children'][0]['Type'].should.equal('ORGANIZATIONAL_UNIT')
@mock_organizations
def test_list_children_exception():
client = boto3.client('organizations', region_name='us-east-1')
org = client.create_organization(FeatureSet='ALL')['Organization']
root_id = client.list_roots()['Roots'][0]['Id']
with assert_raises(ClientError) as e:
response = client.list_children(
ParentId=utils.make_random_root_id(),
ChildType='ACCOUNT'
)
ex = e.exception
ex.operation_name.should.equal('ListChildren')
ex.response['Error']['Code'].should.equal('400')
ex.response['Error']['Message'].should.contain('ParentNotFoundException')
with assert_raises(ClientError) as e:
response = client.list_children(
ParentId=root_id,
ChildType='BLEE'
)
ex = e.exception
ex.operation_name.should.equal('ListChildren')
ex.response['Error']['Code'].should.equal('400')
ex.response['Error']['Message'].should.contain('InvalidInputException')