add support for AWS Organizations

endpoints covers so far:
- create_organization
- describe_organization
- create_account
- describe_account
- list_accounts

all tests passing.
could use some advise from maintaners.
This commit is contained in:
Ashley Gould 2018-07-14 11:35:37 -07:00
commit edbc57e00d
11 changed files with 475 additions and 0 deletions

View file

View file

@ -0,0 +1,24 @@
"""
Temporary functions for checking object structures while specing out
models. This module will go away.
"""
import yaml
import moto
from moto import organizations as orgs
# utils
print(orgs.utils.make_random_org_id())
print(orgs.utils.make_random_root_id())
print(orgs.utils.make_random_account_id())
print(orgs.utils.make_random_create_account_id())
# models
my_org = orgs.models.FakeOrganization(feature_set = 'ALL')
print(yaml.dump(my_org._describe()))
#assert False
my_account = orgs.models.FakeAccount(my_org, AccountName='blee01', Email='blee01@moto-example.org')
print(yaml.dump(my_account))
#assert False

View file

@ -0,0 +1,193 @@
from __future__ import unicode_literals
import boto3
import botocore.exceptions
import sure # noqa
import yaml
import re
import datetime
import moto
from moto import mock_organizations
from moto.organizations.models import (
MASTER_ACCOUNT_ID,
MASTER_ACCOUNT_EMAIL,
ORGANIZATION_ARN_FORMAT,
MASTER_ACCOUNT_ARN_FORMAT,
ACCOUNT_ARN_FORMAT,
)
from .test_organizations_utils import (
ORG_ID_REGEX,
ROOT_ID_REGEX,
ACCOUNT_ID_REGEX,
CREATE_ACCOUNT_STATUS_ID_REGEX,
)
EMAIL_REGEX = "^.+@[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}|[0-9]{1,3}$"
def validate_organization(response):
org = response['Organization']
sorted(org.keys()).should.equal([
'Arn',
'AvailablePolicyTypes',
'FeatureSet',
'Id',
'MasterAccountArn',
'MasterAccountEmail',
'MasterAccountId',
])
org['Id'].should.match(ORG_ID_REGEX)
org['MasterAccountId'].should.equal(MASTER_ACCOUNT_ID)
org['MasterAccountArn'].should.equal(MASTER_ACCOUNT_ARN_FORMAT.format(
org['MasterAccountId'],
org['Id'],
))
org['Arn'].should.equal(ORGANIZATION_ARN_FORMAT.format(
org['MasterAccountId'],
org['Id'],
))
org['MasterAccountEmail'].should.equal(MASTER_ACCOUNT_EMAIL)
org['FeatureSet'].should.be.within(['ALL', 'CONSOLIDATED_BILLING'])
org['AvailablePolicyTypes'].should.equal([{
'Type': 'SERVICE_CONTROL_POLICY',
'Status': 'ENABLED'
}])
#
#'Organization': {
# 'Id': 'string',
# 'Arn': 'string',
# 'FeatureSet': 'ALL'|'CONSOLIDATED_BILLING',
# 'MasterAccountArn': 'string',
# 'MasterAccountId': 'string',
# 'MasterAccountEmail': 'string',
# 'AvailablePolicyTypes': [
# {
# 'Type': 'SERVICE_CONTROL_POLICY',
# 'Status': 'ENABLED'|'PENDING_ENABLE'|'PENDING_DISABLE'
# },
# ]
#}
def validate_account(org, account):
sorted(account.keys()).should.equal([
'Arn',
'Email',
'Id',
'JoinedMethod',
'JoinedTimestamp',
'Name',
'Status',
])
account['Id'].should.match(ACCOUNT_ID_REGEX)
account['Arn'].should.equal(ACCOUNT_ARN_FORMAT.format(
org['MasterAccountId'],
org['Id'],
account['Id'],
))
account['Email'].should.match(EMAIL_REGEX)
account['JoinedMethod'].should.be.within(['INVITED', 'CREATED'])
account['Status'].should.be.within(['ACTIVE', 'SUSPENDED'])
account['Name'].should.be.a(str)
account['JoinedTimestamp'].should.be.a(datetime.datetime)
#'Account': {
# 'Id': 'string',
# 'Arn': 'string',
# 'Email': 'string',
# 'Name': 'string',
# 'Status': 'ACTIVE'|'SUSPENDED',
# 'JoinedMethod': 'INVITED'|'CREATED',
# 'JoinedTimestamp': datetime(2015, 1, 1)
#}
def validate_create_account_status(create_status):
sorted(create_status.keys()).should.equal([
'AccountId',
'AccountName',
'CompletedTimestamp',
'Id',
'RequestedTimestamp',
'State',
])
create_status['Id'].should.match(CREATE_ACCOUNT_STATUS_ID_REGEX)
create_status['AccountId'].should.match(ACCOUNT_ID_REGEX)
create_status['AccountName'].should.be.a(str)
create_status['State'].should.equal('SUCCEEDED')
create_status['RequestedTimestamp'].should.be.a(datetime.datetime)
create_status['CompletedTimestamp'].should.be.a(datetime.datetime)
#'CreateAccountStatus': {
# 'Id': 'string',
# 'AccountName': 'string',
# 'State': 'IN_PROGRESS'|'SUCCEEDED'|'FAILED',
# 'RequestedTimestamp': datetime(2015, 1, 1),
# 'CompletedTimestamp': datetime(2015, 1, 1),
# 'AccountId': 'string',
# 'FailureReason': 'ACCOUNT_LIMIT_EXCEEDED'|'EMAIL_ALREADY_EXISTS'|'INVALID_ADDRESS'|'INVALID_EMAIL'|'CONCURRENT_ACCOUNT_MODIFICATION'|'INTERNAL_FAILURE'
#}
@mock_organizations
def test_create_organization():
client = boto3.client('organizations', region_name='us-east-1')
response = client.create_organization(FeatureSet='ALL')
#print(yaml.dump(response))
validate_organization(response)
response['Organization']['FeatureSet'].should.equal('ALL')
#assert False
@mock_organizations
def test_describe_organization():
client = boto3.client('organizations', region_name='us-east-1')
client.create_organization(FeatureSet='ALL')
response = client.describe_organization()
#print(yaml.dump(response))
validate_organization(response)
#assert False
mockname = 'mock-account'
mockdomain = 'moto-example.org'
mockemail = '@'.join([mockname, mockdomain])
@mock_organizations
def test_create_account():
client = boto3.client('organizations', region_name='us-east-1')
client.create_organization(FeatureSet='ALL')
create_status = client.create_account(
AccountName=mockname, Email=mockemail)['CreateAccountStatus']
#print(yaml.dump(create_status, default_flow_style=False))
validate_create_account_status(create_status)
create_status['AccountName'].should.equal(mockname)
#assert False
@mock_organizations
def test_describe_account():
client = boto3.client('organizations', region_name='us-east-1')
org = client.create_organization(FeatureSet='ALL')['Organization']
account_id = client.create_account(
AccountName=mockname, Email=mockemail)['CreateAccountStatus']['AccountId']
response = client.describe_account(AccountId=account_id)
#print(yaml.dump(response, default_flow_style=False))
validate_account(org, response['Account'])
response['Account']['Name'].should.equal(mockname)
response['Account']['Email'].should.equal(mockemail)
#assert False
@mock_organizations
def test_list_accounts():
client = boto3.client('organizations', region_name='us-east-1')
org = client.create_organization(FeatureSet='ALL')['Organization']
for i in range(5):
name = mockname + str(i)
email = name + '@' + mockdomain
client.create_account(AccountName=name, Email=email)
response = client.list_accounts()
#print(yaml.dump(response, default_flow_style=False))
response.should.have.key('Accounts')
accounts = response['Accounts']
len(accounts).should.equal(5)
for account in accounts:
validate_account(org, account)
accounts[3]['Name'].should.equal(mockname + '3')
accounts[2]['Email'].should.equal(mockname + '2' + '@' + mockdomain)
#assert False

View file

@ -0,0 +1,26 @@
from __future__ import unicode_literals
import sure # noqa
import moto
from moto.organizations import utils
ORG_ID_REGEX = r'o-[a-z0-9]{%s}' % utils.ORG_ID_SIZE
ROOT_ID_REGEX = r'r-[a-z0-9]{%s}' % utils.ROOT_ID_SIZE
ACCOUNT_ID_REGEX = r'[0-9]{%s}' % utils.ACCOUNT_ID_SIZE
CREATE_ACCOUNT_STATUS_ID_REGEX = r'car-[a-z0-9]{%s}' % utils.CREATE_ACCOUNT_STATUS_ID_SIZE
def test_make_random_org_id():
org_id = utils.make_random_org_id()
org_id.should.match(ORG_ID_REGEX)
def test_make_random_root_id():
org_id = utils.make_random_root_id()
org_id.should.match(ROOT_ID_REGEX)
def test_make_random_account_id():
account_id = utils.make_random_account_id()
account_id.should.match(ACCOUNT_ID_REGEX)
def test_make_random_create_account_status_id():
create_account_status_id = utils.make_random_create_account_status_id()
create_account_status_id.should.match(CREATE_ACCOUNT_STATUS_ID_REGEX)