Sceptre: Multi Region Stack Resolution

Created on 30 Mar 2017  ยท  11Comments  ยท  Source: Sceptre/sceptre

Part of my environment deploys 2 VPCs in eu-west-1 and 1 VPC in us-east-1. I'd then like to use a Route53 Hosted Zone to acts as a Globally resilient Private Hosted Zone for these VPCs. I have my root environment set up

config/evertz
โ”œโ”€โ”€ all
โ”‚ย ย  โ””โ”€โ”€ private-hosted-zone.yaml
โ”œโ”€โ”€ config.yaml (region: eu-west-1)
โ”œโ”€โ”€ region-1
โ”‚ย ย  โ”œโ”€โ”€ bastion.yaml
โ”‚ย ย  โ”œโ”€โ”€ config.yaml (region: eu-west-1)
โ”‚ย ย  ...
โ”‚ย ย  โ”œโ”€โ”€ vpc-peer.yaml
โ”‚ย ย  โ””โ”€โ”€ vpc.yaml
โ””โ”€โ”€ region-2
    โ”œโ”€โ”€ config.yaml (region: us-east-1)
    ...
    โ””โ”€โ”€ vpc.yaml 

The configuration of the private-hosted-zone stack is set up to use the resolver to retrieve the VPCID from the outputs of the stacks deployed in different regions

template_path: templates/PrivateHostedZone.yaml

parameters:
  Identifier: {{ var.identifier }}

  ZoneName: {{ var.zone_name }}

  VPCIds:
    - !stack_output evertz/region-1/vpc::VPCID
    - !stack_output evertz/region-1/vpc-peer::VPCID
    - !stack_output evertz/region-2/vpc::VPCID

  Regions:
    - {{ var.region_1 }}
    - {{ var.region_1 }}
    - {{ var.region_2 }}

  NumberOfVPC: '3'

However when attempting to resolve the stack name, it's unable to locate it, because I believe the stack I'm trying to resolve does not exist in the same region as the stack I'm deploying

cross_region_stacks

It would be awesome if the stack resolver looked the stack up in the region that stack was launched in, to help with multi region environments.

Most helpful comment

For anyone interested, I also needed to solve this using custom resolver. I figured out a way to reuse the original StackOutput resolver, only changing the underlying region (if needed) before the resolving is done.

Please note I'l just starting with Sceptre, so no guarantees, but it seems to work fine:

from sceptre.resolvers.stack_output import StackOutput
from sceptre.connection_manager import ConnectionManager
from sceptre.environment import Environment

class StackOutputRegionAware(StackOutput):

    def __init__(self, *args, **kwargs):
        super(StackOutputRegionAware, self).__init__(*args, **kwargs)

    def resolve(self):
        source_stack_path = self.dependency_stack_name
        source_env_path, source_stack_name= source_stack_path.split('/')

        environment = Environment(self.environment_config.sceptre_dir, source_env_path)
        source_stack = environment.stacks[source_stack_name]

        region = source_stack.region

        if self.connection_manager.region != region:
            self.logger.info(
                "Source stack '%s' located in '%s', making sure underying CloudFormation stack " +
                    "is descibed in this region and not in '%s' where resolving the output",
                source_stack_path, region, self.connection_manager.region)

            self.connection_manager = ConnectionManager(
                region=region, iam_role=self.connection_manager.iam_role,
                profile=self.connection_manager.profile)
        else:
            self.logger.info(
                "Source stack '%s' is located in the same region as where resolving the output: '%s'",
                source_stack_path, region)

        output_value = super(StackOutputRegionAware, self).resolve()

        self.logger.info("OutputValue fetched: %s", output_value)

        return output_value

Usage:

parameters:
  # Same argument syntax as stock !stack_output resolver 
  SecondaryBucketName: !stack_output_region_aware dev-bkp/doclib::BucketName

All 11 comments

I just gave this a brief read but AWS Pseudo Parameters might help you here.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html

Yep you're right - resolvers are currently limited to looking up values in the same region as the "current" environment. In theory I don't see why we couldn't implement it but I think it would require a bit of rearchitecting. I'll look into it in more detail and we may include it. For the time being, I think a hardcoded value may be your best bet (sorry!).

I just digested this a little more.

I agree with James, it might be worth using a hook to gather the values.

Oh, I forgot to mention - you could write a custom resolver to do this for you. I don't think it'd be too difficult. You'd need to initialise a boto3.client("cloudformation") in the desired region and make the describe_stack_outputs() call.

Thought that might be the case! I'll see how it goes with either a custom hook or resolver for now.

Cool - let me know how it goes!

I went for a quick workaround, using a custom resolver. Copying the stack output resolver, but using

            client = self.connection_manager.boto_session.client('cloudformation', region_name=self.region)
            response = client.describe_stacks(StackName=stack_name)

to describe the stacks. I modified the input string to include the the region

However - this causes lots of issues with throttling when deploying an environment
fixed the above by implementing the exponential backoff decorator

Hey guys, I'm having the same problem. This is especially useful for CF stack which create AWS::CertificateManager::Certificate for cloudfront (which only work for us-east-1).

ACM Cert ARNs can be exported in us-east-1 region but I need to use them in a different region as a parameter for another CF stack.

Hey guys, I'm having the same problem. This is especially useful for CF stack which create AWS::CertificateManager::Certificate for cloudfront (which only work for us-east-1).

Yeah, I'm having exactly this issue too. I'd _like_ to have the other resources in a different region, but current work-around is to put everything in us-east-1. This does not feel like a good solution.

this will be resolved in #257

For anyone interested, I also needed to solve this using custom resolver. I figured out a way to reuse the original StackOutput resolver, only changing the underlying region (if needed) before the resolving is done.

Please note I'l just starting with Sceptre, so no guarantees, but it seems to work fine:

from sceptre.resolvers.stack_output import StackOutput
from sceptre.connection_manager import ConnectionManager
from sceptre.environment import Environment

class StackOutputRegionAware(StackOutput):

    def __init__(self, *args, **kwargs):
        super(StackOutputRegionAware, self).__init__(*args, **kwargs)

    def resolve(self):
        source_stack_path = self.dependency_stack_name
        source_env_path, source_stack_name= source_stack_path.split('/')

        environment = Environment(self.environment_config.sceptre_dir, source_env_path)
        source_stack = environment.stacks[source_stack_name]

        region = source_stack.region

        if self.connection_manager.region != region:
            self.logger.info(
                "Source stack '%s' located in '%s', making sure underying CloudFormation stack " +
                    "is descibed in this region and not in '%s' where resolving the output",
                source_stack_path, region, self.connection_manager.region)

            self.connection_manager = ConnectionManager(
                region=region, iam_role=self.connection_manager.iam_role,
                profile=self.connection_manager.profile)
        else:
            self.logger.info(
                "Source stack '%s' is located in the same region as where resolving the output: '%s'",
                source_stack_path, region)

        output_value = super(StackOutputRegionAware, self).resolve()

        self.logger.info("OutputValue fetched: %s", output_value)

        return output_value

Usage:

parameters:
  # Same argument syntax as stock !stack_output resolver 
  SecondaryBucketName: !stack_output_region_aware dev-bkp/doclib::BucketName
Was this page helpful?
0 / 5 - 0 ratings

Related issues

brian-provenzano picture brian-provenzano  ยท  6Comments

sathed picture sathed  ยท  3Comments

fabiodouek picture fabiodouek  ยท  5Comments

ngfgrant picture ngfgrant  ยท  3Comments

medbensalem picture medbensalem  ยท  7Comments