Is there a way to declare an abstract resource to inherit from?
Example:
resource "digitalocean_droplet" "worker_abstract" {
abstract = true // ???
name = "swarm-worker-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
]
// other config stuff
provisioner "remote-exec" {
//...
}
}
And then use declared resource with overridden variables:
resource "worker_abstract" "worker_foo" {
count = 2
name = "swarm-worker-foo-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
resource "worker_abstract" "worker_bar" {
count = 5
name = "swarm-worker-bar-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}
This might be a little more "verbose" of a solution than what you're proposing, but this sounds like a perfect use-case for modules in 0.12.
You could create a module, say in the file worker/main.tf
variable "instances" {
type = number
}
variable "name" {
type = string
}
variable "tags" {
type = list(string)
default = []
}
resource "digitalocean_droplet" "worker" {
count = var.instances
name = "swarm-worker-${var.name}-${count.index}"
tags = var.tags
// other config stuff
provisioner "remote-exec" {
//...
}
}
then you could consume your module almost exactly like your example (let's say from the directory above worker)
module "worker_foo" {
source = "./worker"
instances = 2
name = "foo"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
module "worker_bar" {
source = "./worker"
instances = 5
name = "bar"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}
Related
Is it possible to reference an attribute within the same resource?
resource "some_resource" "foo" {
name = "cool-name"
attribute1 = "something-else-cool"
attribute2 = format("%s-%s", this.name, this.attribute1)
}
You can't do it (at least with the current terraform versions).
Instead you can use local values:
locals {
value1 = "something-else-cool"
name = "cool-name"
}
resource "some_resource" "foo" {
name = local.name
attribute1 = local.value1
attribute2 = format("%s-%s", local.name, local.value1)
}
Or simple just use input variables
resource "some_resource" "foo" {
name = var.name
attribute1 = var.someVariable
attribute2 = format("%s-%s", var.name, var.someVariable)
}
variable "name" {
type = string
default = "value"
}
variable "someVariable" {
type = string
default = "value"
}
Or, you may want to look in your provisioner's documentation if it offers data sources in case you want to fetch data from you cloud setup.
Note that you can use self in provisioner and connection blocks, for example
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo The server's IP address is ${self.private_ip}"
}
}
I am trying to output gcp project information by doing something like this :
output "projects" {
value = tomap({
for project_name in ["project_1", "project_2", "project_3"] :
project_name => tomap({
id = google_project."${project_name}".id
number = google_project."${project_name}".number
})
})
description = "Projects"
}
Or like this :
output "projects" {
value = tomap({
for_each = toset([google_project.project_1,google_project.project_2])
id = each.key.id
number = each.key.number
})
description = "Projects"
}
Is it at all possible to use resource names this way? Do I have to specify every resource by duplicating code?
E.g.
output "projects" {
value = tomap({
project_1 = tomap({
id = google_project.project_1.id
number = google_project.project_1.number
})
project_2 = tomap({
id = google_project.project_2.id
number = google_project.project_2.number
})
project_3 = tomap({
id = google_project.project_3 .id
number = google_project.pproject_3 .number
})
})
description = "Projects"
}
EDIT : declared resources.
In main.tf projects 1 to 3 are declared the same way.
resource "google_project" "project_3" {
name = var.projects.project_3.name
project_id = var.projects.project_3.id
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
in variables.tf
variable "projects" {
type = map(object({
name = string
id = string
}))
}
in variables.tfvars
projects = {
project_1= {
name = "project_1"
id = "project_1-12345"
}
project_2= {
name = "project_2"
id = "project_2-12345"
}
project_3= {
name = "project_2"
id = "project_2-12345"
}
}
I misunderstood your question originally. I see now that you want to reference a resource by a variable name. No you cannot do that. But your setup here doesn't really make sense, and seems more complex than it needs to be.
Consider if these options would improve your setup.
locals {
projects = { # This is equivalent to your input.
project_1 = {
name = "project_1"
id = "project_1-12345"
}
project_2 = {
name = "project_2"
id = "project_2-12345"
}
project_3 = {
name = "project_3"
id = "project_3-12345"
}
}
}
resource "google_project" "this" {
for_each = local.projects
name = each.key # or each.value.name / don't really need name
project_id = each.value.id
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
output "projects_from_input" {
description = "You can of course, just use the input."
value = local.projects
}
output "projects_explicit_values" {
description = "Alternatively, if you need a subset of resource values."
value = { for k, v in google_project.this : k => {
name = v.name
id = v.project_id
} }
}
output "complete_resources" {
description = "But you can just output the complete resource."
value = google_project.this
}
I edited my initial answer after seeing the Terraform resource that creates a project. The need is a way to get a resource name in the output bloc with interpolation.
I think if a single resource is used to create all the projets instead of one resource per projet, it's easier to expose this resource in the output bloc.
For example you can configure projects metadata information from a json file, or directly a local variable or a var if needed :
Example for a json file and local variable
mymodule/resource/projects.json :
{
"projects": {
"project_1": {
"id": "project_1",
"number": "23333311"
},
"project_2": {
"id": "project_2",
"number": "33399999"
}
}
}
Then retrieve projects as a variable from locals.tf file :
mymodule/locals.tf :
locals {
projects = jsondecode(file("${path.module}/resource/projects.json"))["projects"]
}
Create your projects in a single resource with a foreach :
resource "google_project" "projects" {
for_each = local.projects
name = each.key
project_id = each.value["id"]
folder_id = google_folder.parent.name
billing_account = data.google_billing_account.acct.id
auto_create_network = false
}
Expose the projects resource in an output.tf file :
output "projects" {
value = google_project.projects
description = "Projects"
}
The same principle can be done with a var instead of local variable.
Given the following resource block:
resource "azuredevops_variable_group" "secrets" {
project_id = ...
name = "my-secrets"
description = "..."
allow_access = true
key_vault {
name = "my-akv"
service_endpoint_id = azuredevops_serviceendpoint_azurerm.dev.id
}
dynamic "variable" {
for_each = [
"secret-1",
"secret-2",
"secret-3",
])
content {
name = variable.value
}
}
}
I need to set one more variable to this resource that is static. How should I merge a dynamic block? something like:
merge (
dynamic "variable" {
for_each = [
"secret-1",
"secret-2",
"secret-3",
])
content {
name = variable.value
}
},
variable {
name = "secret-4"
value = "foo"
}
)
Adding another variable block after the dynamic block will raise Error: "variable.0.value": conflicts with key_vault.
The namespace is created in next way, so "role bindings" are applied depending of the "app_env" code
**variables.tf**
variable app_name {}
variable app_env {}
locals {
custom_role_dev = "Enterprise Development Project"
custom_role_prd = "Enterprise Production Project"
}
**main.tf**
resource "kubernetes_namespace" "kube_ns" {
metadata {
name = var.app_name
}
}
resource "kubernetes_role" "custom_role_dev" {
count var.app_env == "d" ? 1 : 0
metadata {
name = local.custom_role_dev
namespace = var.app_name
}
rule {
api_groups = [""]
resources = ["<options>"]
verbs = ["*"]
}
depends_on = [kubernetes_namespace.kube_ns]
}
resource "kubernetes_role" "custom_role_prd" {
count var.app_env == "p" ? 1 : 0
metadata {
name = local.custom_role_prd
namespace = var.app_name
}
rule {
api_groups = [""]
resources = ["<options>"]
verbs = ["*"]
}
depends_on = [kubernetes_namespace.kube_ns]
}
In order to create several namespace and applying their respective roles, I want to use "lists" to replace "app_name" variable but I don't know how to iterate the "kubernetes_role" block.
I think this 2 links are very close what I want to do
Convert list to map with index in Terraform
Terraform - conditionally creating a resource within a loop
Can this be done with "for_each" or "count"?
I'm trying to create a module for the aws_wafv2_web_acl resource and I can't figure out how to add multiple 'excluded_rule' blocks inside a dynamic block. Is this possible? Here is the resource:
resource "aws_wafv2_web_acl" "web-acl" {
name = var.name
description = ""
scope = "REGIONAL"
default_action {
allow {}
}
dynamic "rule" {
for_each = var.rules
content {
name = rule.value["name"]
priority = rule.value["priority"]
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = rule.value["name"]
vendor_name = "AWS"
excluded_rule {
name = "excluded rule"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
metric_name = rule.value["name"]
}
}
}
visibility_config {
cloudwatch_metrics_enabled = false
sampled_requests_enabled = false
metric_name = "webaclmetric"
}
}
And here are the variables being passed:
name = "test"
rules = [
{"name": "AWSManagedRulesLinuxRuleSet", "priority": 0, "exclusions": "LFI_QUERYARGUMENTS,LFI_URIPATH"},
{"name": "AWSManagedRulesWindowsRuleSet", "priority": 1, "exclusions": "PowerShellCommands_Set1_QUERYARGUMENTS"}
]
it is possible. You may want to take a look at the terraform module I have written for WafV2 web acl -> https://github.com/umotif-public/terraform-aws-waf-webaclv2
Going back to your question you can solve it with the following block:
dynamic "excluded_rule" {
for_each = length(lookup(managed_rule_group_statement.value, "excluded_rule", {})) == 0 ? [] : toset(lookup(managed_rule_group_statement.value, "excluded_rule"))
content {
name = excluded_rule.value
}
}
and then you can pass in the following into your module
managed_rule_group_statement = {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
excluded_rule = [
"SizeRestrictions_QUERYSTRING",
"SizeRestrictions_BODY",
"GenericRFI_QUERYARGUMENTS"
]
}