func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { return nil } netOption := map[string]string{ winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(), } ipamV4Conf := libnetwork.IpamConf{} if config.bridgeConfig.FixedCIDR == "" { ipamV4Conf.PreferredPool = defaultNetworkSpace } else { ipamV4Conf.PreferredPool = config.bridgeConfig.FixedCIDR } v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, }), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), ) if err != nil { return fmt.Errorf("Error creating default network: %v", err) } return nil }
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { return nil } netOption := map[string]string{ winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(), } var ipamOption libnetwork.NetworkOption var subnetPrefix string if config.bridgeConfig.FixedCIDR != "" { subnetPrefix = config.bridgeConfig.FixedCIDR } else { // TP5 doesn't support properly detecting subnet osv := system.GetOSVersion() if osv.Build < 14360 { subnetPrefix = defaultNetworkSpace } } if subnetPrefix != "" { ipamV4Conf := libnetwork.IpamConf{} ipamV4Conf.PreferredPool = subnetPrefix v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} ipamOption = libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil) } _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), "", libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, }), ipamOption, ) if err != nil { return fmt.Errorf("Error creating default network: %v", err) } return nil }
func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnetwork.IpamConf, error) { ipamV4Cfg := []*libnetwork.IpamConf{} ipamV6Cfg := []*libnetwork.IpamConf{} for _, d := range data { iCfg := libnetwork.IpamConf{} iCfg.PreferredPool = d.Subnet iCfg.SubPool = d.IPRange iCfg.Gateway = d.Gateway iCfg.AuxAddresses = d.AuxAddress ip, _, err := net.ParseCIDR(d.Subnet) if err != nil { return nil, nil, fmt.Errorf("Invalid subnet %s : %v", d.Subnet, err) } if ip.To4() != nil { ipamV4Cfg = append(ipamV4Cfg, &iCfg) } else { ipamV6Cfg = append(ipamV6Cfg, &iCfg) } } return ipamV4Cfg, ipamV6Cfg, nil }
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { if n, err := controller.NetworkByName("bridge"); err == nil { if err = n.Delete(); err != nil { return fmt.Errorf("could not delete the default bridge network: %v", err) } } bridgeName := bridge.DefaultBridgeName if config.Bridge.Iface != "" { bridgeName = config.Bridge.Iface } netOption := map[string]string{ bridge.BridgeName: bridgeName, bridge.DefaultBridge: strconv.FormatBool(true), netlabel.DriverMTU: strconv.Itoa(config.Mtu), bridge.EnableIPMasquerade: strconv.FormatBool(config.Bridge.EnableIPMasq), bridge.EnableICC: strconv.FormatBool(config.Bridge.InterContainerCommunication), } // --ip processing if config.Bridge.DefaultIP != nil { netOption[bridge.DefaultBindingIP] = config.Bridge.DefaultIP.String() } ipamV4Conf := libnetwork.IpamConf{} ipamV4Conf.AuxAddresses = make(map[string]string) if nw, _, err := ipamutils.ElectInterfaceAddresses(bridgeName); err == nil { ipamV4Conf.PreferredPool = nw.String() hip, _ := types.GetHostPartIP(nw.IP, nw.Mask) if hip.IsGlobalUnicast() { ipamV4Conf.Gateway = nw.IP.String() } } if config.Bridge.IP != "" { ipamV4Conf.PreferredPool = config.Bridge.IP ip, _, err := net.ParseCIDR(config.Bridge.IP) if err != nil { return err } ipamV4Conf.Gateway = ip.String() } else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" { logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool) } if config.Bridge.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.Bridge.FixedCIDR) if err != nil { return err } ipamV4Conf.SubPool = fCIDR.String() } if config.Bridge.DefaultGatewayIPv4 != nil { ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.Bridge.DefaultGatewayIPv4.String() } var ( ipamV6Conf *libnetwork.IpamConf deferIPv6Alloc bool ) if config.Bridge.FixedCIDRv6 != "" { _, fCIDRv6, err := net.ParseCIDR(config.Bridge.FixedCIDRv6) if err != nil { return err } // In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has // at least 48 host bits, we need to guarantee the current behavior where the containers' // IPv6 addresses will be constructed based on the containers' interface MAC address. // We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints // on this network until after the driver has created the endpoint and returned the // constructed address. Libnetwork will then reserve this address with the ipam driver. ones, _ := fCIDRv6.Mask.Size() deferIPv6Alloc = ones <= 80 if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} } ipamV6Conf.PreferredPool = fCIDRv6.String() } if config.Bridge.DefaultGatewayIPv6 != nil { if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} } ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = config.Bridge.DefaultGatewayIPv6.String() } v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} if ipamV6Conf != nil { v6Conf = append(v6Conf, ipamV6Conf) } // Initialize default network on "bridge" with the same name _, err := controller.NewNetwork("bridge", "bridge", libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, netlabel.EnableIPv6: config.Bridge.EnableIPv6, }), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc)) if err != nil { return fmt.Errorf("Error creating default \"bridge\" network: %v", err) } return nil }
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { if n, err := controller.NetworkByName("bridge"); err == nil { if err = n.Delete(); err != nil { return fmt.Errorf("could not delete the default bridge network: %v", err) } } bridgeName := bridge.DefaultBridgeName if config.bridgeConfig.Iface != "" { bridgeName = config.bridgeConfig.Iface } netOption := map[string]string{ bridge.BridgeName: bridgeName, bridge.DefaultBridge: strconv.FormatBool(true), netlabel.DriverMTU: strconv.Itoa(config.Mtu), bridge.EnableICC: strconv.FormatBool(config.bridgeConfig.InterContainerCommunication), } // --ip processing if config.bridgeConfig.DefaultIP != nil { netOption[bridge.DefaultBindingIP] = config.bridgeConfig.DefaultIP.String() } var ipamV4Conf *libnetwork.IpamConf ipamV4Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} nwList, _, err := netutils.ElectInterfaceAddresses(bridgeName) if err != nil { return errors.Wrap(err, "list bridge addresses failed") } nw := nwList[0] if len(nwList) > 1 && config.bridgeConfig.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR) if err != nil { return errors.Wrap(err, "parse CIDR failed") } // Iterate through in case there are multiple addresses for the bridge for _, entry := range nwList { if fCIDR.Contains(entry.IP) { nw = entry break } } } ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String() hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask) if hip.IsGlobalUnicast() { ipamV4Conf.Gateway = nw.IP.String() } if config.bridgeConfig.IP != "" { ipamV4Conf.PreferredPool = config.bridgeConfig.IP ip, _, err := net.ParseCIDR(config.bridgeConfig.IP) if err != nil { return err } ipamV4Conf.Gateway = ip.String() } else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" { logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool) } if config.bridgeConfig.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR) if err != nil { return err } ipamV4Conf.SubPool = fCIDR.String() } if config.bridgeConfig.DefaultGatewayIPv4 != nil { ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.bridgeConfig.DefaultGatewayIPv4.String() } v4Conf := []*libnetwork.IpamConf{ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} // Initialize default network on "bridge" with the same name _, err = controller.NewNetwork("bridge", "bridge", "", libnetwork.NetworkOptionDriverOpts(netOption), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), libnetwork.NetworkOptionDeferIPv6Alloc(false)) if err != nil { return fmt.Errorf("Error creating default 'bridge' network: %v", err) } return nil }
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { bridgeName := bridge.DefaultBridgeName if config.bridgeConfig.Iface != "" { bridgeName = config.bridgeConfig.Iface } netOption := map[string]string{ bridge.BridgeName: bridgeName, bridge.DefaultBridge: strconv.FormatBool(true), netlabel.DriverMTU: strconv.Itoa(config.Mtu), bridge.EnableIPMasquerade: strconv.FormatBool(config.bridgeConfig.EnableIPMasq), bridge.EnableICC: strconv.FormatBool(config.bridgeConfig.InterContainerCommunication), } // --ip processing if config.bridgeConfig.DefaultIP != nil { netOption[bridge.DefaultBindingIP] = config.bridgeConfig.DefaultIP.String() } var ( ipamV4Conf *libnetwork.IpamConf ipamV6Conf *libnetwork.IpamConf ) ipamV4Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} nwList, nw6List, err := netutils.ElectInterfaceAddresses(bridgeName) if err != nil { return errors.Wrap(err, "list bridge addresses failed") } nw := nwList[0] if len(nwList) > 1 && config.bridgeConfig.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR) if err != nil { return errors.Wrap(err, "parse CIDR failed") } // Iterate through in case there are multiple addresses for the bridge for _, entry := range nwList { if fCIDR.Contains(entry.IP) { nw = entry break } } } ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String() hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask) if hip.IsGlobalUnicast() { ipamV4Conf.Gateway = nw.IP.String() } if config.bridgeConfig.IP != "" { ipamV4Conf.PreferredPool = config.bridgeConfig.IP ip, _, err := net.ParseCIDR(config.bridgeConfig.IP) if err != nil { return err } ipamV4Conf.Gateway = ip.String() } else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" { logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool) } if config.bridgeConfig.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.bridgeConfig.FixedCIDR) if err != nil { return err } ipamV4Conf.SubPool = fCIDR.String() } if config.bridgeConfig.DefaultGatewayIPv4 != nil { ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.bridgeConfig.DefaultGatewayIPv4.String() } var deferIPv6Alloc bool if config.bridgeConfig.FixedCIDRv6 != "" { _, fCIDRv6, err := net.ParseCIDR(config.bridgeConfig.FixedCIDRv6) if err != nil { return err } // In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has // at least 48 host bits, we need to guarantee the current behavior where the containers' // IPv6 addresses will be constructed based on the containers' interface MAC address. // We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints // on this network until after the driver has created the endpoint and returned the // constructed address. Libnetwork will then reserve this address with the ipam driver. ones, _ := fCIDRv6.Mask.Size() deferIPv6Alloc = ones <= 80 if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} } ipamV6Conf.PreferredPool = fCIDRv6.String() // In case the --fixed-cidr-v6 is specified and the current docker0 bridge IPv6 // address belongs to the same network, we need to inform libnetwork about it, so // that it can be reserved with IPAM and it will not be given away to somebody else for _, nw6 := range nw6List { if fCIDRv6.Contains(nw6.IP) { ipamV6Conf.Gateway = nw6.IP.String() break } } } if config.bridgeConfig.DefaultGatewayIPv6 != nil { if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)} } ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = config.bridgeConfig.DefaultGatewayIPv6.String() } v4Conf := []*libnetwork.IpamConf{ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} if ipamV6Conf != nil { v6Conf = append(v6Conf, ipamV6Conf) } // Initialize default network on "bridge" with the same name _, err = controller.NewNetwork("bridge", "bridge", "", libnetwork.NetworkOptionEnableIPv6(config.bridgeConfig.EnableIPv6), libnetwork.NetworkOptionDriverOpts(netOption), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc)) if err != nil { return fmt.Errorf("Error creating default \"bridge\" network: %v", err) } return nil }
func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) { netOptions, err := daemon.networkOptions(config) if err != nil { return nil, err } controller, err := libnetwork.New(netOptions...) if err != nil { return nil, fmt.Errorf("error obtaining controller instance: %v", err) } hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "") if err != nil { return nil, err } // Remove networks not present in HNS for _, v := range controller.Networks() { options := v.Info().DriverOptions() hnsid := options[winlibnetwork.HNSID] found := false for _, v := range hnsresponse { if v.Id == hnsid { found = true break } } if !found { err = v.Delete() if err != nil { return nil, err } } } _, err = controller.NewNetwork("null", "none", libnetwork.NetworkOptionPersist(false)) if err != nil { return nil, err } // discover and add HNS networks to windows // network that exist are removed and added again for _, v := range hnsresponse { var n libnetwork.Network s := func(current libnetwork.Network) bool { options := current.Info().DriverOptions() if options[winlibnetwork.HNSID] == v.Id { n = current return true } return false } controller.WalkNetworks(s) if n != nil { v.Name = n.Name() n.Delete() } netOption := map[string]string{ winlibnetwork.NetworkName: v.Name, winlibnetwork.HNSID: v.Id, } v4Conf := []*libnetwork.IpamConf{} for _, subnet := range v.Subnets { ipamV4Conf := libnetwork.IpamConf{} ipamV4Conf.PreferredPool = subnet.AddressPrefix ipamV4Conf.Gateway = subnet.GatewayAddress v4Conf = append(v4Conf, &ipamV4Conf) } name := v.Name // There is only one nat network supported in windows. // If it exists with a different name add it as the default name if runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) { name = runconfig.DefaultDaemonNetworkMode().NetworkName() } v6Conf := []*libnetwork.IpamConf{} _, err := controller.NewNetwork(strings.ToLower(v.Type), name, libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, }), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), ) if err != nil { logrus.Errorf("Error occurred when creating network %v", err) } } if !config.DisableBridge { // Initialize default driver "bridge" if err := initBridgeDriver(controller, config); err != nil { return nil, err } } return controller, nil }
func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { if n, err := controller.NetworkByName("bridge"); err == nil { if err = n.Delete(); err != nil { return fmt.Errorf("could not delete the default bridge network: %v", err) } } bridgeName := bridge.DefaultBridgeName if config.Bridge.Iface != "" { bridgeName = config.Bridge.Iface } netOption := map[string]string{ bridge.BridgeName: bridgeName, bridge.DefaultBridge: strconv.FormatBool(true), netlabel.DriverMTU: strconv.Itoa(config.Mtu), bridge.EnableIPMasquerade: strconv.FormatBool(config.Bridge.EnableIPMasq), bridge.EnableICC: strconv.FormatBool(config.Bridge.InterContainerCommunication), } // --ip processing if config.Bridge.DefaultIP != nil { netOption[bridge.DefaultBindingIP] = config.Bridge.DefaultIP.String() } ipamV4Conf := libnetwork.IpamConf{} ipamV4Conf.AuxAddresses = make(map[string]string) if nw, _, err := ipamutils.ElectInterfaceAddresses(bridgeName); err == nil { ipamV4Conf.PreferredPool = nw.String() hip, _ := types.GetHostPartIP(nw.IP, nw.Mask) if hip.IsGlobalUnicast() { ipamV4Conf.Gateway = nw.IP.String() } } if config.Bridge.IP != "" { ipamV4Conf.PreferredPool = config.Bridge.IP ip, _, err := net.ParseCIDR(config.Bridge.IP) if err != nil { return err } ipamV4Conf.Gateway = ip.String() } if config.Bridge.FixedCIDR != "" { _, fCIDR, err := net.ParseCIDR(config.Bridge.FixedCIDR) if err != nil { return err } ipamV4Conf.SubPool = fCIDR.String() } if config.Bridge.DefaultGatewayIPv4 != nil { ipamV4Conf.AuxAddresses["DefaultGatewayIPv4"] = config.Bridge.DefaultGatewayIPv4.String() } var ipamV6Conf *libnetwork.IpamConf if config.Bridge.FixedCIDRv6 != "" { _, fCIDRv6, err := net.ParseCIDR(config.Bridge.FixedCIDRv6) if err != nil { return err } if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{} } ipamV6Conf.PreferredPool = fCIDRv6.String() } if config.Bridge.DefaultGatewayIPv6 != nil { if ipamV6Conf == nil { ipamV6Conf = &libnetwork.IpamConf{} } ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = config.Bridge.DefaultGatewayIPv6.String() } v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} v6Conf := []*libnetwork.IpamConf{} if ipamV6Conf != nil { v6Conf = append(v6Conf, ipamV6Conf) } // Initialize default network on "bridge" with the same name _, err := controller.NewNetwork("bridge", "bridge", libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, netlabel.EnableIPv6: config.Bridge.EnableIPv6, }), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf)) if err != nil { return fmt.Errorf("Error creating default \"bridge\" network: %v", err) } return nil }
func (daemon *Daemon) initNetworkController(config *Config, activeSandboxes map[string]interface{}) (libnetwork.NetworkController, error) { netOptions, err := daemon.networkOptions(config, nil, nil) if err != nil { return nil, err } controller, err := libnetwork.New(netOptions...) if err != nil { return nil, fmt.Errorf("error obtaining controller instance: %v", err) } hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "") if err != nil { return nil, err } // Remove networks not present in HNS for _, v := range controller.Networks() { options := v.Info().DriverOptions() hnsid := options[winlibnetwork.HNSID] found := false for _, v := range hnsresponse { if v.Id == hnsid { found = true break } } if !found { // global networks should not be deleted by local HNS if v.Info().Scope() != datastore.GlobalScope { err = v.Delete() if err != nil { logrus.Errorf("Error occurred when removing network %v", err) } } } } _, err = controller.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(false)) if err != nil { return nil, err } defaultNetworkExists := false if network, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { options := network.Info().DriverOptions() for _, v := range hnsresponse { if options[winlibnetwork.HNSID] == v.Id { defaultNetworkExists = true break } } } // discover and add HNS networks to windows // network that exist are removed and added again for _, v := range hnsresponse { var n libnetwork.Network s := func(current libnetwork.Network) bool { options := current.Info().DriverOptions() if options[winlibnetwork.HNSID] == v.Id { n = current return true } return false } controller.WalkNetworks(s) if n != nil { // global networks should not be deleted by local HNS if n.Info().Scope() == datastore.GlobalScope { continue } v.Name = n.Name() // This will not cause network delete from HNS as the network // is not yet populated in the libnetwork windows driver n.Delete() } netOption := map[string]string{ winlibnetwork.NetworkName: v.Name, winlibnetwork.HNSID: v.Id, } v4Conf := []*libnetwork.IpamConf{} for _, subnet := range v.Subnets { ipamV4Conf := libnetwork.IpamConf{} ipamV4Conf.PreferredPool = subnet.AddressPrefix ipamV4Conf.Gateway = subnet.GatewayAddress v4Conf = append(v4Conf, &ipamV4Conf) } name := v.Name // If there is no nat network create one from the first NAT network // encountered if !defaultNetworkExists && runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) { name = runconfig.DefaultDaemonNetworkMode().NetworkName() defaultNetworkExists = true } v6Conf := []*libnetwork.IpamConf{} _, err := controller.NewNetwork(strings.ToLower(v.Type), name, "", libnetwork.NetworkOptionGeneric(options.Generic{ netlabel.GenericData: netOption, }), libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), ) if err != nil { logrus.Errorf("Error occurred when creating network %v", err) } } if !config.DisableBridge { // Initialize default driver "bridge" if err := initBridgeDriver(controller, config); err != nil { return nil, err } } return controller, nil }