From c899a49a95bc05b896b46460735a268678efc1a3 Mon Sep 17 00:00:00 2001
From: Don Kjer <don.kjer@gmail.com>
Date: Wed, 7 Jan 2015 23:42:01 +0000
Subject: [PATCH] Moving NewIndexInfo, NewRepositoryInfo and associated helpers
 into config.go

Signed-off-by: Don Kjer <don.kjer@gmail.com>
---
 docs/auth.go          |  15 --
 docs/config.go        | 312 ++++++++++++++++++++++++++++++++++++++----
 docs/endpoint.go      |  49 -------
 docs/registry.go      | 190 +------------------------
 docs/registry_test.go |   2 +-
 docs/service.go       |   4 +-
 6 files changed, 290 insertions(+), 282 deletions(-)

diff --git a/docs/auth.go b/docs/auth.go
index 8382869b..102078d7 100644
--- a/docs/auth.go
+++ b/docs/auth.go
@@ -17,13 +17,6 @@ import (
 const (
 	// Where we store the config file
 	CONFIGFILE = ".dockercfg"
-
-	// Only used for user auth + account creation
-	INDEXSERVER    = "https://index.docker.io/v1/"
-	REGISTRYSERVER = "https://registry-1.docker.io/v1/"
-	INDEXNAME      = "docker.io"
-
-	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
 )
 
 var (
@@ -43,14 +36,6 @@ type ConfigFile struct {
 	rootPath string
 }
 
-func IndexServerAddress() string {
-	return INDEXSERVER
-}
-
-func IndexServerName() string {
-	return INDEXNAME
-}
-
 // create a base64 encoded auth string to store in config
 func encodeAuth(authConfig *AuthConfig) string {
 	authStr := authConfig.Username + ":" + authConfig.Password
diff --git a/docs/config.go b/docs/config.go
index bd993edd..b5652b15 100644
--- a/docs/config.go
+++ b/docs/config.go
@@ -2,12 +2,16 @@ package registry
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net"
 	"net/url"
+	"regexp"
+	"strings"
 
 	"github.com/docker/docker/opts"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/utils"
 )
 
 // Options holds command line options.
@@ -16,6 +20,30 @@ type Options struct {
 	InsecureRegistries opts.ListOpts
 }
 
+const (
+	// Only used for user auth + account creation
+	INDEXSERVER    = "https://index.docker.io/v1/"
+	REGISTRYSERVER = "https://registry-1.docker.io/v1/"
+	INDEXNAME      = "docker.io"
+
+	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
+)
+
+var (
+	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
+	emptyServiceConfig       = NewServiceConfig(nil)
+	validNamespaceChars      = regexp.MustCompile(`^([a-z0-9-_]*)$`)
+	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
+)
+
+func IndexServerAddress() string {
+	return INDEXSERVER
+}
+
+func IndexServerName() string {
+	return INDEXNAME
+}
+
 // InstallFlags adds command-line options to the top-level flag parser for
 // the current process.
 func (options *Options) InstallFlags() {
@@ -25,34 +53,6 @@ func (options *Options) InstallFlags() {
 	flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
 }
 
-// ValidateMirror validates an HTTP(S) registry mirror
-func ValidateMirror(val string) (string, error) {
-	uri, err := url.Parse(val)
-	if err != nil {
-		return "", fmt.Errorf("%s is not a valid URI", val)
-	}
-
-	if uri.Scheme != "http" && uri.Scheme != "https" {
-		return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
-	}
-
-	if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
-		return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
-	}
-
-	return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
-}
-
-// ValidateIndexName validates an index name.
-func ValidateIndexName(val string) (string, error) {
-	// 'index.docker.io' => 'docker.io'
-	if val == "index."+IndexServerName() {
-		val = IndexServerName()
-	}
-	// *TODO: Check if valid hostname[:port]/ip[:port]?
-	return val, nil
-}
-
 type netIPNet net.IPNet
 
 func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
@@ -124,3 +124,259 @@ func NewServiceConfig(options *Options) *ServiceConfig {
 
 	return config
 }
+
+// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
+// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
+//
+// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
+// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
+// insecure.
+//
+// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
+// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
+// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
+// of insecureRegistries.
+func (config *ServiceConfig) isSecureIndex(indexName string) bool {
+	// Check for configured index, first.  This is needed in case isSecureIndex
+	// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
+	if index, ok := config.IndexConfigs[indexName]; ok {
+		return index.Secure
+	}
+
+	host, _, err := net.SplitHostPort(indexName)
+	if err != nil {
+		// assume indexName is of the form `host` without the port and go on.
+		host = indexName
+	}
+
+	addrs, err := lookupIP(host)
+	if err != nil {
+		ip := net.ParseIP(host)
+		if ip != nil {
+			addrs = []net.IP{ip}
+		}
+
+		// if ip == nil, then `host` is neither an IP nor it could be looked up,
+		// either because the index is unreachable, or because the index is behind an HTTP proxy.
+		// So, len(addrs) == 0 and we're not aborting.
+	}
+
+	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
+	for _, addr := range addrs {
+		for _, ipnet := range config.InsecureRegistryCIDRs {
+			// check if the addr falls in the subnet
+			if (*net.IPNet)(ipnet).Contains(addr) {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+// ValidateMirror validates an HTTP(S) registry mirror
+func ValidateMirror(val string) (string, error) {
+	uri, err := url.Parse(val)
+	if err != nil {
+		return "", fmt.Errorf("%s is not a valid URI", val)
+	}
+
+	if uri.Scheme != "http" && uri.Scheme != "https" {
+		return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
+	}
+
+	if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
+		return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
+	}
+
+	return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
+}
+
+// ValidateIndexName validates an index name.
+func ValidateIndexName(val string) (string, error) {
+	// 'index.docker.io' => 'docker.io'
+	if val == "index."+IndexServerName() {
+		val = IndexServerName()
+	}
+	// *TODO: Check if valid hostname[:port]/ip[:port]?
+	return val, nil
+}
+
+func validateRemoteName(remoteName string) error {
+	var (
+		namespace string
+		name      string
+	)
+	nameParts := strings.SplitN(remoteName, "/", 2)
+	if len(nameParts) < 2 {
+		namespace = "library"
+		name = nameParts[0]
+
+		// the repository name must not be a valid image ID
+		if err := utils.ValidateID(name); err == nil {
+			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
+		}
+	} else {
+		namespace = nameParts[0]
+		name = nameParts[1]
+	}
+	if !validNamespaceChars.MatchString(namespace) {
+		return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
+	}
+	if len(namespace) < 4 || len(namespace) > 30 {
+		return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
+	}
+	if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
+		return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
+	}
+	if strings.Contains(namespace, "--") {
+		return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
+	}
+	if !validRepo.MatchString(name) {
+		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
+	}
+	return nil
+}
+
+func validateNoSchema(reposName string) error {
+	if strings.Contains(reposName, "://") {
+		// It cannot contain a scheme!
+		return ErrInvalidRepositoryName
+	}
+	return nil
+}
+
+// ValidateRepositoryName validates a repository name
+func ValidateRepositoryName(reposName string) error {
+	var err error
+	if err = validateNoSchema(reposName); err != nil {
+		return err
+	}
+	indexName, remoteName := splitReposName(reposName)
+	if _, err = ValidateIndexName(indexName); err != nil {
+		return err
+	}
+	return validateRemoteName(remoteName)
+}
+
+// NewIndexInfo returns IndexInfo configuration from indexName
+func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
+	var err error
+	indexName, err = ValidateIndexName(indexName)
+	if err != nil {
+		return nil, err
+	}
+
+	// Return any configured index info, first.
+	if index, ok := config.IndexConfigs[indexName]; ok {
+		return index, nil
+	}
+
+	// Construct a non-configured index info.
+	index := &IndexInfo{
+		Name:     indexName,
+		Mirrors:  make([]string, 0),
+		Official: false,
+	}
+	index.Secure = config.isSecureIndex(indexName)
+	return index, nil
+}
+
+// GetAuthConfigKey special-cases using the full index address of the official
+// 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 index.Name
+}
+
+// splitReposName breaks a reposName into an index name and remote name
+func splitReposName(reposName string) (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()
+		remoteName = reposName
+	} else {
+		indexName = nameParts[0]
+		remoteName = nameParts[1]
+	}
+	return indexName, remoteName
+}
+
+// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
+func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInfo, error) {
+	if err := validateNoSchema(reposName); err != nil {
+		return nil, err
+	}
+
+	indexName, remoteName := splitReposName(reposName)
+	if err := validateRemoteName(remoteName); err != nil {
+		return nil, err
+	}
+
+	repoInfo := &RepositoryInfo{
+		RemoteName: remoteName,
+	}
+
+	var err error
+	repoInfo.Index, err = config.NewIndexInfo(indexName)
+	if err != nil {
+		return nil, err
+	}
+
+	if repoInfo.Index.Official {
+		normalizedName := repoInfo.RemoteName
+		if strings.HasPrefix(normalizedName, "library/") {
+			// If pull "library/foo", it's stored locally under "foo"
+			normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
+		}
+
+		repoInfo.LocalName = normalizedName
+		repoInfo.RemoteName = normalizedName
+		// If the normalized name does not contain a '/' (e.g. "foo")
+		// then it is an official repo.
+		if strings.IndexRune(normalizedName, '/') == -1 {
+			repoInfo.Official = true
+			// Fix up remote name for official repos.
+			repoInfo.RemoteName = "library/" + normalizedName
+		}
+
+		// *TODO: Prefix this with 'docker.io/'.
+		repoInfo.CanonicalName = repoInfo.LocalName
+	} else {
+		// *TODO: Decouple index name from hostname (via registry configuration?)
+		repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
+		repoInfo.CanonicalName = repoInfo.LocalName
+	}
+	return repoInfo, nil
+}
+
+// GetSearchTerm special-cases using local name for official index, and
+// remote name for private indexes.
+func (repoInfo *RepositoryInfo) GetSearchTerm() string {
+	if repoInfo.Index.Official {
+		return repoInfo.LocalName
+	}
+	return repoInfo.RemoteName
+}
+
+// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
+// lacks registry configuration.
+func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
+	return emptyServiceConfig.NewRepositoryInfo(reposName)
+}
+
+// NormalizeLocalName transforms a repository name into a normalize LocalName
+// Passes through the name without transformation on error (image id, etc)
+func NormalizeLocalName(name string) string {
+	repoInfo, err := ParseRepositoryInfo(name)
+	if err != nil {
+		return name
+	}
+	return repoInfo.LocalName
+}
diff --git a/docs/endpoint.go b/docs/endpoint.go
index 86f53744..95680c5e 100644
--- a/docs/endpoint.go
+++ b/docs/endpoint.go
@@ -157,52 +157,3 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
 	log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
 	return info, nil
 }
-
-// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
-// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
-//
-// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
-// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
-// insecure.
-//
-// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
-// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
-// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
-// of insecureRegistries.
-func (config *ServiceConfig) isSecureIndex(indexName string) bool {
-	// Check for configured index, first.  This is needed in case isSecureIndex
-	// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
-	if index, ok := config.IndexConfigs[indexName]; ok {
-		return index.Secure
-	}
-
-	host, _, err := net.SplitHostPort(indexName)
-	if err != nil {
-		// assume indexName is of the form `host` without the port and go on.
-		host = indexName
-	}
-
-	addrs, err := lookupIP(host)
-	if err != nil {
-		ip := net.ParseIP(host)
-		if ip != nil {
-			addrs = []net.IP{ip}
-		}
-
-		// if ip == nil, then `host` is neither an IP nor it could be looked up,
-		// either because the index is unreachable, or because the index is behind an HTTP proxy.
-		// So, len(addrs) == 0 and we're not aborting.
-	}
-
-	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
-	for _, addr := range addrs {
-		for _, ipnet := range config.InsecureRegistryCIDRs {
-			// check if the addr falls in the subnet
-			if (*net.IPNet)(ipnet).Contains(addr) {
-				return false
-			}
-		}
-	}
-
-	return true
-}
diff --git a/docs/registry.go b/docs/registry.go
index de724ee2..77a78a82 100644
--- a/docs/registry.go
+++ b/docs/registry.go
@@ -10,7 +10,6 @@ import (
 	"net/http"
 	"os"
 	"path"
-	"regexp"
 	"strings"
 	"time"
 
@@ -19,13 +18,9 @@ import (
 )
 
 var (
-	ErrAlreadyExists         = errors.New("Image already exists")
-	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
-	ErrDoesNotExist          = errors.New("Image does not exist")
-	errLoginRequired         = errors.New("Authentication is required.")
-	validNamespaceChars      = regexp.MustCompile(`^([a-z0-9-_]*)$`)
-	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
-	emptyServiceConfig       = NewServiceConfig(nil)
+	ErrAlreadyExists = errors.New("Image already exists")
+	ErrDoesNotExist  = errors.New("Image does not exist")
+	errLoginRequired = errors.New("Authentication is required.")
 )
 
 type TimeoutType uint32
@@ -161,185 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
 	return res, client, err
 }
 
-func validateRemoteName(remoteName string) error {
-	var (
-		namespace string
-		name      string
-	)
-	nameParts := strings.SplitN(remoteName, "/", 2)
-	if len(nameParts) < 2 {
-		namespace = "library"
-		name = nameParts[0]
-
-		// the repository name must not be a valid image ID
-		if err := utils.ValidateID(name); err == nil {
-			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
-		}
-	} else {
-		namespace = nameParts[0]
-		name = nameParts[1]
-	}
-	if !validNamespaceChars.MatchString(namespace) {
-		return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
-	}
-	if len(namespace) < 4 || len(namespace) > 30 {
-		return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
-	}
-	if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
-		return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
-	}
-	if strings.Contains(namespace, "--") {
-		return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
-	}
-	if !validRepo.MatchString(name) {
-		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
-	}
-	return nil
-}
-
-// NewIndexInfo returns IndexInfo configuration from indexName
-func NewIndexInfo(config *ServiceConfig, indexName string) (*IndexInfo, error) {
-	var err error
-	indexName, err = ValidateIndexName(indexName)
-	if err != nil {
-		return nil, err
-	}
-
-	// Return any configured index info, first.
-	if index, ok := config.IndexConfigs[indexName]; ok {
-		return index, nil
-	}
-
-	// Construct a non-configured index info.
-	index := &IndexInfo{
-		Name:     indexName,
-		Mirrors:  make([]string, 0),
-		Official: false,
-	}
-	index.Secure = config.isSecureIndex(indexName)
-	return index, nil
-}
-
-func validateNoSchema(reposName string) error {
-	if strings.Contains(reposName, "://") {
-		// It cannot contain a scheme!
-		return ErrInvalidRepositoryName
-	}
-	return nil
-}
-
-// splitReposName breaks a reposName into an index name and remote name
-func splitReposName(reposName string) (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()
-		remoteName = reposName
-	} else {
-		indexName = nameParts[0]
-		remoteName = nameParts[1]
-	}
-	return indexName, remoteName
-}
-
-// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
-func NewRepositoryInfo(config *ServiceConfig, reposName string) (*RepositoryInfo, error) {
-	if err := validateNoSchema(reposName); err != nil {
-		return nil, err
-	}
-
-	indexName, remoteName := splitReposName(reposName)
-	if err := validateRemoteName(remoteName); err != nil {
-		return nil, err
-	}
-
-	repoInfo := &RepositoryInfo{
-		RemoteName: remoteName,
-	}
-
-	var err error
-	repoInfo.Index, err = NewIndexInfo(config, indexName)
-	if err != nil {
-		return nil, err
-	}
-
-	if repoInfo.Index.Official {
-		normalizedName := repoInfo.RemoteName
-		if strings.HasPrefix(normalizedName, "library/") {
-			// If pull "library/foo", it's stored locally under "foo"
-			normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
-		}
-
-		repoInfo.LocalName = normalizedName
-		repoInfo.RemoteName = normalizedName
-		// If the normalized name does not contain a '/' (e.g. "foo")
-		// then it is an official repo.
-		if strings.IndexRune(normalizedName, '/') == -1 {
-			repoInfo.Official = true
-			// Fix up remote name for official repos.
-			repoInfo.RemoteName = "library/" + normalizedName
-		}
-
-		// *TODO: Prefix this with 'docker.io/'.
-		repoInfo.CanonicalName = repoInfo.LocalName
-	} else {
-		// *TODO: Decouple index name from hostname (via registry configuration?)
-		repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
-		repoInfo.CanonicalName = repoInfo.LocalName
-	}
-	return repoInfo, nil
-}
-
-// ValidateRepositoryName validates a repository name
-func ValidateRepositoryName(reposName string) error {
-	var err error
-	if err = validateNoSchema(reposName); err != nil {
-		return err
-	}
-	indexName, remoteName := splitReposName(reposName)
-	if _, err = ValidateIndexName(indexName); err != nil {
-		return err
-	}
-	return validateRemoteName(remoteName)
-}
-
-// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
-// lacks registry configuration.
-func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
-	return NewRepositoryInfo(emptyServiceConfig, reposName)
-}
-
-// NormalizeLocalName transforms a repository name into a normalize LocalName
-// Passes through the name without transformation on error (image id, etc)
-func NormalizeLocalName(name string) string {
-	repoInfo, err := ParseRepositoryInfo(name)
-	if err != nil {
-		return name
-	}
-	return repoInfo.LocalName
-}
-
-// GetAuthConfigKey special-cases using the full index address of the official
-// 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 index.Name
-}
-
-// GetSearchTerm special-cases using local name for official index, and
-// remote name for private indexes.
-func (repoInfo *RepositoryInfo) GetSearchTerm() string {
-	if repoInfo.Index.Official {
-		return repoInfo.LocalName
-	}
-	return repoInfo.RemoteName
-}
-
 func trustedLocation(req *http.Request) bool {
 	var (
 		trusteds = []string{"docker.com", "docker.io"}
diff --git a/docs/registry_test.go b/docs/registry_test.go
index 511d7eb1..6bf31505 100644
--- a/docs/registry_test.go
+++ b/docs/registry_test.go
@@ -561,7 +561,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 func TestNewIndexInfo(t *testing.T) {
 	testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
 		for indexName, expectedIndexInfo := range expectedIndexInfos {
-			index, err := NewIndexInfo(config, indexName)
+			index, err := config.NewIndexInfo(indexName)
 			if err != nil {
 				t.Fatal(err)
 			} else {
diff --git a/docs/service.go b/docs/service.go
index 310539c4..c34e3842 100644
--- a/docs/service.go
+++ b/docs/service.go
@@ -130,7 +130,7 @@ func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
 		reposName = job.Args[0]
 	)
 
-	repoInfo, err := NewRepositoryInfo(s.Config, reposName)
+	repoInfo, err := s.Config.NewRepositoryInfo(reposName)
 	if err != nil {
 		return job.Error(err)
 	}
@@ -168,7 +168,7 @@ func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
 		indexName = job.Args[0]
 	)
 
-	index, err := NewIndexInfo(s.Config, indexName)
+	index, err := s.Config.NewIndexInfo(indexName)
 	if err != nil {
 		return job.Error(err)
 	}