소스 검색

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

Daemon images search refactoring
Sebastiaan van Stijn 9 년 전
부모
커밋
75109b32db
27개의 변경된 파일590개의 추가작업 그리고 137개의 파일을 삭제
  1. 1 1
      api/client/run.go
  2. 2 2
      api/client/start.go
  3. 3 87
      daemon/daemon.go
  4. 94 0
      daemon/search.go
  5. 357 0
      daemon/search_test.go
  6. 1 1
      distribution/pull.go
  7. 1 1
      distribution/push.go
  8. 1 1
      hack/vendor.sh
  9. 1 1
      registry/registry_test.go
  10. 31 15
      registry/service.go
  11. 1 1
      registry/service_v1.go
  12. 1 1
      registry/service_v2.go
  13. 13 0
      vendor/src/github.com/docker/engine-api/client/checkpoint_create.go
  14. 12 0
      vendor/src/github.com/docker/engine-api/client/checkpoint_delete.go
  15. 22 0
      vendor/src/github.com/docker/engine-api/client/checkpoint_list.go
  16. 0 11
      vendor/src/github.com/docker/engine-api/client/container_inspect.go
  17. 10 3
      vendor/src/github.com/docker/engine-api/client/container_start.go
  18. 1 1
      vendor/src/github.com/docker/engine-api/client/events.go
  19. 1 1
      vendor/src/github.com/docker/engine-api/client/image_list.go
  20. 4 1
      vendor/src/github.com/docker/engine-api/client/interface.go
  21. 1 1
      vendor/src/github.com/docker/engine-api/client/network_list.go
  22. 2 0
      vendor/src/github.com/docker/engine-api/client/request.go
  23. 1 1
      vendor/src/github.com/docker/engine-api/client/volume_list.go
  24. 6 0
      vendor/src/github.com/docker/engine-api/types/client.go
  25. 4 5
      vendor/src/github.com/docker/engine-api/types/container/host_config.go
  26. 14 2
      vendor/src/github.com/docker/engine-api/types/filters/parse.go
  27. 5 0
      vendor/src/github.com/docker/engine-api/types/types.go

+ 1 - 1
api/client/run.go

@@ -234,7 +234,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	}
 	}
 
 
 	//start the container
 	//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
 		// If we have holdHijackedConnection, we should notify
 		// holdHijackedConnection we are going to exit and wait
 		// holdHijackedConnection we are going to exit and wait
 		// to avoid the terminal are not restored.
 		// 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.
 		// 3. Start the container.
-		if err := cli.client.ContainerStart(ctx, container); err != nil {
+		if err := cli.client.ContainerStart(ctx, container, ""); err != nil {
 			cancelFun()
 			cancelFun()
 			<-cErr
 			<-cErr
 			return err
 			return err
@@ -147,7 +147,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containers []string) error {
 func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containers []string) error {
 	var failedContainers []string
 	var failedContainers []string
 	for _, container := range containers {
 	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)
 			fmt.Fprintf(cli.err, "%s\n", err)
 			failedContainers = append(failedContainers, container)
 			failedContainers = append(failedContainers, container)
 		} else {
 		} else {

+ 3 - 87
daemon/daemon.go

@@ -16,7 +16,6 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"regexp"
 	"regexp"
 	"runtime"
 	"runtime"
-	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"syscall"
 	"syscall"
@@ -32,7 +31,6 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	containertypes "github.com/docker/engine-api/types/container"
 	containertypes "github.com/docker/engine-api/types/container"
 	networktypes "github.com/docker/engine-api/types/network"
 	networktypes "github.com/docker/engine-api/types/network"
-	registrytypes "github.com/docker/engine-api/types/registry"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/engine-api/types/strslice"
 	// register graph drivers
 	// register graph drivers
 	_ "github.com/docker/docker/daemon/graphdriver/register"
 	_ "github.com/docker/docker/daemon/graphdriver/register"
@@ -60,7 +58,6 @@ import (
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/docker/volume/store"
 	"github.com/docker/docker/volume/store"
-	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	nwconfig "github.com/docker/libnetwork/config"
 	nwconfig "github.com/docker/libnetwork/config"
@@ -87,7 +84,7 @@ type Daemon struct {
 	configStore               *Config
 	configStore               *Config
 	statsCollector            *statsCollector
 	statsCollector            *statsCollector
 	defaultLogConfig          containertypes.LogConfig
 	defaultLogConfig          containertypes.LogConfig
-	RegistryService           *registry.Service
+	RegistryService           registry.Service
 	EventsService             *events.Events
 	EventsService             *events.Events
 	netController             libnetwork.NetworkController
 	netController             libnetwork.NetworkController
 	volumes                   *store.VolumeStore
 	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
 // NewDaemon sets up everything for the daemon to be able to service
 // requests from the webserver.
 // 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)
 	setDefaultMtu(config)
 
 
 	// Ensure we have compatible and valid configuration options
 	// 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
 // AuthenticateToRegistry checks the validity of credentials in authConfig
 func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
 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
 // 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
 	ProgressOutput progress.Output
 	// RegistryService is the registry service to use for TLS configuration
 	// RegistryService is the registry service to use for TLS configuration
 	// and endpoint lookup.
 	// and endpoint lookup.
-	RegistryService *registry.Service
+	RegistryService registry.Service
 	// ImageEventLogger notifies events for a given image
 	// ImageEventLogger notifies events for a given image
 	ImageEventLogger func(id, name, action string)
 	ImageEventLogger func(id, name, action string)
 	// MetadataStore is the storage backend for distribution-specific
 	// MetadataStore is the storage backend for distribution-specific

+ 1 - 1
distribution/push.go

@@ -31,7 +31,7 @@ type ImagePushConfig struct {
 	ProgressOutput progress.Output
 	ProgressOutput progress.Output
 	// RegistryService is the registry service to use for TLS configuration
 	// RegistryService is the registry service to use for TLS configuration
 	// and endpoint lookup.
 	// and endpoint lookup.
-	RegistryService *registry.Service
+	RegistryService registry.Service
 	// ImageEventLogger notifies events for a given image
 	// ImageEventLogger notifies events for a given image
 	ImageEventLogger func(id, name, action string)
 	ImageEventLogger func(id, name, action string)
 	// MetadataStore is the storage backend for distribution-specific
 	// 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 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-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-connections v0.2.0
 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/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
 clone git github.com/imdario/mergo 0.2.1
 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
 		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")
 	imageName, err := reference.WithName(IndexName + "/test/image")
 	if err != nil {
 	if err != nil {

+ 31 - 15
registry/service.go

@@ -7,35 +7,50 @@ import (
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
 
 
+	"golang.org/x/net/context"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	registrytypes "github.com/docker/engine-api/types/registry"
 	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.
 // of mirrors.
-type Service struct {
+type DefaultService struct {
 	config *serviceConfig
 	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.
 // installed into an engine.
-func NewService(options ServiceOptions) *Service {
-	return &Service{
+func NewService(options ServiceOptions) *DefaultService {
+	return &DefaultService{
 		config: newServiceConfig(options),
 		config: newServiceConfig(options),
 	}
 	}
 }
 }
 
 
 // ServiceConfig returns the public registry service configuration.
 // ServiceConfig returns the public registry service configuration.
-func (s *Service) ServiceConfig() *registrytypes.ServiceConfig {
+func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
 	return &s.config.ServiceConfig
 	return &s.config.ServiceConfig
 }
 }
 
 
 // Auth contacts the public registry with the provided credentials,
 // Auth contacts the public registry with the provided credentials,
 // and returns OK if authentication was successful.
 // and returns OK if authentication was successful.
 // It can be used to verify the validity of a client's credentials.
 // 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
 	serverAddress := authConfig.ServerAddress
 	if serverAddress == "" {
 	if serverAddress == "" {
 		serverAddress = IndexServer
 		serverAddress = IndexServer
@@ -93,7 +108,8 @@ func splitReposSearchTerm(reposName string) (string, string) {
 
 
 // Search queries the public registry for images matching the specified
 // Search queries the public registry for images matching the specified
 // search terms, and returns the results.
 // 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 {
 	if err := validateNoScheme(term); err != nil {
 		return nil, err
 		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
 // ResolveRepository splits a repository name into its components
 // and configuration of the associated registry.
 // 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)
 	return newRepositoryInfo(s.config, name)
 }
 }
 
 
 // ResolveIndex takes indexName and returns index info
 // 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)
 	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
 // 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))
 	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)
 	return s.TLSConfig(mirrorURL.Host)
 }
 }
 
 
 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
 // 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
 // It gives preference to v2 endpoints over v1, mirrors over the actual
 // registry, and HTTPS over plain HTTP.
 // 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)
 	return s.lookupEndpoints(hostname)
 }
 }
 
 
 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
 // 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.
 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
 // Mirrors are not included.
 // 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)
 	allEndpoints, err := s.lookupEndpoints(hostname)
 	if err == nil {
 	if err == nil {
 		for _, endpoint := range allEndpoints {
 		for _, endpoint := range allEndpoints {
@@ -185,7 +201,7 @@ func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint,
 	return endpoints, err
 	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)
 	endpoints, err = s.lookupV2Endpoints(hostname)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 1 - 1
registry/service_v1.go

@@ -6,7 +6,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 	"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
 	var cfg = tlsconfig.ServerDefault
 	tlsConfig := &cfg
 	tlsConfig := &cfg
 	if hostname == DefaultNamespace {
 	if hostname == DefaultNamespace {

+ 1 - 1
registry/service_v2.go

@@ -7,7 +7,7 @@ import (
 	"github.com/docker/go-connections/tlsconfig"
 	"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
 	var cfg = tlsconfig.ServerDefault
 	tlsConfig := &cfg
 	tlsConfig := &cfg
 	if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host {
 	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)
 	err = json.NewDecoder(rdr).Decode(&response)
 	return response, body, err
 	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
 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.
 // 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)
 	ensureReaderClosed(resp)
 	return err
 	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)
 		query.Set("until", ts)
 	}
 	}
 	if options.Filters.Len() > 0 {
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			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{}
 	query := url.Values{}
 
 
 	if options.Filters.Len() > 0 {
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 		if err != nil {
 			return images, err
 			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.
 // APIClient is an interface that clients that talk with a docker server must implement.
 type APIClient interface {
 type APIClient interface {
 	ClientVersion() string
 	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)
 	ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
 	ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, 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)
 	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
 	ContainerRestart(ctx context.Context, container string, timeout int) error
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
 	ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, 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
 	ContainerStop(ctx context.Context, container string, timeout int) error
 	ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
 	ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
 	ContainerUnpause(ctx context.Context, container string) 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) {
 func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
 	query := url.Values{}
 	query := url.Values{}
 	if options.Filters.Len() > 0 {
 	if options.Filters.Len() > 0 {
-		filterJSON, err := filters.ToParam(options.Filters)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			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) {
 func ensureReaderClosed(response *serverResponse) {
 	if response != nil && response.body != nil {
 	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()
 		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{}
 	query := url.Values{}
 
 
 	if filter.Len() > 0 {
 	if filter.Len() > 0 {
-		filterJSON, err := filters.ToParam(filter)
+		filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
 		if err != nil {
 		if err != nil {
 			return volumes, err
 			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"
 	"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.
 // ContainerAttachOptions holds parameters to attach to a container.
 type ContainerAttachOptions struct {
 type ContainerAttachOptions struct {
 	Stream     bool
 	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
 	Ulimits              []*units.Ulimit // List of ulimits to be set in the container
 
 
 	// Applicable to Windows
 	// 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.
 // 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
 	// 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 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,
 // 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
 	Container string
 	Force     bool
 	Force     bool
 }
 }
+
+// Checkpoint represents the details of a checkpoint
+type Checkpoint struct {
+	Name string // Name is the name of the checkpoint
+}