Terraform: How to use security group id in tfvars - amazon-web-services

I am creating an RDS option group with several options and one of them has the parameter "vpc_security_group_memberships" which takes the ID of a security group.
db_options.tfvars:
db_options = [
{
option_name = "OEM_AGENT"
option_settings = [
{
name = "AGENT_REGISTRATION_PASSWORD"
value = "****"
},
{
name = "OMS_HOST"
value = "gridcontrol.example.com"
},
{
name = "OMS_PORT"
value = "4000"
}
]
port = "3872"
version = "13.5.0.0.v1"
vpc_security_group_memberships = [ ]
}
]
And I create a security group in sg.tf:
resource "aws_security_group" "db-sg" {
description = "Allow access to RDS instance"
name = "${var.env}.${var.db_name}.sg"
tags = {
Name = "${var.env}.${var.db_name}.sg"
}
vpc_id = var.vpc_id
}
Is it possible to put the aws_security_group.db-sg.id for use in vpc_security_group_memberships?

Is it possible to put the aws_security_group.db-sg.id for use in vpc_security_group_memberships?
Sadly its not possible. TF variables can't be dynamic. They must be fully defined at plan time. But you can instead use locals. Local values can by dynamic.

Related

Inappropriate value for attribute "value": string required in terraform

I'm creating Elastic beanstalk with terraform inside a vpc and I need to have at least two subnets because when I try to apply with only one I get an error that demands at least two. So here I define two subnets.
resource "aws_elastic_beanstalk_application" "elasticapp" {
name = var.elasticapp
}
resource "aws_elastic_beanstalk_environment" "beanstalkappenv" {
name = var.beanstalkappenv
application = aws_elastic_beanstalk_application.elasticapp.name
solution_stack_name = var.solution_stack_name
tier = var.tier
setting {
namespace = "aws:ec2:vpc"
name = "VPCId"
value = "${aws_vpc.prod-vpc.id}"
}
setting {
namespace = "aws:ec2:vpc"
name = "Subnets"
value = ["${aws_subnet.prod-subnet-public-1.id}", "${aws_subnet.prod-subnet-public-2.id}"]
After running terraform apply I get this error message.
Inappropriate value for attribute "value": string required.
I guess it's a syntax thing but I can't seem to figure it out.
According to the AWS documentation:
The IDs of the Auto Scaling group subnet or subnets. If you have multiple subnets, specify the value as a single comma-separated string of subnet IDs (for example, "subnet-11111111,subnet-22222222")..
You are specifying the subnets in an array. You would want to create a string instead:
resource "aws_elastic_beanstalk_environment" "beanstalkappenv" {
name = var.beanstalkappenv
application = aws_elastic_beanstalk_application.elasticapp.name
solution_stack_name = var.solution_stack_name
tier = var.tier
setting {
namespace = "aws:ec2:vpc"
name = "VPCId"
value = aws_vpc.prod-vpc.id
}
setting {
namespace = "aws:ec2:vpc"
name = "Subnets"
value = "${aws_subnet.prod-subnet-public-1.id},${aws_subnet.prod-subnet-public-2.id}"
}
}

How Do I Use A Terraform Data Source To Reference A Managed Prefix List?

I'm trying to update a terraform module to add a new security group, which will have an inbound rule populated with two managed prefix lists. The prefix lists are shared to my AWS account from a different account using AWS Resource Access Manager, however I have tried referencing prefix lists created within my own AWS account and am seeing the same error.
Below is the terraform I am using:
resource "aws_security_group" "akamai_sg" {
name = "akamai-pl-sg"
description = "Manage access from Akamai to ${var.environment} alb"
vpc_id = var.vpc_id
tags = merge(var.common_tags, tomap({ "Name" = "akamai-pl-sg" }))
revoke_rules_on_delete = true
}
resource "aws_security_group_rule" "akamai_to_internal_alb" {
for_each = toset(var.domains_inc_akamai)
type = "ingress"
description = "Allow Akamai into ${var.environment}${var.domain_name_suffix}-alb"
from_port = var.alb_listener_port
to_port = var.alb_listener_port
protocol = "tcp"
security_group_id = aws_security_group.akamai_sg.id
prefix_list_ids = [data.aws_prefix_list.akamai-site-shield.id, data.aws_prefix_list.akamai-staging.id]
}
data "aws_prefix_list" "akamai-site-shield" {
filter {
name = "prefix-list-id"
values = ["pl-xxxxxxxxxx"]
}
}
data "aws_prefix_list" "akamai-staging" {
filter {
name = "prefix-list-id"
values = ["pl-xxxxxxxxxx"]
}
}
The terraform error I am revieving reads:
"Error: no matching prefix list found; the prefix list ID or name may be invalid or not exist in the current region"
Is anyone able to help, or see where I am going wrong?
Thanks in advance.
Would not be the following possible?
data "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.foo.id
service_name = "com.amazonaws.us-west-2.s3"
}
data "aws_prefix_list" "s3" {
prefix_list_id = aws_vpc_endpoint.s3.prefix_list_id
}
It seems the solution is to use:
data "aws_ec2_managed_prefix_list" "example" {
filter {
name = "prefix-list-name"
values = ["my-prefix-list"]
}
}

Terraform use a list of configuration blocks as an argument

The Terraform resource, aws_db_proxy, has a list of auth block(s) as an argument. Below is an example from the terraform documentation.
Each auth block represents a user, and each user needs a secret in Secrets Manager. Our platform has four different environments (dev,qa,cert,prod), and we do not use secrets in our lower environments to save on costs. Ideally, I would create two lists of auth blocks, one for lower environments and one for upper environments. Then, in the resource I could pick the appropriate one based on environment.
Is there a way to pass a list of auth blocks to the aws_db_proxy resource?
The other solution I was thinking of was to use two separate aws_db_proxy configurations and use the appropriate one for each environment using the count meta-argument. However, I think this could get a little messy.
resource "aws_db_proxy" "example" {
name = "example"
debug_logging = false
engine_family = "MYSQL"
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.example.arn
vpc_security_group_ids = [aws_security_group.example.id]
vpc_subnet_ids = [aws_subnet.example.id]
auth {
auth_scheme = "SECRETS"
description = "user1"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example1.arn
}
auth {
auth_scheme = "SECRETS"
description = "example2"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example2.arn
}
auth {
auth_scheme = "SECRETS"
description = "example3"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example3.arn
}
tags = {
Name = "example"
Key = "value"
}
}
You could use dynamic blocks to create auth blocks dynamically.
An example usage would depend on exactly how are you defing your aws_secretsmanager_secret for each user, but you could also make it dynamic.
Below is sample code. I haven't run it as its aim is to demonstrate the concept of the use of dynamic blocks and how you could make your aws_secretsmanager_secret:
# list of users
variable "proxy_users" {
default = ["user1", "example2", "example3"]
}
# secret for each user
resource "aws_secretsmanager_secret" "mysecret" {
for_each = toset(var.proxy_users)
name = "example${each.key}"
# rest of attributes
}
resource "aws_db_proxy" "example" {
name = "example"
debug_logging = false
engine_family = "MYSQL"
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.example.arn
vpc_security_group_ids = [aws_security_group.example.id]
vpc_subnet_ids = [aws_subnet.example.id]
# create auth for each user
dynamic "auth" {
for_each = var.proxy_users
content {
auth_scheme = "SECRETS"
description = auth.key
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.mysecret[auth.key].arn
}
}
tags = {
Name = "example"
Key = "value"
}
}
Thank you #Marcin
I had the same issue but I needed to insert existing secrets arn. You really helped
I did the following if anybody needs it
locals {
secrets_list = [
"db-credentials/${var.env-name}/user1",
"db-credentials/${var.env-name}/user2",
"db-credentials/${var.env-name}/user3"
]
}
data "aws_secretsmanager_secret" "rds_secrets" {
for_each = toset(local.secrets_list)
name = each.key
}
resource "aws_db_proxy" "rds_db_proxy" {
name = "${var.env-name}-rds-proxy"
engine_family = "MYSQL"
idle_client_timeout = 900
require_tls = true
.
.
.
.
dynamic "auth" {
for_each = local.secrets_list
content {
secret_arn = data.aws_secretsmanager_secret.rds_secrets[auth.value].arn
auth_scheme = "SECRETS"
iam_auth = "REQUIRED"
}
}
}

creating Subnet in a loop in Terraform

I am new to Terraform.
I am trying to create a code in which I can create the subnet in loop but cidrsubnet function is not working out as I don't want to change the subnet mask.
For Example: I want to create the subnet with these IPs: Subnet 1: 10.90.46.0/27, subnet 2: 10.90.46.32/27 subnet3: 10.90.46.64/27 and so on till subnet 8: 10.90.46.224/27
Thanks
Apply a count, which will multiplicate the number of the resource.
variable "vpc_id" {
default = "vpc-123"
}
#Here add all your 8 CIDR's to the list in "subnet_cidr" and for each one add one entry in "subnet_azs". You can repeat values in "subnet_azs" but not in subnet_cidr"
variable "subnet_cidr" {
default = ["10.90.46.0/27", "10.90.46.32/27", "10.90.46.64/27", "10.90.46.224/27"]
}
variable "subnet_azs" {
default = ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1c"]
}
resource "aws_subnet" "my_subnets" {
count = 8
vpc_id = "${var.vpc_id}"
cidr_block = "${element(var.subnet_cidr, count.index)}"
availability_zone = "${element(var.subnet_azs, count.index)}"
}
One way to automatically allocate a sequence of IP address ranges is to use the hashicorp/subnets/cidr module from the Terraform Registry:
module "subnet_addrs" {
source = "hashicorp/subnets/cidr"
version = "1.0.0"
base_cidr_block = "10.90.46.0/24"
networks = [
{ name = "us-east-1a", new_bits = 3 },
{ name = "us-east-1b", new_bits = 3 },
{ name = "us-east-1c", new_bits = 3 },
{ name = "us-east-1d", new_bits = 3 },
{ name = "us-east-1e", new_bits = 3 },
{ name = "us-east-1f", new_bits = 3 },
{ name = "us-east-1g", new_bits = 3 },
{ name = "us-east-1h", new_bits = 3 },
]
}
With the above example, module.subnet_addrs.network_cidr_blocks will be a map like this:
{
"us-east-1a" = "10.90.46.0/27"
"us-east-1b" = "10.90.46.32/27"
"us-east-1c" = "10.90.46.64/27"
"us-east-1d" = "10.90.46.96/27"
"us-east-1e" = "10.90.46.128/27"
"us-east-1f" = "10.90.46.160/27"
"us-east-1g" = "10.90.46.192/27"
"us-east-1h" = "10.90.46.224/27"
}
A map like this can be used directly as the for_each of a resource, so we can then declare the subnets like this, using AWS an example (because you didn't say which cloud vendor you are using):
resource "aws_subnet" "my_subnets" {
for_each = module.subnet_addrs.network_cidr_blocks
vpc_id = var.vpc_id
availability_zone = each.key
cidr_block = each.value
}
There's some guidance in the readme of this module about [things to keep in mind if you intend to rename or renumber networks later, to ensure that the changes you make are compatible with objects that already exist. I'd suggest reviewing that documentation before taking this path to make sure that you'd be able to apply any future changes to your network topology you might imagine making in the future.
For example, the allocations in the above example already cover the entire addressing space "10.90.46.0/24", so if you wanted to add a new subnet in future without introducing any new addressing space you'd need to replace one of those existing subnets with a pair of replacement subnets that both have new_bits = 4 and thus a prefix length of /28 instead of /27, so that you'd have one additional bit available for network numbering.

Creating multiple RDS instances using Terraform count but with different Tags

I have hit upon this requirement of creating multiple RDS instances with all db properties remaining same. Only that the tags be different. I'm using Terraform for my deployments and count really helps me in these situations. But is there a way where my RDS Instances are created using count but Tags should be different.
Code:
resource "aws_db_instance" "rds-mysql" {
count = "${var.RDS_INSTANCE["deploy"] == "true" ? 1 : 0}"
allocated_storage = "${var.RDS_INSTANCE[format("allocated_storage.%s",var.ENVIRONMENT)]}"
auto_minor_version_upgrade = true
backup_retention_period = "${var.RDS_INSTANCE[format("backup_retention_period.%s",var.ENVIRONMENT)]}"
db_subnet_group_name = "${aws_db_subnet_group.rds-mysql.id}"
engine = "${var.RDS_INSTANCE["engine"]}"
final_snapshot_identifier = "${format("%s-%s-%s-rds-mysql-final-snapshot",var.PRODUCT,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
engine_version = "${var.RDS_INSTANCE["engine_version"]}"
instance_class = "${var.RDS_INSTANCE[format("instance_class.%s",var.ENVIRONMENT)]}"
multi_az = "${var.RDS_INSTANCE[format("multi_az.%s",var.ENVIRONMENT)]}"
parameter_group_name = "${aws_db_parameter_group.rds-mysql.id}"
password = "${var.RDS_MASTER_USER_PASSWORD}"
skip_final_snapshot = "${var.RDS_INSTANCE[format("skip_final_snapshot.%s",var.ENVIRONMENT)]}"
storage_encrypted = "${var.RDS_INSTANCE[format("storage_encrypted.%s",var.ENVIRONMENT)]}"
storage_type = "gp2"
username = "${var.RDS_INSTANCE["username"]}"
vpc_security_group_ids = ["${var.SG_RDS_MYSQL_ID}"]
tags {
Name = "${format("%s-%s-%s-rds-mysql",var.PRODUCT,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
Project = "${format("%s-share",var.PRODUCT)}"
Environment = "${var.ENVIRONMENT}"
}
#Resource lifecycle
lifecycle {
ignore_changes = ["allocated_storage","instance_class"]
}
}
Supposingly I deploy 2 RDS and below is what I intend my tags to look like:
#RDS 1
tags {
Name = "${format("%s-%s-%s-rds-mysql",var.PRODUCT1,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
Project = "${format("%s-share",var.PRODUCT1)}"
Environment = "${var.ENVIRONMENT}"
}
#RDS2
tags {
Name = "${format("%s-%s-%s-rds-mysql",var.PRODUCT2,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
Project = "${format("%s-share",var.PRODUCT2)}"
Environment = "${var.ENVIRONMENT}"
}
Please confirm if there's any way this can be achieved.
Above code will make only one RDS or nothing. You cannot create more then 2 RDS with it.
count = "${var.RDS_INSTANCE["deploy"] == "true" ? 1 : 0}"
And I think it is not good idea to create muliple RDS with "count" for different purpose even the spec requirements are same. For example, there are 4 RDS and if you want to scale up one of those RDS. It is hard to manage it. It is better to copy the code and paste it multiple times. Or you can create module for it.
Anyway, you can create different tags for each RDS like below.
Make list variable (var.PRODUCT) and use "element" instead of var.PRODUCT1 or var.PRODUCT2
variable "PRODUCT" {
default = [
"test1",
"test2",
"test3",
]
}
...
tags {
Name = "${format("%s-%s-%s-rds-mysql", element(var.PRODUCT, count.index) ,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
Project = "${format("%s-share", element(var.PRODUCT, count.index))}"
...
}
If it is hard to create new list variable, then you can create local variable for it.
locals {
PRODUCT = ["${var.PRODUCT1}", "${var.PRODUCT2}", "${var.PRODUCT3}"]
}
...
tags {
Name = "${format("%s-%s-%s-rds-mysql", element(local.PRODUCT, count.index) ,var.ENVIRONMENT,var.REGION_SHORT_NAME)}"
Project = "${format("%s-share", element(local.PRODUCT, count.index))}"
...
}