Bladeren bron

daemon: Return all validation errors for NetworkingConfig and EndpointSettings

Thus far, validation code would stop as soon as a bad value was found.
Now, we try to validate as much as we can, to return all errors to the
API client.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
Albin Kerouanton 1 jaar geleden
bovenliggende
commit
4bd0553274
3 gewijzigde bestanden met toevoegingen van 21 en 9 verwijderingen
  1. 10 7
      daemon/container_operations.go
  2. 9 2
      daemon/create.go
  3. 2 0
      docs/api/version-history.md

+ 10 - 7
daemon/container_operations.go

@@ -18,6 +18,7 @@ import (
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
+	"github.com/docker/docker/internal/multierror"
 	"github.com/docker/docker/libnetwork"
 	"github.com/docker/docker/libnetwork"
 	"github.com/docker/docker/libnetwork/netlabel"
 	"github.com/docker/docker/libnetwork/netlabel"
 	"github.com/docker/docker/libnetwork/options"
 	"github.com/docker/docker/libnetwork/options"
@@ -575,30 +576,32 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n
 		ipamConfig = epConfig.IPAMConfig
 		ipamConfig = epConfig.IPAMConfig
 	}
 	}
 
 
+	var errs []error
+
 	if !containertypes.NetworkMode(nwName).IsUserDefined() {
 	if !containertypes.NetworkMode(nwName).IsUserDefined() {
 		hasStaticAddresses := ipamConfig.IPv4Address != "" || ipamConfig.IPv6Address != ""
 		hasStaticAddresses := ipamConfig.IPv4Address != "" || ipamConfig.IPv6Address != ""
 		// On Linux, user specified IP address is accepted only by networks with user specified subnets.
 		// On Linux, user specified IP address is accepted only by networks with user specified subnets.
 		if hasStaticAddresses && !enableIPOnPredefinedNetwork() {
 		if hasStaticAddresses && !enableIPOnPredefinedNetwork() {
-			return runconfig.ErrUnsupportedNetworkAndIP
+			errs = append(errs, runconfig.ErrUnsupportedNetworkAndIP)
 		}
 		}
 		if len(epConfig.Aliases) > 0 && !serviceDiscoveryOnDefaultNetwork() {
 		if len(epConfig.Aliases) > 0 && !serviceDiscoveryOnDefaultNetwork() {
-			return runconfig.ErrUnsupportedNetworkAndAlias
+			errs = append(errs, runconfig.ErrUnsupportedNetworkAndAlias)
 		}
 		}
 	}
 	}
 
 
 	if ipamConfig.IPv4Address != "" {
 	if ipamConfig.IPv4Address != "" {
 		if addr := net.ParseIP(ipamConfig.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
 		if addr := net.ParseIP(ipamConfig.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
-			return fmt.Errorf("invalid IPv4 address: %s", ipamConfig.IPv4Address)
+			errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", ipamConfig.IPv4Address))
 		}
 		}
 	}
 	}
 	if ipamConfig.IPv6Address != "" {
 	if ipamConfig.IPv6Address != "" {
 		if addr := net.ParseIP(ipamConfig.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
 		if addr := net.ParseIP(ipamConfig.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
-			return fmt.Errorf("invalid IPv6 address: %s", ipamConfig.IPv6Address)
+			errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", ipamConfig.IPv6Address))
 		}
 		}
 	}
 	}
 
 
 	if nw == nil {
 	if nw == nil {
-		return nil
+		return multierror.Join(errs...)
 	}
 	}
 
 
 	_, _, nwIPv4Configs, nwIPv6Configs := nw.IpamConfig()
 	_, _, nwIPv4Configs, nwIPv6Configs := nw.IpamConfig()
@@ -624,12 +627,12 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n
 				}
 				}
 			}
 			}
 			if !foundSubnet {
 			if !foundSubnet {
-				return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
+				errs = append(errs, runconfig.ErrUnsupportedNetworkNoSubnetAndIP)
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	return nil
+	return multierror.Join(errs...)
 }
 }
 
 
 // cleanOperationalData resets the operational data from the passed endpoint settings
 // cleanOperationalData resets the operational data from the passed endpoint settings

+ 9 - 2
daemon/create.go

@@ -20,6 +20,7 @@ import (
 	"github.com/docker/docker/daemon/images"
 	"github.com/docker/docker/daemon/images"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
+	"github.com/docker/docker/internal/multierror"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -326,18 +327,24 @@ func (daemon *Daemon) validateNetworkingConfig(nwConfig *networktypes.Networking
 		return nil
 		return nil
 	}
 	}
 
 
+	var errs []error
 	for k, v := range nwConfig.EndpointsConfig {
 	for k, v := range nwConfig.EndpointsConfig {
 		if v == nil {
 		if v == nil {
-			return fmt.Errorf("no EndpointSettings for %s", k)
+			errs = append(errs, fmt.Errorf("invalid config for network %s: EndpointsConfig is nil", k))
+			continue
 		}
 		}
 
 
 		// The referenced network k might not exist when the container is created, so just ignore the error in that case.
 		// The referenced network k might not exist when the container is created, so just ignore the error in that case.
 		nw, _ := daemon.FindNetwork(k)
 		nw, _ := daemon.FindNetwork(k)
 		if err := validateEndpointSettings(nw, k, v); err != nil {
 		if err := validateEndpointSettings(nw, k, v); err != nil {
-			return err
+			errs = append(errs, fmt.Errorf("invalid config for network %s: %w", k, err))
 		}
 		}
 	}
 	}
 
 
+	if len(errs) > 0 {
+		return errdefs.InvalidParameter(multierror.Join(errs...))
+	}
+
 	return nil
 	return nil
 }
 }
 
 

+ 2 - 0
docs/api/version-history.md

@@ -49,6 +49,8 @@ keywords: "API, Docker, rcli, REST, documentation"
   `NetworkingConfig.EndpointSettings`.
   `NetworkingConfig.EndpointSettings`.
 * `POST /containers/create` and `POST /networks/{id}/connect` will now catch
 * `POST /containers/create` and `POST /networks/{id}/connect` will now catch
   validation errors that were previously only returned during `POST /containers/{id}/start`.
   validation errors that were previously only returned during `POST /containers/{id}/start`.
+  These endpoints will also return the full set of validation errors they find,
+  instead of returning only the first one.
   Note that this change is _unversioned_ and applies to all API versions.
   Note that this change is _unversioned_ and applies to all API versions.
 
 
 ## v1.43 API changes
 ## v1.43 API changes