Print each value from terraform list variable - terraform

I have changed this datadog terraform module to support multiple slack channels, basically changed var.alert_channel from string to list(string) How would I print all the slack channels in the array in the last line of the message block?
resource "monitor" "high_error_rate" {
count = length(var.environment)
name = "${var.name} (${upper(var.environment[count.index])}) high error rate"
type = "log alert"
message = <<EOF
${var.name} has increased error log rate throughout last 5 mins
Environment: ${upper(var.environment[count.index])}
#slack-${var.alert_channel}
EOF
What would be the best way to print #slack-${var.alert_channel} for each channel in the list?
I looked into count and for each, but didn't found a working solution for this yet

Related

Setup Cloudwatch Alarm with Terraform that uses a query-expression

My goal is to setup an alarm in Cloudwatch via Terraform, that fires when disk_usage is above a certain treshold. The monitored metrics come from a Non-AWS-Server and are collected via CloudWatch Agent.
My first step was to do this manually, by setting up a metric that Selects the maximum disk_usage of all devices on a selected host:
SELECT MAX(disk_used_percent) FROM CWAgent WHERE host = 'MY_HOST'
I when successfully created an alarm based on this metric. Now I want to do the same thing with Terraform, but I cant figure out how to do that.
If I setup the Terraform-Resource to use a dimension for the host, then I get no results. If I try to setup a metric-query, then I get a conflict between Terraform and AWS, where Terraform tells me that my resource should not declare a "period"-Attribute but AWS demands it and will fail if not provided:
Error: Updating metric alarm failed: ValidationError: Period must not
be null
Currently, my resource looks like this:
resource "aws_cloudwatch_metric_alarm" "disk_usage_alarm" {
alarm_name = "Disk usage alarm on MY_HOST"
alarm_description = "One or more disks on MY_HOST are over 65% capacity"
comparison_operator = "GreaterThanOrEqualToThreshold"
threshold = "65"
evaluation_periods = "2"
datapoints_to_alarm = "1"
treat_missing_data = "missing"
actions_enabled = "false"
insufficient_data_actions = []
alarm_actions = []
ok_actions = []
metric_query {
id = "q1"
label = "Maximum disk_used_percentage for all disks on Host MY_HOST"
return_data = true
expression = "SELECT MAX(disk_used_percent) FROM CWAgent WHERE host = 'MY_HOST'"
}
}
Anyone knows whats wrong here and how to correctly setup this alarm via Terraform?

Is there a way to have division when writing terraform code for a log alert in Datadog?

I want have a terraform code to create a Datadog monitor for the percentage of errors in logs compared with all of them.
This is what I've tried
resource "datadog_monitor" "log_errors_count" {
count = local.memory_usage_threshold.critical \> 0 ? 1 : 0
name = "\[${module.label.id}\] ${length(var.description) \> 0 ? var.description : "Log Errors Percentage"}"
type = "log alert"
query = "logs(\"service:api-member status:error\").index(\"*\").rollup(\"count\").by(\"service\").last(\"${var.period}\") / logs(\"service:api-member\").index(\"*\").rollup(\"count\").by(\"service\").last(\"${var.period}\") \> ${local.logged_errors_threshold.critical}"
monitor_thresholds {
ok = local.logged_errors_threshold.ok
warning = local.logged_errors_threshold.warning
critical = local.logged_errors_threshold.critical
}
}
But it returns:
400 Bad Request: {"errors":["The value provided for parameter 'query' is invalid: invalid operator specified: "]}
I have done this kind of division for a metric alert and it worked fine. Using Datadog dashboard I can create a log monitor the way I want, but it looks like I am missing something when I try to do it using terraform.
Try to escape the internal quotes with a backslash
query = "logs(/"service:api-member status:error/").index(/"/").rollup/"count/").by(/"service/").last(/"${var.period}/") / logs(/"service:api-member/").index(/"*"/).rollup(/"count/").by(/"service/").last(/"${var.period}/") \> ${local.logged_errors_threshold.critical}"

how to use template_file resource in terraform

I want to launch a 5 VM and as soon as launch it will save the IP of that VM in a file
this is the high level idea of what I want to do I want to launch 5 instance and save all IP in a single VM.
I think here template_file will work but i am not sure how to implement this scenario
i tried
#!/bin/bash
touch myip.txt
private_ip=$(google_compute_instance.default.network_interface.0.network_ip)
echo "$private_ip" >> /tmp/ip.sh
resource "null_resource" "coderunner" {
provisioner "file" {
source = "autoo.sh"
destination = "/tmp/autoo.sh"
connection {
host = google_compute_address.static.address
type = "ssh"
user = var.user
private_key = file(var.privatekeypath)
}
}
connection {
host = google_compute_address.static.address
type = "ssh"
user = var.user
private_key = file(var.privatekeypath)
}
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/autoo.sh",
"sh /tmp/autoo.sh",
]
}
depends_on = ["google_compute_instance.default"]
}
but it is not working as soon as script run through an error as
null_resource.coderunner (remote-exec): /tmp/autoo.sh: line 3: google_compute_instance.default.network_interface.0.network_ip: command not found
There are 2 kinds of template files. One is template_file which is data resouce and the other one is templatefile which is a function.
Template_file is used when you have some file you want to transfer from your machine to provisioning instance and change some paramaters according to that machine. For example:
data "template_file" "temp_file" {
template = file("template.yaml")
vars = {
"local_ip" = "my_local_ip"
}
}
( if you want more detail explanation of what I did in this example just post in the comments but I think it's not for your use case )
This is good because you can change this file for each instance you have if you iterate over it with count for example.
As you can see this template doesn't do what you want to do. It's completely different thing.
To do what you want to it's best to use 2 provisioners:
1st you can use file provisioner to copy the script ( which executes for example ip a, along with some other paramaters to cut and filter only for the data you need )
and 2nd one, remote-exec which will execute that script.
Instead of using depend_on, if you use remote-exec provisioner it's best to use sleep command. Sleep hold you on for given amount of time and let's your instance start properly. You need to choose the right amount of sleep time depending on the size and speed of your instance but I usually do 30 seconds.
I hope I understand your question correctly and hope it helped with something.

Cloudwatch alarm creation fails due to heredoc

I am trying to create a composite cloudwatch alarm using terraform. But unfortunately my terraform code breaks with the following error:
Error: error creating CloudWatch Composite Alarm
(node-count-office-time-composite-alarm-DP-1474-desert):
ValidationError: AlarmRule must not contain leading or trailing
whitespace or be null
status code: 400, request id: 272b14ae-e6bd-4e65-8bb8-25372d9a5f7c
Following is my terraform code:
resource "aws_cloudwatch_composite_alarm" "node_count_office_time_alarm" {
depends_on = [aws_cloudwatch_metric_alarm.node_count, aws_cloudwatch_metric_alarm.office_time]
alarm_description = "Composite alarm for node count & office time"
alarm_name = "node-count-office-time-composite-alarm-${local.postfix}"
alarm_actions = [var.sns_topic_arn]
ok_actions = [var.sns_topic_arn]
alarm_rule =<<-EOF
ALARM(${aws_cloudwatch_metric_alarm.node_count.alarm_name}) AND
ALARM(${aws_cloudwatch_metric_alarm.office_time.alarm_name})
EOF
}
I checked many times and there are no leading or trailing spaces in my alarm_rule. Only new line after AND operator. I am using terraform 0.15.3 version. Anyone faces similar issues and how can I resolve this issue? thanks
I did not find the solution to how to make the heredoc working. But I fixed it for the time being using direct string expression instead of heredoc block. Following is the string expression:
alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.node_count.alarm_name}) AND ALARM(${aws_cloudwatch_metric_alarm.office_time.alarm_name})"
I hope it is useful for others if they face the same issue. thanks
Terraform instructions https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_composite_alarm are not accurate as of this writing in 2021.
alarm_rule accepts a single string argument, EOF/heredoc has to be processed to create a literal string:
locals {
alarm_rule_with_newlines = <<-EOF
ALARM(${aws_cloudwatch_metric_alarm.alpha.alarm_name}) OR
ALARM(${aws_cloudwatch_metric_alarm.bravo.alarm_name})
EOF
}
[...]
alarm_rule = trimspace(replace(local.alarm_rule_with_newlines, "/\n+/", " "))
I was not satisfied with neither of proposed answers so I have another solution.
Move your composite alert rules to separate file and just read it:
alarm_rule = file("./composite-alert-rule")
or
alarm_rule = templatefile("./composite-alert-rule", { arg = ... })
if you need to pass some dynamic args.
Check terraform docs for reference:
https://www.terraform.io/language/functions/templatefile
https://www.terraform.io/language/functions/file

terraform: data.aws_subnet, value of 'count' cannot be computed

terraform version 0.11.13
Error: Error refreshing state: 1 error(s) occurred:
data.aws_subnet.private_subnet: data.aws_subnet.private_subnet: value of 'count' cannot be computed
VPC code generated the error above:
resources.tf
data "aws_subnet_ids" "private_subnet_ids" {
vpc_id = "${module.vpc.vpc_id}"
}
data "aws_subnet" "private_subnet" {
count = "${length(data.aws_subnet_ids.private_subnet_ids.ids)}"
#count = "${length(var.private-subnet-mapping)}"
id = "${data.aws_subnet_ids.private_subnet_ids.ids[count.index]}"
}
Change the above code to use count = "${length(var.private-subnet-mapping)}", I successfully provisioned the VPC. But, the output of vpc_private_subnets_ids is empty.
vpc_private_subnets_ids = []
Code provisioned VPC, but got empty list of vpc_private_subnets_ids:
resources.tf
data "aws_subnet_ids" "private_subnet_ids" {
vpc_id = "${module.vpc.vpc_id}"
}
data "aws_subnet" "private_subnet" {
#count = "${length(data.aws_subnet_ids.private_subnet_ids.ids)}"
count = "${length(var.private-subnet-mapping)}"
id = "${data.aws_subnet_ids.private_subnet_ids.ids[count.index]}"
}
outputs.tf
output "vpc_private_subnets_ids" {
value = ["${data.aws_subnet.private_subnet.*.id}"]
}
The output of vpc_private_subnets_ids:
vpc_private_subnets_ids = []
I need the values of vpc_private_subnets_ids. After successfully provisioned VPC use the line, count = "${length(var.private-subnet-mapping)}", I changed code back to count = "${length(data.aws_subnet_ids.private_subnet_ids.ids)}". terraform apply, I got values of the list vpc_private_subnets_ids without above error.
vpc_private_subnets_ids = [
subnet-03199b39c60111111,
subnet-068a3a3e76de66666,
subnet-04b86aa9dbf333333,
subnet-02e1d8baa8c222222
......
]
I cannot use count = "${length(data.aws_subnet_ids.private_subnet_ids.ids)}" when I provision VPC. But, I can use it after VPC provisioned. Any clue?
The problem here seems to be that your VPC isn't created yet and so the data "aws_subnet_ids" "private_subnet_ids" data source read must wait until the apply step, which in turn means that the number of subnets isn't known, and thus the number of data "aws_subnet" "private_subnet" instances isn't predictable and Terraform returns this error.
If this configuration is also the one responsible for creating those subnets then the better design would be to refer to the subnet objects directly. If your module.vpc is also the module creating the subnets then I would suggest to export the subnet ids as an output from that module. For example:
output "subnet_ids" {
value = "${aws_subnet.example.*.id}"
}
Your calling module can then just get those ids directly from module.vpc.subnet_ids, without the need for a redundant extra API call to look them up:
output "vpc_private_subnets_ids" {
value = ["${module.vpc.subnet_ids}"]
}
Aside from the error about count, the configuration you showed also has a race condition because the data "aws_subnet_ids" "private_subnet_ids" block depends only on the VPC itself, and not on the individual VPCs, and so Terraform can potentially read that data source before the subnets have been created. Exporting the subnet ids through module output means that any reference to module.vpc.subnet_ids indirectly depends on all of the subnets and so those downstream actions will wait until all of the subnets have been created.
As a general rule, a particular Terraform configuration should either be managing an object or reading that object via a data source, and not both together. If you do both together then it may sometimes work but it's easy to inadvertently introduce race conditions like this, where Terraform can't tell that the data resource is attempting to consume the result of another resource block that's participating in the same plan.

Resources