diff --git a/libvirt/network.go b/libvirt/network.go index dfb8aa25f..0f3f51ce4 100644 --- a/libvirt/network.go +++ b/libvirt/network.go @@ -142,7 +142,7 @@ func getIPsFromResource(d *schema.ResourceData) ([]libvirtxml.NetworkIP, error) } func getNetworkIPConfig(address string) (*libvirtxml.NetworkIP, *libvirtxml.NetworkDHCP, error) { - _, ipNet, err := net.ParseCIDR(address) + ip, ipNet, err := net.ParseCIDR(address) if err != nil { return nil, nil, fmt.Errorf("error parsing addresses definition '%s': %w", address, err) } @@ -163,23 +163,26 @@ func getNetworkIPConfig(address string) (*libvirtxml.NetworkIP, *libvirtxml.Netw return nil, nil, fmt.Errorf("netmask seems to be too strict: only %d IPs available (%s)", availableSubnetIPCount, family) } - // we should calculate the range served by DHCP. For example, for + // calculate the range served by DHCP. For example, for // 192.168.121.0/24 we will serve 192.168.121.2 - 192.168.121.254 start, end := networkRange(ipNet) - // skip the .0, (for the network), + // skip the .0, (for the network), and skip the .255 (for broadcast) start[len(start)-1]++ + end[len(end)-1]-- + + // assign .1 host address iff host address is not given (.0) + if ip.Mask(ipNet.Mask).Equal(ip) { + ip[len(ip)-1] = 1 + start[len(start)-1]++ // then skip the .1 too. + } - // assign the .1 to the host interface dni := &libvirtxml.NetworkIP{ - Address: start.String(), + Address: ip.String(), Prefix: uint(ones), Family: family, } - start[len(start)-1]++ // then skip the .1 - end[len(end)-1]-- // and skip the .255 (for broadcast) - dhcp := &libvirtxml.NetworkDHCP{ Ranges: []libvirtxml.NetworkDHCPRange{ { diff --git a/libvirt/resource_libvirt_network.go b/libvirt/resource_libvirt_network.go index d7d243bd5..775b8baae 100644 --- a/libvirt/resource_libvirt_network.go +++ b/libvirt/resource_libvirt_network.go @@ -85,6 +85,9 @@ func resourceLibvirtNetwork() *schema.Resource { Elem: &schema.Schema{ Type: schema.TypeString, }, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return sameNetworkAddress(old, new) + }, }, "autostart": { Type: schema.TypeBool, @@ -540,20 +543,11 @@ func resourceLibvirtNetworkRead(ctx context.Context, d *schema.ResourceData, met addresses := []string{} //nolint:mnd for _, address := range networkDef.IPs { - // we get the host interface IP (ie, 10.10.8.1) but we want the network CIDR (ie, 10.10.8.0/24) - // so we need some transformations... addr := net.ParseIP(address.Address) if addr == nil { return diag.Errorf("error parsing IP '%s': %s", address.Address, err) } - bits := net.IPv6len * 8 - if addr.To4() != nil { - bits = net.IPv4len * 8 - } - - mask := net.CIDRMask(int(address.Prefix), bits) - network := addr.Mask(mask) - addresses = append(addresses, fmt.Sprintf("%s/%d", network, address.Prefix)) + addresses = append(addresses, fmt.Sprintf("%s/%d", addr, address.Prefix)) } if len(addresses) > 0 { d.Set("addresses", addresses) diff --git a/libvirt/resource_libvirt_network_test.go b/libvirt/resource_libvirt_network_test.go index 9b5bb0f78..3c1c4f7fc 100644 --- a/libvirt/resource_libvirt_network_test.go +++ b/libvirt/resource_libvirt_network_test.go @@ -27,11 +27,11 @@ func TestAccLibvirtNetwork_Addresses(t *testing.T) { resource "libvirt_network" "%s" { name = "%s" domain = "k8s.local" - addresses = ["10.17.3.0/24"] + addresses = ["10.17.3.1/24"] }`, randomNetworkResource, randomNetworkName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(randomNetworkResourceFull, - "addresses.0", "10.17.3.0/24"), + "addresses.0", "10.17.3.1/24"), resource.TestCheckResourceAttr(randomNetworkResourceFull, "mode", "nat"), ), diff --git a/libvirt/utils_net.go b/libvirt/utils_net.go index c1e2479d5..2246fd6f2 100644 --- a/libvirt/utils_net.go +++ b/libvirt/utils_net.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "fmt" "net" + "strings" ) // randomMACAddress returns a randomized MAC address @@ -70,3 +71,16 @@ func networkRange(network *net.IPNet) (firstIP net.IP, lastIP net.IP) { return netIP.Mask(network.Mask), getLastIP(network, netIP) } + +// sameNetworkAddress returns true if both input strings (in addr/netmask format) +// have the same network address +func sameNetworkAddress(a, b string) bool { + _, netA, errA := net.ParseCIDR(strings.TrimSpace(a)) + _, netB, errB := net.ParseCIDR(strings.TrimSpace(b)) + + if errA != nil || errB != nil { + return false // if either can't be parsed, treat as different + } + + return netA.IP.Equal(netB.IP) && netA.Mask.String() == netB.Mask.String() +}