Skip to content

Commit

Permalink
Add --registry-prepend and --registry-replace with search/pull comman…
Browse files Browse the repository at this point in the history
…ds qualifier to docker daemon

    These options setup a search list of registries from which to pull images.

    Registries will be searched in reverse order.

    --registry-prepend will allow you to add additional registries to docker.io to
    pull images from.

    For example

    --registry-prepend="foo.com/registry,bar.com/registry"

    Would cause a
    docker pull myapp

    to search
    bar.com/registry/myapp
    foo.com/registry/myapp
    docker.io.../myapp

    This would allow companies and vendors to specify registries which contain
    content that the companies will not allowed to be stored at docker.io

    --registry-replace="bar.com/registry"
    Replaces the docker.io registry as the default registry to search.

    This would allow a customer to control which sites that images can be pulled from.

Pull and Search commands iterate over a list of registries given with
options --registry-prepend or --registry-replace. In case of pull command
the iteration stops when a matching image is found. In case of search
command the iteration never stops - all the results are accumulated.

Docker-DCO-1.1-Signed-off-by: Michal Minar <miminar@redhat.com>
Docker-DCO-1.1-Signed-off-by: Dan Walsh <dwalsh@redhat.com> (github: rhatdan)
  • Loading branch information
rhatdan committed Jan 28, 2015
1 parent 9b2b494 commit 314376e
Show file tree
Hide file tree
Showing 17 changed files with 208 additions and 73 deletions.
8 changes: 4 additions & 4 deletions api/client/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {

// 'docker login': login / register a user to registry service.
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true)
cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress("")+"\" is the default.", true)
cmd.Require(flag.Max, 1)

var username, password, email string
Expand All @@ -295,7 +295,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {

utils.ParseFlags(cmd, args, true)

serverAddress := registry.IndexServerAddress()
serverAddress := registry.IndexServerAddress("")
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}
Expand Down Expand Up @@ -400,11 +400,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error {

// log out from a Docker registry
func (cli *DockerCli) CmdLogout(args ...string) error {
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true)
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is specified \""+registry.IndexServerAddress("")+"\" is the default.", true)
cmd.Require(flag.Max, 1)

utils.ParseFlags(cmd, args, false)
serverAddress := registry.IndexServerAddress()
serverAddress := registry.IndexServerAddress("")
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}
Expand Down
2 changes: 1 addition & 1 deletion api/client/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
if passAuthInfo {
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
authConfig := cli.configFile.Configs[registry.IndexServerAddress("")]
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion daemon/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
v.SetInt("NEventsListener", env.GetInt("count"))
v.Set("KernelVersion", kernelVersion)
v.Set("OperatingSystem", operatingSystem)
v.Set("IndexServerAddress", registry.IndexServerAddress())
v.Set("IndexServerAddress", registry.IndexServerAddress(""))
v.SetJson("RegistryConfig", registryConfig)
v.Set("InitSha1", dockerversion.INITSHA1)
v.Set("InitPath", initPath)
Expand Down
14 changes: 14 additions & 0 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/docker/docker/dockerversion"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)

Expand All @@ -37,6 +38,19 @@ func main() {
return
}

if *flDefaultRegistry != "" {
registry.RegistryList = strings.Split(*flDefaultRegistry, ",")
}

if *flPrependRegistry != "" {
regs := strings.Split(*flPrependRegistry, ",")
for r := range regs {
// TODO: we actually prepend here - reflect this in the option name
// (--registry-prepend)
registry.RegistryList = append([]string{regs[r]}, registry.RegistryList...)
}
}

if *flLogLevel != "" {
lvl, err := log.ParseLevel(*flLogLevel)
if err != nil {
Expand Down
20 changes: 11 additions & 9 deletions docker/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,17 @@ func getDaemonConfDir() string {
}

var (
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode\nuse '' (the empty string) to disable setting of a group")
flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level (debug, info, warn, error, fatal)")
flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
flTls = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by --tlsverify flag")
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)")
flPrependRegistry = flag.String([]string{"#registry-prepend", "-registry-prepend"}, "", "Comma separated list of registries to prepend to default registry. Registries will be searched in reverse order")
flDefaultRegistry = flag.String([]string{"#registry-replace", "-registry-replace"}, "", "Comma separated list of registries to replace the default registry. Registries will be searched in reverse order")
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode\nuse '' (the empty string) to disable setting of a group")
flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level (debug, info, warn, error, fatal)")
flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API")
flTls = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by --tlsverify flag")
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)")

// these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized until init() runs
flTrustKey *string
Expand Down
6 changes: 6 additions & 0 deletions docs/man/docker.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ unix://[/path/to/socket] to use.
**-v**=*true*|*false*
Print version information and quit. Default is false.

**--registry-prepend**=""
Comma separated list of registries to prepend to default registry. Registries will be searched in reverse order.

**--registry-replace**=""
Comma separated list of registries to replace the default registry. Registries will be searched in reverse order

**--selinux-enabled**=*true*|*false*
Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver.

Expand Down
29 changes: 29 additions & 0 deletions graph/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,35 @@ import (
"github.com/docker/docker/utils"
)

func (s *TagStore) CmdRegistryPull(job *engine.Job) engine.Status {
var (
tmp = job.Args[0]
status = engine.StatusErr
registries = registry.RegistryList
)
// Unless the index name is specified, iterate over all registries until
// the matching image is found.
if registry.RepositoryNameHasIndex(tmp) {
registries = []string{""}
}
for i, r := range registries {
if i > 0 {
// Prepend the index name to the image/repository.
job.Args[0] = fmt.Sprintf("%s/%s", r, tmp)
} else {
job.Args[0] = tmp
}
status := s.CmdPull(job)
if status == engine.StatusOK {
tagjob := job
tagjob.Args = append(tagjob.Args, tmp)
s.CmdTag(tagjob)
return status
}
}
return status
}

func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
if n := len(job.Args); n != 1 && n != 2 {
return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
Expand Down
2 changes: 1 addition & 1 deletion graph/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (s *TagStore) Install(eng *engine.Engine) error {
"viz": s.CmdViz,
"load": s.CmdLoad,
"import": s.CmdImport,
"pull": s.CmdPull,
"pull": s.CmdRegistryPull,
"push": s.CmdPush,
"image_manifest": s.CmdManifest,
} {
Expand Down
6 changes: 3 additions & 3 deletions registry/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
return &configFile, fmt.Errorf("Invalid Auth config file")
}
authConfig.Email = origEmail[1]
authConfig.ServerAddress = IndexServerAddress()
authConfig.ServerAddress = IndexServerAddress("")
// *TODO: Switch to using IndexServerName() instead?
configFile.Configs[IndexServerAddress()] = authConfig
configFile.Configs[IndexServerAddress("")] = authConfig
} else {
for k, authConfig := range configFile.Configs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
Expand Down Expand Up @@ -262,7 +262,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
return "", fmt.Errorf("Server Error: Server Address not set.")
}

loginAgainstOfficialIndex := serverAddress == IndexServerAddress()
loginAgainstOfficialIndex := serverAddress == INDEXSERVER

// to avoid sending the server address to the server it should be removed before being marshalled
authCopy := *authConfig
Expand Down
6 changes: 3 additions & 3 deletions registry/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func setupTempConfigFile() (*ConfigFile, error) {
Configs: make(map[string]AuthConfig),
}

for _, registry := range []string{"testIndex", IndexServerAddress()} {
for _, registry := range []string{"testIndex", IndexServerAddress("")} {
configFile.Configs[registry] = AuthConfig{
Username: "docker-user",
Password: "docker-pass",
Expand Down Expand Up @@ -81,7 +81,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
}
defer os.RemoveAll(configFile.rootPath)

indexConfig := configFile.Configs[IndexServerAddress()]
indexConfig := configFile.Configs[IndexServerAddress("")]

officialIndex := &IndexInfo{
Official: true,
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
Password: "baz-pass",
Email: "baz@example.com",
}
configFile.Configs[IndexServerAddress()] = officialAuth
configFile.Configs[IndexServerAddress("")] = officialAuth

expectedAuths := map[string]AuthConfig{
"registry.example.com": registryAuth,
Expand Down
69 changes: 52 additions & 17 deletions registry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const (
// Only used for user auth + account creation
INDEXSERVER = "https://index.docker.io/v1/"
REGISTRYSERVER = "https://registry-1.docker.io/v2/"
INDEXNAME = "docker.io"
// The name of official index.
INDEXNAME = "docker.io"

// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
)
Expand All @@ -36,12 +37,33 @@ var (
validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
)

func IndexServerAddress() string {
return INDEXSERVER
// IndexServerName returns the name of default index server.
func IndexServerName() string {
return RegistryList[0]
}

func IndexServerName() string {
return INDEXNAME
// IndexServerAddress returns an index uri for given name. Empty string is
// treated the same as a result of IndexServerName().
func IndexServerAddress(indexName string) string {
if (indexName == "" && RegistryList[0] == INDEXNAME) || indexName == INDEXNAME || indexName == INDEXSERVER {
return INDEXSERVER
} else if indexName != "" {
return fmt.Sprintf("http://%s/v1/", indexName)
} else {
return fmt.Sprintf("http://%s/v1/", RegistryList[0])
}
}

// RegistryServerAddress returns a registry uri for given index name. Empty string
// is treated the same as a result of IndexServerName().
func RegistryServerAddress(indexName string) string {
if (indexName == "" && RegistryList[0] == INDEXNAME) || indexName == INDEXNAME {
return REGISTRYSERVER
} else if indexName != "" {
return fmt.Sprintf("http://%s/v2/", indexName)
} else {
return fmt.Sprintf("http://%s/v2/", RegistryList[0])
}
}

// InstallFlags adds command-line options to the top-level flag parser for
Expand Down Expand Up @@ -114,12 +136,14 @@ func NewServiceConfig(options *Options) *ServiceConfig {
}
}

// Configure public registry.
config.IndexConfigs[IndexServerName()] = &IndexInfo{
Name: IndexServerName(),
Mirrors: options.Mirrors.GetAll(),
Secure: true,
Official: true,
if config.IndexConfigs[IndexServerName()] == nil {
// Configure public registry.
config.IndexConfigs[IndexServerName()] = &IndexInfo{
Name: IndexServerName(),
Mirrors: options.Mirrors.GetAll(),
Secure: IndexServerName() == INDEXNAME,
Official: IndexServerName() == INDEXNAME,
}
}

return config
Expand Down Expand Up @@ -252,13 +276,20 @@ func ValidateRepositoryName(reposName string) error {
if err = validateNoSchema(reposName); err != nil {
return err
}
indexName, remoteName := splitReposName(reposName)
indexName, remoteName := splitReposName(reposName, true)
if _, err = ValidateIndexName(indexName); err != nil {
return err
}
return validateRemoteName(remoteName)
}

// RepositoryNameHasIndex determines whether the given reposName has prepended
// name of index.
func RepositoryNameHasIndex(reposName string) bool {
indexName, _ := splitReposName(reposName, false)
return indexName != ""
}

// NewIndexInfo returns IndexInfo configuration from indexName
func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
var err error
Expand All @@ -276,7 +307,7 @@ func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error)
index := &IndexInfo{
Name: indexName,
Mirrors: make([]string, 0),
Official: false,
Official: indexName == INDEXNAME,
}
index.Secure = config.isSecureIndex(indexName)
return index, nil
Expand All @@ -286,20 +317,24 @@ func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error)
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
func (index *IndexInfo) GetAuthConfigKey() string {
if index.Official {
return IndexServerAddress()
return INDEXSERVER
}
return index.Name
}

// splitReposName breaks a reposName into an index name and remote name
func splitReposName(reposName string) (string, string) {
// fixMissingIndex says to return current index server name if missing in
// reposName
func splitReposName(reposName string, fixMissingIndex bool) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = IndexServerName()
if fixMissingIndex {
indexName = IndexServerName()
}
remoteName = reposName
} else {
indexName = nameParts[0]
Expand All @@ -314,7 +349,7 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInf
return nil, err
}

indexName, remoteName := splitReposName(reposName)
indexName, remoteName := splitReposName(reposName, true)
if err := validateRemoteName(remoteName); err != nil {
return nil, err
}
Expand Down
16 changes: 14 additions & 2 deletions registry/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func scanForAPIVersion(address string) (string, APIVersion) {
chunks []string
apiVersionStr string
)
log.Debugf("endpoint.scanForAPIVersion: starting with address=%s", address)
defer log.Debugf("endpoint.scanForAPIVersion: terminating")

if strings.HasSuffix(address, "/") {
address = address[:len(address)-1]
Expand All @@ -43,6 +45,8 @@ func scanForAPIVersion(address string) (string, APIVersion) {
// NewEndpoint parses the given address to return a registry endpoint.
func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints.
log.Debugf("endpoint.NewEndpoint: starting with index=%v", index)
defer log.Debugf("endpoint.NewEndpoint: terminating")
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
if err != nil {
return nil, err
Expand Down Expand Up @@ -87,13 +91,21 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) {
trimmedAddress string
err error
)
fmt.Printf("endpoint.newEndpoint: starting with address=%s, secure=%t\n", address, secure)
defer fmt.Printf("endpoint.newEndpoint: terminating\n")

if !strings.HasPrefix(address, "http") {
address = "https://" + address
if secure {
address = "https://" + address
} else {
address = "http://" + address
}
}

fmt.Printf("endpoint.newEndpoint: address after prefixing: %s\n", address)
trimmedAddress, endpoint.Version = scanForAPIVersion(address)

fmt.Printf("endpoint.newEndpoint: trimmedAddress=%s, version=%s\n", trimmedAddress, endpoint.Version)
if endpoint.URL, err = url.Parse(trimmedAddress); err != nil {
return nil, err
}
Expand Down Expand Up @@ -162,7 +174,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
func (e *Endpoint) pingV1() (RegistryInfo, error) {
log.Debugf("attempting v1 ping for registry endpoint %s", e)

if e.String() == IndexServerAddress() {
if e.String() == INDEXSERVER {
// Skip the check, we know this one is valid
// (and we never want to fallback to http in case of error)
return RegistryInfo{Standalone: false}, nil
Expand Down

0 comments on commit 314376e

Please sign in to comment.