Cross region replication of AWS ECR repository - amazon-web-services

I am trying to replicate my AWS ECR repository to multiple regions within the same account using terraform. I tried manually from the AWS console it works fine but from terraform, I am not able to find the solution.
What I tried:
I tried to make a separate variable for the region called replicate_region and tried to provide the region in the list but it keeps on giving me an error called
Inappropriate value for attribute "region": string required.
Here is the variable code:
variable "replicate_region" {
description = "value"
type = list(string)
}
Here is my code for ecr replication:
resource "aws_ecr_replication_configuration" "replication" {
replication_configuration {
rule {
destination {
region = var.replicate_region
registry_id = "xxxxxxxx"
}
}}}
Can anyone please help me out?
Thanks,

Your replicate_region should be string, not a list of strings. It should be, e.g.:
variable "replicate_region" {
description = "value"
type = string
default = "us-east-1"
}
Update
Iteration using dynamic block.
variable "replicate_region" {
description = "value"
type = list(string)
default = ["us-east-1", "ap-southeast-1", "ap-south-1"]
}
resource "aws_ecr_replication_configuration" "replication" {
replication_configuration {
rule {
dynamic "destination" {
for_each = toset(var.replicate_region)
content {
region = destination.key
registry_id = "xxxxxxxx"
}
}
}}}

More easy way:
resource "aws_ecr_replication_configuration" "replication" {
replication_configuration {
rule {
destination {
region = "us-east-2"
registry_id = "xxxxxxxx"
}
destination {
region = "ap-southeast-1"
registry_id = "xxxxxxxx"
}
}
}
}

variable "replicas" {
description = "ECR replicas region list"
type = list(string)
default = [
{
region = "aaa"
registry_id = "11111111"
},
{
region = "bbb"
registry_id = "22222222"
}
]
}
resource "aws_ecr_replication_configuration" "replication" {
count = length(var.replicas) != 0 ? 1 : 0
replication_configuration {
rule {
dynamic "destination" {
for_each = var.replicas
content {
region = destination.value.region
registry_id = destination.value.registry_id
}
}
repository_filter {
filter = var.filter
filter_type = "PREFIX_MATCH"
}
}
}
}

Related

Terraform: how to create resource from loop through a objects with inner list

I am working with Terraform and I need to create a Glue Workflow. My target is the following schema:
I don't understand how I can use the "nested loops" to create the resouce from a variable object with a list of string:
My main.tf file is:
provider "aws" {
region = "eu-west-1"
profile = "<MY-STAGE>"
}
locals {
workflow_name = "my_example"
first_job = "Job_start"
my_map = [
{
flow = ["JOB-A1", "JOB-A2", "JOB-A3"]
},
{
flow = ["JOB-B1", "JOB-B2", "JOB-B3"]
}
]
}
resource "aws_glue_workflow" "example" {
name = "example"
}
resource "aws_glue_trigger" "example-start" {
name = "trigger-start"
type = "ON_DEMAND"
workflow_name = local.workflow_name
actions {
job_name = "${replace(lower(local.first_job), "_", "-")}"
}
}
resource "aws_glue_trigger" "this" {
for_each = toset(local.my_map)
name = "trigger-inner--${lower(element(each.key, index(local.my_map, each.key)))}"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.example.name
predicate {
conditions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key))), "_", "-")}"
state = "SUCCEEDED"
}
}
actions {
job_name = "${replace(lower(element(each.key, index(local.my_map, each.key) + 1)), "_", "-")}"
}
}
When I try to do the "plan" I get this error:
| each.key is a string, known only after apply
| local.my_map is tuple with 2 elements
| Call to function "element" failed: cannot read elements from string.
Then, how can I get all the rows of the object and scroll through the list elements?
Any help or pointers would be much appreciated!

Apply same data for two resources in loop terraform

I want to create two Amazon SNS topics with the same aws_iam_policy_document, aws_sns_topic_policy & time_sleep configs.
This is my terraform, my_sns_topic.tf:
resource "aws_sns_topic" "topic_a" {
name = "topic-a"
}
resource "aws_sns_topic" "topic_b" {
name = "topic-b"
}
data "aws_iam_policy_document" "topic_notification" {
version = "2008-10-17"
statement {
sid = "__default_statement_ID"
actions = [
"SNS:Publish"
]
# Cut off some lines for simplification.
## NEW LINE ADDED
statement {
sid = "allow_snowflake_subscription"
principals {
type = "AWS"
identifiers = [var.storage_aws_iam_user_arn]
}
actions = ["SNS:Subscribe"]
resources = [aws_sns_topic.topic_a.arn] # Troubles with this line
}
}
resource "aws_sns_topic_policy" "topic_policy_notification" {
arn = aws_sns_topic.topic_a.arn
policy = data.aws_iam_policy_document.topic_policy_notification.json
}
resource "time_sleep" "topic_wait_10s" {
depends_on = [aws_sns_topic.topic_a]
create_duration = "10s"
}
As you can see here, I set up the configuration only for topic-a. I want to loop this over to apply for topic-b as well.
It would be better to use map and for_each, instead of separately creating "a" and "b" topics:
variable "topics" {
default = ["a", "b"]
}
resource "aws_sns_topic" "topic" {
for_each = toset(var.topics)
name = "topic-${each.key}"
}
data "aws_iam_policy_document" "topic_notification" {
version = "2008-10-17"
statement {
sid = "__default_statement_ID"
actions = [
"SNS:Publish"
]
# Cut off some lines for simplification.
}
resource "aws_sns_topic_policy" "topic_policy_notification" {
for_each = toset(var.topics)
arn = aws_sns_topic.topic[each.key].arn
policy = data.aws_iam_policy_document.topic_policy_notification.json
}
resource "time_sleep" "topic_wait_10s" {
for_each = toset(var.topics)
depends_on = [aws_sns_topic.topic[each.key]]
create_duration = "10s"
}

Terraform aws_s3_bucket_replication_configuration can't generate multiple rules with for_each

I have an S3 bucket with the following "folder" structure:
Bucket1----> /Partner1 ----> /Client1 ----> /User1
| | |--> /User2
| |
| |--> /Client2 ----> /User1
|
|--> /Partner2 ----> /Client1 ----> /User1
and so on.
I'm trying to setup replication from this bucket to another such that a file placed in
Bucket1/Partner1/client1/User1/
should replicate to
Bucket2/Partner1/client1/User1/,
Bucket1/Partner2/client1/User2/
should replicate to
Bucket2/Partner2/client1/User2/,
and so on.
I'm trying to achieve this with the following terraform code:
locals {
s3_folders = [
"Partner1/client1/User1",
"Partner1/client1/User2",
"Partner1/client1/User3",
"Partner1/client1/User4",
"Partner1/client1/User5",
"Partner1/client2/User1",
"Partner1/client3/User1",
"Partner2/client1/User1",
"Partner3/client1/User1"
]
}
resource "aws_s3_bucket_replication_configuration" "replication" {
for_each = local.s3_input_folders
depends_on = [aws_s3_bucket_versioning.source_bucket]
role = aws_iam_role.s3-replication-prod[0].arn
bucket = aws_s3_bucket.source_bucket.id
rule {
id = each.value
filter {
prefix = each.value
}
status = "Enabled"
destination {
bucket = "arn:aws:s3:::${var.app}-dev"
storage_class = "ONEZONE_IA"
access_control_translation {
owner = "Destination"
}
account = var.dev_account_id
}
delete_marker_replication {
status = "Enabled"
}
}
}
This is not looping and creating 10 different rules, rather it overwrites the same rule on every run and I only get one rule as a result.
You should use dynamic block:
resource "aws_s3_bucket_replication_configuration" "replication" {
depends_on = [aws_s3_bucket_versioning.source_bucket]
role = aws_iam_role.s3-replication-prod[0].arn
bucket = aws_s3_bucket.source_bucket.id
dynamic "rule" {
for_each = toset(local.s3_input_folders)
content {
id = rule.value
filter {
prefix = rule.value
}
status = "Enabled"
destination {
bucket = "arn:aws:s3:::${var.app}-dev"
storage_class = "ONEZONE_IA"
access_control_translation {
owner = "Destination"
}
account = var.dev_account_id
}
delete_marker_replication {
status = "Enabled"
}
}
}
}
Thanks, Marcin. The dynamic block construct you mentioned works to create the content blocks but it fails to apply because AWS needs multiple replication rules to be differentiated by priority. So some slight modifications achieve this:
locals {
s3_input_folders_list_counter = tolist([
for i in range(length(local.s3_input_folders)) : i
])
s3_input_folders_count_map = zipmap(local.s3_input_folders_list_counter, tolist(local.s3_input_folders))
}
resource "aws_s3_bucket_replication_configuration" "replication" {
depends_on = [aws_s3_bucket_versioning.source_bucket]
role = aws_iam_role.s3-replication-prod[0].arn
bucket = aws_s3_bucket.source_bucket.id
dynamic "rule" {
for_each = local.s3_input_folders_count_map
content {
id = rule.key
priority = rule.key
filter {
prefix = rule.value
}
status = "Enabled"
destination {
bucket = "arn:aws:s3:::${var.app}-dev"
storage_class = "ONEZONE_IA"
access_control_translation {
owner = "Destination"
}
account = var.dev_account_id
}
delete_marker_replication {
status = "Enabled"
}
}
}
}
which creates rules like these:
+ rule {
+ id = "0"
+ priority = 0
+ status = "Enabled"
...
}
+ rule {
+ id = "1"
+ priority = 1
+ status = "Enabled"
...
}
and so on...

Terraform count within for_each loop

I'm trying to create GCP SQL DBs by iterating a list of string using Terraform's for_each and count parameter and the other loop is for the map keys (maindb & replicadb).
Unfortunately, I get the error that appears below.
Is it possible to do this is Terraform?
variables.tf
variable "sql_var" {
default = {
"maindb" = {
"db_list" = ["firstdb", "secondsdb", "thirddb"],
"disk_size" = "20",
},
"replicadb" = {
"db_list" = ["firstdb"],
"disk_size" = "",
}
}
}
main.tf
resource "google_sql_database_instance" "master_sql_instance" {
...
}
resource "google_sql_database" "database" {
for_each = var.sql_var
name = "${element(each.value.db_list, count.index)}"
instance = "${google_sql_database_instance.master_sql_instance[each.key].name}"
count = "${length(each.value.db_list)}"
}
Error Message
Error: Invalid combination of "count" and "for_each"
on ../main.tf line 43, in resource
"google_sql_database" "database": 43: for_each =
var.sql_var
The "count" and "for_each" meta-arguments are mutually-exclusive, only
one should be used to be explicit about the number of resources to be
created.
What the error message tells you is that you cannot use count and for_each together. It looks like you are trying to create 3 main databases and 1 replica database am I correct? What I would do is create your 2 master instances and then transform your map variable to create the databases.
terraform {
required_version = ">=0.13.3"
required_providers {
google = ">=3.36.0"
}
}
variable "sql_instances" {
default = {
"main_instance" = {
"db_list" = ["first_db", "second_db", "third_db"],
"disk_size" = "20",
},
"replica_instance" = {
"db_list" = ["first_db"],
"disk_size" = "20",
}
}
}
locals {
databases = flatten([
for key, value in var.sql_instances : [
for item in value.db_list : {
name = item
instance = key
}
]
])
sql_databases = {
for item in local.databases :
uuid() => item
}
}
resource "google_sql_database_instance" "sql_instance" {
for_each = var.sql_instances
name = each.key
settings {
disk_size = each.value.disk_size
tier = "db-f1-micro"
}
}
resource "google_sql_database" "sql_database" {
for_each = local.sql_databases
name = each.value.name
instance = each.value.instance
depends_on = [
google_sql_database_instance.sql_instance,
]
}
Then, first run terraform apply -target=google_sql_database_instance.sql_instance and after this run terraform apply.

Conditional flow within the different blocks in resource blocks of terraform

I'm trying to create a terraform script which takes user input and executes accordingly. I basically want to ask if the user wants static IP in Google cloud platform, if yes, then stitch the resource "google_compute_instance" accordingly, otherwise, let it go.
Sharing the code I have written:
variable "create_eip" {
description = "Enter 1 for true, 0 for false"
}
resource "google_compute_address" "external" {
count = "${var.create_eip}"
name = "external-ip",
address_type = "EXTERNAL",
}
resource "google_compute_instance" "compute-engine" {
name = "random",
machine_type = "f1-micro",
boot_disk {
initialize_params {
size = "10",
type = "pd-ssd",
image = "${data.google_compute_image.image.self_link}"
}
}
network_interface {
subnetwork = "default",
access_config {
nat_ip = "${google_compute_address.external.address}"
}
}
}
The error I'm getting here is when the user puts 0 as input the code control still goes to "nat_ip = "${google_compute_address.external.address}""
because of which I get this error:
google_compute_instance.compute-engine: Resource 'google_compute_address.external' not found for variable
'google_compute_address.external.address'.
I also tried it this way by replacing
nat_ip = "${var.create_eip == "1" ? "${google_compute_address.external.address}" : ""}"
(if create_ip = 1, execute "google_compute_address.external.address", else do nothing).
But it is not working as expected.
That's an issue with terraform...
You can't really do an if for something other than the count.
You could try something like that as you can't put condition inside a resource for now:
variable "create_eip" {
description = "Enter 1 for true, 0 for false"
}
resource "google_compute_address" "external" {
count = "${var.create_eip}"
name = "external-ip",
address_type = "EXTERNAL",
}
resource "google_compute_instance" "compute-engine-ip" {
count = "${var.create_eip == 1 ? 1 : 0}"
name = "random",
machine_type = "f1-micro",
boot_disk {
initialize_params {
size = "10",
type = "pd-ssd",
image = "${data.google_compute_image.image.self_link}"
}
}
network_interface {
subnetwork = "default",
access_config {
nat_ip = "${google_compute_address.external.address}"
}
}
}
resource "google_compute_instance" "compute-engine" {
count = "${var.create_eip == 1 ? 0 : 1}"
name = "random",
machine_type = "f1-micro",
boot_disk {
initialize_params {
size = "10",
type = "pd-ssd",
image = "${data.google_compute_image.image.self_link}"
}
}
network_interface {
subnetwork = "default",
access_config {
}
}
}
This code will create a compute instance using the created ip if the variable value is at one, in the other case it will create an ip, you could also add a lifecycle if you want to keep the same ip on the compute_address resource:
lifecycle = {
ignore_changes = ["node_pool"]
}