Input parameters getting disabled while creating AWS SSM task with terrform - amazon-web-services

I have created Maintenance window and task to start EC2 instance using terraform below is the code:
resource "aws_ssm_maintenance_window_task" "StopTask" {
count = var.Enabled == "true" ? 1 : 0
max_concurrency = "100%"
max_errors = "100%"
name = "StopJumpEc2"
priority = 1
service_role_arn = aws_iam_role.EC2_start_stop.0.arn
task_arn = "AWS-StopEC2Instance"
task_type = "AUTOMATION"
window_id = aws_ssm_maintenance_window.StopWindow.0.id
targets {
key = "WindowTargetIds"
values = [
aws_ssm_maintenance_window_target.WinOsPatches_StopServer_Target.0.id
]
}
task_invocation_parameters {
automation_parameters {
document_version = "$DEFAULT"
parameter { // same result if you remove or add this :(
name = "AutomationAssumeRole"
values = [
"",
]
}
parameter {
name = "InstanceId"
values = [
"{{RESOURCE_ID}}",
]
}
}
}
}
While AWS console shows this task as :
This is ending with error below
the supplied parameters for invoking the specified automation document are incorrect.
What can be issue ?

Related

Terraform AWS: SQS destination for Lambda doesn't get added

I have a working AWS project that I'm trying to implement in Terraform.
One of the steps requires a lambda function to query athena and return results to SQS (I am using this module for lambda instead of the original resource). Here is the code:
data "archive_file" "go_package" {
type = "zip"
source_file = "./report_to_SQS_go/main"
output_path = "./report_to_SQS_go/main.zip"
}
resource "aws_sqs_queue" "emails_queue" {
name = "sendEmails_tf"
}
module "lambda_report_to_sqs" {
source = "terraform-aws-modules/lambda/aws"
function_name = "report_to_SQS_Go_tf"
handler = "main"
runtime = "go1.x"
create_package = false
local_existing_package = "./report_to_SQS_go/main.zip"
attach_policy_json = true
policy_json = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect : "Allow"
Action : [
"dynamodb:*",
"lambda:*",
"logs:*",
"athena:*",
"cloudwatch:*",
"s3:*",
"sqs:*"
]
Resource : ["*"]
}
]
})
destination_on_success = aws_sqs_queue.emails_queue.arn
timeout = 200
memory_size = 1024
}
The code works fine and produces the desired output; however, the problem is, SQS doesn't show up as a destination (although the Queue shows up in SQS normally and can send/recieve messages).
I don't think permissions are the problem because I can add SQS destinations manually from the console successfully.
The variable destination_on_success is only used if you also set create_async_event_config as true. Below is extracted from https://github.com/terraform-aws-modules/terraform-aws-lambda/blob/master
variables.tf
############################
# Lambda Async Event Config
############################
variable "create_async_event_config" {
description = "Controls whether async event configuration for Lambda Function/Alias should be created"
type = bool
default = false
}
variable "create_current_version_async_event_config" {
description = "Whether to allow async event configuration on current version of Lambda Function (this will revoke permissions from previous version because Terraform manages only current resources)"
type = bool
default = true
}
.....
variable "destination_on_failure" {
description = "Amazon Resource Name (ARN) of the destination resource for failed asynchronous invocations"
type = string
default = null
}
variable "destination_on_success" {
description = "Amazon Resource Name (ARN) of the destination resource for successful asynchronous invocations"
type = string
default = null
}
main.tf
resource "aws_lambda_function_event_invoke_config" "this" {
for_each = { for k, v in local.qualifiers : k => v if v != null && local.create && var.create_function && !var.create_layer && var.create_async_event_config }
function_name = aws_lambda_function.this[0].function_name
qualifier = each.key == "current_version" ? aws_lambda_function.this[0].version : null
maximum_event_age_in_seconds = var.maximum_event_age_in_seconds
maximum_retry_attempts = var.maximum_retry_attempts
dynamic "destination_config" {
for_each = var.destination_on_failure != null || var.destination_on_success != null ? [true] : []
content {
dynamic "on_failure" {
for_each = var.destination_on_failure != null ? [true] : []
content {
destination = var.destination_on_failure
}
}
dynamic "on_success" {
for_each = var.destination_on_success != null ? [true] : []
content {
destination = var.destination_on_success
}
}
}
}
}
So the destination_on_success is only used in this resource and this resources is only invoked if several conditions are met. The key one being var.create_async_event_config must be true.
You can see the example for this here https://github.com/terraform-aws-modules/terraform-aws-lambda/blob/be6cf9701071bf807cd7864fbcc751ed2552e434/examples/async/main.tf
module "lambda_function" {
source = "../../"
function_name = "${random_pet.this.id}-lambda-async"
handler = "index.lambda_handler"
runtime = "python3.8"
architectures = ["arm64"]
source_path = "${path.module}/../fixtures/python3.8-app1"
create_async_event_config = true
attach_async_event_policy = true
maximum_event_age_in_seconds = 100
maximum_retry_attempts = 1
destination_on_failure = aws_sns_topic.async.arn
destination_on_success = aws_sqs_queue.async.arn
}

Nested for_each in terraform, attempting to self-merge map

i'm trying to account for an annoying case when creating my own private hosted zones where the kinesis-streams endpoint has several non-standard DNS records. I am aiming to create a custom map of objects for each endpoint with information that I need (below):
locals {
endpoint_service_names = {
ec2 = {
name = "com.amazonaws.${data.aws_region.current.name}.ec2"
type = "Interface"
private_dns = false
phz_names = ["ec2.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
"ecr.dkr" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr"
type = "Interface"
private_dns = false
phz_names = ["dkr.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = true
}
"ecr.api" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.api"
type = "Interface"
private_dns = false
phz_names = ["api.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
kinesis-streams = {
name = "com.amazonaws.${data.aws_region.current.name}.kinesis-streams"
type = "Interface"
private_dns = false
phz_names = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
phz_wildcard = true
}
}
In order to use this in my impl, however, I would need to perform a nested for_each which Terraform does not directly allow, so I need to merge my map info into a new map with the service and DNS names in the one object. My problem is similar to: Looping in for_each nested resources with terraform
except I do not have a list in each map element, only on the phz_names. I can't figure out how to get the syntax right to produce something like this:
endpoint_service_dns_list = {
kinesis-streams = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
[...]
}
}
My attempt:
endpoint_service_dns_list = merge([
for servicename, service in local.endpoint_service_names : {
for phz_name in service[0].phz_names :
"${servicename}-${phz_name}" => {
service_name = servicename
phz_name = phz.phz_names
}
}
])
but the syntax highlighting/attempt to indexing obviously fails because I do not have a list for each service, but I am not sure what to replace it with.
Maybe I have missed the point but based on your comments and your kind of expected result in the question you could have something like this. I have added a format that maps then service name to all its urls as a list. Or incase you wanted a mapping of each url to its service name I added an additional local var.
locals {
endpoint_service_names = {
ec2 = {
name = "com.amazonaws.${data.aws_region.current.name}.ec2"
type = "Interface"
private_dns = false
phz_names = ["ec2.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
"ecr.dkr" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr"
type = "Interface"
private_dns = false
phz_names = ["dkr.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = true
}
"ecr.api" = {
name = "com.amazonaws.${data.aws_region.current.name}.ecr.api"
type = "Interface"
private_dns = false
phz_names = ["api.ecr.${data.aws_region.current.name}.amazonaws.com"]
phz_wildcard = false
}
kinesis-streams = {
name = "com.amazonaws.${data.aws_region.current.name}.kinesis-streams"
type = "Interface"
private_dns = false
phz_names = [
"kinesis.${data.aws_region.current.name}.amazonaws.com",
"data-kinesis.${data.aws_region.current.name}.amazonaws.com",
"control-kinesis.${data.aws_region.current.name}.amazonaws.com"
]
phz_wildcard = true
}
}
endpoint_service_dns_list = {for service, attrs in local.endpoint_service_names : service => attrs.phz_names}
endpoint_dns_service_list = merge([for service, attrs in local.endpoint_service_names : {for url in attrs["phz_names"] : url => service}]...)
}
data "aws_region" "current" {}
output "endpoint_service_dns_list" {
value = local.endpoint_service_dns_list
}
output "endpoint_dns_service_list" {
value = local.endpoint_dns_service_list
}
This would map the phz_names with the service as seen in the output, or map each url to its service
endpoint_dns_service_list = {
"api.ecr.eu-west-2.amazonaws.com" = "ecr.api"
"control-kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
"data-kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
"dkr.ecr.eu-west-2.amazonaws.com" = "ecr.dkr"
"ec2.eu-west-2.amazonaws.com" = "ec2"
"kinesis.eu-west-2.amazonaws.com" = "kinesis-streams"
}
endpoint_service_dns_list = {
"ec2" = [
"ec2.eu-west-2.amazonaws.com",
]
"ecr.api" = [
"api.ecr.eu-west-2.amazonaws.com",
]
"ecr.dkr" = [
"dkr.ecr.eu-west-2.amazonaws.com",
]
"kinesis-streams" = [
"kinesis.eu-west-2.amazonaws.com",
"data-kinesis.eu-west-2.amazonaws.com",
"control-kinesis.eu-west-2.amazonaws.com",
]
}
Hopefully one of these is the kind of thing you were looking to do

Cross region replication of AWS ECR repository

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

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.

Terraform: List of AMI specific to ubuntu 20.08 LTS AWS

Problem: I am using terraform to get a list of AMI for a specific OS - ubuntu 20.08
I have checked different examples link
When I use the script this does not give me list of AMI
Script
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-20.08-amd64-server-*"]
}
filter {
name = "virtualization - type"
values = ["hvm"]
}
owners = ["AWS"]
}
I have referred the below link as well
How are data sources used in Terraform?
Output:
[ec2-user#ip-172-31-84-148 ~]$ terraform plan
provider.aws.region
The region where AWS operations will take place. Examples
are us-east-1, us-west-2, etc.
Enter a value: us-east-1
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
data.aws_ami.std_ami: Refreshing state...
------------------------------------------------------------------------
No changes. Infrastructure is up-to-date.
This means that Terraform did not detect any differences between your configuration and real physical resources that exist. As a result, no actions need to be performed.
i am not sure where am I going wrong i have checked a lot of links some i have listed below.
Your data should be:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}
output "test" {
value = data.aws_ami.ubuntu
}
The owner of Ubuntu is not AWS, and the image is ubuntu-focal-20.04-amd64-server-, not ubuntu-xenial-20.08-amd64-server-.
The above results in (us-east-1):
{
"architecture" = "x86_64"
"arn" = "arn:aws:ec2:us-east-1::image/ami-0dba2cb6798deb6d8"
"block_device_mappings" = [
{
"device_name" = "/dev/sda1"
"ebs" = {
"delete_on_termination" = "true"
"encrypted" = "false"
"iops" = "0"
"snapshot_id" = "snap-0f06f1549ff7327c9"
"volume_size" = "8"
"volume_type" = "gp2"
}
"no_device" = ""
"virtual_name" = ""
},
{
"device_name" = "/dev/sdb"
"ebs" = {}
"no_device" = ""
"virtual_name" = "ephemeral0"
},
{
"device_name" = "/dev/sdc"
"ebs" = {}
"no_device" = ""
"virtual_name" = "ephemeral1"
},
]
"creation_date" = "2020-09-08T00:55:25.000Z"
"description" = "Canonical, Ubuntu, 20.04 LTS, amd64 focal image build on 2020-09-07"
"filter" = [
{
"name" = "name"
"values" = [
"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*",
]
},
{
"name" = "virtualization-type"
"values" = [
"hvm",
]
},
]
"hypervisor" = "xen"
"id" = "ami-0dba2cb6798deb6d8"
"image_id" = "ami-0dba2cb6798deb6d8"
"image_location" = "099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200907"
"image_type" = "machine"
"most_recent" = true
"name" = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200907"
"owner_id" = "099720109477"
"owners" = [
"099720109477",
]
"product_codes" = []
"public" = true
"root_device_name" = "/dev/sda1"
"root_device_type" = "ebs"
"root_snapshot_id" = "snap-0f06f1549ff7327c9"
"sriov_net_support" = "simple"
"state" = "available"
"state_reason" = {
"code" = "UNSET"
"message" = "UNSET"
}
"tags" = {}
"virtualization_type" = "hvm"
}