2022-12-03 17:07:04 +00:00
import json
2023-11-30 15:55:51 +00:00
from unittest import SkipTest , TestCase
from uuid import uuid4
import boto3
2022-12-03 17:07:04 +00:00
import pytest
from botocore . exceptions import ClientError
2023-11-30 15:55:51 +00:00
2024-01-07 12:03:33 +00:00
from moto import mock_aws , settings
2022-12-03 17:07:04 +00:00
from moto . cloudformation import cloudformation_backends as cf_backends
2023-11-30 15:55:51 +00:00
from moto . core import DEFAULT_ACCOUNT_ID
2022-12-03 17:07:04 +00:00
from moto . sqs import sqs_backends
TEMPLATE_DCT = {
" AWSTemplateFormatVersion " : " 2010-09-09 " ,
" Description " : " Template creating one queue " ,
" Resources " : {
" MyQueue " : { " Type " : " AWS::SQS::Queue " , " Properties " : { " QueueName " : " testqueue " } }
} ,
}
class TestStackSetMultipleAccounts ( TestCase ) :
def setUp ( self ) - > None :
# Set up multiple organizations/accounts
# Our tests will create resources within these orgs/accounts
#
# | ROOT |
# / \
# / \
# [ou01] [ou02]
# | / | \
# | / [acct02] \
# [acct01] / \
# [ou21] [ou22]
# | |
# [acct21] [acct22]
#
if settings . TEST_SERVER_MODE :
raise SkipTest (
" These tests directly access the backend, which is not possible in ServerMode "
)
mockname = " mock-account "
mockdomain = " moto-example.org "
mockemail = " @ " . join ( [ mockname , mockdomain ] )
self . org = boto3 . client ( " organizations " , region_name = " us-east-1 " )
self . org . create_organization ( FeatureSet = " ALL " )
self . acct01 = self . org . create_account ( AccountName = mockname , Email = mockemail ) [
" CreateAccountStatus "
] [ " AccountId " ]
self . acct02 = self . org . create_account ( AccountName = mockname , Email = mockemail ) [
" CreateAccountStatus "
] [ " AccountId " ]
self . acct21 = self . org . create_account ( AccountName = mockname , Email = mockemail ) [
" CreateAccountStatus "
] [ " AccountId " ]
self . acct22 = self . org . create_account ( AccountName = mockname , Email = mockemail ) [
" CreateAccountStatus "
] [ " AccountId " ]
root_id = self . org . list_roots ( ) [ " Roots " ] [ 0 ] [ " Id " ]
self . ou01 = self . org . create_organizational_unit ( ParentId = root_id , Name = " ou1 " ) [
" OrganizationalUnit "
] [ " Id " ]
self . ou02 = self . org . create_organizational_unit ( ParentId = root_id , Name = " ou2 " ) [
" OrganizationalUnit "
] [ " Id " ]
self . ou21 = self . org . create_organizational_unit (
ParentId = self . ou02 , Name = " ou021 "
) [ " OrganizationalUnit " ] [ " Id " ]
self . ou22 = self . org . create_organizational_unit (
ParentId = self . ou02 , Name = " ou022 "
) [ " OrganizationalUnit " ] [ " Id " ]
self . org . move_account (
AccountId = self . acct01 , SourceParentId = root_id , DestinationParentId = self . ou01
)
self . org . move_account (
AccountId = self . acct02 , SourceParentId = root_id , DestinationParentId = self . ou02
)
self . org . move_account (
AccountId = self . acct21 , SourceParentId = root_id , DestinationParentId = self . ou21
)
self . org . move_account (
AccountId = self . acct22 , SourceParentId = root_id , DestinationParentId = self . ou22
)
self . name = " a " + str ( uuid4 ( ) ) [ 0 : 6 ]
self . client = boto3 . client ( " cloudformation " , region_name = " us-east-1 " )
def _verify_stack_instance ( self , accnt , region = None ) :
resp = self . client . describe_stack_instance (
StackSetName = self . name ,
StackInstanceAccount = accnt ,
StackInstanceRegion = region or " us-east-1 " ,
) [ " StackInstance " ]
2023-07-04 15:53:58 +00:00
assert resp [ " Account " ] == accnt
2022-12-03 17:07:04 +00:00
def _verify_queues ( self , accnt , expected , region = None ) :
2023-07-04 15:53:58 +00:00
assert (
list ( sqs_backends [ accnt ] [ region or " us-east-1 " ] . queues . keys ( ) ) == expected
2022-12-03 17:07:04 +00:00
)
2024-01-07 12:03:33 +00:00
@mock_aws
2022-12-03 17:07:04 +00:00
class TestServiceManagedStacks ( TestStackSetMultipleAccounts ) :
def setUp ( self ) - > None :
super ( ) . setUp ( )
self . client . create_stack_set (
StackSetName = self . name ,
Description = " test creating stack set in multiple accounts " ,
TemplateBody = json . dumps ( TEMPLATE_DCT ) ,
PermissionModel = " SERVICE_MANAGED " ,
AutoDeployment = { " Enabled " : False } ,
)
def test_create_instances__specifying_accounts ( self ) :
with pytest . raises ( ClientError ) as exc :
self . client . create_stack_instances (
StackSetName = self . name , Accounts = [ " 888781156701 " ] , Regions = [ " us-east-1 " ]
)
err = exc . value . response [ " Error " ]
2023-07-04 15:53:58 +00:00
assert err [ " Code " ] == " ValidationError "
assert (
err [ " Message " ]
== " StackSets with SERVICE_MANAGED permission model can only have OrganizationalUnit as target "
2022-12-03 17:07:04 +00:00
)
def test_create_instances__specifying_only_accounts_in_deployment_targets ( self ) :
with pytest . raises ( ClientError ) as exc :
self . client . create_stack_instances (
StackSetName = self . name ,
DeploymentTargets = {
" Accounts " : [ " 888781156701 " ] ,
} ,
Regions = [ " us-east-1 " ] ,
)
err = exc . value . response [ " Error " ]
2023-07-04 15:53:58 +00:00
assert err [ " Code " ] == " ValidationError "
assert err [ " Message " ] == " OrganizationalUnitIds are required "
2022-12-03 17:07:04 +00:00
def test_create_instances___with_invalid_ou ( self ) :
with pytest . raises ( ClientError ) as exc :
self . client . create_stack_instances (
StackSetName = self . name ,
DeploymentTargets = { " OrganizationalUnitIds " : [ " unknown " ] } ,
Regions = [ " us-east-1 " ] ,
)
err = exc . value . response [ " Error " ]
2023-07-04 15:53:58 +00:00
assert err [ " Code " ] == " ValidationError "
assert (
err [ " Message " ]
== " 1 validation error detected: Value ' [unknown] ' at ' deploymentTargets.organizationalUnitIds ' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 68, Member must have length greater than or equal to 6, Member must satisfy regular expression pattern: ^(ou-[a-z0-9] { 4,32}-[a-z0-9] { 8,32}|r-[a-z0-9] { 4,32})$] "
2022-12-03 17:07:04 +00:00
)
def test_create_instances__single_ou ( self ) :
self . client . create_stack_instances (
StackSetName = self . name ,
DeploymentTargets = { " OrganizationalUnitIds " : [ self . ou01 ] } ,
Regions = [ " us-east-1 " ] ,
)
self . _verify_stack_instance ( self . acct01 )
# We have a queue in our account connected to OU-01
self . _verify_queues ( self . acct01 , [ " testqueue " ] )
# No queue has been created in our main account, or unrelated accounts
self . _verify_queues ( DEFAULT_ACCOUNT_ID , [ ] )
self . _verify_queues ( self . acct02 , [ ] )
self . _verify_queues ( self . acct21 , [ ] )
self . _verify_queues ( self . acct22 , [ ] )
def test_create_instances__ou_with_kiddos ( self ) :
self . client . create_stack_instances (
StackSetName = self . name ,
DeploymentTargets = { " OrganizationalUnitIds " : [ self . ou02 ] } ,
Regions = [ " us-east-1 " ] ,
)
self . _verify_stack_instance ( self . acct02 )
self . _verify_stack_instance ( self . acct21 )
self . _verify_stack_instance ( self . acct22 )
# No queue has been created in our main account
self . _verify_queues ( DEFAULT_ACCOUNT_ID , expected = [ ] )
self . _verify_queues ( self . acct01 , expected = [ ] )
# But we do have a queue in our (child)-accounts of OU-02
self . _verify_queues ( self . acct02 , [ " testqueue " ] )
self . _verify_queues ( self . acct21 , [ " testqueue " ] )
self . _verify_queues ( self . acct22 , [ " testqueue " ] )
2024-01-07 12:03:33 +00:00
@mock_aws
2022-12-03 17:07:04 +00:00
class TestSelfManagedStacks ( TestStackSetMultipleAccounts ) :
def setUp ( self ) - > None :
super ( ) . setUp ( )
# Assumptions: All the necessary IAM roles are created
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-prereqs-self-managed.html
self . client . create_stack_set (
StackSetName = self . name ,
Description = " test creating stack set in multiple accounts " ,
TemplateBody = json . dumps ( TEMPLATE_DCT ) ,
)
def test_create_instances__specifying_accounts ( self ) :
self . client . create_stack_instances (
StackSetName = self . name , Accounts = [ self . acct01 ] , Regions = [ " us-east-2 " ]
)
self . _verify_stack_instance ( self . acct01 , region = " us-east-2 " )
# acct01 has a Stack
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct01 ] [ " us-east-2 " ] . stacks ) == 1
2022-12-03 17:07:04 +00:00
# Other acounts do not
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct02 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ self . acct21 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ self . acct22 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ DEFAULT_ACCOUNT_ID ] [ " us-east-2 " ] . stacks ) == 0
2022-12-03 17:07:04 +00:00
# acct01 has a queue
self . _verify_queues ( self . acct01 , [ " testqueue " ] , region = " us-east-2 " )
# other accounts are empty
self . _verify_queues ( self . acct02 , [ ] , region = " us-east-2 " )
self . _verify_queues ( self . acct21 , [ ] , region = " us-east-2 " )
self . _verify_queues ( self . acct22 , [ ] , region = " us-east-2 " )
self . _verify_queues ( DEFAULT_ACCOUNT_ID , [ ] , region = " us-east-2 " )
def test_create_instances__multiple_accounts ( self ) :
self . client . create_stack_instances (
StackSetName = self . name ,
Accounts = [ self . acct01 , self . acct02 ] ,
Regions = [ " us-east-2 " ] ,
)
# acct01 and 02 have a Stack
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct01 ] [ " us-east-2 " ] . stacks ) == 1
assert len ( cf_backends [ self . acct02 ] [ " us-east-2 " ] . stacks ) == 1
2022-12-03 17:07:04 +00:00
# Other acounts do not
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct21 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ self . acct22 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ DEFAULT_ACCOUNT_ID ] [ " us-east-2 " ] . stacks ) == 0
2022-12-03 17:07:04 +00:00
# acct01 and 02 have the queue
self . _verify_queues ( self . acct01 , [ " testqueue " ] , region = " us-east-2 " )
self . _verify_queues ( self . acct02 , [ " testqueue " ] , region = " us-east-2 " )
# other accounts are empty
self . _verify_queues ( self . acct21 , [ ] , region = " us-east-2 " )
self . _verify_queues ( self . acct22 , [ ] , region = " us-east-2 " )
self . _verify_queues ( DEFAULT_ACCOUNT_ID , [ ] , region = " us-east-2 " )
def test_delete_instances ( self ) :
# Create two stack instances
self . client . create_stack_instances (
StackSetName = self . name ,
Accounts = [ self . acct01 , self . acct02 ] ,
Regions = [ " us-east-2 " ] ,
)
# Delete one
self . client . delete_stack_instances (
StackSetName = self . name ,
Accounts = [ self . acct01 ] ,
Regions = [ " us-east-2 " ] ,
RetainStacks = False ,
)
# Act02 still has it's stack
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct02 ] [ " us-east-2 " ] . stacks ) == 1
2022-12-03 17:07:04 +00:00
# Other Stacks are removed
2023-07-04 15:53:58 +00:00
assert len ( cf_backends [ self . acct01 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ self . acct21 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ self . acct22 ] [ " us-east-2 " ] . stacks ) == 0
assert len ( cf_backends [ DEFAULT_ACCOUNT_ID ] [ " us-east-2 " ] . stacks ) == 0
2022-12-03 17:07:04 +00:00
# Acct02 still has it's queue as well
self . _verify_queues ( self . acct02 , [ " testqueue " ] , region = " us-east-2 " )
# Other queues are removed
self . _verify_queues ( self . acct01 , [ ] , region = " us-east-2 " )
self . _verify_queues ( self . acct21 , [ ] , region = " us-east-2 " )
self . _verify_queues ( self . acct22 , [ ] , region = " us-east-2 " )
self . _verify_queues ( DEFAULT_ACCOUNT_ID , [ ] , region = " us-east-2 " )
def test_create_instances__single_ou ( self ) :
with pytest . raises ( ClientError ) as exc :
self . client . create_stack_instances (
StackSetName = self . name ,
DeploymentTargets = { " OrganizationalUnitIds " : [ self . ou01 ] } ,
Regions = [ " us-east-1 " ] ,
)
err = exc . value . response [ " Error " ]
2023-07-04 15:53:58 +00:00
assert err [ " Code " ] == " ValidationError "
assert (
err [ " Message " ]
== " StackSets with SELF_MANAGED permission model can only have accounts as target "
2022-12-03 17:07:04 +00:00
)