Browse Source

Merge pull request #22861 from vdemeester/daemon-images-search-refactoring

Daemon images search refactoring
Sebastiaan van Stijn 9 years ago
parent
commit
75109b32db

+ 1 - 1
api/client/run.go

@@ -234,7 +234,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	}
 
 	//start the container
-	if err := cli.client.ContainerStart(ctx, createResponse.ID); err != nil {
+	if err := cli.client.ContainerStart(ctx, createResponse.ID, ""); err != nil {
 		// If we have holdHijackedConnection, we should notify
 		// holdHijackedConnection we are going to exit and wait
 		// to avoid the terminal are not restored.

+ 2 - 2
api/client/start.go

@@ -113,7 +113,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		})
 
 		// 3. Start the container.
-		if err := cli.client.ContainerStart(ctx, container); err != nil {
+		if err := cli.client.ContainerStart(ctx, container, ""); err != nil {
 			cancelFun()
 			<-cErr
 			return err
@@ -147,7 +147,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containers []string) error {
 	var failedContainers []string
 	for _, container := range containers {
-		if err := cli.client.ContainerStart(ctx, container); err != nil {
+		if err := cli.client.ContainerStart(ctx, container, ""); err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
 			failedContainers = append(failedContainers, container)
 		} else {

+ 3 - 87
daemon/daemon.go

@@ -16,7 +16,6 @@ import (
 	"path/filepath"
 	"regexp"
 	"runtime"
-	"strconv"
 	"strings"
 	"sync"
 	"syscall"
@@ -32,7 +31,6 @@ import (
 	"github.com/docker/engine-api/types"
 	containertypes "github.com/docker/engine-api/types/container"
 	networktypes "github.com/docker/engine-api/types/network"
-	registrytypes "github.com/docker/engine-api/types/registry"
 	"github.com/docker/engine-api/types/strslice"
 	// register graph drivers
 	_ "github.com/docker/docker/daemon/graphdriver/register"
@@ -60,7 +58,6 @@ import (
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/docker/volume/store"
-	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/libnetwork"
 	nwconfig "github.com/docker/libnetwork/config"
@@ -87,7 +84,7 @@ type Daemon struct {
 	configStore               *Config
 	statsCollector            *statsCollector
 	defaultLogConfig          containertypes.LogConfig
-	RegistryService           *registry.Service
+	RegistryService           registry.Service
 	EventsService             *events.Events
 	netController             libnetwork.NetworkController
 	volumes                   *store.VolumeStore
@@ -374,7 +371,7 @@ func (daemon *Daemon) registerLink(parent, child *container.Container, alias str
 
 // NewDaemon sets up everything for the daemon to be able to service
 // requests from the webserver.
-func NewDaemon(config *Config, registryService *registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
+func NewDaemon(config *Config, registryService registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
 	setDefaultMtu(config)
 
 	// Ensure we have compatible and valid configuration options
@@ -888,88 +885,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
 
 // AuthenticateToRegistry checks the validity of credentials in authConfig
 func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
-	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
-}
-
-var acceptedSearchFilterTags = map[string]bool{
-	"is-automated": true,
-	"is-official":  true,
-	"stars":        true,
-}
-
-// SearchRegistryForImages queries the registry for images matching
-// term. authConfig is used to login.
-func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
-	authConfig *types.AuthConfig,
-	headers map[string][]string) (*registrytypes.SearchResults, error) {
-
-	searchFilters, err := filters.FromParam(filtersArgs)
-	if err != nil {
-		return nil, err
-	}
-	if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
-		return nil, err
-	}
-
-	unfilteredResult, err := daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
-	if err != nil {
-		return nil, err
-	}
-
-	var isAutomated, isOfficial bool
-	var hasStarFilter = 0
-	if searchFilters.Include("is-automated") {
-		if searchFilters.ExactMatch("is-automated", "true") {
-			isAutomated = true
-		} else if !searchFilters.ExactMatch("is-automated", "false") {
-			return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
-		}
-	}
-	if searchFilters.Include("is-official") {
-		if searchFilters.ExactMatch("is-official", "true") {
-			isOfficial = true
-		} else if !searchFilters.ExactMatch("is-official", "false") {
-			return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
-		}
-	}
-	if searchFilters.Include("stars") {
-		hasStars := searchFilters.Get("stars")
-		for _, hasStar := range hasStars {
-			iHasStar, err := strconv.Atoi(hasStar)
-			if err != nil {
-				return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
-			}
-			if iHasStar > hasStarFilter {
-				hasStarFilter = iHasStar
-			}
-		}
-	}
-
-	filteredResults := []registrytypes.SearchResult{}
-	for _, result := range unfilteredResult.Results {
-		if searchFilters.Include("is-automated") {
-			if isAutomated != result.IsAutomated {
-				continue
-			}
-		}
-		if searchFilters.Include("is-official") {
-			if isOfficial != result.IsOfficial {
-				continue
-			}
-		}
-		if searchFilters.Include("stars") {
-			if result.StarCount < hasStarFilter {
-				continue
-			}
-		}
-		filteredResults = append(filteredResults, result)
-	}
-
-	return &registrytypes.SearchResults{
-		Query:      unfilteredResult.Query,
-		NumResults: len(filteredResults),
-		Results:    filteredResults,
-	}, nil
+	return daemon.RegistryService.Auth(ctx, authConfig, dockerversion.DockerUserAgent(ctx))
 }
 
 // IsShuttingDown tells whether the daemon is shutting down or not

+ 94 - 0
daemon/search.go

@@ -0,0 +1,94 @@
+package daemon
+
+import (
+	"fmt"
+	"strconv"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/dockerversion"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/filters"
+	registrytypes "github.com/docker/engine-api/types/registry"
+)
+
+var acceptedSearchFilterTags = map[string]bool{
+	"is-automated": true,
+	"is-official":  true,
+	"stars":        true,
+}
+
+// SearchRegistryForImages queries the registry for images matching
+// term. authConfig is used to login.
+func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
+	authConfig *types.AuthConfig,
+	headers map[string][]string) (*registrytypes.SearchResults, error) {
+
+	searchFilters, err := filters.FromParam(filtersArgs)
+	if err != nil {
+		return nil, err
+	}
+	if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
+		return nil, err
+	}
+
+	unfilteredResult, err := daemon.RegistryService.Search(ctx, term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
+	if err != nil {
+		return nil, err
+	}
+
+	var isAutomated, isOfficial bool
+	var hasStarFilter = 0
+	if searchFilters.Include("is-automated") {
+		if searchFilters.UniqueExactMatch("is-automated", "true") {
+			isAutomated = true
+		} else if !searchFilters.UniqueExactMatch("is-automated", "false") {
+			return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
+		}
+	}
+	if searchFilters.Include("is-official") {
+		if searchFilters.UniqueExactMatch("is-official", "true") {
+			isOfficial = true
+		} else if !searchFilters.UniqueExactMatch("is-official", "false") {
+			return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
+		}
+	}
+	if searchFilters.Include("stars") {
+		hasStars := searchFilters.Get("stars")
+		for _, hasStar := range hasStars {
+			iHasStar, err := strconv.Atoi(hasStar)
+			if err != nil {
+				return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
+			}
+			if iHasStar > hasStarFilter {
+				hasStarFilter = iHasStar
+			}
+		}
+	}
+
+	filteredResults := []registrytypes.SearchResult{}
+	for _, result := range unfilteredResult.Results {
+		if searchFilters.Include("is-automated") {
+			if isAutomated != result.IsAutomated {
+				continue
+			}
+		}
+		if searchFilters.Include("is-official") {
+			if isOfficial != result.IsOfficial {
+				continue
+			}
+		}
+		if searchFilters.Include("stars") {
+			if result.StarCount < hasStarFilter {
+				continue
+			}
+		}
+		filteredResults = append(filteredResults, result)
+	}
+
+	return &registrytypes.SearchResults{
+		Query:      unfilteredResult.Query,
+		NumResults: len(filteredResults),
+		Results:    filteredResults,
+	}, nil
+}

+ 357 - 0
daemon/search_test.go

@@ -0,0 +1,357 @@
+package daemon
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/registry"
+	"github.com/docker/engine-api/types"
+	registrytypes "github.com/docker/engine-api/types/registry"
+)
+
+type FakeService struct {
+	registry.DefaultService
+
+	shouldReturnError bool
+
+	term    string
+	results []registrytypes.SearchResult
+}
+
+func (s *FakeService) Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
+	if s.shouldReturnError {
+		return nil, fmt.Errorf("Search unknown error")
+	}
+	return &registrytypes.SearchResults{
+		Query:      s.term,
+		NumResults: len(s.results),
+		Results:    s.results,
+	}, nil
+}
+
+func TestSearchRegistryForImagesErrors(t *testing.T) {
+	errorCases := []struct {
+		filtersArgs       string
+		shouldReturnError bool
+		expectedError     string
+	}{
+		{
+			expectedError:     "Search unknown error",
+			shouldReturnError: true,
+		},
+		{
+			filtersArgs:   "invalid json",
+			expectedError: "invalid character 'i' looking for beginning of value",
+		},
+		{
+			filtersArgs:   `{"type":{"custom":true}}`,
+			expectedError: "Invalid filter 'type'",
+		},
+		{
+			filtersArgs:   `{"is-automated":{"invalid":true}}`,
+			expectedError: "Invalid filter 'is-automated=[invalid]'",
+		},
+		{
+			filtersArgs:   `{"is-automated":{"true":true,"false":true}}`,
+			expectedError: "Invalid filter 'is-automated",
+		},
+		{
+			filtersArgs:   `{"is-official":{"invalid":true}}`,
+			expectedError: "Invalid filter 'is-official=[invalid]'",
+		},
+		{
+			filtersArgs:   `{"is-official":{"true":true,"false":true}}`,
+			expectedError: "Invalid filter 'is-official",
+		},
+		{
+			filtersArgs:   `{"stars":{"invalid":true}}`,
+			expectedError: "Invalid filter 'stars=invalid'",
+		},
+		{
+			filtersArgs:   `{"stars":{"1":true,"invalid":true}}`,
+			expectedError: "Invalid filter 'stars=invalid'",
+		},
+	}
+	for index, e := range errorCases {
+		daemon := &Daemon{
+			RegistryService: &FakeService{
+				shouldReturnError: e.shouldReturnError,
+			},
+		}
+		_, err := daemon.SearchRegistryForImages(context.Background(), e.filtersArgs, "term", nil, map[string][]string{})
+		if err == nil {
+			t.Errorf("%d: expected an error, got nothing", index)
+		}
+		if !strings.Contains(err.Error(), e.expectedError) {
+			t.Errorf("%d: expected error to contain %s, got %s", index, e.expectedError, err.Error())
+		}
+	}
+}
+
+func TestSearchRegistryForImages(t *testing.T) {
+	term := "term"
+	successCases := []struct {
+		filtersArgs     string
+		registryResults []registrytypes.SearchResult
+		expectedResults []registrytypes.SearchResult
+	}{
+		{
+			filtersArgs:     "",
+			registryResults: []registrytypes.SearchResult{},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: "",
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+				},
+			},
+		},
+		{
+			filtersArgs: `{"is-automated":{"true":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: `{"is-automated":{"true":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsAutomated: true,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsAutomated: true,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"is-automated":{"false":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsAutomated: true,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: `{"is-automated":{"false":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsAutomated: false,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsAutomated: false,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"is-official":{"true":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: `{"is-official":{"true":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsOfficial:  true,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsOfficial:  true,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"is-official":{"false":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsOfficial:  true,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: `{"is-official":{"false":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsOfficial:  false,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					IsOfficial:  false,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"stars":{"0":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					StarCount:   0,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					StarCount:   0,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"stars":{"1":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name",
+					Description: "description",
+					StarCount:   0,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{},
+		},
+		{
+			filtersArgs: `{"stars":{"1":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name0",
+					Description: "description0",
+					StarCount:   0,
+				},
+				{
+					Name:        "name1",
+					Description: "description1",
+					StarCount:   1,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name1",
+					Description: "description1",
+					StarCount:   1,
+				},
+			},
+		},
+		{
+			filtersArgs: `{"stars":{"1":true}, "is-official":{"true":true}, "is-automated":{"true":true}}`,
+			registryResults: []registrytypes.SearchResult{
+				{
+					Name:        "name0",
+					Description: "description0",
+					StarCount:   0,
+					IsOfficial:  true,
+					IsAutomated: true,
+				},
+				{
+					Name:        "name1",
+					Description: "description1",
+					StarCount:   1,
+					IsOfficial:  false,
+					IsAutomated: true,
+				},
+				{
+					Name:        "name2",
+					Description: "description2",
+					StarCount:   1,
+					IsOfficial:  true,
+					IsAutomated: false,
+				},
+				{
+					Name:        "name3",
+					Description: "description3",
+					StarCount:   2,
+					IsOfficial:  true,
+					IsAutomated: true,
+				},
+			},
+			expectedResults: []registrytypes.SearchResult{
+				{
+					Name:        "name3",
+					Description: "description3",
+					StarCount:   2,
+					IsOfficial:  true,
+					IsAutomated: true,
+				},
+			},
+		},
+	}
+	for index, s := range successCases {
+		daemon := &Daemon{
+			RegistryService: &FakeService{
+				term:    term,
+				results: s.registryResults,
+			},
+		}
+		results, err := daemon.SearchRegistryForImages(context.Background(), s.filtersArgs, term, nil, map[string][]string{})
+		if err != nil {
+			t.Errorf("%d: %v", index, err)
+		}
+		if results.Query != term {
+			t.Errorf("%d: expected Query to be %s, got %s", index, term, results.Query)
+		}
+		if results.NumResults != len(s.expectedResults) {
+			t.Errorf("%d: expected NumResults to be %d, got %d", index, len(s.expectedResults), results.NumResults)
+		}
+		for _, result := range results.Results {
+			found := false
+			for _, expectedResult := range s.expectedResults {
+				if expectedResult.Name == result.Name &&
+					expectedResult.Description == result.Description &&
+					expectedResult.IsAutomated == result.IsAutomated &&
+					expectedResult.IsOfficial == result.IsOfficial &&
+					expectedResult.StarCount == result.StarCount {
+					found = true
+				}
+			}
+			if !found {
+				t.Errorf("%d: expected results %v, got %v", index, s.expectedResults, results.Results)
+			}
+		}
+	}
+}

+ 1 - 1
distribution/pull.go

@@ -27,7 +27,7 @@ type ImagePullConfig struct {
 	ProgressOutput progress.Output
 	// RegistryService is the registry service to use for TLS configuration
 	// and endpoint lookup.
-	RegistryService *registry.Service
+	RegistryService registry.Service
 	// ImageEventLogger notifies events for a given image
 	ImageEventLogger func(id, name, action string)
 	// MetadataStore is the storage backend for distribution-specific

+ 1 - 1
distribution/push.go

@@ -31,7 +31,7 @@ type ImagePushConfig struct {
 	ProgressOutput progress.Output
 	// RegistryService is the registry service to use for TLS configuration
 	// and endpoint lookup.
-	RegistryService *registry.Service
+	RegistryService registry.Service
 	// ImageEventLogger notifies events for a given image
 	ImageEventLogger func(id, name, action string)
 	// MetadataStore is the storage backend for distribution-specific

+ 1 - 1
hack/vendor.sh

@@ -60,7 +60,7 @@ clone git golang.org/x/net 78cb2c067747f08b343f20614155233ab4ea2ad3 https://gith
 clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-connections v0.2.0
-clone git github.com/docker/engine-api e374c4fb5b121a8fd4295ec5eb91a8068c6304f4
+clone git github.com/docker/engine-api 12fbeb3ac3ca5dc5d0f01d6bac9bda518d46d983
 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
 clone git github.com/imdario/mergo 0.2.1
 

+ 1 - 1
registry/registry_test.go

@@ -661,7 +661,7 @@ func TestMirrorEndpointLookup(t *testing.T) {
 		}
 		return false
 	}
-	s := Service{config: makeServiceConfig([]string{"my.mirror"}, nil)}
+	s := DefaultService{config: makeServiceConfig([]string{"my.mirror"}, nil)}
 
 	imageName, err := reference.WithName(IndexName + "/test/image")
 	if err != nil {

+ 31 - 15
registry/service.go

@@ -7,35 +7,50 @@ import (
 	"net/url"
 	"strings"
 
+	"golang.org/x/net/context"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 	registrytypes "github.com/docker/engine-api/types/registry"
 )
 
-// Service is a registry service. It tracks configuration data such as a list
+// Service is the interface defining what a registry service should implement.
+type Service interface {
+	Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
+	LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
+	LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
+	ResolveRepository(name reference.Named) (*RepositoryInfo, error)
+	ResolveIndex(name string) (*registrytypes.IndexInfo, error)
+	Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
+	ServiceConfig() *registrytypes.ServiceConfig
+	TLSConfig(hostname string) (*tls.Config, error)
+}
+
+// DefaultService is a registry service. It tracks configuration data such as a list
 // of mirrors.
-type Service struct {
+type DefaultService struct {
 	config *serviceConfig
 }
 
-// NewService returns a new instance of Service ready to be
+// NewService returns a new instance of DefaultService ready to be
 // installed into an engine.
-func NewService(options ServiceOptions) *Service {
-	return &Service{
+func NewService(options ServiceOptions) *DefaultService {
+	return &DefaultService{
 		config: newServiceConfig(options),
 	}
 }
 
 // ServiceConfig returns the public registry service configuration.
-func (s *Service) ServiceConfig() *registrytypes.ServiceConfig {
+func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 	return &s.config.ServiceConfig
 }
 
 // Auth contacts the public registry with the provided credentials,
 // and returns OK if authentication was successful.
 // It can be used to verify the validity of a client's credentials.
-func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
+func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
+	// TODO Use ctx when searching for repositories
 	serverAddress := authConfig.ServerAddress
 	if serverAddress == "" {
 		serverAddress = IndexServer
@@ -93,7 +108,8 @@ func splitReposSearchTerm(reposName string) (string, string) {
 
 // Search queries the public registry for images matching the specified
 // search terms, and returns the results.
-func (s *Service) Search(term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
+func (s *DefaultService) Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
+	// TODO Use ctx when searching for repositories
 	if err := validateNoScheme(term); err != nil {
 		return nil, err
 	}
@@ -130,12 +146,12 @@ func (s *Service) Search(term string, authConfig *types.AuthConfig, userAgent st
 
 // ResolveRepository splits a repository name into its components
 // and configuration of the associated registry.
-func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
+func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
 	return newRepositoryInfo(s.config, name)
 }
 
 // ResolveIndex takes indexName and returns index info
-func (s *Service) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
+func (s *DefaultService) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
 	return newIndexInfo(s.config, name)
 }
 
@@ -155,25 +171,25 @@ func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V
 }
 
 // TLSConfig constructs a client TLS configuration based on server defaults
-func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
+func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
 	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
 }
 
-func (s *Service) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
+func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
 	return s.TLSConfig(mirrorURL.Host)
 }
 
 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
 // It gives preference to v2 endpoints over v1, mirrors over the actual
 // registry, and HTTPS over plain HTTP.
-func (s *Service) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	return s.lookupEndpoints(hostname)
 }
 
 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
 // Mirrors are not included.
-func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	allEndpoints, err := s.lookupEndpoints(hostname)
 	if err == nil {
 		for _, endpoint := range allEndpoints {
@@ -185,7 +201,7 @@ func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint,
 	return endpoints, err
 }
 
-func (s *Service) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	endpoints, err = s.lookupV2Endpoints(hostname)
 	if err != nil {
 		return nil, err

+ 1 - 1
registry/service_v1.go

@@ -6,7 +6,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 )
 
-func (s *Service) lookupV1Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *DefaultService) lookupV1Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	var cfg = tlsconfig.ServerDefault
 	tlsConfig := &cfg
 	if hostname == DefaultNamespace {

+ 1 - 1
registry/service_v2.go

@@ -7,7 +7,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 )
 
-func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
+func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
 	var cfg = tlsconfig.ServerDefault
 	tlsConfig := &cfg
 	if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host {

+ 13 - 0
vendor/src/github.com/docker/engine-api/client/checkpoint_create.go

@@ -0,0 +1,13 @@
+package client
+
+import (
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// CheckpointCreate creates a checkpoint from the given container with the given name
+func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
+	resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 12 - 0
vendor/src/github.com/docker/engine-api/client/checkpoint_delete.go

@@ -0,0 +1,12 @@
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// CheckpointDelete deletes the checkpoint with the given name from the given container
+func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error {
+	resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+checkpointID, nil, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 22 - 0
vendor/src/github.com/docker/engine-api/client/checkpoint_list.go

@@ -0,0 +1,22 @@
+package client
+
+import (
+	"encoding/json"
+
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// CheckpointList returns the volumes configured in the docker host.
+func (cli *Client) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) {
+	var checkpoints []types.Checkpoint
+
+	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", nil, nil)
+	if err != nil {
+		return checkpoints, err
+	}
+
+	err = json.NewDecoder(resp.body).Decode(&checkpoints)
+	ensureReaderClosed(resp)
+	return checkpoints, err
+}

+ 0 - 11
vendor/src/github.com/docker/engine-api/client/container_inspect.go

@@ -52,14 +52,3 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
 	err = json.NewDecoder(rdr).Decode(&response)
 	return response, body, err
 }
-
-func (cli *Client) containerInspectWithResponse(ctx context.Context, containerID string, query url.Values) (types.ContainerJSON, *serverResponse, error) {
-	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
-	if err != nil {
-		return types.ContainerJSON{}, serverResp, err
-	}
-
-	var response types.ContainerJSON
-	err = json.NewDecoder(serverResp.body).Decode(&response)
-	return response, serverResp, err
-}

+ 10 - 3
vendor/src/github.com/docker/engine-api/client/container_start.go

@@ -1,10 +1,17 @@
 package client
 
-import "golang.org/x/net/context"
+import (
+	"net/url"
+
+	"golang.org/x/net/context"
+)
 
 // ContainerStart sends a request to the docker daemon to start a container.
-func (cli *Client) ContainerStart(ctx context.Context, containerID string) error {
-	resp, err := cli.post(ctx, "/containers/"+containerID+"/start", nil, nil, nil)
+func (cli *Client) ContainerStart(ctx context.Context, containerID string, checkpointID string) error {
+	query := url.Values{}
+	query.Set("checkpoint", checkpointID)
+
+	resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
 	ensureReaderClosed(resp)
 	return err
 }

+ 1 - 1
vendor/src/github.com/docker/engine-api/client/events.go

@@ -33,7 +33,7 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (io.
 		query.Set("until", ts)
 	}
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 			return nil, err
 		}

+ 1 - 1
vendor/src/github.com/docker/engine-api/client/image_list.go

@@ -15,7 +15,7 @@ func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions
 	query := url.Values{}
 
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 			return images, err
 		}

+ 4 - 1
vendor/src/github.com/docker/engine-api/client/interface.go

@@ -15,6 +15,9 @@ import (
 // APIClient is an interface that clients that talk with a docker server must implement.
 type APIClient interface {
 	ClientVersion() string
+	CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error
+	CheckpointDelete(ctx context.Context, container string, checkpointID string) error
+	CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error)
 	ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
 	ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
 	ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error)
@@ -37,7 +40,7 @@ type APIClient interface {
 	ContainerRestart(ctx context.Context, container string, timeout int) error
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
 	ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error)
-	ContainerStart(ctx context.Context, container string) error
+	ContainerStart(ctx context.Context, container string, checkpointID string) error
 	ContainerStop(ctx context.Context, container string, timeout int) error
 	ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
 	ContainerUnpause(ctx context.Context, container string) error

+ 1 - 1
vendor/src/github.com/docker/engine-api/client/network_list.go

@@ -13,7 +13,7 @@ import (
 func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
 	query := url.Values{}
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 			return nil, err
 		}

+ 2 - 0
vendor/src/github.com/docker/engine-api/client/request.go

@@ -172,6 +172,8 @@ func encodeData(data interface{}) (*bytes.Buffer, error) {
 
 func ensureReaderClosed(response *serverResponse) {
 	if response != nil && response.body != nil {
+		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
+		io.CopyN(ioutil.Discard, response.body, 512)
 		response.body.Close()
 	}
 }

+ 1 - 1
vendor/src/github.com/docker/engine-api/client/volume_list.go

@@ -15,7 +15,7 @@ func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (types.V
 	query := url.Values{}
 
 	if filter.Len() > 0 {
-		filterJSON, err := filters.ToParam(filter)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
 		if err != nil {
 			return volumes, err
 		}

+ 6 - 0
vendor/src/github.com/docker/engine-api/types/client.go

@@ -10,6 +10,12 @@ import (
 	"github.com/docker/go-units"
 )
 
+// CheckpointCreateOptions holds parameters to create a checkpoint from a container
+type CheckpointCreateOptions struct {
+	CheckpointID string
+	Exit         bool
+}
+
 // ContainerAttachOptions holds parameters to attach to a container.
 type ContainerAttachOptions struct {
 	Stream     bool

+ 4 - 5
vendor/src/github.com/docker/engine-api/types/container/host_config.go

@@ -257,11 +257,10 @@ type Resources struct {
 	Ulimits              []*units.Ulimit // List of ulimits to be set in the container
 
 	// Applicable to Windows
-	CPUCount                int64  `json:"CpuCount"`   // CPU count
-	CPUPercent              int64  `json:"CpuPercent"` // CPU percent
-	IOMaximumIOps           uint64 // Maximum IOps for the container system drive
-	IOMaximumBandwidth      uint64 // Maximum IO in bytes per second for the container system drive
-	NetworkMaximumBandwidth uint64 // Maximum bandwidth of the network endpoint in bytes per second
+	CPUCount           int64  `json:"CpuCount"`   // CPU count
+	CPUPercent         int64  `json:"CpuPercent"` // CPU percent
+	IOMaximumIOps      uint64 // Maximum IOps for the container system drive
+	IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive
 }
 
 // UpdateConfig holds the mutable attributes of a Container.

+ 14 - 2
vendor/src/github.com/docker/engine-api/types/filters/parse.go

@@ -215,10 +215,22 @@ func (filters Args) ExactMatch(field, source string) bool {
 	}
 
 	// try to match full name value to avoid O(N) regular expression matching
-	if fieldValues[source] {
+	return fieldValues[source]
+}
+
+// UniqueExactMatch returns true if there is only one filter and the source matches exactly this one.
+func (filters Args) UniqueExactMatch(field, source string) bool {
+	fieldValues := filters.fields[field]
+	//do not filter if there is no filter set or cannot determine filter
+	if len(fieldValues) == 0 {
 		return true
 	}
-	return false
+	if len(filters.fields[field]) != 1 {
+		return false
+	}
+
+	// try to match full name value to avoid O(N) regular expression matching
+	return fieldValues[source]
 }
 
 // FuzzyMatch returns true if the source matches exactly one of the filters,

+ 5 - 0
vendor/src/github.com/docker/engine-api/types/types.go

@@ -471,3 +471,8 @@ type NetworkDisconnect struct {
 	Container string
 	Force     bool
 }
+
+// Checkpoint represents the details of a checkpoint
+type Checkpoint struct {
+	Name string // Name is the name of the checkpoint
+}