1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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

Daemon images search refactoring
This commit is contained in:
Sebastiaan van Stijn 2016-05-26 12:34:31 +02:00
commit 75109b32db
27 changed files with 590 additions and 137 deletions

View file

@ -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.

View file

@ -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 {

View file

@ -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
daemon/search.go Normal file
View file

@ -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
daemon/search_test.go Normal file
View file

@ -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)
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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
}