github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/funcs/cidr_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package funcs
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  func TestCidrHost(t *testing.T) {
    14  	tests := []struct {
    15  		Prefix  cty.Value
    16  		Hostnum cty.Value
    17  		Want    cty.Value
    18  		Err     bool
    19  	}{
    20  		{
    21  			cty.StringVal("192.168.1.0/24"),
    22  			cty.NumberIntVal(5),
    23  			cty.StringVal("192.168.1.5"),
    24  			false,
    25  		},
    26  		{
    27  			cty.StringVal("192.168.1.0/24"),
    28  			cty.NumberIntVal(-5),
    29  			cty.StringVal("192.168.1.251"),
    30  			false,
    31  		},
    32  		{
    33  			cty.StringVal("192.168.1.0/24"),
    34  			cty.NumberIntVal(-256),
    35  			cty.StringVal("192.168.1.0"),
    36  			false,
    37  		},
    38  		{
    39  			// We inadvertently inherited a pre-Go1.17 standard library quirk
    40  			// if parsing zero-prefix parts as decimal rather than octal.
    41  			// Go 1.17 resolved that quirk by making zero-prefix invalid, but
    42  			// we've preserved our existing behavior for backward compatibility,
    43  			// on the grounds that these functions are for generating addresses
    44  			// rather than validating or processing them. We do always generate
    45  			// a canonical result regardless of the input, though.
    46  			cty.StringVal("010.001.0.0/24"),
    47  			cty.NumberIntVal(6),
    48  			cty.StringVal("10.1.0.6"),
    49  			false,
    50  		},
    51  		{
    52  			cty.StringVal("192.168.1.0/30"),
    53  			cty.NumberIntVal(255),
    54  			cty.UnknownVal(cty.String),
    55  			true, // 255 doesn't fit in two bits
    56  		},
    57  		{
    58  			cty.StringVal("192.168.1.0/30"),
    59  			cty.NumberIntVal(-255),
    60  			cty.UnknownVal(cty.String),
    61  			true, // 255 doesn't fit in two bits
    62  		},
    63  		{
    64  			cty.StringVal("not-a-cidr"),
    65  			cty.NumberIntVal(6),
    66  			cty.UnknownVal(cty.String),
    67  			true, // not a valid CIDR mask
    68  		},
    69  		{
    70  			cty.StringVal("10.256.0.0/8"),
    71  			cty.NumberIntVal(6),
    72  			cty.UnknownVal(cty.String),
    73  			true, // can't have an octet >255
    74  		},
    75  		{ // fractions are Not Ok
    76  			cty.StringVal("10.256.0.0/8"),
    77  			cty.NumberFloatVal(.75),
    78  			cty.UnknownVal(cty.String),
    79  			true,
    80  		},
    81  	}
    82  
    83  	for _, test := range tests {
    84  		t.Run(fmt.Sprintf("cidrhost(%#v, %#v)", test.Prefix, test.Hostnum), func(t *testing.T) {
    85  			got, err := CidrHost(test.Prefix, test.Hostnum)
    86  
    87  			if test.Err {
    88  				if err == nil {
    89  					t.Fatal("succeeded; want error")
    90  				}
    91  				return
    92  			} else if err != nil {
    93  				t.Fatalf("unexpected error: %s", err)
    94  			}
    95  
    96  			if !got.RawEquals(test.Want) {
    97  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
    98  			}
    99  		})
   100  	}
   101  }
   102  
   103  func TestCidrNetmask(t *testing.T) {
   104  	tests := []struct {
   105  		Prefix cty.Value
   106  		Want   cty.Value
   107  		Err    bool
   108  	}{
   109  		{
   110  			cty.StringVal("192.168.1.0/24"),
   111  			cty.StringVal("255.255.255.0"),
   112  			false,
   113  		},
   114  		{
   115  			cty.StringVal("192.168.1.0/32"),
   116  			cty.StringVal("255.255.255.255"),
   117  			false,
   118  		},
   119  		{
   120  			cty.StringVal("0.0.0.0/0"),
   121  			cty.StringVal("0.0.0.0"),
   122  			false,
   123  		},
   124  		{
   125  			// We inadvertently inherited a pre-Go1.17 standard library quirk
   126  			// if parsing zero-prefix parts as decimal rather than octal.
   127  			// Go 1.17 resolved that quirk by making zero-prefix invalid, but
   128  			// we've preserved our existing behavior for backward compatibility,
   129  			// on the grounds that these functions are for generating addresses
   130  			// rather than validating or processing them.
   131  			cty.StringVal("010.001.0.0/24"),
   132  			cty.StringVal("255.255.255.0"),
   133  			false,
   134  		},
   135  		{
   136  			cty.StringVal("not-a-cidr"),
   137  			cty.UnknownVal(cty.String),
   138  			true, // not a valid CIDR mask
   139  		},
   140  		{
   141  			cty.StringVal("110.256.0.0/8"),
   142  			cty.UnknownVal(cty.String),
   143  			true, // can't have an octet >255
   144  		},
   145  		{
   146  			cty.StringVal("1::/64"),
   147  			cty.UnknownVal(cty.String),
   148  			true, // IPv6 is invalid
   149  		},
   150  	}
   151  
   152  	for _, test := range tests {
   153  		t.Run(fmt.Sprintf("cidrnetmask(%#v)", test.Prefix), func(t *testing.T) {
   154  			got, err := CidrNetmask(test.Prefix)
   155  
   156  			if test.Err {
   157  				if err == nil {
   158  					t.Fatal("succeeded; want error")
   159  				}
   160  				return
   161  			} else if err != nil {
   162  				t.Fatalf("unexpected error: %s", err)
   163  			}
   164  
   165  			if !got.RawEquals(test.Want) {
   166  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   167  			}
   168  		})
   169  	}
   170  }
   171  
   172  func TestCidrSubnet(t *testing.T) {
   173  	tests := []struct {
   174  		Prefix  cty.Value
   175  		Newbits cty.Value
   176  		Netnum  cty.Value
   177  		Want    cty.Value
   178  		Err     bool
   179  	}{
   180  		{
   181  			cty.StringVal("192.168.2.0/20"),
   182  			cty.NumberIntVal(4),
   183  			cty.NumberIntVal(6),
   184  			cty.StringVal("192.168.6.0/24"),
   185  			false,
   186  		},
   187  		{
   188  			cty.StringVal("fe80::/48"),
   189  			cty.NumberIntVal(16),
   190  			cty.NumberIntVal(6),
   191  			cty.StringVal("fe80:0:0:6::/64"),
   192  			false,
   193  		},
   194  		{ // IPv4 address encoded in IPv6 syntax gets normalized
   195  			cty.StringVal("::ffff:192.168.0.0/112"),
   196  			cty.NumberIntVal(8),
   197  			cty.NumberIntVal(6),
   198  			cty.StringVal("192.168.6.0/24"),
   199  			false,
   200  		},
   201  		{
   202  			cty.StringVal("fe80::/48"),
   203  			cty.NumberIntVal(33),
   204  			cty.NumberIntVal(6),
   205  			cty.StringVal("fe80::3:0:0:0/81"),
   206  			false,
   207  		},
   208  		{
   209  			// We inadvertently inherited a pre-Go1.17 standard library quirk
   210  			// if parsing zero-prefix parts as decimal rather than octal.
   211  			// Go 1.17 resolved that quirk by making zero-prefix invalid, but
   212  			// we've preserved our existing behavior for backward compatibility,
   213  			// on the grounds that these functions are for generating addresses
   214  			// rather than validating or processing them. We do always generate
   215  			// a canonical result regardless of the input, though.
   216  			cty.StringVal("010.001.0.0/24"),
   217  			cty.NumberIntVal(4),
   218  			cty.NumberIntVal(1),
   219  			cty.StringVal("10.1.0.16/28"),
   220  			false,
   221  		},
   222  		{ // not enough bits left
   223  			cty.StringVal("192.168.0.0/30"),
   224  			cty.NumberIntVal(4),
   225  			cty.NumberIntVal(6),
   226  			cty.UnknownVal(cty.String),
   227  			true,
   228  		},
   229  		{ // can't encode 16 in 2 bits
   230  			cty.StringVal("192.168.0.0/168"),
   231  			cty.NumberIntVal(2),
   232  			cty.NumberIntVal(16),
   233  			cty.UnknownVal(cty.String),
   234  			true,
   235  		},
   236  		{ // not a valid CIDR mask
   237  			cty.StringVal("not-a-cidr"),
   238  			cty.NumberIntVal(4),
   239  			cty.NumberIntVal(6),
   240  			cty.UnknownVal(cty.String),
   241  			true,
   242  		},
   243  		{ // can't have an octet >255
   244  			cty.StringVal("10.256.0.0/8"),
   245  			cty.NumberIntVal(4),
   246  			cty.NumberIntVal(6),
   247  			cty.UnknownVal(cty.String),
   248  			true,
   249  		},
   250  		{ // fractions are Not Ok
   251  			cty.StringVal("10.256.0.0/8"),
   252  			cty.NumberFloatVal(2.0 / 3.0),
   253  			cty.NumberFloatVal(.75),
   254  			cty.UnknownVal(cty.String),
   255  			true,
   256  		},
   257  	}
   258  
   259  	for _, test := range tests {
   260  		t.Run(fmt.Sprintf("cidrsubnet(%#v, %#v, %#v)", test.Prefix, test.Newbits, test.Netnum), func(t *testing.T) {
   261  			got, err := CidrSubnet(test.Prefix, test.Newbits, test.Netnum)
   262  
   263  			if test.Err {
   264  				if err == nil {
   265  					t.Fatal("succeeded; want error")
   266  				}
   267  				return
   268  			} else if err != nil {
   269  				t.Fatalf("unexpected error: %s", err)
   270  			}
   271  
   272  			if !got.RawEquals(test.Want) {
   273  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   274  			}
   275  		})
   276  	}
   277  }
   278  func TestCidrSubnets(t *testing.T) {
   279  	tests := []struct {
   280  		Prefix  cty.Value
   281  		Newbits []cty.Value
   282  		Want    cty.Value
   283  		Err     string
   284  	}{
   285  		{
   286  			cty.StringVal("10.0.0.0/21"),
   287  			[]cty.Value{
   288  				cty.NumberIntVal(3),
   289  				cty.NumberIntVal(3),
   290  				cty.NumberIntVal(3),
   291  				cty.NumberIntVal(4),
   292  				cty.NumberIntVal(4),
   293  				cty.NumberIntVal(4),
   294  				cty.NumberIntVal(7),
   295  				cty.NumberIntVal(7),
   296  				cty.NumberIntVal(7),
   297  			},
   298  			cty.ListVal([]cty.Value{
   299  				cty.StringVal("10.0.0.0/24"),
   300  				cty.StringVal("10.0.1.0/24"),
   301  				cty.StringVal("10.0.2.0/24"),
   302  				cty.StringVal("10.0.3.0/25"),
   303  				cty.StringVal("10.0.3.128/25"),
   304  				cty.StringVal("10.0.4.0/25"),
   305  				cty.StringVal("10.0.4.128/28"),
   306  				cty.StringVal("10.0.4.144/28"),
   307  				cty.StringVal("10.0.4.160/28"),
   308  			}),
   309  			``,
   310  		},
   311  		{
   312  			// We inadvertently inherited a pre-Go1.17 standard library quirk
   313  			// if parsing zero-prefix parts as decimal rather than octal.
   314  			// Go 1.17 resolved that quirk by making zero-prefix invalid, but
   315  			// we've preserved our existing behavior for backward compatibility,
   316  			// on the grounds that these functions are for generating addresses
   317  			// rather than validating or processing them. We do always generate
   318  			// a canonical result regardless of the input, though.
   319  			cty.StringVal("010.0.0.0/21"),
   320  			[]cty.Value{
   321  				cty.NumberIntVal(3),
   322  			},
   323  			cty.ListVal([]cty.Value{
   324  				cty.StringVal("10.0.0.0/24"),
   325  			}),
   326  			``,
   327  		},
   328  		{
   329  			cty.StringVal("10.0.0.0/30"),
   330  			[]cty.Value{
   331  				cty.NumberIntVal(1),
   332  				cty.NumberIntVal(3),
   333  			},
   334  			cty.UnknownVal(cty.List(cty.String)),
   335  			`would extend prefix to 33 bits, which is too long for an IPv4 address`,
   336  		},
   337  		{
   338  			cty.StringVal("10.0.0.0/8"),
   339  			[]cty.Value{
   340  				cty.NumberIntVal(1),
   341  				cty.NumberIntVal(1),
   342  				cty.NumberIntVal(1),
   343  			},
   344  			cty.UnknownVal(cty.List(cty.String)),
   345  			`not enough remaining address space for a subnet with a prefix of 9 bits after 10.128.0.0/9`,
   346  		},
   347  		{
   348  			cty.StringVal("10.0.0.0/8"),
   349  			[]cty.Value{
   350  				cty.NumberIntVal(1),
   351  				cty.NumberIntVal(0),
   352  			},
   353  			cty.UnknownVal(cty.List(cty.String)),
   354  			`must extend prefix by at least one bit`,
   355  		},
   356  		{
   357  			cty.StringVal("10.0.0.0/8"),
   358  			[]cty.Value{
   359  				cty.NumberIntVal(1),
   360  				cty.NumberIntVal(-1),
   361  			},
   362  			cty.UnknownVal(cty.List(cty.String)),
   363  			`must extend prefix by at least one bit`,
   364  		},
   365  		{
   366  			cty.StringVal("fe80::/48"),
   367  			[]cty.Value{
   368  				cty.NumberIntVal(1),
   369  				cty.NumberIntVal(33),
   370  			},
   371  			cty.UnknownVal(cty.List(cty.String)),
   372  			`may not extend prefix by more than 32 bits`,
   373  		},
   374  	}
   375  
   376  	for _, test := range tests {
   377  		t.Run(fmt.Sprintf("cidrsubnets(%#v, %#v)", test.Prefix, test.Newbits), func(t *testing.T) {
   378  			got, err := CidrSubnets(test.Prefix, test.Newbits...)
   379  			wantErr := test.Err != ""
   380  
   381  			if wantErr {
   382  				if err == nil {
   383  					t.Fatal("succeeded; want error")
   384  				}
   385  				if err.Error() != test.Err {
   386  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", err.Error(), test.Err)
   387  				}
   388  				return
   389  			} else if err != nil {
   390  				t.Fatalf("unexpected error: %s", err)
   391  			}
   392  
   393  			if !got.RawEquals(test.Want) {
   394  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   395  			}
   396  		})
   397  	}
   398  }