You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: network_ubuntu.go
    path of file: ./repos/juju/cloudconfig/cloudinit
    
    the code of the file until where you have to start completion: // Copyright 2013, 2015, 2018 Canonical Ltd.
// Copyright 2015 Cloudbase Solutions SRL
// Licensed under the AGPLv3, see LICENCE file for details.

package cloudinit

import (
	"bytes"
	"fmt"
	"net"
	"strings"

	"github.com/juju/collections/set"
	"github.com/juju/errors"
	"github.com/juju/loggo"

	corenetwork "github.com/juju/juju/core/network"
	"github
    
func GenerateENITemplate(interfaces corenetwork.InterfaceInfos) (string, error) {
	if len(interfaces) == 0 {
		return "", errors.Errorf("missing container network config")
	}
	logger.Debugf("generating /e/n/i template from %#v", interfaces)

	prepared, err := PrepareNetworkConfigFromInterfaces(interfaces)
	if err != nil {
		return "", errors.Trace(err)
	}

	var output bytes.Buffer
	gateway4Handled := false
	gateway6Handled := false
	hasV4Interface := false
	hasV6Interface := false
	for _, name := range prepared.InterfaceNames {
		output.WriteString("\n")
		if name == "lo" {
			output.WriteString("auto ")
			autoStarted := strings.Join(prepared.AutoStarted, " ")
			output.WriteString(autoStarted + "\n\n")
			output.WriteString("iface lo inet loopback\n")

			dnsServers := strings.Join(prepared.DNSServers, " ")
			if dnsServers != "" {
				output.WriteString("  dns-nameservers ")
				output.WriteString(dnsServers + "\n")
			}

			dnsSearchDomains := strings.Join(prepared.DNSSearchDomains, " ")
			if dnsSearchDomains != "" {
				output.WriteString("  dns-search ")
				output.WriteString(dnsSearchDomains + "\n")
			}
			continue
		}

		address, hasAddress := prepared.NameToAddress[name]
		if !hasAddress {
			output.WriteString("iface " + name + " inet manual\n")
			continue
		} else if address == string(corenetwork.ConfigDHCP) {
			output.WriteString("iface " + name + " inet dhcp\n")
			// We're expecting to get a default gateway
			// from the DHCP lease.
			gateway4Handled = true
			continue
		}

		_, network, err := net.ParseCIDR(address)
		if err != nil {
			return "", errors.Annotatef(err, "invalid address for interface %q: %q", name, address)
		}

		isIpv4 := network.IP.To4() != nil

		if isIpv4 {
			output.WriteString("iface " + name + " inet static\n")
			hasV4Interface = true
		} else {
			output.WriteString("iface " + name + " inet6 static\n")
			hasV6Interface = true
		}
		output.WriteString("  address " + address + "\n")

		if isIpv4 {
			if !gateway4Handled && prepared.Gateway4Address != "" {
				gatewayIP := net.ParseIP(prepared.Gateway4Address)
				if network.Contains(gatewayIP) {
					output.WriteString("  gateway " + prepared.Gateway4Address + "\n")
					gateway4Handled = true // write it only once
				}
			}
		} else {
			if !gateway6Handled && prepared.Gateway6Address != "" {
				gatewayIP := net.ParseIP(prepared.Gateway6Address)
				if network.Contains(gatewayIP) {
					output.WriteString("  gateway " + prepared.Gateway6Address + "\n")
					gateway4Handled = true // write it only once
				}
			}
		}

		if mtu, ok := prepared.NameToMTU[name]; ok {
			output.WriteString(fmt.Sprintf("  mtu %d\n", mtu))
		}

		for _, route := range prepared.NameToRoutes[name] {
			output.WriteString(fmt.Sprintf("  post-up ip route add %s via %s metric %d\n",
				route.DestinationCIDR, route.GatewayIP, route.Metric))
			output.WriteString(fmt.Sprintf("  pre-down ip route del %s via %s metric %d\n",
				route.DestinationCIDR, route.GatewayIP, route.Metric))
		}
	}

	generatedConfig := output.String()
	logger.Debugf("generated network config:\n%s", generatedConfig)

	if hasV4Interface && !gateway4Handled {
		logger.Infof("generated network config has no ipv4 gateway")
	}

	if hasV6Interface && !gateway6Handled {
		logger.Infof("generated network config has no ipv6 gateway")
	}

	return generatedConfig, nil
}