github.com/moby/docker@v26.1.3+incompatible/api/server/router/network/network_routes.go (about)

     1  package network // import "github.com/docker/docker/api/server/router/network"
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/api/server/httputils"
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/backend"
    12  	"github.com/docker/docker/api/types/filters"
    13  	"github.com/docker/docker/api/types/network"
    14  	"github.com/docker/docker/api/types/versions"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/docker/docker/libnetwork"
    17  	"github.com/docker/docker/libnetwork/scope"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    22  	if err := httputils.ParseForm(r); err != nil {
    23  		return err
    24  	}
    25  
    26  	filter, err := filters.FromJSON(r.Form.Get("filters"))
    27  	if err != nil {
    28  		return err
    29  	}
    30  
    31  	if err := network.ValidateFilters(filter); err != nil {
    32  		return err
    33  	}
    34  
    35  	var list []types.NetworkResource
    36  	nr, err := n.cluster.GetNetworks(filter)
    37  	if err == nil {
    38  		list = nr
    39  	}
    40  
    41  	// Combine the network list returned by Docker daemon if it is not already
    42  	// returned by the cluster manager
    43  	localNetworks, err := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: versions.LessThan(httputils.VersionFromContext(ctx), "1.28")})
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	var idx map[string]bool
    49  	if len(list) > 0 {
    50  		idx = make(map[string]bool, len(list))
    51  		for _, n := range list {
    52  			idx[n.ID] = true
    53  		}
    54  	}
    55  	for _, n := range localNetworks {
    56  		if idx[n.ID] {
    57  			continue
    58  		}
    59  		list = append(list, n)
    60  	}
    61  
    62  	if list == nil {
    63  		list = []types.NetworkResource{}
    64  	}
    65  
    66  	return httputils.WriteJSON(w, http.StatusOK, list)
    67  }
    68  
    69  type invalidRequestError struct {
    70  	cause error
    71  }
    72  
    73  func (e invalidRequestError) Error() string {
    74  	return e.cause.Error()
    75  }
    76  
    77  func (e invalidRequestError) InvalidParameter() {}
    78  
    79  type ambigousResultsError string
    80  
    81  func (e ambigousResultsError) Error() string {
    82  	return "network " + string(e) + " is ambiguous"
    83  }
    84  
    85  func (ambigousResultsError) InvalidParameter() {}
    86  
    87  func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    88  	if err := httputils.ParseForm(r); err != nil {
    89  		return err
    90  	}
    91  
    92  	term := vars["id"]
    93  	var (
    94  		verbose bool
    95  		err     error
    96  	)
    97  	if v := r.URL.Query().Get("verbose"); v != "" {
    98  		if verbose, err = strconv.ParseBool(v); err != nil {
    99  			return errors.Wrapf(invalidRequestError{err}, "invalid value for verbose: %s", v)
   100  		}
   101  	}
   102  	networkScope := r.URL.Query().Get("scope")
   103  
   104  	// In case multiple networks have duplicate names, return error.
   105  	// TODO (yongtang): should we wrap with version here for backward compatibility?
   106  
   107  	// First find based on full ID, return immediately once one is found.
   108  	// If a network appears both in swarm and local, assume it is in local first
   109  
   110  	// For full name and partial ID, save the result first, and process later
   111  	// in case multiple records was found based on the same term
   112  	listByFullName := map[string]types.NetworkResource{}
   113  	listByPartialID := map[string]types.NetworkResource{}
   114  
   115  	// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
   116  	// Instead there should be a backend function to just get one network.
   117  	filter := filters.NewArgs(filters.Arg("idOrName", term))
   118  	if networkScope != "" {
   119  		filter.Add("scope", networkScope)
   120  	}
   121  	networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true, Verbose: verbose})
   122  	for _, nw := range networks {
   123  		if nw.ID == term {
   124  			return httputils.WriteJSON(w, http.StatusOK, nw)
   125  		}
   126  		if nw.Name == term {
   127  			// No need to check the ID collision here as we are still in
   128  			// local scope and the network ID is unique in this scope.
   129  			listByFullName[nw.ID] = nw
   130  		}
   131  		if strings.HasPrefix(nw.ID, term) {
   132  			// No need to check the ID collision here as we are still in
   133  			// local scope and the network ID is unique in this scope.
   134  			listByPartialID[nw.ID] = nw
   135  		}
   136  	}
   137  
   138  	nwk, err := n.cluster.GetNetwork(term)
   139  	if err == nil {
   140  		// If the get network is passed with a specific network ID / partial network ID
   141  		// or if the get network was passed with a network name and scope as swarm
   142  		// return the network. Skipped using isMatchingScope because it is true if the scope
   143  		// is not set which would be case if the client API v1.30
   144  		if strings.HasPrefix(nwk.ID, term) || networkScope == scope.Swarm {
   145  			// If we have a previous match "backend", return it, we need verbose when enabled
   146  			// ex: overlay/partial_ID or name/swarm_scope
   147  			if nwv, ok := listByPartialID[nwk.ID]; ok {
   148  				nwk = nwv
   149  			} else if nwv, ok := listByFullName[nwk.ID]; ok {
   150  				nwk = nwv
   151  			}
   152  			return httputils.WriteJSON(w, http.StatusOK, nwk)
   153  		}
   154  	}
   155  
   156  	networks, _ = n.cluster.GetNetworks(filter)
   157  	for _, nw := range networks {
   158  		if nw.ID == term {
   159  			return httputils.WriteJSON(w, http.StatusOK, nw)
   160  		}
   161  		if nw.Name == term {
   162  			// Check the ID collision as we are in swarm scope here, and
   163  			// the map (of the listByFullName) may have already had a
   164  			// network with the same ID (from local scope previously)
   165  			if _, ok := listByFullName[nw.ID]; !ok {
   166  				listByFullName[nw.ID] = nw
   167  			}
   168  		}
   169  		if strings.HasPrefix(nw.ID, term) {
   170  			// Check the ID collision as we are in swarm scope here, and
   171  			// the map (of the listByPartialID) may have already had a
   172  			// network with the same ID (from local scope previously)
   173  			if _, ok := listByPartialID[nw.ID]; !ok {
   174  				listByPartialID[nw.ID] = nw
   175  			}
   176  		}
   177  	}
   178  
   179  	// Find based on full name, returns true only if no duplicates
   180  	if len(listByFullName) == 1 {
   181  		for _, v := range listByFullName {
   182  			return httputils.WriteJSON(w, http.StatusOK, v)
   183  		}
   184  	}
   185  	if len(listByFullName) > 1 {
   186  		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on name", len(listByFullName))
   187  	}
   188  
   189  	// Find based on partial ID, returns true only if no duplicates
   190  	if len(listByPartialID) == 1 {
   191  		for _, v := range listByPartialID {
   192  			return httputils.WriteJSON(w, http.StatusOK, v)
   193  		}
   194  	}
   195  	if len(listByPartialID) > 1 {
   196  		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on ID prefix", len(listByPartialID))
   197  	}
   198  
   199  	return libnetwork.ErrNoSuchNetwork(term)
   200  }
   201  
   202  func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   203  	if err := httputils.ParseForm(r); err != nil {
   204  		return err
   205  	}
   206  
   207  	var create types.NetworkCreateRequest
   208  	if err := httputils.ReadJSON(r, &create); err != nil {
   209  		return err
   210  	}
   211  
   212  	if nws, err := n.cluster.GetNetworksByName(create.Name); err == nil && len(nws) > 0 {
   213  		return libnetwork.NetworkNameError(create.Name)
   214  	}
   215  
   216  	// For a Swarm-scoped network, this call to backend.CreateNetwork is used to
   217  	// validate the configuration. The network will not be created but, if the
   218  	// configuration is valid, ManagerRedirectError will be returned and handled
   219  	// below.
   220  	nw, err := n.backend.CreateNetwork(create)
   221  	if err != nil {
   222  		if _, ok := err.(libnetwork.ManagerRedirectError); !ok {
   223  			return err
   224  		}
   225  		id, err := n.cluster.CreateNetwork(create)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		nw = &types.NetworkCreateResponse{
   230  			ID: id,
   231  		}
   232  	}
   233  
   234  	return httputils.WriteJSON(w, http.StatusCreated, nw)
   235  }
   236  
   237  func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   238  	if err := httputils.ParseForm(r); err != nil {
   239  		return err
   240  	}
   241  
   242  	var connect types.NetworkConnect
   243  	if err := httputils.ReadJSON(r, &connect); err != nil {
   244  		return err
   245  	}
   246  
   247  	// Unlike other operations, we does not check ambiguity of the name/ID here.
   248  	// The reason is that, In case of attachable network in swarm scope, the actual local network
   249  	// may not be available at the time. At the same time, inside daemon `ConnectContainerToNetwork`
   250  	// does the ambiguity check anyway. Therefore, passing the name to daemon would be enough.
   251  	return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
   252  }
   253  
   254  func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   255  	if err := httputils.ParseForm(r); err != nil {
   256  		return err
   257  	}
   258  
   259  	var disconnect types.NetworkDisconnect
   260  	if err := httputils.ReadJSON(r, &disconnect); err != nil {
   261  		return err
   262  	}
   263  
   264  	return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
   265  }
   266  
   267  func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   268  	if err := httputils.ParseForm(r); err != nil {
   269  		return err
   270  	}
   271  
   272  	nw, err := n.findUniqueNetwork(vars["id"])
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if nw.Scope == "swarm" {
   277  		if err = n.cluster.RemoveNetwork(nw.ID); err != nil {
   278  			return err
   279  		}
   280  	} else {
   281  		if err := n.backend.DeleteNetwork(nw.ID); err != nil {
   282  			return err
   283  		}
   284  	}
   285  	w.WriteHeader(http.StatusNoContent)
   286  	return nil
   287  }
   288  
   289  func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   290  	if err := httputils.ParseForm(r); err != nil {
   291  		return err
   292  	}
   293  
   294  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	pruneReport, err := n.backend.NetworksPrune(ctx, pruneFilters)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   304  }
   305  
   306  // findUniqueNetwork will search network across different scopes (both local and swarm).
   307  // NOTE: This findUniqueNetwork is different from FindNetwork in the daemon.
   308  // In case multiple networks have duplicate names, return error.
   309  // First find based on full ID, return immediately once one is found.
   310  // If a network appears both in swarm and local, assume it is in local first
   311  // For full name and partial ID, save the result first, and process later
   312  // in case multiple records was found based on the same term
   313  // TODO (yongtang): should we wrap with version here for backward compatibility?
   314  func (n *networkRouter) findUniqueNetwork(term string) (types.NetworkResource, error) {
   315  	listByFullName := map[string]types.NetworkResource{}
   316  	listByPartialID := map[string]types.NetworkResource{}
   317  
   318  	filter := filters.NewArgs(filters.Arg("idOrName", term))
   319  	networks, _ := n.backend.GetNetworks(filter, backend.NetworkListConfig{Detailed: true})
   320  	for _, nw := range networks {
   321  		if nw.ID == term {
   322  			return nw, nil
   323  		}
   324  		if nw.Name == term && !nw.Ingress {
   325  			// No need to check the ID collision here as we are still in
   326  			// local scope and the network ID is unique in this scope.
   327  			listByFullName[nw.ID] = nw
   328  		}
   329  		if strings.HasPrefix(nw.ID, term) {
   330  			// No need to check the ID collision here as we are still in
   331  			// local scope and the network ID is unique in this scope.
   332  			listByPartialID[nw.ID] = nw
   333  		}
   334  	}
   335  
   336  	networks, _ = n.cluster.GetNetworks(filter)
   337  	for _, nw := range networks {
   338  		if nw.ID == term {
   339  			return nw, nil
   340  		}
   341  		if nw.Name == term {
   342  			// Check the ID collision as we are in swarm scope here, and
   343  			// the map (of the listByFullName) may have already had a
   344  			// network with the same ID (from local scope previously)
   345  			if _, ok := listByFullName[nw.ID]; !ok {
   346  				listByFullName[nw.ID] = nw
   347  			}
   348  		}
   349  		if strings.HasPrefix(nw.ID, term) {
   350  			// Check the ID collision as we are in swarm scope here, and
   351  			// the map (of the listByPartialID) may have already had a
   352  			// network with the same ID (from local scope previously)
   353  			if _, ok := listByPartialID[nw.ID]; !ok {
   354  				listByPartialID[nw.ID] = nw
   355  			}
   356  		}
   357  	}
   358  
   359  	// Find based on full name, returns true only if no duplicates
   360  	if len(listByFullName) == 1 {
   361  		for _, v := range listByFullName {
   362  			return v, nil
   363  		}
   364  	}
   365  	if len(listByFullName) > 1 {
   366  		return types.NetworkResource{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName)))
   367  	}
   368  
   369  	// Find based on partial ID, returns true only if no duplicates
   370  	if len(listByPartialID) == 1 {
   371  		for _, v := range listByPartialID {
   372  			return v, nil
   373  		}
   374  	}
   375  	if len(listByPartialID) > 1 {
   376  		return types.NetworkResource{}, errdefs.InvalidParameter(errors.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID)))
   377  	}
   378  
   379  	return types.NetworkResource{}, errdefs.NotFound(libnetwork.ErrNoSuchNetwork(term))
   380  }