Templatefile inside dashboard_body - terraform

I was wondering if its possible to use templatefile inside of a dashboard_body resource. I'm trying the following.
dashboard_body = <<EOF
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
templatefile("${path.module}/backends.tmpl", { instances = aws_instance.web })
],
"period": 300,
"stat": "Average",
"region": "us-east-1",
"title": "EC2 Instance CPU"
}
}
]
}
EOF
With templatefile
%{ for instance in instances ~}
[
"AWS/EC2",
"CPUUtilization",
"InstanceId",
"${instance.id}"
]
%{ endfor ~}
However I get the following error message when I run terraform apply.
Error: "dashboard_body" contains an invalid JSON: invalid character 'e' in literal true (expecting 'r')
on dashboards.tf line 1, in resource "aws_cloudwatch_dashboard" "main":
1: resource "aws_cloudwatch_dashboard" "main" {
Thanks in advance for your help.

Yes you can use a template file. It looks like you have a small syntax error here:
templatefile("${path.module}/backends.tmpl", { instances = aws_instance.web })
You should wrap the entire function in an interpolation block, like so:
${templatefile("${path.module}/backends.tmpl", { instances = aws_instance.web })}

Related

BICEP - Parameter file variable assignment

I was following the repo for separate parameter file to each env as defined in the https://github.com/Azure/bicep/discussions/4586
I tried the separate parameters file for dev, stage, prod but the value assignment in main module variable remains flagged by intelligence even though it exists same param exist in the respective parameter file.
Other approach I tried is loadjson variable, but it does not show auto completion for items under subnet block as it stopes right after value.
Maybe I am overthinking and not applying the correct approach, Perhaps I should ignore intellisense and try deploying by applying parameter and hope it will auto pick correct value during the deployment param check.
Here is my parameter file and the same value applies to each env param json.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"department": {
"value": "finance"
},
"saAccountCount": {
"value": 1
},
"vmCount": {
"value": 1
},
"locationIndex": { //idenx 1 = app server, 2=AD, 3=Tool server, 4= dchp server
"value": 1
},
"appRoleIndex": { //idenx 1 = westus2, 2= westus, 3= eastus, 4=centralus, 5=uswest3
"value": 1
},
"appRole": {
"value": {
"Applicatoin Server": "ap",
"Active Directory": "dc",
"Tool server": "tool",
"DHCP server": "dhcp"
}
},
"environment": {
"value": "dev"
},
"addressPrefixes": {
"value": [
"172.16.0.0/20"
]
},
"dnsServers": {
"value": [
"1.1.1.1",
"4.4.4.4"
]
},
"locationList": {
"value": {
"westus2": "azw2",
"westus": "azw",
"Eastus": "aze",
"CentralUS": "azc",
"westus3": "azw3"
}
},
"subnets": {
"value": [
{
"name": "frontend",
"subnetPrefix": "172.16.2.0/24",
"delegation": "Microsoft.Web/serverfarms",
"privateEndpointNetworkPolicies": "disabled",
"serviceEndpoints": [
{
"service": "Microsoft.KeyVault",
"locations": [
"*"
]
},
{
"service": "Microsoft.Web",
"locations": [
"*"
]
}
]
},
{
"name": "backend",
"subnetPrefix": "172.16.3.0/24",
"delegation": "Microsoft.Web/serverfarms",
"privateEndpointNetworkPolicies": "enabled",
"serviceEndpoints": [
{
"service": "Microsoft.KeyVault",
"locations": [
"*"
]
},
{
"service": "Microsoft.Web",
"locations": [
"*"
]
},
{
"service": "Microsoft.AzureCosmosDB",
"locations": [
"*"
]
}
]
}
]
}
}
}
You appear to be attempting to deploy an Azure Resource Management (ARM) template using a parameter file.
The parameter file is used to pass values to the ARM template during deployment. The parameter file must use the same types as the ARM template and can only include values for the ARM template's parameters.
You will receive an error if the parameter file contains extra parameters that do not match the ARM template's parameters.
In the same deployment process, you can use both inline parameters and a local parameter file. If you specify a parameter's value in both the local parameter file and inline, the inline value takes priority.
Refer to create a parameter file of an ARM template
About the different parameters file for dev, stage, and prod, it's likely that the parameter file is not correctly linked to the ARM template.
You can deploy the ARM template with the parameter file to determine if it will automatically select the proper value during the deployment parameter check.
Regarding the loadjson variable, it is possible that the loadjson variable is not properly formatted.
You can double-check the loadjson variable's format to ensure it's proper.
After a workaround on this, I created a sample parameter.json file for a webapp to deploy in a production environment and that worked for me.
Note: Alternatively, You can use az deployment group create with a parameters file and deploy into Azure to avoid these conflicts.

terraform templatefile Function Invalid template interpolation value(AKS Cluster Name)

I try to provision an azure dashboard using terraform module and template file but I get an error message:
Call to function "templatefile" failed:
src/main/terraform/environment/modules/someservice/resources/dashboards/infrastructure-dashboard.tpl:41,39-55:
Invalid template interpolation value; Cannot include the given value in a
string template: string required., and 25 other diagnostic(s).
The error is being caused by the code in the template. It does not accept the variable declared in the dashboard properties. The code in the template causing the error:
"clusterName": "${k8s_cluster_name}",
Terraform code:
resource "azurerm_dashboard" "infra-dashboard" {
name = "${upper(terraform.workspace)}-Infrastructure"
resource_group_name = azurerm_resource_group.rgp-dashboards.name
location = azurerm_resource_group.rgp-dashboards.location
tags = {
source = "terraform"
}
dashboard_properties = templatefile("${path.module}/resources/dashboards/infrastructure-dashboard.tpl",
{
md_content = var.infra_dashboard_md_content,
dashboard_title = var.infra_dashboard_title,
dashboard_subtitle = var.infra_dashboard_subtitle,
sub_id = data.azurerm_subscription.current.subscription_id,
rgp_k8s = azurerm_resource_group.seervice-k8s.name,
k8s_cluster_name = azurerm_kubernetes_cluster.service-k8s.*.name,
rgp_service_env_global_name = azurerm_resource_group.service-env-global.name,
log_analyt_wrkspc = local.log_analyt_wrkspc
})
}
Snippet from the tmeplate where the error occurs:
"1": {
"position": {
"x": 0,
"y": 4,
"colSpan": 9,
"rowSpan": 4
},
"metadata": {
"inputs": [
{
"name": "queryParams",
"value": {
"metricQueryId": "node-count",
"clusterName": "${k8s_cluster_name}",
"clusterResourceId": "/subscriptions/${sub_id}/resourceGroups/${rgp_k8s}/providers/Microsoft.ContainerService/managedClusters/${k8s_cluster_name}",
"workspaceResourceId": "/subscriptions/${sub_id}/resourcegroups/${rgp_service_env_global_name}/providers/microsoft.operationalinsights/workspaces/${log_analyt_wrkspc}",
"timeRange": {
"options": {},
"relative": {
"duration": 21600000
}
},
"cpuFilterSelection": "total",
"memoryFilterSelection": "total_memoryrss"
}
}
For example:
join(",", azurerm_kubernetes_cluster.service-k8s.*.name)
will join all entries with an ",". Replace it with whatever you need
As luk2302 said azurerm_kubernetes_cluster.service-k8s.*.name it is not a string. In fact it is a tuple

How do I make my data template file recognise a number in terraform

THe problem I am facing now is this. I am trying to make my policy more flexible. So I shifted them into a file instead of using EOF.
How to make the template file recognise a number value?
"${max_untagged_images}" and "${max_tagged_images}" are suppose to be numbers.
Aws lifecycle policy:
resource "aws_ecr_lifecycle_policy" "lifecycle" {
count = length(aws_ecr_repository.repo)
repository = aws_ecr_repository.repo[count.index].name
depends_on = [aws_ecr_repository.repo]
policy = var.policy_type == "app" ? data.template_file.lifecycle_policy_app.rendered : data.template_file.lifecycle_policy_infra.rendered
}
Data template:
data "template_file" "lifecycle_policy_app" {
template = file("lifecyclePolicyApp.json")
vars = {
max_untagged_images = var.max_untagged_images
max_tagged_images = var.max_tagged_images
env = var.env
}
}
Policy:
{
"rules": [
{
"rulePriority": 1,
"description": "Expire untagged images older than ${max_untagged_images} days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": "${max_untagged_images}"
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Expire tagged images of ${env}, older than ${max_tagged_images} days",
"selection": {
"tagStatus": "tagged",
"countType": "imageCountMoreThan",
"countNumber": "${max_tagged_images}",
"tagPrefixList": [
"${env}"
]
},
"action": {
"type": "expire"
}
}
]
}
I would try the following 2 steps:
Remove the double quotes that around the "${max_tagged_images}"
Use terraform function called tonumber in order to convert it to a number:
tonumber("1")
(Follow the official documentation: https://www.terraform.io/docs/configuration/functions/tonumber.html)

Loop on Widget part

How to iterate only the widget part of the terraform script and get all the widget in a single dashboard?
locals {
instances = csvdecode(file("${path.module}/sample.csv"))
}
// if we use count it will loop this part
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "my-dashboard"
dashboard_body = <<EOF
{
"widgets": [
{
"type":"metric",
"x":0,
"y":0,
"width":12,
"height":6,
"properties":{
"metrics":[
for itr in local.instances.id:
[
"AWS/EC2",
"CPUUtilization",
"InstanceId",
itr // want this section to fetch the value form excel
]
],
"period":300,
"stat":"Average",
"region":"ap-south-1",
"title":"EC2 Instance CPU ",
"annotations": {
"horizontal": [
{
"label": "Untitled annotation",
"value": 2
}]}
}},]}EOF}
If your goal is to generate JSON, it's generally better to use jsonencode rather than template_file, because it can handle the JSON syntax details automatically and thus avoid the need to tweak annoying details of a text template to get the JSON right.
For example:
dashboard_body = jsonencode({
"widgets": [
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
for inst in local.instances : [
"AWS/EC2",
"CPUUtilization",
"InstanceId",
inst.id,
]
],
"period": 300,
# etc, etc
},
],
})
By using jsonencode you can use any of Terraform's normal language features and functions to produce your data structure, and leave the jsonencode function to turn that into valid JSON syntax at the end.
I tried to use two template_file resources.
sample.csv for test
instance_id
i-00001
i-00002
i-00003
create a template_file resouce for loop,
data "template_file" "ec2_metric" {
count = length(local.instances)
template = jsonencode([ "AWS/EC2", "CPUUtilization", "InstanceId", element(local.instances.*.instance_id, count.index)])
}
create a template_file for whole json
data "template_file" "widgets" {
template = <<JSON
{
"widgets": [
{
"type":"metric",
"x":0,
"y":0,
"width":12,
"height":6,
"properties":{
"metrics":[
${join(", \n ", data.template_file.ec2_metric.*.rendered)}
],
"period":300,
"stat":"Average",
"region":"ap-south-1",
"title":"EC2 Instance CPU ",
"annotations": {
"horizontal": [
{
"label": "Untitled annotation",
"value": 2
}]}
}},]}
JSON
}
using template_file,
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "my-dashboard"
dashboard_body = data.template_file.widgets.rendered
...
}
Test template_file.widgets
output "test" {
value = data.template_file.widgets.rendered
}
result
Outputs:
test = {
"widgets": [
{
"type":"metric",
"x":0,
"y":0,
"width":12,
"height":6,
"properties":{
"metrics":[
["AWS/EC2","CPUUtilization","InstanceId","i-00001"],
["AWS/EC2","CPUUtilization","InstanceId","i-00002"],
["AWS/EC2","CPUUtilization","InstanceId","i-00003"]
],
"period":300,
"stat":"Average",
"region":"ap-south-1",
"title":"EC2 Instance CPU ",
"annotations": {
"horizontal": [
{
"label": "Untitled annotation",
"value": 2
}]}
}},]}

Using a list variable for ECS task in container_definitions with terraform

In terraform I am attempting to pass a variable (list) to a module that we built. This variable needs to be used within a aws_ecs_task_definition resource in the container_definitions.
Right now I am just starting with an empty default list defined as a variable:
variable "task_enviornment" {
type = "list"
default = []
}
My ECS task definition looks like this:
resource "aws_ecs_task_definition" "ecs_task_definition" {
family = "${var.ecs_family}"
network_mode = "awsvpc"
task_role_arn = "${aws_iam_role.iam_role.arn}"
execution_role_arn = "${data.aws_iam_role.iam_ecs_task_execution_role.arn}"
requires_compatibilities = ["FARGATE"]
cpu = "${var.fargate_cpu}"
memory = "${var.fargate_memory}"
container_definitions =<<DEFINITION
[
{
"cpu": ${var.fargate_cpu},
"image": "${var.app_image}",
"memory": ${var.fargate_memory},
"name": "OURNAME",
"networkMode": "awsvpc",
"environment": "${jsonencode(var.task_enviornment)}",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group" : "${aws_cloudwatch_log_group.fargate-logs.name}",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "demo"
}
},
"portMappings": [
{
"containerPort": ${var.app_port},
"hostPort": ${var.app_port}
}
]
}
]
DEFINITION
}
The part I am having a problem with with the "environment" part:
"environment": "${jsonencode(var.task_enviornment)}",
I have tried a few different ways to get this to work.
If I do "environment": "${jsonencode(var.task_enviornment)}",
I get ECS Task Definition container_definitions is invalid: Error decoding JSON: json: cannot unmarshal string into Go struct field ContainerDefinition.Environment of type []*ecs.KeyValuePair
If I do "environment": "${var.task_enviornment}", or "environment": ["${var.task_enviornment}"],
I get At column 1, line 1: output of an HIL expression must be a string, or a single list (argument 8 is TypeList) in:
Then it just outputs the contents of container_definitions
I did also try adding default values and I was getting similar error messages. However I do need to be able to handle no values being sent in, so an empty list.
variable "task_enviornment" {
type = "list"
default = [
{
"name" = "BUCKET",
"value" = "test"
}
]
}
After a lot of investigation and a fresh set of eyes looking at this figured out the solution. I am unsure why this fixes it, and I feel like this is likely a bug.
Needed to do 2 things to fix this.
Remove type = "list" from the variable definition.
variable "task_environment" {
default = []
}
Remove the quotes when using the variable:
"environment": ${jsonencode(var.task_environment)},
The below solution should work
in variable.tf
variable "app_environments_vars" {
type = list(map(string))
default = []
description = "environment variable needed by the application"
default = [
{
"name" = "BUCKET",
"value" = "test"
},{
"name" = "BUCKET1",
"value" = "test1"
}]
and in your task definition, you can use ${jsonencode(var.app_environments_vars)} similar to
container_definitions =<<DEFINITION
[
{
"cpu": ${var.fargate_cpu},
"image": "${var.app_image}",
"memory": ${var.fargate_memory},
"name": "OURNAME",
"networkMode": "awsvpc",
"environment": ${jsonencode(var.app_environments_vars)},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group" : "${aws_cloudwatch_log_group.fargate-logs.name}",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "demo"
}
},
"portMappings": [
{
"containerPort": ${var.app_port},
"hostPort": ${var.app_port}
}
]
}
]
My guess is that you are trying to use the type "map" instead of lists, as showed above, the removal from type specification will work.
Example:
List_sample = [1,2,3]
Map_sample = { key_name = "value" }
Reference: Terraform - Type Constraints

Resources