Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terraform - Conditional Data Source

In terraform, is there any way to conditionally use a data source? For example:

 data "aws_ami" "application" {
     most_recent = true
     filter {
         name = "tag:environment"
         values = ["${var.environment}"]
     }
     owners = ["self"]
}

I'm hoping to be able to pass in an environment variable via the command line, and based on that, determine whether or not to fetch this data source.

I know with resources you can use the count property, but it doesn't seem you can use that with data sources.

I would consider tucking this code away in a module, but modules also can't use the count parameter.

Lastly, another option would be to provide a "Default" value for the data source, if it returned null, but I don't think that's doable either.

Are there any other potential solutions for this?

like image 873
djt Avatar asked Jan 25 '17 18:01

djt


People also ask

Can we use if condition in Terraform?

Terraform doesn't support if-statements, so this code won't work. However, you can accomplish the same thing by using the count parameter and taking advantage of two properties: If you set count to 1 on a resource, you get one copy of that resource; if you set count to 0, that resource is not created at all.

Can I use count with data source Terraform?

count is a meta-argument defined by the Terraform language. It can be used with modules and with every resource type.


1 Answers

You can use a conditional on data sources the same as you can with resources and also from Terraform 0.13+ on modules as well:

variable "lookup_ami" {
  default = true
}

 data "aws_ami" "application" {
   count       = var.lookup_ami ? 1 : 0
   most_recent = true
   filter {
     name   = "tag:environment"
     values = [var.environment]
   }
   owners = ["self"]
}

One use case for this in Terraform 0.12+ is to utilise the lazy evaluation of ternary statements like with the following:

variable "internal" {
  default = true
}

data "aws_route53_zone" "private_zone" {
  count        = var.internal ? 1 : 0
  name         = var.domain
  vpc_id       = var.vpc_id
  private_zone = var.internal
}

data "aws_route53_zone" "public_zone" {
  count        = var.internal ? 0 : 1
  name         = var.domain
  private_zone = var.internal
}

resource "aws_route53_record" "www" {
   zone_id = var.internal ? data.aws_route53_zone.private_zone.zone_id : data.aws_route53_zone.public_zone.zone_id
   name    = "www.${var.domain}"
   type    = "A"
   alias {
     name                   = aws_elb.lb.dns_name
     zone_id                = aws_elb.lb.zone_id
     evaluate_target_health = false
   }
}

This would create a record in the private zone when var.internal is true and instead create a record in the public zone when var.internal is false.

For this specific use case you could also use Terraform 0.12+'s null to rewrite this more simply:

variable "internal" {
  default = true
}

data "aws_route53_zone" "zone" {
  name         = var.domain
  vpc_id       = var.internal ? var.vpc_id : null
  private_zone = var.internal
}

resource "aws_route53_record" "www" {
   zone_id = data.aws_route53_zone.zone.zone_id
   name    = "www.${data.aws_route53_zone.zone.name}"
   type    = "A"
   alias {
     name                   = aws_elb.lb.dns_name
     zone_id                = aws_elb.lb.zone_id
     evaluate_target_health = false
   }
}

This would only pass the vpc_id parameter to the aws_route53_zone data source if var.internal is set to true as you can't set vpc_id when private_zone is false.


Old Terraform 0.11 and earlier answer:

You can in fact use a conditional on the count of data sources but I've yet to manage to work out a good use case for it when I've tried.

As an example I successfully had this working:

data "aws_route53_zone" "private_zone" {
  count        = "${var.internal == "true" ? 1 : 0}"
  name         = "${var.domain}"
  vpc_id       = "${var.vpc_id}"
  private_zone = "true"
}

data "aws_route53_zone" "public_zone" {
  count        = "${var.internal == "true" ? 0 : 1}"
  name         = "${var.domain}"
  private_zone = "false"
}

But then had issues in how to then select the output of it because Terraform will evaluate any variables in the ternary conditional before deciding which side of the ternary to use (instead of lazy evaluation). So something like this doesn't work:

resource "aws_route53_record" "www" {
   zone_id = "${var.internal ? data.aws_route53_zone.private_zone.zone_id : data.aws_route53_zone.public_zone.zone_id}"
   name = "www.example.com"
   type = "A"
   alias {
     name                   = "${aws_elb.lb.dns_name}"
     zone_id                = "${aws_elb.lb.zone_id }"
     evaluate_target_health = "false"
   }
}

Because if internal is true then you get the private_zone data source but not the public_zone data source and so the second half of the ternary fails to evaluate because data.aws_route53_zone.public_zone.zone_id isn't defined and equally with the other way around too.

In your case you probably just want to conditionally use the data source so might be able to do something like this:

variable "dynamic_ami" { default = "true" }
variable "default_ami" { default = "ami-123456" }

data "aws_ami" "application" {
  most_recent = true
  filter {
    name = "tag:environment"
    values = ["${var.environment}"]
  }
  owners = ["self"]
}

resource "aws_instance" "app" {
    ami = "${var.dynamic_ami == "true" ? data.aws_ami.application.id : var.default_ami}"
    instance_type = "t2.micro"
}
like image 109
ydaetskcoR Avatar answered Sep 19 '22 08:09

ydaetskcoR