Passing certificate arn to ingress annotation using Terraform - amazon-web-services

Background
Hi all,
Terraform newbie here.
I'm trying to poll an existing AWS certificate ARN and use that value in my ingress.tf file ingress object annotation.
As a first step, I tried to poll the value using the below terraform code:
# get-certificate-arn.tf
data "aws_acm_certificate" "test" {
domain = "test.example.com"
statuses = ["ISSUED"]
}
output "test" {
value = data.aws_acm_certificate.test.*.arn
description = "TESTING"
}
When I run this code, it gives me my certificate ARN back (YEY!) like the example below:
Changes to Outputs:
+ debugging = [
+ [
+ "arn:aws:acm:us-east-1:1234567890:certificate/12345abc-123-456-789def-12345etc",
]
Question:
I'd like to take this to the next level and use the output from above to feed the ingress annotations as shown by "???" in the code below:
# ingress.tf
resource "kubernetes_ingress_v1" "test_ingress" {
metadata {
name = "test-ingress"
namespace = "default"
annotations = {
"alb.ingress.kubernetes.io/certificate-arn" = ????
...etc...
}
}
I've tried:
"alb.ingress.kubernetes.io/certificate-arn" = data.aws_acm_certificate.test.*.arn
which doesn't work but I can't quite figure out how to pass the value from the get-certificate-arn.tf "data.aws_acm_certificate.test.arn" to the ingress.tf file.
The error I get is:
Error: Incorrect attribute value type
│
│ on ingress.tf line 6, in resource "kubernetes_ingress_v1" "test_ingress":
│ 6: annotations = {
│ 9: "alb.ingress.kubernetes.io/certificate-arn" = data.aws_acm_certificate.test.*.arn
[...truncated...]
│ 16: }
│ ├────────────────
│ │ data.aws_acm_certificate.test is object with 11 attributes
│
│ Inappropriate value for attribute "annotations": element "alb.ingress.kubernetes.io/certificate-arn": string required.
If anyone could advise how (IF?!) one can pass a variable to kubernetes_ingress_v1 'annotations' that would be amazing. I'm still learning Terraform and am still reviewing the fundamentals of passing variables around.

Have you tried maybe using :
"${data.aws_acm_certificate.test.arn}"
or alternatively
you can build the whole annotations block as a local
local{
ingress_annotations = {
somekey = somevalue
some_other_key = data.aws_acm_certificate.test.arn
}
and using it in the resource
annotations = local.ingress_annotations
I'm not that keen on TF
but you might need to have a more complex setup with a for loop.
local{
ingress_annotations = [
{key = value } ,{key = data.aws_acm_certificate.test.arn}
]
}
resource "kubernetes_ingress_v1" "test_ingress" {
metadata {
name = "test-ingress"
namespace = "default"
annotations = {for line in local.ingress_annotations : line.key => line.value
}
}

In the end, the solution was a typo in the data field, removing the "*" resolved the issue. For interests sake, if you want to combine two certificates to an ingress annotation you can join them as shown here[1]:
"alb.ingress.kubernetes.io/certificate-arn" = format("%s,%s",data.aws_acm_certificate.test.arn,data.aws_acm_certificate.test2.arn)

Related

terraform how to specify value of apigateway invoke_url using template file feature - locals or vars dont work

I have apigateway invoke_url that is unique and created by terraform, this unique url usually has form of
https://123abc.execute-api.us-east-1.amazonaws.com
That value has to go inside index.html for s3 object, I'm using template feature of terraform:
<!DOCTYPE html>
<html>
SOME CODE
var backend_url = "${backend_api_gateway}/voting"
some code
});
</html>
Trying with 'locals' in tf did not work:
locals {
backend_api_gateway = "${aws_apigatewayv2_stage.default.invoke_url}"
}
resource "aws_s3_object" "index_file_vote" {
bucket = aws_s3_bucket.frontend_vote.id
key = "index.html"
content = templatefile("./vote/index.html.tpl", {
backend_api_url = local.backend_api_gateway
})
depends_on = [aws_s3_bucket.frontend_vote, aws_apigatewayv2_api.main_apigateway]
}
It gives error:
Invalid function argument
│
│ on s3_bucket_vote.tf line 93, in resource "aws_s3_object" "index_file_vote":
│ 93: content = templatefile("./vote/index.html.tpl", {
│ 94: backend_api_url = local.backend_api_gateway
│ 95: })
│ ├────────────────
│ │ local.backend_api_gateway will be known only after apply│
│ Invalid value for "vars" parameter: vars map does not contain
│ key "backend_api_gateway", referenced at
│ ./vote/index.html.tpl:34,28-47.
Trying with vars, declaring future to be created apigateway's invoke url did not work:
variable "backend_api_gateway" {
type = string
default = "${aws_apigatewayv2_stage.default.invoke_url}" // error 'variables not allowed'
}
Since there are no modules involved, this should be easy to fix. First, the value assigned to the variable has to go away as it is not possible to use it that way. Second, you actually do not even need it. Third, you are using explicit dependencies, which is also not needed. Additionally, even though there is nothing wrong with using local variable, it is not needed. Here is the change required:
resource "aws_s3_object" "index_file_vote" {
bucket = aws_s3_bucket.frontend_vote.id
key = "index.html"
content = templatefile("./vote/index.html.tpl", {
backend_api_url = aws_apigatewayv2_stage.default.invoke_url
})
}

The argument "ami" is required, but no definition was found

I am new to terraform, I am learning modules. I am stuck in a problem.
I have modules folder as root which contain two folders EC2 and IAM and they have terraform code in them. In the same modules folder I have main.tf file which is a module file for calling EC2 instance terraform code.
For your information, EC2 instance folder contain two files one for instance resource and second is for defining variables.
My EC2 file looks like this.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
count = "${var.ec2_count}"
ami = "${var.ami}"
instance_type = "${var.instance_type}"
tags = {
Name = "App_Instance"
}
}
My Variable.tf file looks like this.
variable "ec2_count" {
type = number
default = 1
}
variable "ami" {
type = string
}
variable "instance_type" {
type = string
}
My main.tf file looks like this.
module "EC2" {
source = "./EC2"
}
Now I want that when I type terraform plan command, it should take input at the command prompt but it is showing me below error.
PS: I don't want to pass the value of the variable in the module.
C:\Users\PC\Desktop\AWS_Modules\Modules>terraform plan
╷
│ Error: Missing required argument
│
│ on main.tf line 1, in module "EC2":
│ 1: module "EC2" {
│
│ The argument "ami" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│
│ on main.tf line 1, in module "EC2":
│ 1: module "EC2" {
│
│ The argument "instance_type" is required, but no definition was found.
Since you are new to modules, there are a few things to consider:
Where are you defining the variables
How are you passing the values to variables required by the module
Ideally, you would pass the values to variables when calling the module:
module "EC2" {
source = "./EC2"
ami = "ami-gafadfafa"
instance_type = "t3.micro"
}
However, since you said you do not want to do that, then you need to assign the default values to variables on the module level. This can be achieved with:
variable "ec2_count" {
type = number
default = 1
}
variable "ami" {
type = string
default = "ami-gafadfafa"
}
variable "instance_type" {
type = string
default = "t3.micro"
}
This way, if you do not provide values when calling the module, the instance will be created and you are still leaving the option to provide the values if someone else were to use your module.
The third option is to have a separate variables.tf file in the root module (i.e., the same directory where you are calling the module from) and not define the values for variables. It would be basically a copy+paste of the file you have on the module level without defining any default values. Here is an example:
# variables.tf on the root module level
variable "ec2_count" {
type = number
}
variable "ami" {
type = string
}
variable "instance_type" {
type = string
}
However, in this case, it is impossible not to use the input variable names when calling the module. For this to work with providing the values on the CLI, the code would have to change to:
module "EC2" {
source = "./EC2"
ec2_count = var.ec2_count
ami = var.ami
instance_type = var.instance_type
}
There is no other way the module can know how to map the values you want it to consume without actually setting those variables to a certain value when calling the module.

AWS WAFv2 Terraform import ID issues

I'm trying to import a aws_wafv2 web acl that I've created via the console. I setup the terraform up but for some reason it won't let me import, it says my ID is not available. Any guidance on this is greatly appreciated.
Here is my main.tf
resource "aws_wafv2_web_acl" "waf_acl_buddyman" {
name = "waf_acl_buddyman"
description = "WAF ACL for buddyman"
id = data.terraform_remote_state.aws_wafv2_web_acl.outputs.aws_wafv2_web_acl_af_acl_buddyman_id
scope = "REGIONAL"
default_action {
allow {}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "waf_acl_buddyman"
sampled_requests_enabled = true
}
rule {
name = "waf_buddyman_acl"
priority = 0
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule {
name = "SizeRestrictions_QUERYSTRING"
}
excluded_rule {
name = "NoUserAgent_HEADER"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "friendly-rule-metric-name"
sampled_requests_enabled = true
}
}
}
my output.tf looks like this:
output "aws_wafv2_web_acl_af_acl_buddyman_id" {
value = aws_wafv2_web_acl.waf_acl_buddyman.id
}
When I run terraform import aws_wafv2_web_acl.waf_acl_buddyman baf6e249-ec50-45df-ae9f-073e73f83900/waf_acl_buddyman/REGIONAL it shows:
Acquiring state lock. This may take a few moments...
aws_wafv2_web_acl.waf_acl_buddyman: Importing from ID "baf6e249-ec50-45df-ae9f-073e73f83900/waf_acl_buddyman/REGIONAL"...
aws_wafv2_web_acl.waf_acl_buddyman: Import prepared!
Prepared aws_wafv2_web_acl for import
aws_wafv2_web_acl.waf_acl_buddyman: Refreshing state... [id=baf6e249-ec50-45df-ae9f-073e73f83900]
╷
│ Error: Cannot import non-existent remote object
│
│ While attempting to import an existing object to "aws_wafv2_web_acl.waf_acl_buddyman", the provider detected that no object exists with the given id. Only
│ pre-existing objects can be imported; check that the id is correct and that it is associated with the provider's configured region or endpoint, or use
│ "terraform apply" to create a new remote object for this resource.
╵
Releasing state lock. This may take a few moments...
Output is what Terraform module produces. But you want to take Output as input. Guess why it's empty?
How to perform import.

Data resource of Network_interface throws invalid count argument Terraform

I am trying to get the network interface ids of a VPC endpoint using the data resource of aws_network_interface, the code for which looks like
resource "aws_vpc_endpoint" "api-gw" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.${var.aws_region}.execute-api"
vpc_endpoint_type = "Interface"
security_group_ids = [aws_security_group.datashop_sg.id]
private_dns_enabled = true
subnet_ids = [data.aws_subnet.private-1.id]
}
data "aws_network_interface" "endpoint-api-gw" {
count = length(aws_vpc_endpoint.api-gw.network_interface_ids)
id = tolist(aws_vpc_endpoint.api-gw.network_interface_ids)[count.index]
}
I get the following error
Error: Invalid count argument
│
│ in data "aws_network_interface" "endpoint-api-gw":
│ count = length(aws_vpc_endpoint.api-gw.network_interface_ids)
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work
│ around this, use the -target argument to first apply only the resources that the count depends on.
I have also tried the for_each and it gives similar error of it is dependent on resources. I am running out of ideas. It would be of great if someone can help
The error is clear:
count = length(aws_vpc_endpoint.api-gw.network_interface_ids)
is only known after apply. You can't do this. count value must be known at plan time. You have to run your TF in two stages:
Execute TF with -target to deploy only aws_vpc_endpoint.api-gw using option.
Execute it again, to deploy the rest.
Otherwise, you have to re-factor you code, and fully eliminate the dependency of the count on aws_vpc_endpoint.api-gw.network_interface_ids.

Error: Incorrect attribute value type module.network.private_subnets[0] is tuple with 3 elements

I am running into an issue while trying to upgrade from terraform 11 to terraform 12. I was previously using the following syntax to retrieve the 3rd element from a list of ids from a module. The module output is like so:
# Subnets
output "private_subnets" {
description = "List of IDs of private subnets"
value = ["${aws_subnet.private.*.id}"]
}
Previously, this worked with terraform 11
subnet_id = "${element(module.network.private_subnets,3)}"
I thought that I could use the index of 2 to get the same results, but I get the following error:
Error: Incorrect attribute value type
on terraformfile.tf line 65, in resource "aws_instance" "myinstance":
65: subnet_id = module.network.private_subnets[2]
|----------------
| module.network.private_subnets[2] is tuple with 3 elements
Any help with this would be greatly appreciated.
The currently used value:
value = ["${aws_subnet.private.*.id}"]
produces a list of lists. For example,
[
[
"subnet-0f5b759e80ffcf305",
"subnet-0500c8c2a40e5b381",
],
]
If you want to keep using this in that form, later, when you use element you have to do following:
subnet_id = element(module.network.private_subnets[0], 3)
Alternatively, redefine private_subnets to be:
value = aws_subnet.private.*.id
The answer from #Marcin holds good but I hit a very similar error in a slightly different situation and I took a different approach to resolve it.
Lets say you have a aws_security_group_rule rule and each of the data -sourced item in the list below i.e data.aws_subnet.app1_subnets.*.cidr_block and data.aws_subnet.app2_subnets.*.cidr_block are a tuple with lets say X elements - You might hit an error -
Error: Incorrect attribute value type
on stack.tf line 44, in resource "aws_security_group_rule" "ds_security_group_ingress_rule":
44: cidr_blocks = [
45: data.aws_subnet.app1_subnets.*.cidr_block,
46: data.aws_subnet.app2_subnets.*.cidr_block
47: ]
|----------------
| data.aws_subnet.app1_subnets.*.cidr_block is tuple with 3 elements
| data.aws_subnet.app2_subnets.*.cidr_block is tuple with 3 elements
Inappropriate value for attribute "cidr_blocks": element 0: string required.
I had to use the flatten function to resolve the issue which quite literally flattens the content..
Change the below:
cidr_blocks = [
data.aws_subnet.app1_subnets.*.cidr_block,
data.aws_subnet.app2_subnets.*.cidr_block
]
as
cidr_blocks = flatten([
data.aws_subnet.app1_subnets.*.cidr_block,
data.aws_subnet.app2_subnets.*.cidr_block
])
I had same issue , flatten HELPED !! .
the configuration file looked like this :
resource "aws_db_subnet_group" "for-database" {
name = "for-database"
description = "private subnets in different AZs"
subnet_ids = module.vpc.private_subnets
}
got this error :
│ Error: Unsupported attribute
│
│ 39: value = module.vpc.private_subnets.subnet_count
│ ├────────────────
│ │ module.vpc.private_subnets is tuple with 2 elements
│
│ This value does not have any attributes.
solution:
resource "aws_db_subnet_group" "for-database" {
name = "for-database"
description = "private subnets in different AZs"
subnet_ids = flatten([module.vpc.private_subnets])
}
it was quite difficult for me but i handled it via flatten function i took tuple and pass it to flatten function
/* Subnets group for DBs */
resource "aws_db_subnet_group" "default" {
name = "main"
subnet_ids = flatten([aws_subnet.PrivateSubnets.*.id])
tags = {
Name = "Private Subnet group"
}
}