New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to raise an error #15469

Open
timgurney-ee opened this Issue Jul 4, 2017 · 6 comments

Comments

Projects
None yet
5 participants
@timgurney-ee

timgurney-ee commented Jul 4, 2017

One feature I have not been able to find (or code around) is the lack of ability to raise an intentional error.

There are times where I need certain things to be correct for example 2 lists to be of the same size, or a variable to be one of a certain set of values etc.

So the addition of a raise_error function or similar would be useful. I was thinking of it being an additional interpolation function something that works like this:

raise_error(condition, message)

for example

raise_error(${length(var.array1) == length(var.array2)}, 'The arrays need to be the same length')

if the condition fails, then the message is displayed otherwise the processing continues.

@apparentlymart

This comment has been minimized.

Contributor

apparentlymart commented Jul 5, 2017

Hi @timgurney-ee! Thanks for this suggestion.

It sounds like what you're looking to do here is to guarantee certain invariants that your configuration depends on and prevent Terraform from trying to process a configuration if those invariants don't hold.

I've thought before about the idea of having a way to make "assertions" in the Terraform config that get tested before Terraform will take any further action. For example (made-up syntax here just for illustration purposes):

require "matching_arrays" {
  test = "${length(var.array1) == length(var.array2)}"
  message = "array1 and array2 must be the same length"
}

I'd imagined this working by creating a new node in the graph representing the requirement, and then visiting that node during all graph walks. If the test expression returns <computed> (because it refers to a resource attribute we can't know yet) then we'd proceed optimistically assuming the assertion is true, but if it returns a concrete false we'd fail.

In the above example where only var. interpolations are used, this would be able to fail early in the "validate" walk, which would be the most ideal behavior.

The following case is trickier:

require "distinct_foo_and_baz" {
  test = "${aws_instance.foo.id != aws_instance.baz.id}"
  message = "instances foo and baz must be distinct"
}

This is a contrived example, but it illustrates a case where we'd be unable to return the error until after both instances are created in the apply step. Fortunately I think most real-world uses-cases of this would apply to variables and data sources, and would thus generally be taken care of during either the "validate" or the "plan" step.

Another part of this would be defining which resources should only be processed if the invariant is satisfied. This could be achieved by a new special node type in depends_on, like this:

resource "aws_instance" "bar" {
  # ... (normal attributes) ...

  depends_on = [
    "require.matching_arrays",
    "require.distinct_foo_and_baz",
  ]
}

This would then let Terraform know that it mustn't try to real with this instance until after the requirement has been checked. Without this, Terraform's normal concurrent processing of resources could allow the instance to get processed before the assertion is processed, in the event that the assertion is being processed at apply time due to referring to other computed resource attributes.

This is all just a sketch for now. Not sure what this would actually look like, but I'm curious to hear if you think the above would help solve the problem you're trying to solve. I think a first-class block would work better for this than an interpolation function because it gives us this ability to control the processing order via normal dependencies, which would be much harder with an interpolation function.

@timgurney-ee

This comment has been minimized.

timgurney-ee commented Jul 5, 2017

I think you have definitely hit on what I was trying to do with the suggestion.

For the use case I was thinking of it would normally be known vars however the extended examples with interpolated results also make a lot of sense but also highlight the complexity of the problem.

The overall descriptions would solve the problem I am looking at, assert is effectively what I am thinking of. I didn't take my thoughts as deep or as detailed you, as I was only considering a smaller subset of issues.

I would be happy if this came in in stages, maying stage one just being pre-defined vars with values, and then maybe extending it to handle interpolation at a later stage?

@Jamie-BitFlight

This comment has been minimized.

Jamie-BitFlight commented Nov 10, 2017

Hey guys,

I found a way today how you can hack in asserts into the current version of Terraform.
I have banged out this example https://www.linkedin.com/pulse/devops-how-do-assertion-test-terraform-template-jamie-nelson/

TL;DR

variable "environment_list" {
  description = "Environment ID"
  type = "list"
  default = ["dev", "qa", "prod"]
}
variable "env" {
description = "Which environment do you want (options: dev, qa, prod):"
}
resource "null_resource" "is_environment_name_valid" {
  count = "${contains(var.environment_list, var.env) == true ? 0 : 1}"
  "ERROR: The env value can only be: dev, qa or prod" = true
}

or to use your test:

resource "null_resource" "is_array_length_correct" {
  count = "${length(var.array1) == length(var.array2) ? 0 : 1}"
  "array1 and array2 must be the same length" = true
}
@timgurney-ee

This comment has been minimized.

timgurney-ee commented Nov 10, 2017

Nice work around!

@fishpen0

This comment has been minimized.

fishpen0 commented Dec 6, 2017

Based on discussion in #16848, it would be valuable for it to be possible to raise errors even when terraform is run using -target. The current workaround and proposed solution does not make this possible.

If the require method had an additional flag for always_run or something similar, this would cover additional use cases

@louis-gounot

This comment has been minimized.

louis-gounot commented May 29, 2018

Why not using a data source from a provider instead ?

An assert provider with an assert_equals data source for exemple (can be extended later if required) ?

We can still create explicit dependencies if necessary.

Main advantage I see is that most data sources can be checked during the refresh phase for sanity checks (not resource dependent). It also allows to reuse existing terraform logic without adding new grammar to the language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment