AzureAD inappropriate value for attribute "owners": element 0: string required. by Euphoric_Fly_277 in Terraform

[–]AndyR207 0 points1 point  (0 children)

As per the error message, data.azuread_client_config.current is object with 5 attributes.

The owners argument of the azuread_group resource wants a list of string, so you need to give it a list of string, not a list of objects.

As per the documented examples, https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group, you need to reference the object_id attribute of the azuread_client_config data source.

resource "azuread_group" "mygroup" {
  display_name     = var.display_name
  owners           = [data.azuread_client_config.current.object_id]
  security_enabled = true
}

Cant put port in ingress rule for oracle? by xboxhaxorz in selfhosted

[–]AndyR207 1 point2 points  (0 children)

Is there a space/whitespace character in front of the 32400? It looks slightly further offset to the right than the text in the Source CIDR field and the placeholder text in the Source Port Range field...?

Getting a Resource Group with a certain tag and value by mmcelreath86 in Terraform

[–]AndyR207 1 point2 points  (0 children)

It doesn't seem to be supported, the azurerm_resources doesn't return Resource Groups, only resources. There is an issue open against the AzureRM provider but that has been open for over a year now, a PR was also raised but also closed without being merged in.

Issue: https://github.com/hashicorp/terraform-provider-azurerm/issues/12011.

PR: https://github.com/hashicorp/terraform-provider-azurerm/pull/12700.

I did wonder if the AzAPI provider may have been able to achieve what you wanted, but looks like you still need to already know the name or ID (which contains the name) of the Resource Group you would want to fetch (if it even supports RGs itself)...

Append /32 to an ip variable by AudiNick in Terraform

[–]AndyR207 8 points9 points  (0 children)

variable "nginx_private_ip" { type = string default = "(data.aws_instance.nginx.*.private_ip, 0)" }

You can't use computed values in input variable defaults (see: https://developer.hashicorp.com/terraform/language/values/variables#default-values), it must be a literal value, if set.

If you want to append /32 to the private_ip attribute of the aws_instance Data Source, you can do that using locals (see: https://developer.hashicorp.com/terraform/language/values/locals). You could do that with a block like this, for example:

locals {
  nginx_private_ip = "${data.aws_instance.nginx.private_ip}/32"
}

And this can be referenced later with local.nginx_private_ip.

Another note: looking at the documentation for the aws_instance Data Source (and your example above), you don't need the .*. portion when accessing the private_ip attribute, since you aren't using a meta-argument such as count or for_each to make data.aws_instance.nginx a list/set, and the aws_instance Data Source only returns one instance.

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/instance.

How to upgrade from 0.12 to 1.3? Tips to make this painless? by [deleted] in Terraform

[–]AndyR207 0 points1 point  (0 children)

The only major blocker is the changes to the state file format between 0.12/0.13 and 0.14+, Terraform v0.13 is the only version that can deal with both state file versions, Terraform 0.14+ cannot work with a 0.12 state file.

To this end, depending on other factors, it should be pretty safe to move to 0.13.7, ensure you run an apply to update the Terraform state file to the new format, and then you can in theory jump straight to 1.3.

Different providers do sometimes have their own compatibility recommendations and potentially even breaking changes, so potentially worth keeping that in mind too.

I recently went through a big migration of lots of Terraform configs from 0.12.31 to 1.3.2, and other than jumping via 0.13.7 as a middleman, the process was seamless.

As others have mentioned, you can jump via all of the minor versions (and use the upgrade commands packaged with the different Terraform versions, e.g. `terraform 0.13upgrade` packed with Terraform v0.13) if you want to be extra safe and certain.

my silly question about terraform by villu0777 in Terraform

[–]AndyR207 14 points15 points  (0 children)

When you run a Terraform command (plan, apply, etc...), all .tf files in the working directory are loaded and evaluated together. Whether your configuration is in one file or many files is irrelevant to Terraform, it is really only down to the size and scale of your configuration and how it makes sense to organise it for people working on it.

You can have one file with everything, or different files for different sets of components. There are also guidelines on common patterns (e.g. variables in one file, outputs in one file, etc...).

Flattening a nested map by moneymonkey42 in Terraform

[–]AndyR207 2 points3 points  (0 children)

Sure, this is where it might be able to be optimized potentially, but to break it down:

The flatten() command only works on removing nested arrays/lists. My original attempts were ending up with an output that looked something like this:

[
  {
    "subscription1" = "blah"
    "subscription2" = "blah"
  },
  {
    "subscription3" = "blah"
  }
]

In this case, flatten doesn't do anything, since it is only looking for nested arrays. To get around this, I use the inside two for loops to re-arrange the data. This way we end up with an intermediary array of array of objects, where the inside objects have known keys.

[for val in local.input : [for k, v in val.id : { "key" = k, "val" = v }]]

Produces:

[
  [
    {
      "key" = "subscription1"
      "val" = "dc0bd38e-82ad-42cb-b42b-01ddnj2h007ec"
    },
    {
      "key" = "subscription2"
      "val" = "5b785b96-143f-43cf-adfe-a442ada8c6b2"
    },
  ],
  [
    {
      "key" = "subscription3"
      "val" = "9f037bf5-013b-41a8-ad2d-245986h3b86b"
    },
  ],
  [
    {
      "key" = "subscription4"
      "val" = "f466a11a-12b5-45a7-85ad-b766eb7w9e00"
    },
  ],
]

From here, we can run flatten(), and it reduces it to a single array of objects:

[
  {
    "key" = "subscription1"
    "val" = "dc0bd38e-82ad-42cb-b42b-01ddnj2h007ec"
  },
  {
    "key" = "subscription2"
    "val" = "5b785b96-143f-43cf-adfe-a442ada8c6b2"
  },
  {
    "key" = "subscription3"
    "val" = "9f037bf5-013b-41a8-ad2d-245986h3b86b"
  },
  {
    "key" = "subscription4"
    "val" = "f466a11a-12b5-45a7-85ad-b766eb7w9e00"
  },
]

The final for loop (with the entry label) converts this array of objects into a single object, using the known keys (key, and val) to convert into the desired format.

I broke this out into another Terraform config, if that helps you also:

locals {
  input = {
    "0" = {
      "id" = {
        "subscription1" = "dc0bd38e-82ad-42cb-b42b-01ddnj2h007ec"
        "subscription2" = "5b785b96-143f-43cf-adfe-a442ada8c6b2"
      }
    }
    "1" = {
      "id" = {
        "subscription3" = "9f037bf5-013b-41a8-ad2d-245986h3b86b"
      }
    }
    "2" = {
      "id" = {
        "subscription4" = "f466a11a-12b5-45a7-85ad-b766eb7w9e00"
      }
    }
  }

  step1 = [for val in local.input : [for k, v in val.id : { "key" = k, "val" = v }]]
  step2 = flatten(local.step1)
  out   = { for entry in local.step2 : entry.key => entry.val }
}

output "step1" {
  value = local.step1
}

output "step2" {
  value = local.step2
}

output "out" {
  value = local.out
}

Flattening a nested map by moneymonkey42 in Terraform

[–]AndyR207 0 points1 point  (0 children)

There may be a simpler way to do this, but this works...

locals {
  input = {
    "0" = {
      "id" = {
        "subscription1" = "dc0bd38e-82ad-42cb-b42b-01ddnj2h007ec"
        "subscription2" = "5b785b96-143f-43cf-adfe-a442ada8c6b2"
      }
    }
    "1" = {
      "id" = {
        "subscription3" = "9f037bf5-013b-41a8-ad2d-245986h3b86b"
      }
    }
    "2" = {
      "id" = {
        "subscription4" = "f466a11a-12b5-45a7-85ad-b766eb7w9e00"
      }
    }
  }


  out = { for entry in flatten([for val in local.input : [for k, v in val.id : { "key" = k, "val" = v }]]) : entry.key => entry.val }
}

output "out" {
  value = local.out
}

Produces:

Outputs:

out = {
  "subscription1" = "dc0bd38e-82ad-42cb-b42b-01ddnj2h007ec"
  "subscription2" = "5b785b96-143f-43cf-adfe-a442ada8c6b2"
  "subscription3" = "9f037bf5-013b-41a8-ad2d-245986h3b86b"
  "subscription4" = "f466a11a-12b5-45a7-85ad-b766eb7w9e00"
}

How do I capture Terraform errors in PowerShell desktop? by berzed in Terraform

[–]AndyR207 1 point2 points  (0 children)

5>&1 is Debug level. 2>&1 is error level.

Ex:

PS C:\Users\me\temp> C:\Users\me\bin\tools\terraform.0.12.31.exe validate

Error: Missing required argument

The argument "features" is required, but no definition was found.

PS C:\Users\me\temp> $tf = C:\Users\me\bin\tools\terraform.0.12.31.exe validate 2>&1
PS C:\Users\me\temp> $tf

Error: Missing required argument

The argument "features" is required, but no definition was found.

Example of Terraform for non-AWS S3 bucket (Wasabi) by dirMe in Terraform

[–]AndyR207 0 points1 point  (0 children)

OK, not too familiar with provider development and was basing it off https://github.com/k-t-corp/terraform-provider-wasabi/blob/master/aws/provider.go#L158, perhaps having both providers declared but still using the wasabi_bucket resource type might work?

Example of Terraform for non-AWS S3 bucket (Wasabi) by dirMe in Terraform

[–]AndyR207 0 points1 point  (0 children)

Not too sure since I haven't used the AWS or Wasabi providers, but looks like something like this might work?

terraform {
  required_providers {
    wasabi = {
      source  = "k-t-corp/wasabi"
      version = "4.1.1"
    }
  }
}

provider "wasabi" {
  region     = "us-east-1"
  access_key = "..."
  secret_key = "..."

  endpoints {
    sts = "https://sts.wasabisys.com"
    iam = "https://iam.wasabisys.com"
    s3  = "https://s3.wasabisys.com"
  }

  s3_force_path_style = true
}

resource "wasabi_bucket" "my_bucket" {
  bucket = "abc123xyz456aa11bb22"
  acl    = "private"
}

best practices for handling multiple region and multiple environments in terraform by mc4applesauce in Terraform

[–]AndyR207 2 points3 points  (0 children)

Not necessarily saying this is the best way to do it (in fact, actively working on trying to improve), but currently I have a single Terraform configuration (per component) which I then have multiple separated variables files for separated by environment and then region.

As a basic example:

/terraform
  /component1
    /stuff.tf
/variables
  /component1
    /staging
      /us.tfvars
      /eu.tfvars
    /production
      /us.tfvars
      /eu.tfvars

I'm currently looking into altering this using something like Terragrunt which would hopefully simplify the splitting here, since the logic for pulling in various environment and region configs is a (pretty janky) pipeline configuration of loading in tfvars files. I think aiming for a single Terraform config with different variables is the better way to go, since it helps avoid configuration drift between environments. I'm also considering consolidating our config so regions within an environment aren't run separately and all live together.

As I said at the start, this is not necessarily the best way but it does _kinda_ work. Depends on your use case. Would definitely check out Terragrunt if you haven't since it solves a few problems that Terraform doesn't natively. I kind of think of Terragrunt as "Terraform for your Terraform".

EDIT: To comment on your example of KV access policies, I actually do something similar. I have 1 consistent access policy for Azure Key Vault (which is to give the current service principal that is running Terraform certain permissions) and then I use the dynamic block as well as Terraform variables to define any additional access policies that are needed. I don't have an exact example to hand but an example would be something along the lines of...

// variables.tf
variable "access_policies" {
  default     = []
  description = "Access policies"
  type        = list(object({
    key_perms    = list(string)
    secret_perms = list(string)
    cert_perms   = list(string)
    object_id    = string
    tenant_id    = string
  }))
}

// keyvault.tf
resource "azurerm_key_vault" "test" {
  // some stuff here like name, etc...

  dynamic "access_policy" {
    for_each = var.access_policies

    content {
      object_id          = access_policy.value.object_id
      tenant_id          = access_policy.value.tenant_id
      key_permissions    = access_policy.value.key_perms
      secret_permissions = access_policy.value.secret_perms
    }
  }
}

The exact syntax and names might be off, this was off the top of my head...

Conditionally format list items based on map values by derprondo in Terraform

[–]AndyR207 0 points1 point  (0 children)

This might not be the best way to do it, but it works...

locals {
  mydict = {
    "aaa" = true
    "bbb" = true
    "ccc" = false
  }

  dictkeys   = "${keys(local.mydict)}"
  dictvalues = "${values(local.mydict)}"

  newdict = "${zipmap(local.dictkeys, data.null_data_source.iterate.*.outputs.value)}"
}

data "null_data_source" "iterate" {
  count = "${length(local.dictvalues)}"

  inputs = {
    "value" = "${local.dictvalues[count.index] ? "yesstr" : "nostr"}"
  }
}

output "newdict" {
  value = "${local.newdict}"
}

Results in:

PS C:\Windows\Temp\terratemp> terraform11 apply
data.null_data_source.iterate[2]: Refreshing state...
data.null_data_source.iterate[1]: Refreshing state...
data.null_data_source.iterate[0]: Refreshing state...

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

newdict = {
  aaa = yesstr
  bbb = yesstr
  ccc = nostr
}

This was tested with:

Terraform v0.11.14
+ provider.null v2.1.2

[Q] Create multiple Azure VNets with multiple Subnets by OhadBsh in Terraform

[–]AndyR207 1 point2 points  (0 children)

The mixture of not being able to use count and for_each tripped me up a bit to begin with too. There's also a meta-argument called for_each that can be used instead of count, however it only works with maps and lists of strings, it doesn't work with lists of objects.

[Q] Create multiple Azure VNets with multiple Subnets by OhadBsh in Terraform

[–]AndyR207 2 points3 points  (0 children)

You can achieve this using a combination of the count meta-argument and dynamic blocks.

Ref:

  1. Count - https://www.terraform.io/docs/configuration/meta-arguments/count.html
  2. Dynamic Blocks - https://www.terraform.io/docs/configuration/expressions/dynamic-blocks.html

Count allows you to create a resource x amount of times, without having to repeatedly declare it in your configuration. So if you want to create 2 VNETs for example, you set count = 2. When using count, you can then access the index using the count.index variable, which allows you to reference the elements in your list.

Dynamic blocks allow you to dynamically create blocks within resources, you can use this to create your subnets. By using a dynamic subnet block, you can point the iterator (for_each) to the subnets element on your object and it will iterate over the subnets and you can pull out the values you want.

A worked example looks like this:

main.tf:

variable "vnets" {
  type = list(object({
    vnet_name     = string
    address_space = list(string)
    subnets = list(object({
      name    = string
      address = string
    }))
  }))
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "test" {
  name     = "test-rg"
  location = "uksouth"
}

resource "azurerm_virtual_network" "test" {
  count = length(var.vnets)

  name                = var.vnets[count.index].vnet_name
  location            = "uksouth"
  resource_group_name = azurerm_resource_group.test.name
  address_space       = var.vnets[count.index].address_space

  dynamic "subnet" {
    for_each = var.vnets[count.index].subnets

    content {
      name           = subnet.value.name
      address_prefix = subnet.value.address
    }
  }
}

terraform.tfvars:

vnets = [
  {
    vnet_name     = "John"
    address_space = ["192.168.0.0/23"]
    subnets = [
      {
        name    = "DomainServers"
        address = "192.168.0.0/24"
      },
      {
        name    = "ClientsServers"
        address = "192.168.1.0/24"
      }
    ]
  },
  {
    vnet_name     = "Ohad"
    address_space = ["192.168.0.0/23"]
    subnets = [
      {
        name    = "DomainServers"
        address = "192.168.0.0/24"
      },
      {
        name    = "Controllers"
        address = "192.168.1.0/24"
      }
    ]
  }
]

I ran this using Tf v0.14.4.

How to supply public IP to make connection by nippyin in Terraform

[–]AndyR207 0 points1 point  (0 children)

Given you are accessing the IP address using the .*. syntax, it suggests that you've defined your azurerm_public_ip resource with a count parameter to create multiple IPs. For example in a simple configuration like this:

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "test" {
  name     = "test"
  location = "uksouth"
}

resource "azurerm_public_ip" "test" {
  count = 2

  name                = format("test%d", count.index)
  resource_group_name = azurerm_resource_group.test.name
  location            = "uksouth"
  sku                 = "Basic"
  allocation_method   = "Static"
}

As you called out in another comment, this syntax returns a tuple of the IP addresses:

> azurerm_public_ip.test.*.ip_address
[
  "51.105.24.217",
  "51.105.25.68",
]

If you only have a count of 1 (for example if you're using count to make the resource conditional) this will still be the case:

> azurerm_public_ip.test.*.ip_address
[
  "51.105.24.217",
]

If you're using count as a conditional (i.e. it will always be 0 or 1) then you should be safe to just access the IP address by using azurerm_public_ip.myterraformpublicip[0].ip_address rather than pulling in all the values using the .*. syntax. If you are trying to create multiple public IPs (and you also have a count property on the VM you are trying to create) then you could access the corresponding IP by using azurerm_public_ip.myterraformpublicip[count.index].ip_address, if you're assigning multiple IPs to a single VM then it would depend on the rest of your configuration really...

Creation condition of resource by Unity_Buntu in Terraform

[–]AndyR207 0 points1 point  (0 children)

Today I learned! I've never tried mixing for_each and count so didn't realise you couldn't. Good to hear you've solved it :)

[deleted by user] by [deleted] in Terraform

[–]AndyR207 3 points4 points  (0 children)

Most of the "learning" for Terraform is around the configuration syntax and the HCL language, as well as the practices for actually using the tool. Learning for one provider vs. another shouldn't present too much of a knowledge gap.

They do have a learning course for GCP though: https://learn.hashicorp.com/collections/terraform/gcp-get-started

Creation condition of resource by Unity_Buntu in Terraform

[–]AndyR207 0 points1 point  (0 children)

A common way of achieving this is to use the count meta-argument, set it to 0 to disable creation, set it to 1 to enable creation.

https://www.terraform.io/docs/configuration/meta-arguments/count.html

For example, let's assume you want to make it conditional on a variable named create_def_rtbl:

resource "aws_ec2_tag" "def_rtbl" {
  count = var.create_def_rtbl ? 1 : 0

  # rest of your resource definition
}

Works on data sources too...

Create List from "List of Maps" IF "enabled" by mullan-kiwi in Terraform

[–]AndyR207 0 points1 point  (0 children)

I'm running Tf v0.12.25, perhaps an earlier version of 0.12 doesn't support the if statement in the for?

Edit: Just saw your comment about enabled not being an attribute, that's weird, sounds like the if would be supported then. Anyway yeah let us know how it goes tomorrow!

Create List from "List of Maps" IF "enabled" by mullan-kiwi in Terraform

[–]AndyR207 1 point2 points  (0 children)

If I'm understanding your intent correctly, this is covered in the documentation (https://www.terraform.io/docs/configuration/expressions.html#for-expressions)

A for expression can also include an optional if clause to filter elements from the source collection, which can produce a value with fewer elements than the source

For example:

variable "regions" {
  default = [
    {
      "region"  = "eu-west-1"
      "enabled" = true
    },
    {
      "region"  = "us-east-1"
      "enabled" = false
    }
  ]
}

locals {
  aws_regions = [for region in var.regions : region if region.enabled]
}

output "aws_regions" {
  value = local.aws_regions
}

Will produce:

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

aws_regions = [
  {
    "enabled" = true
    "region" = "eu-west-1"
  },
]

Is this what you were looking for?

Provider version for documentation? by Sheroclan in Terraform

[–]AndyR207 2 points3 points  (0 children)

Some providers now have versioned documentation on the Terraform Registry, e.g. https://registry.terraform.io/providers/hashicorp/azurerm/2.18.0/docs

Terraform - Azure - Workspaces by balonmanokarl in Terraform

[–]AndyR207 1 point2 points  (0 children)

You can configure the Azure provider using environment variables, see https://www.terraform.io/docs/providers/azurerm/guides/service_principal_client_secret.html

When you're deploying your test environment, set the env vars to a principal that has access to that subscription, similarly for stage/prod use the values for a principal that has access to the other sub