How do I get values from a resource in another module

Hi

I’m using HashiCorp’s github example (Manage GitHub Users, Teams, and Repository Permissions | Terraform | HashiCorp Developer) to create teams and team members. In my example i’m using two module’s one to create teams and one to create team members. In this local i need values for team slug and id but how do i get those from my module called teams and pass it to this local? Also I have multiple teams so i will get multiple slug and id for all teams

team_members_temp = flatten([
    for team, members in local.team_members_files : [
      for tn, t in github_team.all : {
        name    = t.name
        id      = t.id
        slug    = t.slug
        members = members
      } if t.slug == team
    ]
  ])

github_team.all in this local variable is inside a module called teams

When posting code, please consider the guidance in Welcome to the forum - please reformat your message - don’t make your readers have to read your code with no indentation.

Try to provide potential answerers with a clear understanding of how your configuration is set up. The link to the tutorial does not accomplish this - it is far too long and it is unclear which bit of it you might be referring to.

Posting your own Terraform configuration as a GitHub repo that you link to is an effective way to show configurations containing multiple directories.

You haven’t shown the code so I’m not sure, but this sounds dangerously like an antipattern to me. Too many people seem to somehow come up with the idea that they should separate every little thing into its own module. Teams, and who is in the teams, are pretty closely related concepts.

Hi, thanks for the answer. I apologies for the indentation issue

Can I use a single module for team_members and teams.csv when they have different keys? the reason why i tried to use 2 modules is because the keys differ in those CSV files

In teams module I have:

resource "github_team" "all" {
    name                        = var.name
    description                 = var.description
    privacy                     = var.privacy
    create_default_maintainer   = true
}

This resource creates teams in github based on my teams.csv. In my variable.tf I’m trying to get slug and id for the teams created but I don’t know how to do it, i’m trying to retrieve information directly from module.teams.github_team.all which I think is wrong. Do I need an output variable?:

team_members_temp = flatten([
    for team, members in var.github_team_members : [
      for tn, t in module.teams.github_team.all : {
        name    = t.name
        id      = t.id
        slug    = t.slug
        members = members
      } if t.slug == team
    ]
  ])

  # Create object for each team-user relationship
  team_members = flatten([
    for team in local.team_members_temp : [
      for member in team.members : {
        name     = "${team.slug}-${member.username}"
        team_id  = team.id
        username = member.username
        role     = member.role
      }
    ]
  ])

  team_membership = {
    for tm in local.team_members : tm.name => tm
  }

Yes if you want a value contained within a module (e.g. something from a resource, data source or local variable) you need to expose that using an output block, similarly to how you need to use a variable block to get things into a module.

Screenshots are not a good way to share code for feedback, as specific lines can’t be quoted from them.

In your screenshot, you have shown a single module block which appears to be taking care of both teams and team membership. That’s the opposite of your previous statements that you are using two separate modules.

Then, your most recent post contains copy/pasted code snippets which seem to suggest a different module layout entirely.

If you want people to help, you have to allow them to understand the context of the problem. At the moment, you’re not doing that.

Sorry I cannot share my git repo as it is against company policy

I’m indeed using multiple modules. The source in my picture downloads all the modules needed. I have one module called teams taking care of creating teams in github the other one is team_membership which adds members to the created team.

module "teams" {
  source                                                   = "./teams"
  for_each = local.teams
  name                                                     = each.value.name
  description                                              = each.value.description
  privacy                                                  = each.value.privacy
}

module "team-members" {
  source                                                   = "./team-members"
  for_each = local.team_membership
  team_id                                                  = each.value.team_id
  username                                                 = each.value.username
  role                                                     = each.value.role
}

These are my locals

teams = {
    for _, team in var.github_teams : team.name => team
  }

# Create temp object that has team ID and CSV contents
  team_members_temp = flatten([
    for team, members in var.github_team_members : [
      for tn, t in module.teams.github_team.all : {
        name    = t.name
        id      = t.id
        slug    = t.slug
        members = members
      } if t.slug == team
    ]
  ])

  # Create object for each team-user relationship
  team_members = flatten([
    for team in local.team_members_temp : [
      for member in team.members : {
        name     = "${team.slug}-${member.username}"
        team_id  = team.id
        username = member.username
        role     = member.role
      }
    ]
  ])

  team_membership = {
    for tm in local.team_members : tm.name => tm
  }
}

in team module in main.tf I have

resource “github_team” “all” {
name = var.name
description = var.description
privacy = var.privacy
create_default_maintainer = true
}

I understand this is difficult to follow without accessing the repo but I don't have any other option.

Ah! Now that you have shared all your module blocks, I’m finally able to understand roughly how all your code fits together.

For future reference, a great way to ask this question would be to set up a representative model version of your code, minus your company’s secrets, in a brand new Git repo, and paste a link to that. With a clear overview of the directory and module structures involved, it would cut past all the initial confusion.

Anyway, back to the actual issue…

As I mentioned back in an earlier comment:

Now you’ve shown some of the code, I’m only more convinced.

So, I think you should delete this code entirely:

And move the relevant code from the teams and team-members subdirectories up a level.

You haven’t actually shown the full contents of both directories, but based on the general description of what you’re doing, I imagine both of them contain very few resource blocks each - possibly only one.

Removing the extra level of modules will enable you to do away with a lot of unnecessary boilerplate transferring values back and forth, and enable you to focus on the important functionality.

Here is my code. All data is in GitHub - adajou/terraform and modules is in GitHub - adajou/terraform-module

what would be the most efficient way to code this? use one module for team and team_members and one for organization and organization members? if so how can I pass CSV files that has different keys?

Sorry for my basic questions, I’m kind of new to the terraform world

Thanks.

No, use one module for everything. There should be no module blocks inside the GitHub - adajou/terraform-module repository at all.

As I’ve said in a couple of previous responses already, you appear to be trying to put every single resource in its own module, which is generally a mistake, and is making things much harder for you than it needs to be.

I don’t fully understand your question, but I think this will no longer be an issue at all once you’re no longer needing to pass the values into the extra layer of modules.

In short: Stop making things complicated by using too many modules.

Thanks for the reply. Much appreciated. Just to understand here. I should still use GitHub - adajou/terraform-module but only have one main module in it. How would the structure look like? is it possible for you to give me a code or a structure example?

I actually get the idea now. Thanks for the help

Yes, probably, if you’re building a re-usable component that will be used from multiple other repositories in your organization. If your organization will only be consuming it from exactly one other repository, then I would move it into that repository rather than keeping it in a separate Git repository.

Indeed - by which I mean, the repository is already a module by itself, so there should be zero module blocks contained within it.

resource "github_organization_settings" "organization_settings" {
  # I'm not sure about the for_each in this block, your code is a
  # bit indecisive about whether it configures one organization
  # or multiple organizations.
  ...
}

resource "github_membership" "organization_memberships" {
  for_each = ...
  ...
}

resource "github_team" "all" {
  for_each = ...
  ...
}

resource "github_team_membership" "members" {
  for_each = ...
  ...
}

In this design, the github_team_membership for_each expression will be a bit complicated, as it needs to flatten looping over both teams, and members within those teams. There is a fairly standard, if complicated, Terraform idiom for this, which I recently gave an example of here: Attach multiple policy to single SSO permission set - #2 by maxb and it is also mentioned in a slightly different form in the Terraform docs: https://developer.hashicorp.com/terraform/language/functions/flatten#flattening-nested-structures-for-for_each

There is a potential alternative design in which the github_team and github_team_membership resources are moved into a child module (one
module, not separate team and a team-membership modules), so that looping over teams can be done at the module for_each, and looping over members at the resource "github_team_membership" for_each. It is a matter of personal preference regarding Terraform code style as to which design is used.

Thanks. I changed my code to use single module as suggested GitHub - adajou/terraform-module the problem is terraform does not recognize team_membership. Here I have two different ways of getting the key but none seems to work, any idea why I don’t get team members?:

#for_each                    = local.team_membership
    for_each                    = { for team-membership in var.github_team_members : "var.github_team.name-${team-membership.username}" => team-membership }

First, a quick digression into code layout - I think your code would be more readable if you did not use locals so much. If you have an expression that is only used in one for_each, just write it inline where needed. This means the reader doesn’t need to track down the definition in another file, to understand how a given block is being parameterized.

You can even move the variable block associated with each function to be with its other blocks as well - there’s nothing special about the filename variables.tf.

But that’s just my opinion on code style.



The actual problem with this code:

is that team-membership holds the list of members. Terraform complains that asking for .username of a list doesn’t make sense.

What you actually need is something more like this:

resource "github_team_membership" "members" {
  for_each = {
    for item in flatten([
      for team_name, team_members in var.github_team_members : [
        for member in team_members : {
          team_name = team_name
          username  = member.username
          role      = member.role
        }
      ]
    ])
    : "${item.team_name} ${item.username}" => item
  }

  team_id  = github_team.all[each.value.team_name].id
  username = each.value.username
  role     = each.value.role
}

Note the complex for_each expression that:

  • loops over teams (middle for)
  • loops over members within each team (innermost for)
  • captures all the relevant info from multiple levels of looping into one object (innermost pair of curly braces)
  • turns the flattened list of objects back into a map, assigning a suitably unique string key to each membership (outermost for)

Also note that you no longer need to explicitly write out a depends_on since Terraform can already observe the dependency from the direct reference to github_team.all[each.value.team_name].id.

That should take care of the team memberships.



Separately I believe you have an issue with the organization memberships:

resource "github_membership" "organization_memberships" {
  for_each = {
    for mem in var.github_organization_members : "${mem.username}-${mem.role}" => mem
  }
  username = each.value.username
  role     = each.value.role
}

(In the above, I’ve updated the code style to match what I said at the start of this reply, so we can look at the for_each expression in the context of the whole resource block.)

The problem is that you’re using username and also role as part of the unique key - I’m pretty sure that’s not how GitHub works - I believe you can’t add the same user as both a member and an admin - instead, admin includes member rights.

Thanks for the reply and the code. The team membership did not work and I have a question about team_name. What is a team_name in this context because my github_team_members only has username and role. The team name is the name of the CSV. There is a separate teams CSV file that has all the team names

I am working with the structure of the variable you already defined:

You have this defined as a map with team names as keys.

These are my CSV files which are mapped to the variable above

Any further help? that method didn’t work. Not getting any suggested new members.

I think it is something wrong with the method because I don’t get any suggestion even with:

  team_id                       = github_team.all["oxygen"].id
  username                      = each.value.username
  role                          = each.value.role

I know my suggestion worked, because I tested out the terraform plan operation myself, locally, and got a valid expected plan.

I did have to also fix up the incorrect paths to the team members data files in terraform/main.tf at main · adajou/terraform · GitHub though, for that to work - there’s a missing data-files/ in the paths.