diff --git a/api/client/run.go b/api/client/run.go index 6af7cd2a11..79d52d96a9 100644 --- a/api/client/run.go +++ b/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. diff --git a/api/client/start.go b/api/client/start.go index 594212671b..03f53ed89e 100644 --- a/api/client/start.go +++ b/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 { diff --git a/daemon/daemon.go b/daemon/daemon.go index eac802459d..02ea115c73 100644 --- a/daemon/daemon.go +++ b/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 ®istrytypes.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 diff --git a/daemon/search.go b/daemon/search.go new file mode 100644 index 0000000000..a62f70ee13 --- /dev/null +++ b/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 ®istrytypes.SearchResults{ + Query: unfilteredResult.Query, + NumResults: len(filteredResults), + Results: filteredResults, + }, nil +} diff --git a/daemon/search_test.go b/daemon/search_test.go new file mode 100644 index 0000000000..b3dcb49d3d --- /dev/null +++ b/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 ®istrytypes.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) + } + } + } +} diff --git a/distribution/pull.go b/distribution/pull.go index 7b6825d3e5..4fbf17fc4b 100644 --- a/distribution/pull.go +++ b/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 diff --git a/distribution/push.go b/distribution/push.go index fffbc56e42..56a6bd7624 100644 --- a/distribution/push.go +++ b/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 diff --git a/hack/vendor.sh b/hack/vendor.sh index 31964d813c..a5cd27da87 100755 --- a/hack/vendor.sh +++ b/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 diff --git a/registry/registry_test.go b/registry/registry_test.go index 7442ebc036..39a01bcd4a 100644 --- a/registry/registry_test.go +++ b/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 { diff --git a/registry/service.go b/registry/service.go index 3006e8ab84..d48063cd78 100644 --- a/registry/service.go +++ b/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 diff --git a/registry/service_v1.go b/registry/service_v1.go index 56121eea4b..5d7e89891a 100644 --- a/registry/service_v1.go +++ b/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 { diff --git a/registry/service_v2.go b/registry/service_v2.go index 4113d57d56..5e62f8ff8c 100644 --- a/registry/service_v2.go +++ b/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 { diff --git a/vendor/src/github.com/docker/engine-api/client/checkpoint_create.go b/vendor/src/github.com/docker/engine-api/client/checkpoint_create.go new file mode 100644 index 0000000000..23883cc06c --- /dev/null +++ b/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 +} diff --git a/vendor/src/github.com/docker/engine-api/client/checkpoint_delete.go b/vendor/src/github.com/docker/engine-api/client/checkpoint_delete.go new file mode 100644 index 0000000000..a4e9ed0c06 --- /dev/null +++ b/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 +} diff --git a/vendor/src/github.com/docker/engine-api/client/checkpoint_list.go b/vendor/src/github.com/docker/engine-api/client/checkpoint_list.go new file mode 100644 index 0000000000..ef5ec261b6 --- /dev/null +++ b/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 +} diff --git a/vendor/src/github.com/docker/engine-api/client/container_inspect.go b/vendor/src/github.com/docker/engine-api/client/container_inspect.go index afd71eefcb..bbf560e631 100644 --- a/vendor/src/github.com/docker/engine-api/client/container_inspect.go +++ b/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 -} diff --git a/vendor/src/github.com/docker/engine-api/client/container_start.go b/vendor/src/github.com/docker/engine-api/client/container_start.go index 12a979422e..ff11c4cf0e 100644 --- a/vendor/src/github.com/docker/engine-api/client/container_start.go +++ b/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 } diff --git a/vendor/src/github.com/docker/engine-api/client/events.go b/vendor/src/github.com/docker/engine-api/client/events.go index e379ce0a29..f22a18e1d3 100644 --- a/vendor/src/github.com/docker/engine-api/client/events.go +++ b/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 } diff --git a/vendor/src/github.com/docker/engine-api/client/image_list.go b/vendor/src/github.com/docker/engine-api/client/image_list.go index 347810e663..7408258231 100644 --- a/vendor/src/github.com/docker/engine-api/client/image_list.go +++ b/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 } diff --git a/vendor/src/github.com/docker/engine-api/client/interface.go b/vendor/src/github.com/docker/engine-api/client/interface.go index 2c6872f534..2dc9b22987 100644 --- a/vendor/src/github.com/docker/engine-api/client/interface.go +++ b/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 diff --git a/vendor/src/github.com/docker/engine-api/client/network_list.go b/vendor/src/github.com/docker/engine-api/client/network_list.go index 813109c180..0569552496 100644 --- a/vendor/src/github.com/docker/engine-api/client/network_list.go +++ b/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 } diff --git a/vendor/src/github.com/docker/engine-api/client/request.go b/vendor/src/github.com/docker/engine-api/client/request.go index 5b283a4f95..21ed0d0fc9 100644 --- a/vendor/src/github.com/docker/engine-api/client/request.go +++ b/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() } } diff --git a/vendor/src/github.com/docker/engine-api/client/volume_list.go b/vendor/src/github.com/docker/engine-api/client/volume_list.go index bb4c40d5f9..7c6ccf834f 100644 --- a/vendor/src/github.com/docker/engine-api/client/volume_list.go +++ b/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 } diff --git a/vendor/src/github.com/docker/engine-api/types/client.go b/vendor/src/github.com/docker/engine-api/types/client.go index fa3b2cfb45..1b529a905f 100644 --- a/vendor/src/github.com/docker/engine-api/types/client.go +++ b/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 diff --git a/vendor/src/github.com/docker/engine-api/types/container/host_config.go b/vendor/src/github.com/docker/engine-api/types/container/host_config.go index 531408c94d..039fa04e4e 100644 --- a/vendor/src/github.com/docker/engine-api/types/container/host_config.go +++ b/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. diff --git a/vendor/src/github.com/docker/engine-api/types/filters/parse.go b/vendor/src/github.com/docker/engine-api/types/filters/parse.go index 0e0d7e3805..dc2c48b894 100644 --- a/vendor/src/github.com/docker/engine-api/types/filters/parse.go +++ b/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, diff --git a/vendor/src/github.com/docker/engine-api/types/types.go b/vendor/src/github.com/docker/engine-api/types/types.go index cb2dc9ac9d..7994c11811 100644 --- a/vendor/src/github.com/docker/engine-api/types/types.go +++ b/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 +}