github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/networks.go (about)

     1  package compat
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  
     9  	nettypes "github.com/containers/common/libnetwork/types"
    10  	netutil "github.com/containers/common/libnetwork/util"
    11  	"github.com/hanks177/podman/v4/libpod"
    12  	"github.com/hanks177/podman/v4/libpod/define"
    13  	"github.com/hanks177/podman/v4/pkg/api/handlers/utils"
    14  	api "github.com/hanks177/podman/v4/pkg/api/types"
    15  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    16  	"github.com/hanks177/podman/v4/pkg/domain/infra/abi"
    17  	"github.com/hanks177/podman/v4/pkg/util"
    18  	"github.com/docker/docker/api/types"
    19  
    20  	dockerNetwork "github.com/docker/docker/api/types/network"
    21  	"github.com/gorilla/schema"
    22  	"github.com/pkg/errors"
    23  	"github.com/sirupsen/logrus"
    24  )
    25  
    26  func InspectNetwork(w http.ResponseWriter, r *http.Request) {
    27  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
    28  
    29  	// scope is only used to see if the user passes any illegal value, verbose is not used but implemented
    30  	// for compatibility purposes only.
    31  	query := struct {
    32  		scope   string `schema:"scope"`
    33  		verbose bool   `schema:"verbose"`
    34  	}{
    35  		scope: "local",
    36  	}
    37  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
    38  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    39  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    40  		return
    41  	}
    42  
    43  	if query.scope != "local" {
    44  		utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg)
    45  		return
    46  	}
    47  	name := utils.GetName(r)
    48  	net, err := runtime.Network().NetworkInspect(name)
    49  	if err != nil {
    50  		utils.NetworkNotFound(w, name, err)
    51  		return
    52  	}
    53  	report, err := convertLibpodNetworktoDockerNetwork(runtime, net)
    54  	if err != nil {
    55  		utils.InternalServerError(w, err)
    56  		return
    57  	}
    58  	utils.WriteResponse(w, http.StatusOK, report)
    59  }
    60  
    61  func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettypes.Network) (*types.NetworkResource, error) {
    62  	cons, err := runtime.GetAllContainers()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	containerEndpoints := make(map[string]types.EndpointResource, len(cons))
    67  	for _, con := range cons {
    68  		data, err := con.Inspect(false)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  		if netData, ok := data.NetworkSettings.Networks[network.Name]; ok {
    73  			ipv4Address := ""
    74  			if netData.IPAddress != "" {
    75  				ipv4Address = fmt.Sprintf("%s/%d", netData.IPAddress, netData.IPPrefixLen)
    76  			}
    77  			ipv6Address := ""
    78  			if netData.GlobalIPv6Address != "" {
    79  				ipv6Address = fmt.Sprintf("%s/%d", netData.GlobalIPv6Address, netData.GlobalIPv6PrefixLen)
    80  			}
    81  			containerEndpoint := types.EndpointResource{
    82  				Name:        con.Name(),
    83  				EndpointID:  netData.EndpointID,
    84  				MacAddress:  netData.MacAddress,
    85  				IPv4Address: ipv4Address,
    86  				IPv6Address: ipv6Address,
    87  			}
    88  			containerEndpoints[con.ID()] = containerEndpoint
    89  		}
    90  	}
    91  	ipamConfigs := make([]dockerNetwork.IPAMConfig, 0, len(network.Subnets))
    92  	for _, sub := range network.Subnets {
    93  		ipamConfig := dockerNetwork.IPAMConfig{
    94  			Subnet:  sub.Subnet.String(),
    95  			Gateway: sub.Gateway.String(),
    96  			// TODO add range
    97  		}
    98  		ipamConfigs = append(ipamConfigs, ipamConfig)
    99  	}
   100  	ipamDriver := network.IPAMOptions["driver"]
   101  	if ipamDriver == nettypes.HostLocalIPAMDriver {
   102  		ipamDriver = "default"
   103  	}
   104  	ipam := dockerNetwork.IPAM{
   105  		Driver:  ipamDriver,
   106  		Options: network.IPAMOptions,
   107  		Config:  ipamConfigs,
   108  	}
   109  
   110  	report := types.NetworkResource{
   111  		Name:   network.Name,
   112  		ID:     network.ID,
   113  		Driver: network.Driver,
   114  		// TODO add Created: ,
   115  		Internal:   network.Internal,
   116  		EnableIPv6: network.IPv6Enabled,
   117  		Labels:     network.Labels,
   118  		Options:    network.Options,
   119  		IPAM:       ipam,
   120  		Scope:      "local",
   121  		Attachable: false,
   122  		Ingress:    false,
   123  		ConfigFrom: dockerNetwork.ConfigReference{},
   124  		ConfigOnly: false,
   125  		Containers: containerEndpoints,
   126  		Peers:      nil,
   127  		Services:   nil,
   128  	}
   129  	return &report, nil
   130  }
   131  
   132  func ListNetworks(w http.ResponseWriter, r *http.Request) {
   133  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   134  	filterMap, err := util.PrepareFilters(r)
   135  	if err != nil {
   136  		utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   137  		return
   138  	}
   139  
   140  	options := entities.NetworkListOptions{
   141  		Filters: *filterMap,
   142  	}
   143  
   144  	ic := abi.ContainerEngine{Libpod: runtime}
   145  	nets, err := ic.NetworkList(r.Context(), options)
   146  	if err != nil {
   147  		utils.InternalServerError(w, err)
   148  		return
   149  	}
   150  	reports := make([]*types.NetworkResource, 0, len(nets))
   151  	for _, net := range nets {
   152  		report, err := convertLibpodNetworktoDockerNetwork(runtime, net)
   153  		if err != nil {
   154  			utils.InternalServerError(w, err)
   155  			return
   156  		}
   157  		reports = append(reports, report)
   158  	}
   159  	utils.WriteResponse(w, http.StatusOK, reports)
   160  }
   161  
   162  func CreateNetwork(w http.ResponseWriter, r *http.Request) {
   163  	var (
   164  		networkCreate types.NetworkCreateRequest
   165  		network       nettypes.Network
   166  	)
   167  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   168  	if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil {
   169  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   170  		return
   171  	}
   172  
   173  	network.Name = networkCreate.Name
   174  	if networkCreate.Driver == "" {
   175  		networkCreate.Driver = nettypes.DefaultNetworkDriver
   176  	}
   177  	network.Driver = networkCreate.Driver
   178  	network.Labels = networkCreate.Labels
   179  	network.Internal = networkCreate.Internal
   180  	network.IPv6Enabled = networkCreate.EnableIPv6
   181  
   182  	// FIXME use docker options and convert them to valid libpod options
   183  	// network.Options = networkCreate.Options
   184  
   185  	// dns is only enabled for the bridge driver
   186  	if network.Driver == nettypes.BridgeNetworkDriver {
   187  		network.DNSEnabled = true
   188  	}
   189  
   190  	if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 {
   191  		for _, conf := range networkCreate.IPAM.Config {
   192  			s := nettypes.Subnet{}
   193  			if len(conf.Subnet) > 0 {
   194  				var err error
   195  				subnet, err := nettypes.ParseCIDR(conf.Subnet)
   196  				if err != nil {
   197  					utils.InternalServerError(w, errors.Wrap(err, "failed to parse subnet"))
   198  					return
   199  				}
   200  				s.Subnet = subnet
   201  			}
   202  			if len(conf.Gateway) > 0 {
   203  				gw := net.ParseIP(conf.Gateway)
   204  				if gw == nil {
   205  					utils.InternalServerError(w, errors.Errorf("failed to parse gateway ip %s", conf.Gateway))
   206  					return
   207  				}
   208  				s.Gateway = gw
   209  			}
   210  			if len(conf.IPRange) > 0 {
   211  				_, net, err := net.ParseCIDR(conf.IPRange)
   212  				if err != nil {
   213  					utils.InternalServerError(w, errors.Wrap(err, "failed to parse ip range"))
   214  					return
   215  				}
   216  				startIP, err := netutil.FirstIPInSubnet(net)
   217  				if err != nil {
   218  					utils.InternalServerError(w, errors.Wrap(err, "failed to get first ip in range"))
   219  					return
   220  				}
   221  				lastIP, err := netutil.LastIPInSubnet(net)
   222  				if err != nil {
   223  					utils.InternalServerError(w, errors.Wrap(err, "failed to get last ip in range"))
   224  					return
   225  				}
   226  				s.LeaseRange = &nettypes.LeaseRange{
   227  					StartIP: startIP,
   228  					EndIP:   lastIP,
   229  				}
   230  			}
   231  			network.Subnets = append(network.Subnets, s)
   232  		}
   233  		// FIXME can we use the IPAM driver and options?
   234  	}
   235  
   236  	ic := abi.ContainerEngine{Libpod: runtime}
   237  	newNetwork, err := ic.NetworkCreate(r.Context(), network)
   238  	if err != nil {
   239  		utils.InternalServerError(w, err)
   240  		return
   241  	}
   242  
   243  	body := struct {
   244  		ID      string `json:"Id"`
   245  		Warning string
   246  	}{
   247  		ID: newNetwork.ID,
   248  	}
   249  	utils.WriteResponse(w, http.StatusCreated, body)
   250  }
   251  
   252  func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
   253  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   254  	ic := abi.ContainerEngine{Libpod: runtime}
   255  
   256  	query := struct {
   257  		Force   bool  `schema:"force"`
   258  		Timeout *uint `schema:"timeout"`
   259  	}{
   260  		// This is where you can override the golang default value for one of fields
   261  	}
   262  
   263  	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
   264  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   265  		utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
   266  		return
   267  	}
   268  
   269  	options := entities.NetworkRmOptions{
   270  		Force:   query.Force,
   271  		Timeout: query.Timeout,
   272  	}
   273  
   274  	name := utils.GetName(r)
   275  	reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
   276  	if err != nil {
   277  		utils.Error(w, http.StatusInternalServerError, err)
   278  		return
   279  	}
   280  	if len(reports) == 0 {
   281  		utils.Error(w, http.StatusInternalServerError, errors.Errorf("internal error"))
   282  		return
   283  	}
   284  	report := reports[0]
   285  	if report.Err != nil {
   286  		if errors.Cause(report.Err) == define.ErrNoSuchNetwork {
   287  			utils.Error(w, http.StatusNotFound, define.ErrNoSuchNetwork)
   288  			return
   289  		}
   290  		utils.InternalServerError(w, report.Err)
   291  		return
   292  	}
   293  
   294  	utils.WriteResponse(w, http.StatusNoContent, nil)
   295  }
   296  
   297  // Connect adds a container to a network
   298  func Connect(w http.ResponseWriter, r *http.Request) {
   299  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   300  
   301  	var netConnect types.NetworkConnect
   302  	if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
   303  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   304  		return
   305  	}
   306  
   307  	netOpts := nettypes.PerNetworkOptions{}
   308  
   309  	name := utils.GetName(r)
   310  	if netConnect.EndpointConfig != nil {
   311  		if netConnect.EndpointConfig.Aliases != nil {
   312  			netOpts.Aliases = netConnect.EndpointConfig.Aliases
   313  		}
   314  
   315  		// if IP address is provided
   316  		if len(netConnect.EndpointConfig.IPAddress) > 0 {
   317  			staticIP := net.ParseIP(netConnect.EndpointConfig.IPAddress)
   318  			if staticIP == nil {
   319  				utils.Error(w, http.StatusInternalServerError,
   320  					errors.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress))
   321  				return
   322  			}
   323  			netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
   324  		}
   325  
   326  		if netConnect.EndpointConfig.IPAMConfig != nil {
   327  			// if IPAMConfig.IPv4Address is provided
   328  			if len(netConnect.EndpointConfig.IPAMConfig.IPv4Address) > 0 {
   329  				staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv4Address)
   330  				if staticIP == nil {
   331  					utils.Error(w, http.StatusInternalServerError,
   332  						errors.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address))
   333  					return
   334  				}
   335  				netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
   336  			}
   337  			// if IPAMConfig.IPv6Address is provided
   338  			if len(netConnect.EndpointConfig.IPAMConfig.IPv6Address) > 0 {
   339  				staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv6Address)
   340  				if staticIP == nil {
   341  					utils.Error(w, http.StatusInternalServerError,
   342  						errors.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
   343  					return
   344  				}
   345  				netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
   346  			}
   347  		}
   348  		// If MAC address is provided
   349  		if len(netConnect.EndpointConfig.MacAddress) > 0 {
   350  			staticMac, err := net.ParseMAC(netConnect.EndpointConfig.MacAddress)
   351  			if err != nil {
   352  				utils.Error(w, http.StatusInternalServerError,
   353  					errors.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
   354  				return
   355  			}
   356  			netOpts.StaticMAC = nettypes.HardwareAddr(staticMac)
   357  		}
   358  	}
   359  	err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netOpts)
   360  	if err != nil {
   361  		if errors.Cause(err) == define.ErrNoSuchCtr {
   362  			utils.ContainerNotFound(w, netConnect.Container, err)
   363  			return
   364  		}
   365  		if errors.Cause(err) == define.ErrNoSuchNetwork {
   366  			utils.Error(w, http.StatusNotFound, err)
   367  			return
   368  		}
   369  		utils.Error(w, http.StatusInternalServerError, err)
   370  		return
   371  	}
   372  	utils.WriteResponse(w, http.StatusOK, "OK")
   373  }
   374  
   375  // Disconnect removes a container from a network
   376  func Disconnect(w http.ResponseWriter, r *http.Request) {
   377  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   378  
   379  	var netDisconnect types.NetworkDisconnect
   380  	if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil {
   381  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   382  		return
   383  	}
   384  
   385  	name := utils.GetName(r)
   386  	err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force)
   387  	if err != nil {
   388  		if errors.Cause(err) == define.ErrNoSuchCtr {
   389  			utils.Error(w, http.StatusNotFound, err)
   390  			return
   391  		}
   392  		if errors.Cause(err) == define.ErrNoSuchNetwork {
   393  			utils.Error(w, http.StatusNotFound, err)
   394  			return
   395  		}
   396  		utils.Error(w, http.StatusInternalServerError, err)
   397  		return
   398  	}
   399  	utils.WriteResponse(w, http.StatusOK, "OK")
   400  }
   401  
   402  // Prune removes unused networks
   403  func Prune(w http.ResponseWriter, r *http.Request) {
   404  	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
   405  	filterMap, err := util.PrepareFilters(r)
   406  	if err != nil {
   407  		utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
   408  		return
   409  	}
   410  
   411  	ic := abi.ContainerEngine{Libpod: runtime}
   412  	pruneOptions := entities.NetworkPruneOptions{
   413  		Filters: *filterMap,
   414  	}
   415  	pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions)
   416  	if err != nil {
   417  		utils.Error(w, http.StatusInternalServerError, err)
   418  		return
   419  	}
   420  	type response struct {
   421  		NetworksDeleted []string
   422  	}
   423  	prunedNetworks := []string{}
   424  	for _, pr := range pruneReports {
   425  		if pr.Error != nil {
   426  			logrus.Error(pr.Error)
   427  			continue
   428  		}
   429  		prunedNetworks = append(prunedNetworks, pr.Name)
   430  	}
   431  	utils.WriteResponse(w, http.StatusOK, response{NetworksDeleted: prunedNetworks})
   432  }