├── LICENSE ├── README.md ├── main.tf ├── outputs.tf ├── test ├── .gitignore ├── README.md ├── null_name.tf └── simple.tf ├── variables.tf └── versions.tf /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 HashiCorp, Inc. 2 | 3 | Mozilla Public License, version 2.0 4 | 5 | 1. Definitions 6 | 7 | 1.1. “Contributor” 8 | 9 | means each individual or legal entity that creates, contributes to the 10 | creation of, or owns Covered Software. 11 | 12 | 1.2. “Contributor Version” 13 | 14 | means the combination of the Contributions of others (if any) used by a 15 | Contributor and that particular Contributor’s Contribution. 16 | 17 | 1.3. “Contribution” 18 | 19 | means Covered Software of a particular Contributor. 20 | 21 | 1.4. “Covered Software” 22 | 23 | means Source Code Form to which the initial Contributor has attached the 24 | notice in Exhibit A, the Executable Form of such Source Code Form, and 25 | Modifications of such Source Code Form, in each case including portions 26 | thereof. 27 | 28 | 1.5. “Incompatible With Secondary Licenses” 29 | means 30 | 31 | a. that the initial Contributor has attached the notice described in 32 | Exhibit B to the Covered Software; or 33 | 34 | b. that the Covered Software was made available under the terms of version 35 | 1.1 or earlier of the License, but not also under the terms of a 36 | Secondary License. 37 | 38 | 1.6. “Executable Form” 39 | 40 | means any form of the work other than Source Code Form. 41 | 42 | 1.7. “Larger Work” 43 | 44 | means a work that combines Covered Software with other material, in a separate 45 | file or files, that is not Covered Software. 46 | 47 | 1.8. “License” 48 | 49 | means this document. 50 | 51 | 1.9. “Licensable” 52 | 53 | means having the right to grant, to the maximum extent possible, whether at the 54 | time of the initial grant or subsequently, any and all of the rights conveyed by 55 | this License. 56 | 57 | 1.10. “Modifications” 58 | 59 | means any of the following: 60 | 61 | a. any file in Source Code Form that results from an addition to, deletion 62 | from, or modification of the contents of Covered Software; or 63 | 64 | b. any new file in Source Code Form that contains any Covered Software. 65 | 66 | 1.11. “Patent Claims” of a Contributor 67 | 68 | means any patent claim(s), including without limitation, method, process, 69 | and apparatus claims, in any patent Licensable by such Contributor that 70 | would be infringed, but for the grant of the License, by the making, 71 | using, selling, offering for sale, having made, import, or transfer of 72 | either its Contributions or its Contributor Version. 73 | 74 | 1.12. “Secondary License” 75 | 76 | means either the GNU General Public License, Version 2.0, the GNU Lesser 77 | General Public License, Version 2.1, the GNU Affero General Public 78 | License, Version 3.0, or any later versions of those licenses. 79 | 80 | 1.13. “Source Code Form” 81 | 82 | means the form of the work preferred for making modifications. 83 | 84 | 1.14. “You” (or “Your”) 85 | 86 | means an individual or a legal entity exercising rights under this 87 | License. For legal entities, “You” includes any entity that controls, is 88 | controlled by, or is under common control with You. For purposes of this 89 | definition, “control” means (a) the power, direct or indirect, to cause 90 | the direction or management of such entity, whether by contract or 91 | otherwise, or (b) ownership of more than fifty percent (50%) of the 92 | outstanding shares or beneficial ownership of such entity. 93 | 94 | 95 | 2. License Grants and Conditions 96 | 97 | 2.1. Grants 98 | 99 | Each Contributor hereby grants You a world-wide, royalty-free, 100 | non-exclusive license: 101 | 102 | a. under intellectual property rights (other than patent or trademark) 103 | Licensable by such Contributor to use, reproduce, make available, 104 | modify, display, perform, distribute, and otherwise exploit its 105 | Contributions, either on an unmodified basis, with Modifications, or as 106 | part of a Larger Work; and 107 | 108 | b. under Patent Claims of such Contributor to make, use, sell, offer for 109 | sale, have made, import, and otherwise transfer either its Contributions 110 | or its Contributor Version. 111 | 112 | 2.2. Effective Date 113 | 114 | The licenses granted in Section 2.1 with respect to any Contribution become 115 | effective for each Contribution on the date the Contributor first distributes 116 | such Contribution. 117 | 118 | 2.3. Limitations on Grant Scope 119 | 120 | The licenses granted in this Section 2 are the only rights granted under this 121 | License. No additional rights or licenses will be implied from the distribution 122 | or licensing of Covered Software under this License. Notwithstanding Section 123 | 2.1(b) above, no patent license is granted by a Contributor: 124 | 125 | a. for any code that a Contributor has removed from Covered Software; or 126 | 127 | b. for infringements caused by: (i) Your and any other third party’s 128 | modifications of Covered Software, or (ii) the combination of its 129 | Contributions with other software (except as part of its Contributor 130 | Version); or 131 | 132 | c. under Patent Claims infringed by Covered Software in the absence of its 133 | Contributions. 134 | 135 | This License does not grant any rights in the trademarks, service marks, or 136 | logos of any Contributor (except as may be necessary to comply with the 137 | notice requirements in Section 3.4). 138 | 139 | 2.4. Subsequent Licenses 140 | 141 | No Contributor makes additional grants as a result of Your choice to 142 | distribute the Covered Software under a subsequent version of this License 143 | (see Section 10.2) or under the terms of a Secondary License (if permitted 144 | under the terms of Section 3.3). 145 | 146 | 2.5. Representation 147 | 148 | Each Contributor represents that the Contributor believes its Contributions 149 | are its original creation(s) or it has sufficient rights to grant the 150 | rights to its Contributions conveyed by this License. 151 | 152 | 2.6. Fair Use 153 | 154 | This License is not intended to limit any rights You have under applicable 155 | copyright doctrines of fair use, fair dealing, or other equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under the 169 | terms of this License. You must inform recipients that the Source Code Form 170 | of the Covered Software is governed by the terms of this License, and how 171 | they can obtain a copy of this License. You may not attempt to alter or 172 | restrict the recipients’ rights in the Source Code Form. 173 | 174 | 3.2. Distribution of Executable Form 175 | 176 | If You distribute Covered Software in Executable Form then: 177 | 178 | a. such Covered Software must also be made available in Source Code Form, 179 | as described in Section 3.1, and You must inform recipients of the 180 | Executable Form how they can obtain a copy of such Source Code Form by 181 | reasonable means in a timely manner, at a charge no more than the cost 182 | of distribution to the recipient; and 183 | 184 | b. You may distribute such Executable Form under the terms of this License, 185 | or sublicense it under different terms, provided that the license for 186 | the Executable Form does not attempt to limit or alter the recipients’ 187 | rights in the Source Code Form under this License. 188 | 189 | 3.3. Distribution of a Larger Work 190 | 191 | You may create and distribute a Larger Work under terms of Your choice, 192 | provided that You also comply with the requirements of this License for the 193 | Covered Software. If the Larger Work is a combination of Covered Software 194 | with a work governed by one or more Secondary Licenses, and the Covered 195 | Software is not Incompatible With Secondary Licenses, this License permits 196 | You to additionally distribute such Covered Software under the terms of 197 | such Secondary License(s), so that the recipient of the Larger Work may, at 198 | their option, further distribute the Covered Software under the terms of 199 | either this License or such Secondary License(s). 200 | 201 | 3.4. Notices 202 | 203 | You may not remove or alter the substance of any license notices (including 204 | copyright notices, patent notices, disclaimers of warranty, or limitations 205 | of liability) contained within the Source Code Form of the Covered 206 | Software, except that You may alter any license notices to the extent 207 | required to remedy known factual inaccuracies. 208 | 209 | 3.5. Application of Additional Terms 210 | 211 | You may choose to offer, and to charge a fee for, warranty, support, 212 | indemnity or liability obligations to one or more recipients of Covered 213 | Software. However, You may do so only on Your own behalf, and not on behalf 214 | of any Contributor. You must make it absolutely clear that any such 215 | warranty, support, indemnity, or liability obligation is offered by You 216 | alone, and You hereby agree to indemnify every Contributor for any 217 | liability incurred by such Contributor as a result of warranty, support, 218 | indemnity or liability terms You offer. You may include additional 219 | disclaimers of warranty and limitations of liability specific to any 220 | jurisdiction. 221 | 222 | 4. Inability to Comply Due to Statute or Regulation 223 | 224 | If it is impossible for You to comply with any of the terms of this License 225 | with respect to some or all of the Covered Software due to statute, judicial 226 | order, or regulation then You must: (a) comply with the terms of this License 227 | to the maximum extent possible; and (b) describe the limitations and the code 228 | they affect. Such description must be placed in a text file included with all 229 | distributions of the Covered Software under this License. Except to the 230 | extent prohibited by statute or regulation, such description must be 231 | sufficiently detailed for a recipient of ordinary skill to be able to 232 | understand it. 233 | 234 | 5. Termination 235 | 236 | 5.1. The rights granted under this License will terminate automatically if You 237 | fail to comply with any of its terms. However, if You become compliant, 238 | then the rights granted under this License from a particular Contributor 239 | are reinstated (a) provisionally, unless and until such Contributor 240 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 241 | if such Contributor fails to notify You of the non-compliance by some 242 | reasonable means prior to 60 days after You have come back into compliance. 243 | Moreover, Your grants from a particular Contributor are reinstated on an 244 | ongoing basis if such Contributor notifies You of the non-compliance by 245 | some reasonable means, this is the first time You have received notice of 246 | non-compliance with this License from such Contributor, and You become 247 | compliant prior to 30 days after Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, counter-claims, 251 | and cross-claims) alleging that a Contributor Version directly or 252 | indirectly infringes any patent, then the rights granted to You by any and 253 | all Contributors for the Covered Software under Section 2.1 of this License 254 | shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 257 | license agreements (excluding distributors and resellers) which have been 258 | validly granted by You or Your distributors under this License prior to 259 | termination shall survive termination. 260 | 261 | 6. Disclaimer of Warranty 262 | 263 | Covered Software is provided under this License on an “as is” basis, without 264 | warranty of any kind, either expressed, implied, or statutory, including, 265 | without limitation, warranties that the Covered Software is free of defects, 266 | merchantable, fit for a particular purpose or non-infringing. The entire 267 | risk as to the quality and performance of the Covered Software is with You. 268 | Should any Covered Software prove defective in any respect, You (not any 269 | Contributor) assume the cost of any necessary servicing, repair, or 270 | correction. This disclaimer of warranty constitutes an essential part of this 271 | License. No use of any Covered Software is authorized under this License 272 | except under this disclaimer. 273 | 274 | 7. Limitation of Liability 275 | 276 | Under no circumstances and under no legal theory, whether tort (including 277 | negligence), contract, or otherwise, shall any Contributor, or anyone who 278 | distributes Covered Software as permitted above, be liable to You for any 279 | direct, indirect, special, incidental, or consequential damages of any 280 | character including, without limitation, damages for lost profits, loss of 281 | goodwill, work stoppage, computer failure or malfunction, or any and all 282 | other commercial damages or losses, even if such party shall have been 283 | informed of the possibility of such damages. This limitation of liability 284 | shall not apply to liability for death or personal injury resulting from such 285 | party’s negligence to the extent applicable law prohibits such limitation. 286 | Some jurisdictions do not allow the exclusion or limitation of incidental or 287 | consequential damages, so this exclusion and limitation may not apply to You. 288 | 289 | 8. Litigation 290 | 291 | Any litigation relating to this License may be brought only in the courts of 292 | a jurisdiction where the defendant maintains its principal place of business 293 | and such litigation shall be governed by laws of that jurisdiction, without 294 | reference to its conflict-of-law provisions. Nothing in this Section shall 295 | prevent a party’s ability to bring cross-claims or counter-claims. 296 | 297 | 9. Miscellaneous 298 | 299 | This License represents the complete agreement concerning the subject matter 300 | hereof. If any provision of this License is held to be unenforceable, such 301 | provision shall be reformed only to the extent necessary to make it 302 | enforceable. Any law or regulation which provides that the language of a 303 | contract shall be construed against the drafter shall not be used to construe 304 | this License against a Contributor. 305 | 306 | 307 | 10. Versions of the License 308 | 309 | 10.1. New Versions 310 | 311 | Mozilla Foundation is the license steward. Except as provided in Section 312 | 10.3, no one other than the license steward has the right to modify or 313 | publish new versions of this License. Each version will be given a 314 | distinguishing version number. 315 | 316 | 10.2. Effect of New Versions 317 | 318 | You may distribute the Covered Software under the terms of the version of 319 | the License under which You originally received the Covered Software, or 320 | under the terms of any subsequent version published by the license 321 | steward. 322 | 323 | 10.3. Modified Versions 324 | 325 | If you create software not governed by this License, and you want to 326 | create a new license for such software, you may create and use a modified 327 | version of this License if you rename the license and remove any 328 | references to the name of the license steward (except to note that such 329 | modified license differs from this License). 330 | 331 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 332 | If You choose to distribute Source Code Form that is Incompatible With 333 | Secondary Licenses under the terms of this version of the License, the 334 | notice described in Exhibit B of this License must be attached. 335 | 336 | Exhibit A - Source Code Form License Notice 337 | 338 | This Source Code Form is subject to the 339 | terms of the Mozilla Public License, v. 340 | 2.0. If a copy of the MPL was not 341 | distributed with this file, You can 342 | obtain one at 343 | http://mozilla.org/MPL/2.0/. 344 | 345 | If it is not possible or desirable to put the notice in a particular file, then 346 | You may include the notice in a location (such as a LICENSE file in a relevant 347 | directory) where a recipient would be likely to look for such a notice. 348 | 349 | You may add additional accurate notices of copyright ownership. 350 | 351 | Exhibit B - “Incompatible With Secondary Licenses” Notice 352 | 353 | This Source Code Form is “Incompatible 354 | With Secondary Licenses”, as defined by 355 | the Mozilla Public License, v. 2.0. 356 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform CIDR Subnets Module 2 | 3 | This is a simple Terraform module for calculating subnet addresses under a 4 | particular CIDR prefix. 5 | 6 | **This module requires Terraform v0.12.10 or later.** 7 | 8 | ```hcl 9 | module "subnet_addrs" { 10 | source = "hashicorp/subnets/cidr" 11 | 12 | base_cidr_block = "10.0.0.0/8" 13 | networks = [ 14 | { 15 | name = "foo" 16 | new_bits = 8 17 | }, 18 | { 19 | name = "bar" 20 | new_bits = 8 21 | }, 22 | { 23 | name = "baz" 24 | new_bits = 4 25 | }, 26 | { 27 | name = "beep" 28 | new_bits = 8 29 | }, 30 | { 31 | name = "boop" 32 | new_bits = 8 33 | }, 34 | ] 35 | } 36 | ``` 37 | 38 | The module assigns consecutive IP address blocks to each of the requested 39 | networks, packing them densely in the address space. The `network_cidr_blocks` 40 | output is then a map from the given `name` strings 41 | to the allocated CIDR prefixes: 42 | 43 | ```hcl 44 | { 45 | foo = "10.0.0.0/16" 46 | bar = "10.1.0.0/16" 47 | baz = "10.16.0.0/12" 48 | beep = "10.32.0.0/16" 49 | boop = "10.33.0.0/16" 50 | } 51 | ``` 52 | 53 | The `new_bits` values are the number of _additional_ address bits to use for 54 | numbering the new networks. Because network `foo` has a `new_bits` of 8, 55 | and the base CIDR block has an existing prefix of 8, its final prefix length 56 | is 8 + 8 = 16. `baz` has a `new_bits` of 4, so its final prefix length is 57 | only 8 + 4 = 12 bits. 58 | 59 | If the order of the given networks is significant, the alternative output 60 | `networks` is a list with an object for each requested network that has 61 | the following attributes: 62 | 63 | * `name` is the same name that was given in the request. 64 | * `new_bits` echoes back the number of new bits given in the request. 65 | * `cidr_block` is the allocated CIDR prefix for the network. 66 | 67 | If you need the CIDR block addresses in order and don't need the names, you 68 | can use `module.subnet_addrs.networks[*].cidr_block` to obtain that 69 | flattened list. 70 | 71 | ## Changing Networks Later 72 | 73 | When initially declaring your network addressing scheme, you can declare your 74 | networks in any order. However, the positions of the networks in the request 75 | list affects the assigned network numbers, so when making changes later it's 76 | important to take care to avoid implicitly renumbering other networks. 77 | 78 | The safest approach is to only add new networks to the end of the list and 79 | to never remove an existing network or or change its `new_bits` value. If 80 | an existing allocation becomes obsolute, you can set its name explicitly to 81 | `null` to skip allocating it a prefix but to retain the space it previously 82 | occupied in the address space: 83 | 84 | ```hcl 85 | module "subnet_addrs" { 86 | source = "hashicorp/subnets/cidr" 87 | 88 | base_cidr_block = "10.0.0.0/8" 89 | networks = [ 90 | { 91 | name = null # formerly "foo", but no longer used 92 | new_bits = 8 93 | }, 94 | { 95 | name = "bar" 96 | new_bits = 8 97 | }, 98 | ] 99 | } 100 | ``` 101 | 102 | In the above example, the `network_cidr_blocks` output would have the 103 | following value: 104 | 105 | ``` 106 | { 107 | bar = "10.1.0.0/16" 108 | } 109 | ``` 110 | 111 | `foo` has been excluded, but its former prefix `10.0.0.0/16` is now skipped 112 | altogether so `bar` retains its allocation of `10.1.0.0/16`. 113 | 114 | Because the `networks` output is a list that preserves the element indices 115 | of the requested networks, it _does_ still include the skipped networks, but 116 | with their `name` and `cidr_blocks` attributes set to null: 117 | 118 | ``` 119 | [ 120 | { 121 | name = null 122 | new_bits = 8 123 | cidr_block = null 124 | }, 125 | { 126 | name = "bar" 127 | new_bits = 8 128 | cidr_block = "10.1.0.0/16" 129 | }, 130 | ] 131 | ``` 132 | 133 | We don't recommend using the `networks` output when networks are skipped in 134 | this way, but if you _do_ need to preserve the indices while excluding the 135 | null items you could use a `for` expression to project the indices into 136 | attributes of the objects: 137 | 138 | ``` 139 | [ 140 | for i, n in module.subnet_addrs.networks : { 141 | index = i 142 | name = n.name 143 | cidr_block = n.cidr_block 144 | } 145 | if n.cidr_block != null 146 | ] 147 | ``` 148 | 149 | Certain edits to existing allocations are possible without affecting 150 | subsequent allocations, as long as you are careful to ensure that the new 151 | allocation occupies the same address space as whatever replaced it. 152 | 153 | For example, it's safe to replace a single allocation anywhere in the 154 | list with a pair of consecutive allocations whose `new_bits` value is one 155 | greater. If you have an allocation with `new_bits` set to 4, you can replace 156 | it with two allocations that have `new_bits` set to 5 as long as those two 157 | new allocations retain their position in the overall list: 158 | 159 | ```hcl 160 | networks = [ 161 | # "foo-1" and "foo-2" replace the former "foo", taking half of the 162 | # former address space each: 10.0.0.0/17 and 10.0.128.0/17, respectively. 163 | { 164 | name = "foo-1" 165 | new_bits = 9 166 | }, 167 | { 168 | name = "foo-2" 169 | new_bits = 9 170 | }, 171 | 172 | # "bar" is still at 10.1.0.0/16 173 | { 174 | name = "bar" 175 | new_bits = 8 176 | }, 177 | ] 178 | ``` 179 | 180 | When making in-place edits to existing networks, be sure to verify that the 181 | result is as you expected using `terraform plan` before applying, to avoid 182 | disruption to already-provisioned networks that you want to keep. 183 | 184 | ## Vendor-specific Examples 185 | 186 | The following sections show how you might use a result from this module to 187 | declare real network subnets in various specific cloud virtual network 188 | systems. 189 | 190 | `module.subnet_addrs` in the following examples represent references to the 191 | declared module. You are free to name the module whatever makes sense in your 192 | context, but the names must agree between the declaration and the references. 193 | 194 | ### AliCloud Virtual Private Cloud 195 | 196 | ```hcl 197 | module "subnet_addrs" { 198 | source = "hashicorp/subnets/cidr" 199 | 200 | base_cidr_block = "10.0.0.0/16" 201 | networks = [ 202 | { 203 | name = "cn-beijing-a" 204 | new_bits = 8 205 | }, 206 | { 207 | name = "cn-beijing-b" 208 | new_bits = 8 209 | }, 210 | ] 211 | } 212 | 213 | resource "alicloud_vpc" "example" { 214 | name = "example" 215 | cidr_block = module.subnet_addrs.base_cidr_block 216 | } 217 | 218 | resource "alicloud_vswitch" "example" { 219 | for_each = module.subnet_addrs.network_cidr_blocks 220 | 221 | vpc_id = alicloud_vpc.example.id 222 | availability_zone = each.key 223 | cidr_block = each.value 224 | } 225 | ``` 226 | 227 | ### Amazon Virtual Private Cloud 228 | 229 | ```hcl 230 | module "subnet_addrs" { 231 | source = "hashicorp/subnets/cidr" 232 | 233 | base_cidr_block = "10.0.0.0/16" 234 | networks = [ 235 | { 236 | name = "us-west-2a" 237 | new_bits = 8 238 | }, 239 | { 240 | name = "us-west-2b" 241 | new_bits = 8 242 | }, 243 | ] 244 | } 245 | 246 | resource "aws_vpc" "example" { 247 | cidr_block = module.subnet_addrs.base_cidr_block 248 | } 249 | 250 | resource "aws_subnet" "example" { 251 | for_each = module.subnet_addrs.network_cidr_blocks 252 | 253 | vpc_id = aws_vpc.example.id 254 | availability_zone = each.key 255 | cidr_block = each.value 256 | } 257 | ``` 258 | 259 | ### Microsoft Azure Virtual Networks 260 | 261 | ```hcl 262 | module "subnet_addrs" { 263 | source = "hashicorp/subnets/cidr" 264 | 265 | base_cidr_block = "10.0.0.0/16" 266 | networks = [ 267 | { 268 | name = "foo" 269 | new_bits = 8 270 | }, 271 | { 272 | name = "bar" 273 | new_bits = 8 274 | }, 275 | ] 276 | } 277 | 278 | resource "azurerm_resource_group" "example" { 279 | name = "example" 280 | location = "West US" 281 | } 282 | 283 | resource "azurerm_virtual_network" "example" { 284 | resource_group_name = azurerm_resource_group.example.name 285 | location = azurerm_resource_group.example.location 286 | 287 | name = "example" 288 | address_space = [module.subnet_addrs.base_cidr_block] 289 | 290 | dynamic "subnet" { 291 | for_each = module.subnet_addrs.network_cidr_blocks 292 | content { 293 | name = subnet.key 294 | address_prefix = subnet.value 295 | } 296 | } 297 | } 298 | ``` 299 | 300 | ### Google Cloud Platform 301 | 302 | ```hcl 303 | module "subnet_addrs" { 304 | source = "hashicorp/subnets/cidr" 305 | 306 | base_cidr_block = "10.0.0.0/16" 307 | networks = [ 308 | { 309 | name = "us-central" 310 | new_bits = 8 311 | }, 312 | { 313 | name = "us-west2" 314 | new_bits = 8 315 | }, 316 | ] 317 | } 318 | 319 | resource "google_compute_network" "example" { 320 | name = "example" 321 | auto_create_subnetworks = false 322 | } 323 | 324 | resource "google_compute_subnetwork" "example" { 325 | for_each = module.subnet_addrs.network_cidr_blocks 326 | 327 | network = google_compute_network.example.self_link 328 | name = each.key 329 | ip_cidr_range = each.value 330 | region = each.key 331 | } 332 | ``` 333 | 334 | ## Network Allocations in a CSV file 335 | 336 | It may be convenient to represent your table of network definitions in a CSV 337 | file rather than writing them out as object values directly in the Terraform 338 | configuration, because CSV allows for a denser representation of such a table 339 | that might be easier to quickly scan. 340 | 341 | You can create a CSV file `subnets.csv` containing the following and place 342 | it inside your own calling module: 343 | 344 | ``` 345 | "name","newbits" 346 | "foo","8" 347 | "","8" 348 | "baz","8" 349 | ``` 350 | 351 | Since CSV cannot represent null, we'll use the empty string to represent an 352 | obsolete network that must still have reserved address space. When editing 353 | the CSV file after the resulting allocations have been used, be sure to keep 354 | in mind the restrictions under 355 | [_Changing Networks Later_](#changing-networks-later) above. 356 | 357 | We can use Terraform's `csvdecode` function to parse this file and pass the 358 | result into the CIDR Subnets Module: 359 | 360 | ```hcl 361 | module "subnet_addrs" { 362 | source = "hashicorp/subnets/cidr" 363 | 364 | base_cidr_block = "10.0.0.0/16" 365 | networks = [ 366 | for r in csvdecode(file("${path.module}/subnets.csv")) : { 367 | name = r.name != "" ? r.name : null 368 | new_bits = tonumber(r.new_bits) 369 | } 370 | ] 371 | } 372 | ``` 373 | 374 | ## Contributing 375 | 376 | This module is intentionally simple and is considered feature complete. We do 377 | not plan to add any additional functionality and we're unlikely to accept 378 | pull requests proposing new functionality. 379 | 380 | If you find errors in the documentation above, please open an issue or a 381 | pull request! 382 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | addrs_by_idx = cidrsubnets(var.base_cidr_block, var.networks[*].new_bits...) 3 | addrs_by_name = { for i, n in var.networks : n.name => local.addrs_by_idx[i] if n.name != null } 4 | network_objs = [for i, n in var.networks : { 5 | name = n.name 6 | new_bits = n.new_bits 7 | cidr_block = n.name != null ? local.addrs_by_idx[i] : tostring(null) 8 | }] 9 | } 10 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "network_cidr_blocks" { 2 | value = tomap(local.addrs_by_name) 3 | description = "A map from network names to allocated address prefixes in CIDR notation." 4 | } 5 | 6 | output "networks" { 7 | value = tolist(local.network_objs) 8 | description = "A list of objects corresponding to each of the objects in the input variable 'networks', each extended with a new attribute 'cidr_block' giving the network's allocated address prefix." 9 | } 10 | 11 | output "base_cidr_block" { 12 | value = var.base_cidr_block 13 | description = "Echoes back the base_cidr_block input variable value, for convenience if passing the result of this module elsewhere as an object." 14 | } 15 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfstate* 2 | .terraform/* 3 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Terraform CIDR Subnets Module test cases 2 | 3 | This directory is a Terraform configuration that you can apply to verify the 4 | behavior of this module. 5 | 6 | It uses 7 | [a third-party provider called `testing`](https://github.com/apparentlymart/terraform-provider-testing) which must be manually installed in one of 8 | Terraform's plugin search directories before running `terraform init`. 9 | 10 | With the configuration successfully initialized, run `terraform apply` to see 11 | if it produces any errors. If the apply completes successfully then the tests 12 | have passed. 13 | -------------------------------------------------------------------------------- /test/null_name.tf: -------------------------------------------------------------------------------- 1 | module "null_name" { 2 | source = "../" 3 | 4 | base_cidr_block = "10.0.0.0/8" 5 | networks = [ 6 | { 7 | name = null 8 | new_bits = 8 9 | }, 10 | { 11 | name = "bar" 12 | new_bits = 8 13 | }, 14 | { 15 | name = null 16 | new_bits = 8 17 | }, 18 | ] 19 | } 20 | 21 | data "testing_assertions" "null_name" { 22 | subject = "Call with null names" 23 | 24 | equal "network_cidr_blocks" { 25 | statement = "has the expected network_cidr_blocks" 26 | 27 | got = module.null_name.network_cidr_blocks 28 | want = tomap({ 29 | # the first network is skipped because its name is null, but it 30 | # still occupies address space. 31 | # The last network is also skipped, but it does not affect any 32 | # other addresses because there are no further networks after it. 33 | bar = "10.1.0.0/16" 34 | }) 35 | } 36 | 37 | equal "networks" { 38 | statement = "has the expected networks" 39 | 40 | got = module.null_name.networks 41 | want = tolist([ 42 | { 43 | cidr_block = tostring(null) 44 | name = tostring(null) 45 | new_bits = 8 46 | }, 47 | { 48 | cidr_block = "10.1.0.0/16" 49 | name = "bar" 50 | new_bits = 8 51 | }, 52 | { 53 | cidr_block = tostring(null) 54 | name = tostring(null) 55 | new_bits = 8 56 | }, 57 | ]) 58 | } 59 | } 60 | 61 | output "null_name" { 62 | value = module.null_name 63 | } 64 | -------------------------------------------------------------------------------- /test/simple.tf: -------------------------------------------------------------------------------- 1 | module "simple" { 2 | source = "../" 3 | 4 | base_cidr_block = "10.0.0.0/8" 5 | networks = [ 6 | { 7 | name = "foo" 8 | new_bits = 8 9 | }, 10 | { 11 | name = "bar" 12 | new_bits = 8 13 | }, 14 | { 15 | name = "baz" 16 | new_bits = 4 17 | }, 18 | { 19 | name = "beep" 20 | new_bits = 8 21 | }, 22 | { 23 | name = "boop" 24 | new_bits = 8 25 | }, 26 | ] 27 | } 28 | 29 | data "testing_assertions" "simple" { 30 | subject = "Simple call" 31 | 32 | equal "network_cidr_blocks" { 33 | statement = "has the expected network_cidr_blocks" 34 | 35 | got = module.simple.network_cidr_blocks 36 | want = tomap({ 37 | foo = "10.0.0.0/16" 38 | bar = "10.1.0.0/16" 39 | baz = "10.16.0.0/12" 40 | beep = "10.32.0.0/16" 41 | boop = "10.33.0.0/16" 42 | }) 43 | } 44 | 45 | equal "networks" { 46 | statement = "has the expected networks" 47 | 48 | got = module.simple.networks 49 | want = tolist([ 50 | { 51 | cidr_block = "10.0.0.0/16" 52 | name = "foo" 53 | new_bits = 8 54 | }, 55 | { 56 | cidr_block = "10.1.0.0/16" 57 | name = "bar" 58 | new_bits = 8 59 | }, 60 | { 61 | cidr_block = "10.16.0.0/12" 62 | name = "baz" 63 | new_bits = 4 64 | }, 65 | { 66 | cidr_block = "10.32.0.0/16" 67 | name = "beep" 68 | new_bits = 8 69 | }, 70 | { 71 | cidr_block = "10.33.0.0/16" 72 | name = "boop" 73 | new_bits = 8 74 | }, 75 | ]) 76 | } 77 | } 78 | 79 | output "simple" { 80 | value = module.simple 81 | } 82 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "base_cidr_block" { 3 | type = string 4 | description = "A network address prefix in CIDR notation that all of the requested subnetwork prefixes will be allocated within." 5 | } 6 | 7 | variable "networks" { 8 | type = list(object({ 9 | name = string 10 | new_bits = number 11 | })) 12 | description = "A list of objects describing requested subnetwork prefixes. new_bits is the number of additional network prefix bits to add, in addition to the existing prefix on base_cidr_block." 13 | } 14 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.12.10" 3 | } 4 | --------------------------------------------------------------------------------