github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/cidr_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package funcs 7 8 import ( 9 "fmt" 10 "testing" 11 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 func TestCidrHost(t *testing.T) { 16 tests := []struct { 17 Prefix cty.Value 18 Hostnum cty.Value 19 Want cty.Value 20 Err bool 21 }{ 22 { 23 cty.StringVal("192.168.1.0/24"), 24 cty.NumberIntVal(5), 25 cty.StringVal("192.168.1.5"), 26 false, 27 }, 28 { 29 cty.StringVal("192.168.1.0/24"), 30 cty.NumberIntVal(-5), 31 cty.StringVal("192.168.1.251"), 32 false, 33 }, 34 { 35 cty.StringVal("192.168.1.0/24"), 36 cty.NumberIntVal(-256), 37 cty.StringVal("192.168.1.0"), 38 false, 39 }, 40 { 41 // We inadvertently inherited a pre-Go1.17 standard library quirk 42 // if parsing zero-prefix parts as decimal rather than octal. 43 // Go 1.17 resolved that quirk by making zero-prefix invalid, but 44 // we've preserved our existing behavior for backward compatibility, 45 // on the grounds that these functions are for generating addresses 46 // rather than validating or processing them. We do always generate 47 // a canonical result regardless of the input, though. 48 cty.StringVal("010.001.0.0/24"), 49 cty.NumberIntVal(6), 50 cty.StringVal("10.1.0.6"), 51 false, 52 }, 53 { 54 cty.StringVal("192.168.1.0/30"), 55 cty.NumberIntVal(255), 56 cty.UnknownVal(cty.String), 57 true, // 255 doesn't fit in two bits 58 }, 59 { 60 cty.StringVal("192.168.1.0/30"), 61 cty.NumberIntVal(-255), 62 cty.UnknownVal(cty.String), 63 true, // 255 doesn't fit in two bits 64 }, 65 { 66 cty.StringVal("not-a-cidr"), 67 cty.NumberIntVal(6), 68 cty.UnknownVal(cty.String), 69 true, // not a valid CIDR mask 70 }, 71 { 72 cty.StringVal("10.256.0.0/8"), 73 cty.NumberIntVal(6), 74 cty.UnknownVal(cty.String), 75 true, // can't have an octet >255 76 }, 77 { // fractions are Not Ok 78 cty.StringVal("10.256.0.0/8"), 79 cty.NumberFloatVal(.75), 80 cty.UnknownVal(cty.String), 81 true, 82 }, 83 } 84 85 for _, test := range tests { 86 t.Run(fmt.Sprintf("cidrhost(%#v, %#v)", test.Prefix, test.Hostnum), func(t *testing.T) { 87 got, err := CidrHost(test.Prefix, test.Hostnum) 88 89 if test.Err { 90 if err == nil { 91 t.Fatal("succeeded; want error") 92 } 93 return 94 } else if err != nil { 95 t.Fatalf("unexpected error: %s", err) 96 } 97 98 if !got.RawEquals(test.Want) { 99 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 100 } 101 }) 102 } 103 } 104 105 func TestCidrNetmask(t *testing.T) { 106 tests := []struct { 107 Prefix cty.Value 108 Want cty.Value 109 Err bool 110 }{ 111 { 112 cty.StringVal("192.168.1.0/24"), 113 cty.StringVal("255.255.255.0"), 114 false, 115 }, 116 { 117 cty.StringVal("192.168.1.0/32"), 118 cty.StringVal("255.255.255.255"), 119 false, 120 }, 121 { 122 cty.StringVal("0.0.0.0/0"), 123 cty.StringVal("0.0.0.0"), 124 false, 125 }, 126 { 127 // We inadvertently inherited a pre-Go1.17 standard library quirk 128 // if parsing zero-prefix parts as decimal rather than octal. 129 // Go 1.17 resolved that quirk by making zero-prefix invalid, but 130 // we've preserved our existing behavior for backward compatibility, 131 // on the grounds that these functions are for generating addresses 132 // rather than validating or processing them. 133 cty.StringVal("010.001.0.0/24"), 134 cty.StringVal("255.255.255.0"), 135 false, 136 }, 137 { 138 cty.StringVal("not-a-cidr"), 139 cty.UnknownVal(cty.String), 140 true, // not a valid CIDR mask 141 }, 142 { 143 cty.StringVal("110.256.0.0/8"), 144 cty.UnknownVal(cty.String), 145 true, // can't have an octet >255 146 }, 147 { 148 cty.StringVal("1::/64"), 149 cty.UnknownVal(cty.String), 150 true, // IPv6 is invalid 151 }, 152 } 153 154 for _, test := range tests { 155 t.Run(fmt.Sprintf("cidrnetmask(%#v)", test.Prefix), func(t *testing.T) { 156 got, err := CidrNetmask(test.Prefix) 157 158 if test.Err { 159 if err == nil { 160 t.Fatal("succeeded; want error") 161 } 162 return 163 } else if err != nil { 164 t.Fatalf("unexpected error: %s", err) 165 } 166 167 if !got.RawEquals(test.Want) { 168 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 169 } 170 }) 171 } 172 } 173 174 func TestCidrSubnet(t *testing.T) { 175 tests := []struct { 176 Prefix cty.Value 177 Newbits cty.Value 178 Netnum cty.Value 179 Want cty.Value 180 Err bool 181 }{ 182 { 183 cty.StringVal("192.168.2.0/20"), 184 cty.NumberIntVal(4), 185 cty.NumberIntVal(6), 186 cty.StringVal("192.168.6.0/24"), 187 false, 188 }, 189 { 190 cty.StringVal("fe80::/48"), 191 cty.NumberIntVal(16), 192 cty.NumberIntVal(6), 193 cty.StringVal("fe80:0:0:6::/64"), 194 false, 195 }, 196 { // IPv4 address encoded in IPv6 syntax gets normalized 197 cty.StringVal("::ffff:192.168.0.0/112"), 198 cty.NumberIntVal(8), 199 cty.NumberIntVal(6), 200 cty.StringVal("192.168.6.0/24"), 201 false, 202 }, 203 { 204 cty.StringVal("fe80::/48"), 205 cty.NumberIntVal(33), 206 cty.NumberIntVal(6), 207 cty.StringVal("fe80::3:0:0:0/81"), 208 false, 209 }, 210 { 211 // We inadvertently inherited a pre-Go1.17 standard library quirk 212 // if parsing zero-prefix parts as decimal rather than octal. 213 // Go 1.17 resolved that quirk by making zero-prefix invalid, but 214 // we've preserved our existing behavior for backward compatibility, 215 // on the grounds that these functions are for generating addresses 216 // rather than validating or processing them. We do always generate 217 // a canonical result regardless of the input, though. 218 cty.StringVal("010.001.0.0/24"), 219 cty.NumberIntVal(4), 220 cty.NumberIntVal(1), 221 cty.StringVal("10.1.0.16/28"), 222 false, 223 }, 224 { // not enough bits left 225 cty.StringVal("192.168.0.0/30"), 226 cty.NumberIntVal(4), 227 cty.NumberIntVal(6), 228 cty.UnknownVal(cty.String), 229 true, 230 }, 231 { // can't encode 16 in 2 bits 232 cty.StringVal("192.168.0.0/168"), 233 cty.NumberIntVal(2), 234 cty.NumberIntVal(16), 235 cty.UnknownVal(cty.String), 236 true, 237 }, 238 { // not a valid CIDR mask 239 cty.StringVal("not-a-cidr"), 240 cty.NumberIntVal(4), 241 cty.NumberIntVal(6), 242 cty.UnknownVal(cty.String), 243 true, 244 }, 245 { // can't have an octet >255 246 cty.StringVal("10.256.0.0/8"), 247 cty.NumberIntVal(4), 248 cty.NumberIntVal(6), 249 cty.UnknownVal(cty.String), 250 true, 251 }, 252 { // fractions are Not Ok 253 cty.StringVal("10.256.0.0/8"), 254 cty.NumberFloatVal(2.0 / 3.0), 255 cty.NumberFloatVal(.75), 256 cty.UnknownVal(cty.String), 257 true, 258 }, 259 } 260 261 for _, test := range tests { 262 t.Run(fmt.Sprintf("cidrsubnet(%#v, %#v, %#v)", test.Prefix, test.Newbits, test.Netnum), func(t *testing.T) { 263 got, err := CidrSubnet(test.Prefix, test.Newbits, test.Netnum) 264 265 if test.Err { 266 if err == nil { 267 t.Fatal("succeeded; want error") 268 } 269 return 270 } else if err != nil { 271 t.Fatalf("unexpected error: %s", err) 272 } 273 274 if !got.RawEquals(test.Want) { 275 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 276 } 277 }) 278 } 279 } 280 func TestCidrSubnets(t *testing.T) { 281 tests := []struct { 282 Prefix cty.Value 283 Newbits []cty.Value 284 Want cty.Value 285 Err string 286 }{ 287 { 288 cty.StringVal("10.0.0.0/21"), 289 []cty.Value{ 290 cty.NumberIntVal(3), 291 cty.NumberIntVal(3), 292 cty.NumberIntVal(3), 293 cty.NumberIntVal(4), 294 cty.NumberIntVal(4), 295 cty.NumberIntVal(4), 296 cty.NumberIntVal(7), 297 cty.NumberIntVal(7), 298 cty.NumberIntVal(7), 299 }, 300 cty.ListVal([]cty.Value{ 301 cty.StringVal("10.0.0.0/24"), 302 cty.StringVal("10.0.1.0/24"), 303 cty.StringVal("10.0.2.0/24"), 304 cty.StringVal("10.0.3.0/25"), 305 cty.StringVal("10.0.3.128/25"), 306 cty.StringVal("10.0.4.0/25"), 307 cty.StringVal("10.0.4.128/28"), 308 cty.StringVal("10.0.4.144/28"), 309 cty.StringVal("10.0.4.160/28"), 310 }), 311 ``, 312 }, 313 { 314 // We inadvertently inherited a pre-Go1.17 standard library quirk 315 // if parsing zero-prefix parts as decimal rather than octal. 316 // Go 1.17 resolved that quirk by making zero-prefix invalid, but 317 // we've preserved our existing behavior for backward compatibility, 318 // on the grounds that these functions are for generating addresses 319 // rather than validating or processing them. We do always generate 320 // a canonical result regardless of the input, though. 321 cty.StringVal("010.0.0.0/21"), 322 []cty.Value{ 323 cty.NumberIntVal(3), 324 }, 325 cty.ListVal([]cty.Value{ 326 cty.StringVal("10.0.0.0/24"), 327 }), 328 ``, 329 }, 330 { 331 cty.StringVal("10.0.0.0/30"), 332 []cty.Value{ 333 cty.NumberIntVal(1), 334 cty.NumberIntVal(3), 335 }, 336 cty.UnknownVal(cty.List(cty.String)), 337 `would extend prefix to 33 bits, which is too long for an IPv4 address`, 338 }, 339 { 340 cty.StringVal("10.0.0.0/8"), 341 []cty.Value{ 342 cty.NumberIntVal(1), 343 cty.NumberIntVal(1), 344 cty.NumberIntVal(1), 345 }, 346 cty.UnknownVal(cty.List(cty.String)), 347 `not enough remaining address space for a subnet with a prefix of 9 bits after 10.128.0.0/9`, 348 }, 349 { 350 cty.StringVal("10.0.0.0/8"), 351 []cty.Value{ 352 cty.NumberIntVal(1), 353 cty.NumberIntVal(0), 354 }, 355 cty.UnknownVal(cty.List(cty.String)), 356 `must extend prefix by at least one bit`, 357 }, 358 { 359 cty.StringVal("10.0.0.0/8"), 360 []cty.Value{ 361 cty.NumberIntVal(1), 362 cty.NumberIntVal(-1), 363 }, 364 cty.UnknownVal(cty.List(cty.String)), 365 `must extend prefix by at least one bit`, 366 }, 367 { 368 cty.StringVal("fe80::/48"), 369 []cty.Value{ 370 cty.NumberIntVal(1), 371 cty.NumberIntVal(33), 372 }, 373 cty.UnknownVal(cty.List(cty.String)), 374 `may not extend prefix by more than 32 bits`, 375 }, 376 } 377 378 for _, test := range tests { 379 t.Run(fmt.Sprintf("cidrsubnets(%#v, %#v)", test.Prefix, test.Newbits), func(t *testing.T) { 380 got, err := CidrSubnets(test.Prefix, test.Newbits...) 381 wantErr := test.Err != "" 382 383 if wantErr { 384 if err == nil { 385 t.Fatal("succeeded; want error") 386 } 387 if err.Error() != test.Err { 388 t.Fatalf("wrong error\ngot: %s\nwant: %s", err.Error(), test.Err) 389 } 390 return 391 } else if err != nil { 392 t.Fatalf("unexpected error: %s", err) 393 } 394 395 if !got.RawEquals(test.Want) { 396 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 397 } 398 }) 399 } 400 } 401 402 func TestCidrContains(t *testing.T) { 403 noError := func(err error) bool { return err == nil } 404 405 tests := []struct { 406 Prefix cty.Value 407 Address cty.Value 408 Want cty.Value 409 ErrFn func(error) bool 410 }{ 411 { 412 // IPv4, contained (IP). 413 cty.StringVal("192.168.2.0/20"), 414 cty.StringVal("192.168.2.1"), 415 cty.True, 416 noError, 417 }, 418 { 419 // IPv4, contained (CIDR). 420 cty.StringVal("192.168.2.0/20"), 421 cty.StringVal("192.168.2.0/22"), 422 cty.True, 423 noError, 424 }, 425 { 426 // IPv4, not contained. 427 cty.StringVal("192.168.2.0/20"), 428 cty.StringVal("192.126.2.1"), 429 cty.False, 430 noError, 431 }, 432 { 433 // IPv4, not contained (CIDR). 434 cty.StringVal("192.168.2.0/20"), 435 cty.StringVal("192.126.2.0/18"), 436 cty.False, 437 noError, 438 }, 439 { 440 // IPv6, contained. 441 cty.StringVal("fe80::/48"), 442 cty.StringVal("fe80::1"), 443 cty.True, 444 noError, 445 }, 446 { 447 // IPv6, not contained. 448 cty.StringVal("fe80::/48"), 449 cty.StringVal("fe81::1"), 450 cty.False, 451 noError, 452 }, 453 { 454 // Address family mismatch: IPv4 containing_prefix, IPv6 contained_ip_or_prefix (IP). 455 cty.StringVal("192.168.2.0/20"), 456 cty.StringVal("fe80::1"), 457 cty.NilVal, 458 func(err error) bool { 459 return err != nil && err.Error() == "address family mismatch: 192.168.2.0/20 vs. fe80::1" 460 }, 461 }, 462 { 463 // Address family mismatch: IPv4 containing_prefix, IPv6 contained_ip_or_prefix (prefix). 464 cty.StringVal("192.168.2.0/20"), 465 cty.StringVal("fe80::/24"), 466 cty.NilVal, 467 func(err error) bool { 468 return err != nil && err.Error() == "address family mismatch: 192.168.2.0/20 vs. fe80::/24" 469 }, 470 }, 471 { 472 // Address family mismatch: IPv6 containing_prefix, IPv4 contained_ip_or_prefix (IP). 473 cty.StringVal("fe80::/48"), 474 cty.StringVal("192.168.2.1"), 475 cty.NilVal, 476 func(err error) bool { 477 return err != nil && err.Error() == "address family mismatch: fe80::/48 vs. 192.168.2.1" 478 }, 479 }, 480 { 481 // Address family mismatch: IPv6 containing_prefix, IPv4 contained_ip_or_prefix (prefix). 482 cty.StringVal("fe80::/48"), 483 cty.StringVal("192.168.2.0/20"), 484 cty.NilVal, 485 func(err error) bool { 486 return err != nil && err.Error() == "address family mismatch: fe80::/48 vs. 192.168.2.0/20" 487 }, 488 }, 489 { 490 // Input error: invalid CIDR address. 491 cty.StringVal("not-a-cidr"), 492 cty.StringVal("192.168.2.1"), 493 cty.NilVal, 494 func(err error) bool { 495 return err != nil && err.Error() == "invalid CIDR address: not-a-cidr" 496 }, 497 }, 498 { 499 // Input error: invalid IP address. 500 cty.StringVal("192.168.2.0/20"), 501 cty.StringVal("not-an-address"), 502 cty.NilVal, 503 func(err error) bool { 504 return err != nil && err.Error() == "invalid IP address or prefix: not-an-address" 505 }, 506 }, 507 } 508 509 for _, test := range tests { 510 t.Run(fmt.Sprintf("cidrcontains(%#v, %#v)", test.Prefix, test.Address), func(t *testing.T) { 511 got, err := CidrContains(test.Prefix, test.Address) 512 513 if !test.ErrFn(err) { 514 t.Errorf("unexpected error: %v", err) 515 } 516 517 if !got.RawEquals(test.Want) { 518 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 519 } 520 }) 521 } 522 }