Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Organizations support #1727

Merged
merged 17 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions IMPLEMENTATION_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3092,23 +3092,23 @@
- [ ] update_server
- [ ] update_server_engine_attributes

## organizations - 0% implemented
## organizations - 30% implemented
- [ ] accept_handshake
- [ ] attach_policy
- [ ] cancel_handshake
- [ ] create_account
- [ ] create_organization
- [ ] create_organizational_unit
- [X] create_account
- [X] create_organization
- [X] create_organizational_unit
- [ ] create_policy
- [ ] decline_handshake
- [ ] delete_organization
- [ ] delete_organizational_unit
- [ ] delete_policy
- [ ] describe_account
- [X] describe_account
- [ ] describe_create_account_status
- [ ] describe_handshake
- [ ] describe_organization
- [ ] describe_organizational_unit
- [X] describe_organization
- [X] describe_organizational_unit
- [ ] describe_policy
- [ ] detach_policy
- [ ] disable_aws_service_access
Expand All @@ -3118,20 +3118,20 @@
- [ ] enable_policy_type
- [ ] invite_account_to_organization
- [ ] leave_organization
- [ ] list_accounts
- [ ] list_accounts_for_parent
- [X] list_accounts
- [X] list_accounts_for_parent
- [ ] list_aws_service_access_for_organization
- [ ] list_children
- [X] list_children
- [ ] list_create_account_status
- [ ] list_handshakes_for_account
- [ ] list_handshakes_for_organization
- [ ] list_organizational_units_for_parent
- [ ] list_parents
- [X] list_organizational_units_for_parent
- [X] list_parents
- [ ] list_policies
- [ ] list_policies_for_target
- [ ] list_roots
- [X] list_roots
- [ ] list_targets_for_policy
- [ ] move_account
- [X] move_account
- [ ] remove_account_from_organization
- [ ] update_organizational_unit
- [ ] update_policy
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ 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 |
|------------------------------------------------------------------------------|
| Organizations | @mock_organizations | some core endpoints done |
|------------------------------------------------------------------------------|
| Polly | @mock_polly | all endpoints done |
|------------------------------------------------------------------------------|
| RDS | @mock_rds | core endpoints done |
Expand Down
1 change: 1 addition & 0 deletions moto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .iam import mock_iam, mock_iam_deprecated # flake8: noqa
from .kinesis import mock_kinesis, mock_kinesis_deprecated # flake8: noqa
from .kms import mock_kms, mock_kms_deprecated # flake8: noqa
from .organizations import mock_organizations # flake8: noqa
from .opsworks import mock_opsworks, mock_opsworks_deprecated # flake8: noqa
from .polly import mock_polly # flake8: noqa
from .rds import mock_rds, mock_rds_deprecated # flake8: noqa
Expand Down
2 changes: 2 additions & 0 deletions moto/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from moto.kms import kms_backends
from moto.logs import logs_backends
from moto.opsworks import opsworks_backends
from moto.organizations import organizations_backends
from moto.polly import polly_backends
from moto.rds2 import rds2_backends
from moto.redshift import redshift_backends
Expand Down Expand Up @@ -74,6 +75,7 @@
'kinesis': kinesis_backends,
'kms': kms_backends,
'opsworks': opsworks_backends,
'organizations': organizations_backends,
'polly': polly_backends,
'redshift': redshift_backends,
'rds': rds2_backends,
Expand Down
6 changes: 6 additions & 0 deletions moto/organizations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import unicode_literals
from .models import organizations_backend
from ..core.models import base_decorator

organizations_backends = {"global": organizations_backend}
mock_organizations = base_decorator(organizations_backends)
296 changes: 296 additions & 0 deletions moto/organizations/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
from __future__ import unicode_literals

import datetime
import re

from moto.core import BaseBackend, BaseModel
from moto.core.exceptions import RESTError
from moto.core.utils import unix_time
from moto.organizations import utils


class FakeOrganization(BaseModel):

def __init__(self, feature_set):
self.id = utils.make_random_org_id()
self.root_id = utils.make_random_root_id()
self.feature_set = feature_set
self.master_account_id = utils.MASTER_ACCOUNT_ID
self.master_account_email = utils.MASTER_ACCOUNT_EMAIL
self.available_policy_types = [{
'Type': 'SERVICE_CONTROL_POLICY',
'Status': 'ENABLED'
}]

@property
def arn(self):
return utils.ORGANIZATION_ARN_FORMAT.format(self.master_account_id, self.id)

@property
def master_account_arn(self):
return utils.MASTER_ACCOUNT_ARN_FORMAT.format(self.master_account_id, self.id)

def describe(self):
return {
'Organization': {
'Id': self.id,
'Arn': self.arn,
'FeatureSet': self.feature_set,
'MasterAccountArn': self.master_account_arn,
'MasterAccountId': self.master_account_id,
'MasterAccountEmail': self.master_account_email,
'AvailablePolicyTypes': self.available_policy_types,
}
}


class FakeAccount(BaseModel):

def __init__(self, organization, **kwargs):
self.organization_id = organization.id
self.master_account_id = organization.master_account_id
self.create_account_status_id = utils.make_random_create_account_status_id()
self.id = utils.make_random_account_id()
self.name = kwargs['AccountName']
self.email = kwargs['Email']
self.create_time = datetime.datetime.utcnow()
self.status = 'ACTIVE'
self.joined_method = 'CREATED'
self.parent_id = organization.root_id

@property
def arn(self):
return utils.ACCOUNT_ARN_FORMAT.format(
self.master_account_id,
self.organization_id,
self.id
)

@property
def create_account_status(self):
return {
'CreateAccountStatus': {
'Id': self.create_account_status_id,
'AccountName': self.name,
'State': 'SUCCEEDED',
'RequestedTimestamp': unix_time(self.create_time),
'CompletedTimestamp': unix_time(self.create_time),
'AccountId': self.id,
}
}

def describe(self):
return {
'Account': {
'Id': self.id,
'Arn': self.arn,
'Email': self.email,
'Name': self.name,
'Status': self.status,
'JoinedMethod': self.joined_method,
'JoinedTimestamp': unix_time(self.create_time),
}
}


class FakeOrganizationalUnit(BaseModel):

def __init__(self, organization, **kwargs):
self.type = 'ORGANIZATIONAL_UNIT'
self.organization_id = organization.id
self.master_account_id = organization.master_account_id
self.id = utils.make_random_ou_id(organization.root_id)
self.name = kwargs.get('Name')
self.parent_id = kwargs.get('ParentId')
self._arn_format = utils.OU_ARN_FORMAT

@property
def arn(self):
return self._arn_format.format(
self.master_account_id,
self.organization_id,
self.id
)

def describe(self):
return {
'OrganizationalUnit': {
'Id': self.id,
'Arn': self.arn,
'Name': self.name,
}
}


class FakeRoot(FakeOrganizationalUnit):

def __init__(self, organization, **kwargs):
super(FakeRoot, self).__init__(organization, **kwargs)
self.type = 'ROOT'
self.id = organization.root_id
self.name = 'Root'
self.policy_types = [{
'Type': 'SERVICE_CONTROL_POLICY',
'Status': 'ENABLED'
}]
self._arn_format = utils.ROOT_ARN_FORMAT

def describe(self):
return {
'Id': self.id,
'Arn': self.arn,
'Name': self.name,
'PolicyTypes': self.policy_types
}


class OrganizationsBackend(BaseBackend):

def __init__(self):
self.org = None
self.accounts = []
self.ou = []

def create_organization(self, **kwargs):
self.org = FakeOrganization(kwargs['FeatureSet'])
self.ou.append(FakeRoot(self.org))
return self.org.describe()

def describe_organization(self):
if not self.org:
raise RESTError(
'AWSOrganizationsNotInUseException',
"Your account is not a member of an organization."
)
return self.org.describe()

def list_roots(self):
return dict(
Roots=[ou.describe() for ou in self.ou if isinstance(ou, FakeRoot)]
)

def create_organizational_unit(self, **kwargs):
new_ou = FakeOrganizationalUnit(self.org, **kwargs)
self.ou.append(new_ou)
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:
raise RESTError(
'ParentNotFoundException',
"You specified parent that doesn't exist."
)
return parent_id

def describe_organizational_unit(self, **kwargs):
ou = self.get_organizational_unit_by_id(kwargs['OrganizationalUnitId'])
return ou.describe()

def list_organizational_units_for_parent(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
return dict(
OrganizationalUnits=[
{
'Id': ou.id,
'Arn': ou.arn,
'Name': ou.name,
}
for ou in self.ou
if ou.parent_id == parent_id
]
)

def create_account(self, **kwargs):
new_account = FakeAccount(self.org, **kwargs)
self.accounts.append(new_account)
return new_account.create_account_status

def get_account_by_id(self, account_id):
account = next((
account for account in self.accounts
if account.id == account_id
), 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()

def list_accounts(self):
return dict(
Accounts=[account.describe()['Account'] for account in self.accounts]
)

def list_accounts_for_parent(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
return dict(
Accounts=[
account.describe()['Account']
for account in self.accounts
if account.parent_id == parent_id
]
)

def move_account(self, **kwargs):
new_parent_id = self.validate_parent_id(kwargs['DestinationParentId'])
self.validate_parent_id(kwargs['SourceParentId'])
account = self.get_account_by_id(kwargs['AccountId'])
index = self.accounts.index(account)
self.accounts[index].parent_id = new_parent_id

def list_parents(self, **kwargs):
if re.compile(r'[0-9]{12}').match(kwargs['ChildId']):
child_object = self.get_account_by_id(kwargs['ChildId'])
else:
child_object = self.get_organizational_unit_by_id(kwargs['ChildId'])
return dict(
Parents=[
{
'Id': ou.id,
'Type': ou.type,
}
for ou in self.ou
if ou.id == child_object.parent_id
]
)

def list_children(self, **kwargs):
parent_id = self.validate_parent_id(kwargs['ParentId'])
if kwargs['ChildType'] == 'ACCOUNT':
obj_list = self.accounts
elif kwargs['ChildType'] == 'ORGANIZATIONAL_UNIT':
obj_list = self.ou
else:
raise RESTError(
'InvalidInputException',
'You specified an invalid value.'
)
return dict(
Children=[
{
'Id': obj.id,
'Type': kwargs['ChildType'],
}
for obj in obj_list
if obj.parent_id == parent_id
]
)


organizations_backend = OrganizationsBackend()
Loading