Restricting Stacks

Note: Premium video content requires a subscription.

If you want to restrict which stacks can be deployed to, you can use:

config/app.rb

Terraspace.configure do |config|
  config.allow.stacks = ["stack1"]
  # config.deny.stacks = ["stack2"]
end

Note: If both deny and allow is set, deny rules always wins over allow rules.

Example 1: Simple Allow

Let’s say you have stacks:

$ terraspace list
app/stacks/stack1
app/stacks/stack2

config/app.rb

Terraspace.configure do |config|
  config.allow.stacks = ["stack1"]
end

You will only be able to deploy stack1. If you try to deploy stack2, you’ll get an error message:

$ terraspace up stack2
ERROR: The configs do not allow this.
This stack is not allowed to be used for TS_ENV=dev
Allow stacks: stack1
$

Example 2: Simple Deny

Let’s say you have stacks:

$ terraspace list
app/stacks/stack1
app/stacks/stack2

config/app.rb

Terraspace.configure do |config|
  config.deny.stacks = ["stack1"]
end

You will only be able to deploy stack2. If you try to deploy stack1 though, you’ll get an error message:

$ terraspace up stack1
ERROR: The configs do not allow this.
This stack is not allowed to be used for TS_ENV=dev
Deny stacks: stack1
$

Example 3: Allow Specific Stacks for Specific Envs with Multiple Files

The examples above are not very useful. They are just to help explain how the option works. A more interesting and practical example is to allow terraspace up to only deploy specific stacks for specific environments.

Let’s say we have 3 stacks:

$ terraspace list
app/stacks/route53
app/stacks/stack1
app/stacks/stack2

Route53 resources are global and may not always make sense to deploy for every env. Here are the terraspace up commands to show how we only want these stacks to deploy with specific environments.

TS_ENV=global terraspace up route53
TS_ENV=dev    terraspace up stack1
TS_ENV=prod   terraspace up stack1
TS_ENV=dev    terraspace up stack2
TS_ENV=prod   terraspace up stack2

To configure terraspace up so that it will only be allowed deploy with the global env, we can do this:

config/app.rb

Terraspace.configure do |config|
  config.deny.stacks = ["route53"]
end

So we generally deny the route53 stack from all environments. Then we override the config allow it in the global environment like so:

config/envs/global.rb

Terraspace.configure do |config|
  config.allow.stacks = ["route53"]
  config.deny.stacks = nil # important to reset for TS_ENV=global
end

Running terraspace up for the route53 stack with envs other than TS_ENV=global results in something like this:

$ TS_ENV=dev terraspace up route53
Building .terraspace-cache/dev/stacks/route53
ERROR: The configs do not allow this.
This stack is not allowed to be used for TS_ENV=dev
Deny stacks: route53
$ TS_ENV=prod terraspace up route53
Building .terraspace-cache/prod/stacks/route53
ERROR: The configs do not allow this.
This stack is not allowed to be used for TS_ENV=prod
Deny stacks: route53
$

You will only be allowed to deploy the route53 stack with TS_ENV=global

$ TS_ENV=global terraspace up route53
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:
...
$

Example 4: Allow Specific Stacks for Specific Envs with One File

Some folks may prefer or feel it’s easier not to have the configuration in multiple files. To use one file instead:

config/app.rb

Terraspace.configure do |config|
  if Terraspace.env == "global"
    config.allow.stacks = ["route53"]
  else
    config.deny.stacks = ["route53"]
  end
end

This is one of the benefits that come with the way Terraspace handles configurations. You have access to the power of a full programming language, Ruby, when you need it. Of course, with great power comes great responsibility.

Question: Should you use multiple files or one file?

It depends. If the configurations are small, it’s clearer to have them in one file. If the configurations get complex, separating them into multiple files makes sense. It also depends a lot on personal preference.

Example 5: Complete Customization

For even more control over which stacks are allowed, you can assign an object that implements call and returns an Array. This is an advanced technique. Example:

config/app.rb

class AllowStacks
  def call(stack)
    if Terraspace.env == "global"
      ["route53"]
    end
  end
end
class DenyStacks
  def call(stack)
    if Terraspace.env != "global"
      ["route53"]
    end
  end
end

Terraspace.configure do |config|
  config.allow.stacks = AllowStacks
  config.deny.stacks = DenyStacks
end

The AllowStacks class defines the call method and takes the stack argument. Its value is the name of the current stack being deployed. The AllowStacks and DenyStacks classes should return an Array or nil. Returning nil is the same as not setting the option.

Including and Excluding Stacks Inference

If you are using config.allow.stacks and config.deny.stacks, then config.all.include_stacks and config.all.exclude_stacks are inferred from those settings. For example:

Let’s say we have 3 stacks:

$ terraspace list
app/stacks/route53
app/stacks/stack1
app/stacks/stack2

If you want to only allow terraspace up to deploy the route53 stack for TS_ENV=global environment. Configure:

config/app.rb

Terraspace.configure do |config|
  if Terraspace.env == "global"
    config.allow.stacks = ["route53"]
  else
    config.deny.stacks = ["route53"]
  end
end

Then the route53 stack will only be allowed to be deployed with TS_ENV=global. If TS_ENV=dev, then the route53 stack will be prevented from deploying. Example:

$ TS_ENV=global terraspace up route53
...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:
...
$ TS_ENV=dev terraspace up route53
Building .terraspace-cache/dev/stacks/route53
ERROR: The configs do not allow this.
This stack is not allowed to be used for TS_ENV=dev
Deny stacks: route53

By default, terraspace set the options like this:

config.all.include_stacks = config.allow.stacks
config.all.exclude_stacks = config.deny.stacks

This means:

$ TS_ENV=global terraspace all up
Will run:
    terraspace up route53 # batch 1

And

$ TS_ENV=dev terraspace all up
Will run:
    terraspace up stack1 # batch 1
    terraspace up stack2 # batch 1

The the route53 stack will only be deployed when TS_ENV=global by inference. You only have to set the config.allow.stacks and config.deny.stacks and terraspace all automatically knew what to do.

You can change this default behavior with config.all.consider_allow_deny_stacks = false. You would then need to set config.all.include_stacks and config.all.exclude_stacks explicitly.

More docs: Terraspace All Excluding Stacks.

More tools: