mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add Unit test to daemon.SearchRegistryForImages…
… and refactor a little bit some daemon on the way. - Move `SearchRegistryForImages` to a new file (`daemon/search.go`) as `daemon.go` is getting pretty big. - `registry.Service` is now an interface (allowing us to decouple it a little bit and thus unit test easily). - Add some unit test for `SearchRegistryForImages`. - Use UniqueExactMatch for search filters - And use empty restore id for now in client.ContainerStart. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
5812b6927c
commit
636c276f67
11 changed files with 493 additions and 110 deletions
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -31,7 +30,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"
|
||||||
|
@ -63,7 +61,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"
|
||||||
|
@ -93,7 +90,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
|
||||||
|
@ -614,7 +611,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
|
||||||
|
@ -1144,88 +1141,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))
|
return daemon.RegistryService.Auth(ctx, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsShuttingDown tells whether the daemon is shutting down or not
|
// IsShuttingDown tells whether the daemon is shutting down or not
|
||||||
|
|
94
daemon/search.go
Normal file
94
daemon/search.go
Normal 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 ®istrytypes.SearchResults{
|
||||||
|
Query: unfilteredResult.Query,
|
||||||
|
NumResults: len(filteredResults),
|
||||||
|
Results: filteredResults,
|
||||||
|
}, nil
|
||||||
|
}
|
357
daemon/search_test.go
Normal file
357
daemon/search_test.go
Normal 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 ®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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
func NewService(options ServiceOptions) *DefaultService {
|
||||||
return &Service{
|
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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue