Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Use map instead of list for all subnet type #535

Closed
wants to merge 10 commits into from

Conversation

achachw
Copy link

@achachw achachw commented Oct 26, 2020

Description

Defining subnet as list is destructive, when trying to remove one/multiple subnet from that list.

Motivation and Context

Let's say i want to deploy this configuration :

module "vpc-test" {
  ...
  cidr             = "10.40.0.0/16"
  azs              = ["us-east-1a", "us-east-1b"]
  private_subnets  = ["10.40.3.0/24", "10.40.4.0/24","10.40.5.0/24", "10.40.6.0/24"]

Once applied, i remove one subnet from that list

module "vpc-test" {
  ...
  cidr             = "10.40.0.0/16"
  azs              = ["us-east-1a", "us-east-1b"]
  private_subnets  = ["10.40.3.0/24", "10.40.5.0/24", "10.40.6.0/24"]

If i apply this last configuration all private subnets will be destroyed and 3 new ones will be created

#403
#178

Breaking Changes

The idea is to use map instead of list for subnets definitions.
From

private_subnets  = ["10.40.3.0/24", "10.40.5.0/24", "10.40.6.0/24"]

to

private_subnets = {
    "subnet-1" = {
      cidr = "10.40.3.0/24",
      az   = "eu-west-1a"
    },
    "subnet-2" = {
      cidr = "10.40.5.0/24",
      az   = "eu-west-1b"
    },
    "subnet-3" = {
      cidr = "10.40.6.0/24",
      az   = "eu-west-1c"
    }
  }

How Has This Been Tested?

module "vpc" {
  source = ".."
  name = "automate-vpc"

  cidr = "20.10.0.0/16"
  enable_nat_gateway = true
  one_nat_gateway_per_az = true

  private_subnets = {
    "subnet-1" = {
      cidr = "20.10.1.0/24",
      az   = "eu-west-1a"
    },
    "subnet-2" = {
      cidr = "20.10.2.0/24",
      az   = "eu-west-1a"
    }
  public_subnets = {
        "subnet-3" = {
          cidr = "20.10.11.0/24",
          az   = "eu-west-1a",
          public_subnet_ipv6_prefixes = 2
        }
     }
  }

Copy link

@aperigault aperigault left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is many unnecessary new lines in main.tf and README.md


tags = merge(
{
"Name" = format(
"%s-${var.private_subnet_suffix}-%s",
var.name,
element(var.azs, count.index),
)
each.key, )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You change the Name format. Is it voluntary ?

private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24", "10.0.4.0/24", "10.0.5.0/24"]
redshift_subnets = ["10.0.41.0/24", "10.0.42.0/24"]
intra_subnets = ["10.0.51.0/24", "10.0.52.0/24", "10.0.53.0/24"]
database__subnets = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Comment on lines +57 to +65
private_subnets = {
"subnet-1" = {
cidr = "10.0.1.0/24",
az = "eu-west-1a"
},
"subnet-2" = {
cidr = "10.0.2.0/24",
az = "eu-west-1a"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonbabenko I didn't test it for now, but Is this structure of the subnets variable ok for you ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure is fine, but I am thinking about an important note on the implementation - aim to have backward compatibility (type list(string) should be converted into map(map(any)). Have type = any on variables.

We can't just ask users to rewrite their code at once even if we release a new major version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure is fine, but I am thinking about an important note on the implementation - aim to have backward compatibility (type list(string) should be converted into map(map(any)). Have type = any on variables.

Are you proposing to support for both list(string) and map(map(string)) inputs ? Or to help users to move as easy as possible to the new the input structure (map(map(string))) ?

We can't just ask users to rewrite their code at once even if we release a new major version.

I think we have to document it. We can't do more without adding an extra tool that we should maintain. We can probably provide something for states manipulation, but I'm not sure we want to go through terraform code generation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. type = any (since type = list(string)|map(map(any)) or similar is not supported). Yes, support both types of values transparently for the users.
  2. Agree. We have to document but first, let's try to make it backward compatible. Code generation or even relying on tools like tfmigrate is not the best option for such modules to my mind.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. type = any (since type = list(string)|map(map(any)) or similar is not supported). Yes, support both types of values transparently for the users.

I was thinking about using map(map(string)) and not map(map(any)). But using any is also ok for me.

  1. Agree. We have to document but first, let's try to make it backward compatible. Code generation or even relying on tools like tfmigrate is not the best option for such modules to my mind.

Can you please elaborate your point ? How do you want to handle this backward compatibility without a lot of complexity ? If you have something in mind, please share it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@barryib

locals {
  one_map = try(tomap(var.subnets), {for k in var.subnets: k => {}})
}

variable "subnets" {
  type = any
  default = ["10.0.0.0/20", "10.0.1.0/20"]
#  default = {
#    "10.0.0.0/20" = { az = "eu-west-1a", tags = { Name: "first" }}
#    "10.0.1.0/20" = { az = "eu-west-1b", tags = { Name: "second" }}
#  }
}

var.subnets can be list or map, and Terraform will convert that into a map. See around this time - https://youtu.be/xpfGBgLgpWo?t=1520

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humm.. sounds nice. I'll try that. But this still involve state manipulation right ? It just help users to not modify their code base.

Should we use this for transition only (said for 1 or 2 major release) and we invite them to change their code ?

IMHO, I think that users will be more concerne about state migration and to avoid rebuilding all their infrastructure. The minor code change can be acceptable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, manual state manipulation will probably still required, unless we can generate keys to match indexes in a list, like this (to use maps but keep old indexes):

one_map = try(tomap(var.subnets), {for k,v in var.subnets: k => { cidr = v }})

I think if we use the type conversion, we can keep using it for a long time (more than 1-2 major releases) to support both cases transparently. But we should also see if it is going to look ugly and hard to maintain, we can drop support for older structure (list).

Copy link
Member

@barryib barryib Nov 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, manual state manipulation will probably still required, unless we can generate keys to match indexes in a list, like this (to use maps but keep old indexes):

That was the first thing I tried, but I'm afraid that it won't work as expected. As you mention, map keys are strings and you can't change their type (unless you have an idea).

# random_pet.node_groups will be destroyed
  - resource "random_pet" "node_groups" {
      - id        = "wondrous-honeybee" -> null
      - keepers   = {
          - "foo" = "10.0.0.0/20"
        } -> null
      - length    = 2 -> null
      - separator = "-" -> null
    }

  # random_pet.node_groups[1] will be destroyed
  - resource "random_pet" "node_groups" {
      - id        = "topical-grizzly" -> null
      - keepers   = {
          - "foo" = "10.0.1.0/20"
        } -> null
      - length    = 2 -> null
      - separator = "-" -> null
    }

  # random_pet.node_groups["0"] will be created
  + resource "random_pet" "node_groups" {
      + id        = (known after apply)
      + keepers   = {
          + "foo" = "10.0.0.0/20"
        }
      + length    = 2
      + separator = "-"
    }

  # random_pet.node_groups["1"] will be created
  + resource "random_pet" "node_groups" {
      + id        = (known after apply)
      + keepers   = {
          + "foo" = "10.0.1.0/20"
        }
      + length    = 2
      + separator = "-"
    }

random_pet.node_groups => random_pet.node_groups["0"]
random_pet.node_groups[1] => random_pet.node_groups["1"]

Please find bellow my random_pet snippet:

  1. I run it first with a count
  2. I used the magic to convert it to map and used with for_each
resource "random_pet" "node_groups" {
  for_each = local.one_map
  #count     = length(var.list_subnets)
  separator = "-"
  length    = 2

  keepers = {
    foo = each.value.cidr
    #foo = var.list_subnets[count.index]
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No surprise here then :) Good that you double-checked it.

@barryib
Copy link
Member

barryib commented Nov 23, 2020

@antonbabenko how can we move on this ? Is this something you want to add in this module ? Are you planning any general rewrite on the vpc module ? @achachw and I want to help, but need some guidance here. By example, variable map definition will be a good start. Should we experiment terraform-aws-modules/terraform-aws-eks#1101 here ?

@antonbabenko
Copy link
Member

I don't plan to have any rewrites in this module but if you want to work on this PR and experiment with this feature and migrations it would be awesome. At some point, we will have to move forward and introduce migrations natively or using 3rd party tools.

I recommend working on smaller things so that it is easier to read the code.

@hansh0801
Copy link

track

@github-actions
Copy link

This PR has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this PR will be closed in 10 days

@github-actions github-actions bot added the stale label Jan 26, 2022
@github-actions
Copy link

github-actions bot commented Feb 6, 2022

This PR was automatically closed because of stale in 10 days

@github-actions github-actions bot closed this Feb 6, 2022
@mustafa89
Copy link

Is there any traction on this? we would love to have this feature.

@bryantbiggs
Copy link
Member

@mustafa89 there is work going on in the background for this but it will take some time. based on prior experience in major changes with modules that are heavily utilized we will have to consider all options

@mustafa89
Copy link

mustafa89 commented Feb 16, 2022

Alright, looking forward to it, just a small note

 "subnet-1" = {
      cidr = "10.40.3.0/24",
      az   = "eu-west-1a"
    },
    "subnet-2" = {
      cidr = "10.40.5.0/24",
      az   = "eu-west-1b"
    },
    "subnet-3" = {
      cidr = "10.40.6.0/24",
      az   = "eu-west-1c"
    }

the subnet-1,2,3 feels redundant. I guess a list of maps should also work and simplify it.

private_subnets = [
    {
      cidr = "10.40.3.0/24",
      az   = "eu-west-1a"
    },
    {
      cidr = "10.40.5.0/24",
      az   = "eu-west-1b"
    },
    {
      cidr = "10.40.6.0/24",
      az   = "eu-west-1c"
    },
]

but yeah, just my 2 cents.

@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants