Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

amazon web services - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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"
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...