2018-02-05 16:05:59 -05:00
package network // import "github.com/docker/docker/api/server/router/network"
2015-09-25 06:19:17 -04:00
import (
2018-04-19 18:30:59 -04:00
"context"
2015-09-25 06:19:17 -04:00
"encoding/json"
2018-11-05 08:50:33 -05:00
"io"
2015-09-25 06:19:17 -04:00
"net/http"
2017-03-09 14:42:10 -05:00
"strconv"
2017-01-19 06:04:26 -05:00
"strings"
2015-09-25 06:19:17 -04:00
"github.com/docker/docker/api/server/httputils"
2016-09-06 14:46:37 -04:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
2017-02-02 08:48:53 -05:00
"github.com/docker/docker/api/types/versions"
2018-01-11 14:53:06 -05:00
"github.com/docker/docker/errdefs"
2021-04-05 20:24:47 -04:00
"github.com/docker/docker/libnetwork"
netconst "github.com/docker/docker/libnetwork/datastore"
2017-07-19 10:20:13 -04:00
"github.com/pkg/errors"
2015-09-25 06:19:17 -04:00
)
func ( n * networkRouter ) getNetworksList ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
2018-05-23 10:03:02 -04:00
filter , err := filters . FromJSON ( r . Form . Get ( "filters" ) )
2015-09-25 06:19:17 -04:00
if err != nil {
return err
}
2018-05-23 10:03:02 -04:00
if err := network . ValidateFilters ( filter ) ; err != nil {
2022-03-02 07:52:51 -05:00
return err
2016-12-29 03:10:40 -05:00
}
2018-05-23 10:03:02 -04:00
var list [ ] types . NetworkResource
nr , err := n . cluster . GetNetworks ( filter )
if err == nil {
list = nr
2015-09-25 06:19:17 -04:00
}
2015-11-10 03:57:06 -05:00
2016-06-13 22:52:49 -04:00
// Combine the network list returned by Docker daemon if it is not already
// returned by the cluster manager
2018-05-23 10:03:02 -04:00
localNetworks , err := n . backend . GetNetworks ( filter , types . NetworkListConfig { Detailed : versions . LessThan ( httputils . VersionFromContext ( ctx ) , "1.28" ) } )
if err != nil {
return err
}
2017-02-02 08:48:53 -05:00
2018-05-23 10:03:02 -04:00
var idx map [ string ] bool
if len ( list ) > 0 {
idx = make ( map [ string ] bool , len ( list ) )
for _ , n := range list {
idx [ n . ID ] = true
2017-02-02 08:48:53 -05:00
}
2018-05-23 10:03:02 -04:00
}
for _ , n := range localNetworks {
if idx [ n . ID ] {
continue
}
list = append ( list , n )
2015-11-10 03:57:06 -05:00
}
2018-05-23 10:03:02 -04:00
if list == nil {
list = [ ] types . NetworkResource { }
2016-06-13 22:52:49 -04:00
}
2018-05-23 10:03:02 -04:00
2015-09-25 06:19:17 -04:00
return httputils . WriteJSON ( w , http . StatusOK , list )
}
2017-07-19 10:20:13 -04:00
type invalidRequestError struct {
cause error
}
func ( e invalidRequestError ) Error ( ) string {
return e . cause . Error ( )
}
func ( e invalidRequestError ) InvalidParameter ( ) { }
type ambigousResultsError string
func ( e ambigousResultsError ) Error ( ) string {
return "network " + string ( e ) + " is ambiguous"
}
func ( ambigousResultsError ) InvalidParameter ( ) { }
2017-09-28 22:13:44 -04:00
func nameConflict ( name string ) error {
2017-11-28 23:09:37 -05:00
return errdefs . Conflict ( libnetwork . NetworkNameError ( name ) )
2017-09-28 22:13:44 -04:00
}
2015-09-25 06:19:17 -04:00
func ( n * networkRouter ) getNetwork ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
2017-01-19 06:04:26 -05:00
term := vars [ "id" ]
2017-03-09 14:42:10 -05:00
var (
verbose bool
err error
)
if v := r . URL . Query ( ) . Get ( "verbose" ) ; v != "" {
if verbose , err = strconv . ParseBool ( v ) ; err != nil {
2017-07-19 10:20:13 -04:00
return errors . Wrapf ( invalidRequestError { err } , "invalid value for verbose: %s" , v )
2017-03-09 14:42:10 -05:00
}
}
2017-06-11 14:04:35 -04:00
scope := r . URL . Query ( ) . Get ( "scope" )
2017-01-19 06:04:26 -05:00
// In case multiple networks have duplicate names, return error.
// TODO (yongtang): should we wrap with version here for backward compatibility?
// First find based on full ID, return immediately once one is found.
// If a network appears both in swarm and local, assume it is in local first
// For full name and partial ID, save the result first, and process later
// in case multiple records was found based on the same term
listByFullName := map [ string ] types . NetworkResource { }
listByPartialID := map [ string ] types . NetworkResource { }
2018-05-23 10:03:02 -04:00
// TODO(@cpuguy83): All this logic for figuring out which network to return does not belong here
// Instead there should be a backend function to just get one network.
filter := filters . NewArgs ( filters . Arg ( "idOrName" , term ) )
if scope != "" {
filter . Add ( "scope" , scope )
}
nw , _ := n . backend . GetNetworks ( filter , types . NetworkListConfig { Detailed : true , Verbose : verbose } )
2017-01-19 06:04:26 -05:00
for _ , network := range nw {
2018-05-23 10:03:02 -04:00
if network . ID == term {
return httputils . WriteJSON ( w , http . StatusOK , network )
2017-01-19 06:04:26 -05:00
}
2018-05-23 10:03:02 -04:00
if network . Name == term {
2017-01-19 06:04:26 -05:00
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
2018-05-23 10:03:02 -04:00
listByFullName [ network . ID ] = network
2017-01-19 06:04:26 -05:00
}
2018-05-23 10:03:02 -04:00
if strings . HasPrefix ( network . ID , term ) {
2017-01-19 06:04:26 -05:00
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
2018-05-23 10:03:02 -04:00
listByPartialID [ network . ID ] = network
2017-01-19 06:04:26 -05:00
}
}
2017-07-28 13:32:10 -04:00
nwk , err := n . cluster . GetNetwork ( term )
if err == nil {
// If the get network is passed with a specific network ID / partial network ID
// or if the get network was passed with a network name and scope as swarm
// return the network. Skipped using isMatchingScope because it is true if the scope
// is not set which would be case if the client API v1.30
if strings . HasPrefix ( nwk . ID , term ) || ( netconst . SwarmScope == scope ) {
2018-01-11 22:05:18 -05:00
// If we have a previous match "backend", return it, we need verbose when enabled
// ex: overlay/partial_ID or name/swarm_scope
if nwv , ok := listByPartialID [ nwk . ID ] ; ok {
nwk = nwv
} else if nwv , ok := listByFullName [ nwk . ID ] ; ok {
nwk = nwv
}
2017-07-28 13:32:10 -04:00
return httputils . WriteJSON ( w , http . StatusOK , nwk )
}
}
2018-05-23 10:03:02 -04:00
nr , _ := n . cluster . GetNetworks ( filter )
2017-01-19 06:04:26 -05:00
for _ , network := range nr {
2018-05-23 10:03:02 -04:00
if network . ID == term {
2017-01-19 06:04:26 -05:00
return httputils . WriteJSON ( w , http . StatusOK , network )
}
2018-05-23 10:03:02 -04:00
if network . Name == term {
2017-01-19 06:04:26 -05:00
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByFullName) may have already had a
// network with the same ID (from local scope previously)
if _ , ok := listByFullName [ network . ID ] ; ! ok {
listByFullName [ network . ID ] = network
}
}
2018-05-23 10:03:02 -04:00
if strings . HasPrefix ( network . ID , term ) {
2017-01-19 06:04:26 -05:00
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByPartialID) may have already had a
// network with the same ID (from local scope previously)
if _ , ok := listByPartialID [ network . ID ] ; ! ok {
listByPartialID [ network . ID ] = network
}
}
}
// Find based on full name, returns true only if no duplicates
if len ( listByFullName ) == 1 {
for _ , v := range listByFullName {
return httputils . WriteJSON ( w , http . StatusOK , v )
2016-06-13 22:52:49 -04:00
}
2015-09-25 06:19:17 -04:00
}
2017-01-19 06:04:26 -05:00
if len ( listByFullName ) > 1 {
2017-07-19 10:20:13 -04:00
return errors . Wrapf ( ambigousResultsError ( term ) , "%d matches found based on name" , len ( listByFullName ) )
2017-01-19 06:04:26 -05:00
}
// Find based on partial ID, returns true only if no duplicates
if len ( listByPartialID ) == 1 {
for _ , v := range listByPartialID {
return httputils . WriteJSON ( w , http . StatusOK , v )
}
}
if len ( listByPartialID ) > 1 {
2017-07-19 10:20:13 -04:00
return errors . Wrapf ( ambigousResultsError ( term ) , "%d matches found based on ID prefix" , len ( listByPartialID ) )
2017-01-19 06:04:26 -05:00
}
return libnetwork . ErrNoSuchNetwork ( term )
2015-09-25 06:19:17 -04:00
}
func ( n * networkRouter ) postNetworkCreate ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
2016-04-13 04:33:46 -04:00
var create types . NetworkCreateRequest
2015-09-25 06:19:17 -04:00
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
if err := httputils . CheckForJSON ( r ) ; err != nil {
return err
}
if err := json . NewDecoder ( r . Body ) . Decode ( & create ) ; err != nil {
2018-11-05 08:50:33 -05:00
if err == io . EOF {
return errdefs . InvalidParameter ( errors . New ( "got EOF while reading request body" ) )
}
return errdefs . InvalidParameter ( err )
2015-09-25 06:19:17 -04:00
}
2016-12-26 07:53:13 -05:00
if nws , err := n . cluster . GetNetworksByName ( create . Name ) ; err == nil && len ( nws ) > 0 {
2017-09-28 22:13:44 -04:00
return nameConflict ( create . Name )
2016-06-29 21:08:55 -04:00
}
2016-03-28 13:41:06 -04:00
nw , err := n . backend . CreateNetwork ( create )
2015-09-25 06:19:17 -04:00
if err != nil {
2017-01-19 01:49:10 -05:00
var warning string
if _ , ok := err . ( libnetwork . NetworkNameError ) ; ok {
// check if user defined CheckDuplicate, if set true, return err
// otherwise prepare a warning message
if create . CheckDuplicate {
2017-09-28 22:13:44 -04:00
return nameConflict ( create . Name )
2017-01-19 01:49:10 -05:00
}
warning = libnetwork . NetworkNameError ( create . Name ) . Error ( )
}
2016-06-13 22:52:49 -04:00
if _ , ok := err . ( libnetwork . ManagerRedirectError ) ; ! ok {
return err
}
2016-12-26 07:53:13 -05:00
id , err := n . cluster . CreateNetwork ( create )
2016-06-13 22:52:49 -04:00
if err != nil {
return err
}
2017-01-19 01:49:10 -05:00
nw = & types . NetworkCreateResponse {
ID : id ,
Warning : warning ,
}
2015-09-25 06:19:17 -04:00
}
2016-03-28 13:41:06 -04:00
return httputils . WriteJSON ( w , http . StatusCreated , nw )
2015-09-25 06:19:17 -04:00
}
func ( n * networkRouter ) postNetworkConnect ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
var connect types . NetworkConnect
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
if err := httputils . CheckForJSON ( r ) ; err != nil {
return err
}
if err := json . NewDecoder ( r . Body ) . Decode ( & connect ) ; err != nil {
2018-11-05 08:50:33 -05:00
if err == io . EOF {
return errdefs . InvalidParameter ( errors . New ( "got EOF while reading request body" ) )
}
return errdefs . InvalidParameter ( err )
2015-09-25 06:19:17 -04:00
}
2018-01-09 01:41:48 -05:00
// Unlike other operations, we does not check ambiguity of the name/ID here.
// The reason is that, In case of attachable network in swarm scope, the actual local network
// may not be available at the time. At the same time, inside daemon `ConnectContainerToNetwork`
// does the ambiguity check anyway. Therefore, passing the name to daemon would be enough.
return n . backend . ConnectContainerToNetwork ( connect . Container , vars [ "id" ] , connect . EndpointConfig )
2015-09-25 06:19:17 -04:00
}
func ( n * networkRouter ) postNetworkDisconnect ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
var disconnect types . NetworkDisconnect
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
if err := httputils . CheckForJSON ( r ) ; err != nil {
return err
}
if err := json . NewDecoder ( r . Body ) . Decode ( & disconnect ) ; err != nil {
2018-11-05 08:50:33 -05:00
if err == io . EOF {
return errdefs . InvalidParameter ( errors . New ( "got EOF while reading request body" ) )
}
return errdefs . InvalidParameter ( err )
2015-09-25 06:19:17 -04:00
}
2016-08-26 16:08:28 -04:00
return n . backend . DisconnectContainerFromNetwork ( disconnect . Container , vars [ "id" ] , disconnect . Force )
2015-09-25 06:19:17 -04:00
}
func ( n * networkRouter ) deleteNetwork ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
2016-03-05 11:41:51 -05:00
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
2017-10-31 15:46:53 -04:00
nw , err := n . findUniqueNetwork ( vars [ "id" ] )
if err != nil {
return err
}
if nw . Scope == "swarm" {
if err = n . cluster . RemoveNetwork ( nw . ID ) ; err != nil {
return err
}
} else {
if err := n . backend . DeleteNetwork ( nw . ID ) ; err != nil {
2016-09-27 14:04:36 -04:00
return err
}
2016-03-05 11:41:51 -05:00
}
w . WriteHeader ( http . StatusNoContent )
return nil
2015-09-25 06:19:17 -04:00
}
2016-10-18 00:36:52 -04:00
func ( n * networkRouter ) postNetworksPrune ( ctx context . Context , w http . ResponseWriter , r * http . Request , vars map [ string ] string ) error {
if err := httputils . ParseForm ( r ) ; err != nil {
return err
}
2017-09-26 07:59:45 -04:00
pruneFilters , err := filters . FromJSON ( r . Form . Get ( "filters" ) )
2016-12-07 17:02:13 -05:00
if err != nil {
return err
}
2017-04-11 15:52:33 -04:00
pruneReport , err := n . backend . NetworksPrune ( ctx , pruneFilters )
2016-10-18 00:36:52 -04:00
if err != nil {
return err
}
return httputils . WriteJSON ( w , http . StatusOK , pruneReport )
}
2017-10-31 15:46:53 -04:00
// findUniqueNetwork will search network across different scopes (both local and swarm).
2018-01-15 12:26:43 -05:00
// NOTE: This findUniqueNetwork is different from FindNetwork in the daemon.
2017-10-31 15:46:53 -04:00
// In case multiple networks have duplicate names, return error.
// First find based on full ID, return immediately once one is found.
// If a network appears both in swarm and local, assume it is in local first
// For full name and partial ID, save the result first, and process later
// in case multiple records was found based on the same term
// TODO (yongtang): should we wrap with version here for backward compatibility?
func ( n * networkRouter ) findUniqueNetwork ( term string ) ( types . NetworkResource , error ) {
listByFullName := map [ string ] types . NetworkResource { }
listByPartialID := map [ string ] types . NetworkResource { }
2018-05-23 10:03:02 -04:00
filter := filters . NewArgs ( filters . Arg ( "idOrName" , term ) )
nw , _ := n . backend . GetNetworks ( filter , types . NetworkListConfig { Detailed : true } )
2017-10-31 15:46:53 -04:00
for _ , network := range nw {
2018-05-23 10:03:02 -04:00
if network . ID == term {
return network , nil
2017-10-31 15:46:53 -04:00
}
2018-05-23 10:03:02 -04:00
if network . Name == term && ! network . Ingress {
2017-10-31 15:46:53 -04:00
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
2018-05-23 10:03:02 -04:00
listByFullName [ network . ID ] = network
2017-10-31 15:46:53 -04:00
}
2018-05-23 10:03:02 -04:00
if strings . HasPrefix ( network . ID , term ) {
2017-10-31 15:46:53 -04:00
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
2018-05-23 10:03:02 -04:00
listByPartialID [ network . ID ] = network
2017-10-31 15:46:53 -04:00
}
}
2018-05-23 10:03:02 -04:00
nr , _ := n . cluster . GetNetworks ( filter )
2017-10-31 15:46:53 -04:00
for _ , network := range nr {
if network . ID == term {
return network , nil
}
if network . Name == term {
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByFullName) may have already had a
// network with the same ID (from local scope previously)
if _ , ok := listByFullName [ network . ID ] ; ! ok {
listByFullName [ network . ID ] = network
}
}
if strings . HasPrefix ( network . ID , term ) {
// Check the ID collision as we are in swarm scope here, and
// the map (of the listByPartialID) may have already had a
// network with the same ID (from local scope previously)
if _ , ok := listByPartialID [ network . ID ] ; ! ok {
listByPartialID [ network . ID ] = network
}
}
}
// Find based on full name, returns true only if no duplicates
if len ( listByFullName ) == 1 {
for _ , v := range listByFullName {
return v , nil
}
}
if len ( listByFullName ) > 1 {
2017-11-28 23:09:37 -05:00
return types . NetworkResource { } , errdefs . InvalidParameter ( errors . Errorf ( "network %s is ambiguous (%d matches found based on name)" , term , len ( listByFullName ) ) )
2017-10-31 15:46:53 -04:00
}
// Find based on partial ID, returns true only if no duplicates
if len ( listByPartialID ) == 1 {
for _ , v := range listByPartialID {
return v , nil
}
}
if len ( listByPartialID ) > 1 {
2017-11-28 23:09:37 -05:00
return types . NetworkResource { } , errdefs . InvalidParameter ( errors . Errorf ( "network %s is ambiguous (%d matches found based on ID prefix)" , term , len ( listByPartialID ) ) )
2017-10-31 15:46:53 -04:00
}
2017-11-28 23:09:37 -05:00
return types . NetworkResource { } , errdefs . NotFound ( libnetwork . ErrNoSuchNetwork ( term ) )
2017-10-31 15:46:53 -04:00
}