Terraspace deploy in Multi Clients

Hello,

I’m exploring the terraspace framework in alternative to teragrunt.

Which is the project structure do you recommend if i have several clients (each one with own account) and i want to deploy the same stack to my all clients? The deployment is done via my aws account assuming a cross account role.

I need to have a different tfvars file per client, and when i do a change in terraspace code, i want with one command deploy in alll clients i have configured.

with a Terragrunt, i’m able to accomplish that using their recommend approach: https://github.com/gruntwork-io/terragrunt-infrastructure-live-example

Thanks and all the best to the project.

Regards,
Fábio Santos

Current Terragrunt Structure?

Am guessing that the current terragrunt setup is something like this?

├── dev
│   ├── client1
│   │   ├── stack1
│   │   │   └── terragrunt.hcl
│   │   └── stack2
│   │       └── terragrunt.hcl
│   └── client2
│       ├── stack1
│       │   └── terragrunt.hcl
│       └── stack2
│           └── terragrunt.hcl
└── prod
    ├── client1
    │   ├── stack1
    │   │   └── terragrunt.hcl
    │   └── stack2
    │       └── terragrunt.hcl
    └── client2
        ├── stack1
        │   └── terragrunt.hcl
        └── stack2
            └── terragrunt.hcl

Then you deploy one all the stacks for a client like this?

cd dev/client1  && terragrunt apply-all
cd dev/client2  && terragrunt apply-all
cd prod/client1 && terragrunt apply-all

And to deploy to all clients?

cd dev  && terragrunt apply-all
cd prod && terragrunt apply-all

Unsure if that’s the structure you have. :man_shrugging:t2:


Approach #1: Using Terraspace Built-In Defaults

One approach is using different TS_ENV with the client in the value. Though the built-in defaults may not be a good fit for the above mono-repo structure, it would look something like this:

app/stacks
├── stack1
│  ├── main.tf
│  ├── outputs.tf
│  ├── tfvars
│  │  ├── dev-client1.tfvars
│  │  ├── dev-client2.tfvars
│  │  ├── prod-client1.tfvars
│  │  └── prod-client2.tfvars
│  └── variables.tf
└── stack2
    ├── main.tf
    ├── outputs.tf
    ├── tfvars
    │  ├── dev-client1.tfvars
    │  ├── dev-client2.tfvars
    │  ├── prod-client1.tfvars
    │  └── prod-client2.tfvars
    └── variables.tf

The commands would be:

TS_ENV=dev-client1  terraspace all up
TS_ENV=dev-client2  terraspace all up
TS_ENV=prod-client1 terraspace all up

That’s one way it could be done. Don’t think it’s great because it mixes multiple client tfvars in the same folder.


Approach #2: Terraspace Custom Layering

Another approach is to define your own custom layering. Terraspace can be customized for different needs. Something like:

config/inits/layering.rb

Terraspace::Layering.module_eval do
  def pre_layers
    ["clients/#{ENV['CLIENT']}/#{@mod.name}"]
  end
end

The Terraspace project structure would be:

app
└── stacks
    ├── stack1
    │   ├── main.tf
    │   ├── outputs.tf
    │   └── variables.tf
    └── stack2
        ├── main.tf
        ├── outputs.tf
        └── variables.tf
config/terraform/tfvars
└── clients
    ├── client1
    │   ├── stack1
    │   │   ├── dev.tfvars
    │   │   └── prod.tfvars
    │   └── stack2
    │       ├── dev.tfvars
    │       └── prod.tfvars
    └── client2
        ├── stack1
        │   ├── dev.tfvars
        │   └── prod.tfvars
        └── stack2
            ├── dev.tfvars
            └── prod.tfvars

The commands would be:

CLIENT=client1 TS_ENV=dev  terraspace build
CLIENT=client2 TS_ENV=dev  terraspace build
CLIENT=client1 TS_ENV=prod terraspace build
Click to see example build:
$ CLIENT=client1 terraspace build
Building one stack to build all stacks
Building .terraspace-cache/us-west-2/dev/stacks/stack1
Built in .terraspace-cache/us-west-2/dev/stacks/stack1
$ tree .terraspace-cache
.terraspace-cache
└── us-west-2
    └── dev
        ├── modules
        │   └── example
        │       ├── main.tf
        │       ├── outputs.tf
        │       └── variables.tf
        └── stacks
            ├── demo
            ├── stack1
            │   ├── 1-project-clients-client1-stack1-dev.auto.tfvars
            │   ├── backend.tf
            │   ├── main.tf
            │   ├── outputs.tf
            │   ├── provider.tf
            │   └── variables.tf
            └── stack2
                ├── 1-project-clients-client1-stack2-dev.auto.tfvars
                ├── backend.tf
                ├── main.tf
                ├── outputs.tf
                ├── provider.tf
                └── variables.tf

That might be the right amount of trade-offs.


Approach #3: Separate Terraspace Projects

Consider separating repos per client and make the stacks themselves reusable. Then would also consider using the seed/tfvars/stacks/STACK folder instead of the app/stack/STACK/tfvars. See: https://terraspace.cloud/docs/tfvars/lookups/ This decouples the tfvars files from the vendor/stacks folder which would be managed via a Terrafile. Then you’ll be able to:

cd client1-repo && TS_ENV=dev  terraspace all up
cd client2-repo && TS_ENV=dev  terraspace all up
cd client1-repo && TS_ENV=prod terraspace all up

It comes at a cost of the complexity of multiple repos, though. Some like this and some do not :man_shrugging:t2:

Deploying All Clients

Terraspace is more env focus and so you cannot deploy to all clients at once. Think that’s better handled as separate wrapper script/command that calls Terraspace multiple times. There are tradeoffs. Some more thoughts here:

1 Like

Hi, I am exploring the use of Approach #2. Is there a recommendation on how to ignore a stack against a specific CLIENT (AWS_ACCOUNT in my case)? I tried different file and folder structures under config/envs and config/ using ACCOUNT_ID.rb with config.all.ignore_stacks set.

CLIENT=000000000000 AWS_PROFILE=000000000000 terraspace all up -y

still deploys all stacks. The same dev.rb works as expected. Thanks.