Terraform find latest ami via data - amazon-web-services

I'm trying to implement some sort of mechanism where someone can fill in a variable which defines if it's going to deploy an Amazon Linux machine or a self-created packer machine. But for some reason it's not picking up the AWS AMI while it does find my own. Here's the code:
Main.tf of the module:
data "aws_ami" "latest" {
most_recent = true
owners = [var.owner]
filter {
name = "name"
values = [lookup(var.default_ami, var.ami)]
}
}
resource "aws_instance" "test-ec2deployment" {
ami = data.aws_ami.latest.id
variables.tf:
variable "default_ami" {
type = map
description = "Choose windows 2016 or 2019"
default = {
"2016" = "WIN2016-CUSTOM*"
"2019" = "WIN2019-CUSTOM*"
"linux" = "ami-0fb02dcdd38048fb9"
}
}
#Above are the options, here you need to make a decision.
variable "ami" {
description = "You can either choose the 2019 or the 2016 image or the linux image."
default = "2019"
}
And finally the main.tf which calls on the module:
module "aws_instance" {
shared_credentials_file = ""
source = ""
env_name = ""
volume_size = "60"
ami = "linux"
}
Like i said, it does find the correct image when i'm entering either 2019 or 2016. The error message is as follows:
module.aws_instance.data.aws_ami.latest: Refreshing state...
Error: Your query returned no results. Please change your search criteria and try again.
Any idea's?
Thanks for the help!
PS: I've emptied some fields on purpose.

Marcin's answer is spot on. AMI name and AMI id are two different things, you cannot use AMI id to search AMI based on its name. For using AMI id, you need to use "image-id" filter. To get a complete list of filters that we can use for searching AMI, check this.
Now, coming to your problem. You can use "name" and "image-id" filter together to get the required AMI. If you don't have value for any of them use "*". I solved it in the following way -
variable "ami" {
description = "You can either choose the 2019 or the 2016 image or the linux image."
default = "2019"
}
variable "default_ami" {
type = map(any)
default = {
"linux" = {
name = "*"
ami_id = "ami-0fb02dcdd38048fb9"
},
"2016" = {
name = "WIN2016-CUSTOM*"
ami_id = "*"
},
"2019" = {
name = "WIN2019-CUSTOM*"
ami_id = "*"
}
}
}
data "aws_ami" "latest" {
most_recent = true
owners = [var.owner]
filter {
name = "name"
values = ["${var.default_ami[var.ami]["name"]}"]
}
filter {
name = "image-id"
values = ["${var.default_ami[var.ami]["ami_id"]}"]
}
}
resource "aws_instance" "test-ec2deployment" {
ami = data.aws_ami.latest.id
instance_type = var.instance_type
}

AMI name and AMI id are two different things. You are using AMI id (ami-0fb02dcdd38048fb9) to search AMI based on its name. This will not work obviously. You have to replace ami-0fb02dcdd38048fb9 with AMI name.
You haven't specified how you named your linux AMI, maybe Linux2019-CUSTOM*? Whatever the name you've used, you have to use that, not AMI id.

If you are searching by name, you may want to use wildcards. For example, there is an ecs-optimized ami named amzn2-ami-ecs-hvm-2.0.20220318-x86_64-ebs. However, terraform couldn't find it. When I used wildcards, it worked: amzn2-ami-ecs-hvm-2.0.202*-x86_64-ebs
The actual code looks as so:
data "aws_ami" "ecs_optimized_ami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-ecs-hvm-2.0.202*-x86_64-ebs"]
}
}

Related

Assign aws name identification to an ec2 instance using Terraform

I created an ec2 instance using Terraform code and added one tag. But I would like to add a default instance name as well to appear in the field shown in the below screenshot.
How do I do that using Terraform?.
Right now, I tried the default argument for the tag but did not seem to work. I find it hard to figure out from the doc.
Code that I currently used to create it:
resource "aws_instance" "webserver" {
ami = "ami-09a41e26df464c548"
instance_type = "t2.micro"
tags = {
default = "my-ec2-instance"
"Terraform" = "Yes"
}
}
You would want to add a tag with the key of Name:
resource "aws_instance" "webserver" {
ami = "ami-09a41e26df464c548"
instance_type = "t2.micro"
tags = {
Name = "my-ec2-instance"
"Terraform" = "Yes"
}
}
The name of the instance will appear as such:

Interpolate data source

I am trying to create some generic Terraform code which supports different MySQL version AMIs. AMIs of MySQL are not in my AWS account. They are in a different AWS account and shared with my account. I am using
a data source to get the latest MySQL AMIs from different account. Now I want to have some thing like this
terraform apply -var somevar=mysql5.6 (This should use mysql5.6 AMI for creating resources)
terraform apply -var somevar=mysql5.5 (This should use mysql5.5 AMI for creating resources)
But the problem is that I can't variablize/interpolate data source mentioned in resource section. Is there any other way to get what I am looking for?
This is the excerpt of what I tried till now
data "aws_ami" "mysql56" {
owners = ["xxxxxxxxxxxx"]
most_recent = true
filter {
name = "image-id"
values = ["${var.os_to_ami["mysql56"]}"]
}
}
data "aws_ami" "mysql55" {
owners = ["xxxxxxxxxxxx"]
most_recent = true
filter {
name = "image-id"
values = ["${var.os_to_ami["mysql55"]}"]
}
}
variable "os_to_ami" {
type = "map"
description = "OS to AMI mapping"
default = {
"mysql56" = "ami-xxxxxxxxxxxxxxxxx"
"mysql57" = "ami-yyyyyyyyyyyyyyyyy"
}
}
resource "aws_instance" "web" {
ami = "${data.aws_ami.mysql56}"
...
}
I am using Terraform v0.11.0.
Your data sources aren't doing anything useful because you're passing the AMI ID into them which defeats the point of them. Instead you should be using the aws_ami data source to fetch the AMI ID based on some criteria such as the owner and the name. This would then let you more simply look these up based on a variable.
Assuming the AMIs are being shared by account 123456789012 and have the names mysql/mysql-5.5/20200818T212500Z and mysql/mysql-5.6/20200818T212500Z then you'd do something like the following:
variable "mysql_version" {}
data "aws_ami" "mysql" {
most_recent = true
filter {
name = "name"
values = ["mysql/mysql-${var.mysql_version}/*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["123456789012"]
}
resource "aws_instance" "mysql" {
ami = data.aws_ami.mysql.id
instance_type = "t3.micro"
}
With the above you have a required mysql_version variable that must be provided. This is then interpolated into the AMI name to search for and will return the latest one matching those criteria so if a new AMI was to be published by the 123456789012 account owner for the same MySQL version with a different timestamp then it would want to recreate your instance with that new AMI.

can we create an ec2 image using terraform by ami name created packer?

I am trying my hands on terraform and packer.
I have created a costume image with packer.
I know we can create an image with ami id.
I have tried:
resource "aws_instance" "packer-yellowpages" {
ami = "*******"
instance_type = "t3.micro"
tags ={
Name = "demo"
}
}
I was wondering if we can do the same with ami name?
the reason I am thinking about this is: I read somewhere that cloud provider scrap the AMI id. So is there a way I can do this some other way apart from id.
OR implement some storage plan to access the store and access the image?
The aws_ami data source can be used to fetch information on an AMI based on tags such as "name".
data "aws_ami" "example" {
executable_users = ["self"]
most_recent = true
owners = ["self"]
filter {
name = "name"
values = ["myami-*"]
}
}
resource "aws_instance" "packer-yellowpages" {
ami = data.aws_ami.example.id
instance_type = "t3.micro"
tags ={
Name = "demo"
}
}
Try to use these instead to see if it works?
data "aws_ami" "example" {
executable_users = ["self"]
most_recent = true
owners = ["self"]
name_regex = "yellowpages"
}
It may be AMI Name, not Name tag.

Using Count in Terraform to create Launch Configuration

I have 3 different version of an AMI, for 3 different nodes in a cluster.
data "aws_ami" "node1"
{
# Use the most recent AMI that matches the pattern below in 'values'.
most_recent = true
filter {
name = "name"
values = ["AMI_node1*"]
}
filter {
name = "tag:version"
values = ["${var.node1_version}"]
}
}
data "aws_ami" "node2"
{
# Use the most recent AMI that matches the pattern below in 'values'.
most_recent = true
filter {
name = "name"
values = ["AMI_node2*"]
}
filter {
name = "tag:version"
values = ["${var.node2_version}"]
}
}
data "aws_ami" "node3"
{
...
}
I would like to create 3 different Launch Configuration and Auto Scaling Group using each of the AMIs respectively.
resource "aws_launch_configuration" "node"
{
count = "${local.node_instance_count}"
# Name-prefix must be used otherwise terraform fails to perform updates to existing launch configurations due to
# a name conflict: LCs are immutable and the LC cannot be destroyed without destroying attached ASGs as well, which
# terraform will not do. Using name-prefix lets a new LC be created and swapped into the ASG.
name_prefix = "${var.environment_name}-node${count.index + 1}-"
image_id = "${data.aws_ami.node[count.index].image_id}"
instance_type = "${var.default_ec2_instance_type}"
...
}
However, I am not able use aws_ami.node1, aws_ami.node2, aws_ami.node3 using the cound.index the way I have shown above. I get the following error:
Error reading config for aws_launch_configuration[node]: parse error at 1:39: expected "}" but found "."
Is there another way I can do this in Terraform?
Indexing data sources isn't something that's doable; at the moment.
You're likely better off simply dropping the data sources you've defined and codifying the image IDs into a Terraform map variable.
variable "node_image_ids" {
type = "map"
default = {
"node1" = "1234434"
"node2" = "1233334"
"node3" = "1222434"
}
}
Then, consume it:
image_id = "${lookup(var.node_image_ids, concat("node", count.index), "some_default_image_id")}"
The downside of this is that you'll need to manually update the image id when images are upgraded.

Define tags in central section in TerraForm

I'm playing around with Terraform for a bit and I was wondering if this is possible. It's best practice to assign tags to each resource you create on AWS (for example). So, what you do first is come up with a tagging strategy (for example, which business unit, a name of the app, a team responsible for it, ...).
However, in Terraform, this means that you have to repeat each tags-block for each resource. This isn't very convenient and if you want to update 1 of the tag names, you have to update each resource that you created.
For example:
resource "aws_vpc" "vpc" {
cidr_block = "${var.cidr}"
tags {
Name = "${var.name}"
Project = "${var.projectname}"
Environment = "${var.environment}"
}
}
If I want to create a Subnet and EC2 in that VPC with the same tags, I have to repeat that tags-block. If I want to update 1 of the tag names later on, I have to update each resource individually, which is very time consuming and tedious.
Is there a possibility to create a block of tags in a centralized location and refer to that? I was thinking of Modules, but that doesn't seem to fit the definition of a module.
You can also try local values from version 0.10.3. It allows you to assign a symbolic local name to an expression so it can be used multiple times in configuration without repetition.
# Define the common tags for all resources
locals {
common_tags = {
Component = "awesome-app"
Environment = "production"
}
}
# Create a resource that blends the common tags with instance-specific tags.
resource "aws_instance" "server" {
ami = "ami-123456"
instance_type = "t2.micro"
tags = "${merge(
local.common_tags,
map(
"Name", "awesome-app-server",
"Role", "server"
)
)}"
}
Terraform version .12 onwords,
This is the variable
variable "sns_topic_name" {
type = string
default = "VpnTopic"
description = "Name of the sns topic"
}
This is the code
locals {
common_tags = {
Terraform = true
}
}
# Create a Resource
resource "aws_sns_topic" "sns_topic" {
name = var.sns_topic_name
tags = merge(
local.common_tags,
{
"Name" = var.sns_topic_name
}
)
}
Output will be
+ tags = {
+ "Name" = "VpnTopic"
+ "Terraform" = "true"
}
Terraform now natively support it for AWS provider.
Check out here
As of version 3.38.0 of the Terraform AWS Provider, the Terraform Configuration language also enables provider-level tagging. Reference Link
# Terraform 0.12 and later syntax
provider "aws" {
# ... other configuration ...
default_tags {
tags = {
Environment = "Production"
Owner = "Ops"
}
}
}
resource "aws_vpc" "example" {
# ... other configuration ...
# This configuration by default will internally combine tags defined
# within the provider configuration block and those defined here
tags = {
Name = "MyVPC"
}
}
For "aws_vpc.example" resouce below tags will be assigned, which is combination of tags defined under provider and tags defined under aws_vps section:
+ tags = {
+ "Environment" = "Production"
+ "Owner" = "Ops"
+ "Name" = "MyVPC"
}