I'm trying to create separate modules(git repos) to create for several azure resources using terraform.
For example I want to create module-1 which will create aks cluster service and default node pool. I want to create separate module-2 which will create user node pools. So is there anyway I can import values from module-1 like aks cluster id, network id etc by just giving cluster name or any other identifier? If I use 'source' I had to give values everytime and default values are not same always for all the clusters. I don't want to hardcode the ids in tf files. Ofcourse I will use same state file.
So basically in terraform is it possible to get values from another azure resource which is created in terraform.
thanks,
Santosh
Using terraform_remote_state data resource
One way you can achieve this is by leveraging the terraform_remote_state data element.
On your module-1 main tf, output any attribute that other modules or repos would use. Then, pull that information you need on module-2 main via the data “terraform_remote_state” resource by providing the location of the state file used by module-1 .
Using a single main tf
The way I would approach this scenario is by defining outputs from module-1, and have those outputs passed in as parameters to module-2.
For example, you could have something like the following:
On your main tf file
module "module-1" {
source = "/path/to/module-1"
... < some parameters to module-1 ...>
}
module "module-2" {
source = "/path/to/module-2"
... < some parameters to module-2 ...>
aks-cluster-id = module.module-1.aks-cluster-id
}
Add the following output to module-1
output "aks-cluster-id" {
# Replace this with the proper resource and attribute based on how your cluster is created
value = azure.aks_cluster.aks-cluster-id
description = "AKS Cluster ID"
}
Terraform's documentation is actually pretty good and provides some useful examples. https://www.terraform.io/language/values/outputs
Related
I am currently using Terraform to create Azure vdi.
My current structure is
main.tf
input.tf
module/ main.tf, var.tf
So whenever a user joined the particular team, I do the following steps
Let say user1
Currently I am creating user1 folder with input.tf file and create the resource, Similar when user2 comes I am doing the same process (folder user2).
Is there any better way to handle such requirement ??
There are several possibilities to do that:
Separate config files and folders - you are already doing this, and as the number of users increases, it will be a problem. But it is also most flexible, as you can custom modify TF files for each user separately.
Use a set of users as an input. So you have one folder and one copy of your TF code, but you maintain set variable with users. Then use for_each to create different instance of module for each user. For example:
variable "users" {
type = set(string)
default = ["user1", "user2"]
}
module "vdi" {
source = "./module"
for_each = var.users
}
Use a separate workspace for each user.
I have the following use-case: I'm using a combination of the Azure DevOps pipelines and Terraform to synchronize our TAP for Grafana (v7.4). Intention is that we can tweak and tune our dashboards on Test, and push the changes to Acceptance (and Production) via the pipelines.
I've got one pipeline that pulls in the state of the Test environment and writes it to a set of json files (for the dashboards) and a single json array (for the folders).
The second pipeline should use these resources to synchronize the Acceptance environment.
This works flawlessly for the dashboards, but I'm hitting a snag putting the dashboards in the right folder dynamically. Here's my latest working code:
resource "grafana_folder" "folders" {
for_each = toset(var.grafana_folders)
title = each.key
}
resource "grafana_dashboard" "dashboards" {
for_each = fileset(path.module, "../dashboards/*.json")
config_json = file("${path.module}/${each.key}")
}
The folder resources pushes the folders based on a variable list of names that I pass via variables. This generates the folders correctly.
The dashboard resource pushes the dashboards correctly, based on all dashboard files in the specified folder.
But now I'd like to make sure the dashboards end up in the right folder. The provider specifies that I need to do this based on the folder UID, which is generated when the folder is created. So I'd like to take the output from the grafana_folder resource and use it in the grafana_dashboard resource. I'm trying the following:
resource "grafana_folder" "folders" {
for_each = toset(var.grafana_folders)
title = each.key
}
resource "grafana_dashboard" "dashboards" {
for_each = fileset(path.module, "../dashboards/*.json")
config_json = file("${path.module}/${each.key}")
folder = lookup(transpose(grafana_folder.folders), "Station_Details", "Station_Details")
depends_on = [grafana_folder.folders]
}
If I read the Grafana Provider github correctly, the grafana_folder resource should output a map of [uid, title]. So I figured if I transpose that map, and (by way of test) lookup a folder title that I know exists, I can test the concept.
This gives the following error:
on main.tf line 38, in resource "grafana_dashboard" "dashboards":
38: folder = lookup(transpose(grafana_folder.folders),
"Station_Details", "Station_Details")
Invalid value for "default" parameter: the default value must have the
same type as the map elements.
Both Uid and Title should be strings, so I'm obviously overlooking something.
Does anyone have an inkling where I'm going wrong and/or have suggestions on how I can do this (better)?
I think the problem this error is trying to report is that grafana_folder.folders is a map of objects, and so passing it to transpose doesn't really make sense but seems to be succeeding because Terraform has found some clever way to do automatic type conversions to produce some result, but then that result (due to the signature of transpose) is a map of lists rather than a map of strings, and so "Station_Details" (a string, rather than a list) isn't a valid fallback value for that lookup.
My limited familiarity with folders in Grafana leaves me unsure as to what to suggest instead, but I expect the final expression will look something like the following:
folder = grafana_folder.folders[SOMETHING].id
SOMETHING here will be an expression that allows you to know for a given dashboard which folder key it ought to belong to. I'm not seeing an answer to that from what you shared in your question, but just as a placeholder to make this a complete answer I'll suggest that one option would be to make a local map from dashboard filename to folder name:
locals {
# a local value probably isn't actually the right answer
# here, but I'm just showing it as a placeholder for one
# possible way to map from dashboard filename to folder
# name. These names should all be elements of
# var.grafana_folders in order for this to work.
dashboard_folders = {
"example1.json" = "example-folder"
"example2.json" = "example-folder"
"example3.json" = "another-folder"
}
}
resource "grafana_dashboard" "dashboards" {
for_each = fileset("${path.module}/dashboards", "*.json")
config_json = file("${path.module}/dashboards/${each.key}")
folder = grafana_folder.folders[local.dashboard_folders[each.key]].id
}
I am using terraform 0.13.5 to create aws_iam resources
I have 2 terraform resources as follows
module "calls_aws_iam_policy_attachment" {
# This calls an external module to
# which among other things creates a policy attachment
# resource attaching the roles to the policy
source = ""
name = "xoyo"
roles = ["rolex", "roley"]
policy_arn = "POLICY_NAME"
}
resource "aws_iam_policy_attachment" "policies_attached" {
# This creates a policy attachment resource attaching the roles to the policy
# The roles here are a superset of the roles in the above module
roles = ["role1", "role2", "rolex", "roley"]
policy_arn = "POLICY_NAME"
name = "NAME"
# I was hoping that adding the depends on block here would mean this
# resource is always created after the above module
depends_on = [ module.calls_aws_iam_policy_attachment ]
}
The first module creates a policy and attaches some roles. I cannot edit this module
The second resource attaches more roles to the same policy along with other policies
the second resource depends_on the first resource, so I would expect that the policy attachements of the second resource always overwrite those of the first resource
In reality, the policy attachments in each resource overwrite each other on each consecutive build. So that on the first build, the second resources attachments are applied and on the second build the first resources attachements are applied and so on and so forth.
Can someone tell me why this is happening? Does depends_on not work for resources that overwrite each other?
Is there an easy fix without combining both my resources together into the same resource?
As to why this is happening:
during the first run terraform deploys the first resources, then the second ones - this order is due to the depends_on relation (the next steps work regardless of any depends_on). The second ones overwrite the first ones
during the second deploy terraform looks at what needs to be done:
the first ones are missing (were overwritten), they need to be created
the second ones are fine, terraform ignores them for this update
now only the first ones will be created and they will overwrite the second ones
during the third run the same happens but the exact other way around, seconds are missing, first are ignored, second overwrite first
repeat as often as you want, you will never end up with a stable deployment.
Solution: do not specify conflicting things in terraform. Terraform is supposed to be a description of what the infrastructure should look like - and saying "this resource should only have property A" and "this resource should only have property B" is contradictory, terraform will not be able to handle this gracefully.
What you should do specifically: do not use aws_iam_policy_attachment, basically ever, look at the big red box in the docs. Use multiple aws_iam_role_policy_attachment instead, they are additive, they will not overwrite each other.
I'm new to Terraform, and I'm working on a project to use Docker/AWS ECR/ECS infrastructure on AWS. I see in this post where the author specify something like
data "aws_ecs_task_definition" "test" {
task_definition = "${aws_ecs_task_definition.test.family}"
depends_on = ["aws_ecs_task_definition.test"]
}
resource "aws_ecs_task_definition" "test" {
family = "test-family"
# ...
}
why is he using both data source AND resource on aws_ecs_task_definition? I can't find an explanation or similar example after hours of digging into the official doc as well as googling articles.
I see later on when he's setting up the service, he uses the following code to reference both of them: (again, I'm not sure what's going on here)
task_definition = "${aws_ecs_task_definition.test.family}:${max("${aws_ecs_task_definition.test.revision}", "${data.aws_ecs_task_definition.test.revision}")}"
I am now confused of what is the difference between using both data & resource on the same type, versus just using resource. Is there any difference in terms of lifecycle?
I'm now trying to create a AWS ECR for my docker image and I want terraform to manage it (create/update/destroy), should I use both data source & resource for the type aws_ecr_repository as well?
It makes sense. The guy is using the data source to get the latest task definition revision. This is because he might be using some other tool(jenkins/circleci) to push changes to the task definition or revision.
Hence, if he will run that code again then terraform should pick up the latest version and update the ecs service accordingly.
Check the below code:
resource "aws_ecs_service" "test-ecs-service" {
name = "test-vz-service"
cluster = "${aws_ecs_cluster.test-ecs-cluster.id}"
task_definition = "${aws_ecs_task_definition.test.family}:${max("${aws_ecs_task_definition.test.revision}", "${data.aws_ecs_task_definition.test.revision}")}"
desired_count = 1
iam_role = "${aws_iam_role.ecs-service-role.name}"
load_balancer {
target_group_arn = "${aws_alb_target_group.test.id}"
container_name = "nginx"
container_port = "80"
}
He is updating the service with the latest revision. He is using MAX function which is returning the maximum value. You may check terraform interpolation syntax, here.
if the task definition does not exist, will this terraform script create it?
Yes, It will create it with respect to the task definition which it has in it state file. If you have created task definition manually then it will increment the revision number.
if task definition exists and the data source block retrieved it, will the resource block re-create another revised task definition, or will it just do nothing?
If there is a change in any of the configuration of the resource then it will create new task definition and that task definition will be allocated to ecs service resource but if there is no change in the resource then it will do nothing.
I'm also unclear if this terraform script is intended to run only once (initial infra creation) or upon change?
This should be run at the time of infra creation or if you wanted to do any other update to task definition resource.
I have two plans, in which I am creating two different servers(just for example otherwise it's really complex). In one plan, I am outputing the value of the security group like this:
output "security_group_id" {
value = "${aws_security_group.security_group.id}"
}
I have second plan, in which I want to use that value, how I can achieve it, I have tried couple of things but nothing work for me.
I know how to use the output value return by module but don't know that how I can use the output of one plan to another.
When an output is used in the top-level module of a configuration (the directory where you run terraform plan) its value is recorded in the Terraform state.
In order to use this value from another configuration, the state must be published to a location where it can be read by the other configuration. The usual way to achieve this is to use Remote State.
With remote state enabled for the first configuration, it becomes possible to read the resulting values from the second configuration using the terraform_remote_state data source.
For example, it's possible to keep the state for the first configuration in Amazon S3 by using a backend configuration like the following:
terraform {
backend "s3" {
bucket = "example-s3-bucket"
key = "example-bucket-key"
region = "us-east-1"
}
}
After adding this to the first configuration, Terraform will prompt you to run terraform init to initialize the new backend, which includes migrating the existing state to be stored on S3.
Then in the second configuration this can be retrieved by providing the same configuration to the terraform_remote_state data source:
data "terraform_remote_state" "example" {
backend = "s3"
config {
bucket = "example-s3-bucket"
key = "example-bucket-key"
region = "us-east-1"
}
}
resource "aws_instance" "foo" {
# ...
vpc_security_group_ids = "${data.terraform_remote_state.example.security_group_id}"
}
Note that since the second configuration is reading the state from the first it is necessary to terraform apply the first configuration so that this value will actually be recorded in the state. The second config must be re-applied any time the outputs are changed in the first.
For the local backend the process is same. In first step, we need to declare the following code snippet to publish the state.
terraform {
backend local {
path = "./terraform.tfstate"
}
}
When you execute terraform init and terraform apply command, please observe that in .terraform directory new terraform.tfsate file would be created which contains backend information and tell terraform to use the following tfstate file.
Now in the second configuration we need to use data source to import the outputs by using this code snippet
data "terraform_remote_state" "test" {
backend = "local"
config {
path = "${path.module}/../regionalvpc/terraform.tfstate"
}
}