func (d *Disco) discover(hostname svchost.Hostname) (*Host, error) {
	discoURL := &url.URL{
		Scheme: "https",
		Host:   hostname.String(),
		Path:   discoPath,
	}
	client := &http.Client{
		Transport: d.Transport,
		Timeout:   discoTimeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			log.Printf("[DEBUG] Service discovery redirected to %s", req.URL)
			if len(via) > maxRedirects {
				return errors.New("too many redirects")  
			}
			return nil
		},
	}
	req := &http.Request{
		Header: make(http.Header),
		Method: "GET",
		URL:    discoURL,
	}
	req.Header.Set("Accept", "application/json")
	creds, err := d.CredentialsForHost(hostname)
	if err != nil {
		log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", hostname, err)
	}
	if creds != nil {
		creds.PrepareRequest(req)
	}
	log.Printf("[DEBUG] Service discovery for %s at %s", hostname, discoURL)
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Failed to request discovery document: %v", err)
	}
	defer resp.Body.Close()
	host := &Host{
		discoURL:  resp.Request.URL,
		hostname:  hostname.ForDisplay(),
		transport: d.Transport,
	}
	if resp.StatusCode == 404 {
		return host, nil
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Failed to request discovery document: %s", resp.Status)
	}
	contentType := resp.Header.Get("Content-Type")
	mediaType, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, fmt.Errorf("Discovery URL has a malformed Content-Type %q", contentType)
	}
	if mediaType != "application/json" {
		return nil, fmt.Errorf("Discovery URL returned an unsupported Content-Type %q", mediaType)
	}
	if resp.ContentLength > maxDiscoDocBytes {
		return nil, fmt.Errorf(
			"Discovery doc response is too large (got %d bytes; limit %d)",
			resp.ContentLength, maxDiscoDocBytes,
		)
	}
	lr := io.LimitReader(resp.Body, maxDiscoDocBytes)
	servicesBytes, err := ioutil.ReadAll(lr)
	if err != nil {
		return nil, fmt.Errorf("Error reading discovery document body: %v", err)
	}
	var services map[string]interface{}
	err = json.Unmarshal(servicesBytes, &services)
	if err != nil {
		return nil, fmt.Errorf("Failed to decode discovery document as a JSON object: %v", err)
	}
	host.services = services
	return host, nil
}
discover implements the actual discovery process, with its result cached
by the public-facing Discover method.
package disco
import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"time"
	"github.com/hashicorp/terraform-svchost"
	"github.com/hashicorp/terraform-svchost/auth"
)
const (
	discoPath = "/.well-known/terraform.json"
	maxRedirects = 3
	discoTimeout = 11 * time.Second
	maxDiscoDocBytes = 1 * 1024 * 1024
)
var httpTransport = defaultHttpTransport()
type Disco struct {
	hostCache map[svchost.Hostname]*Host
	credsSrc  auth.CredentialsSource
	Transport http.RoundTripper
}
func New() *Disco {
	return NewWithCredentialsSource(nil)
}
func NewWithCredentialsSource(credsSrc auth.CredentialsSource) *Disco {
	return &Disco{
		hostCache: make(map[svchost.Hostname]*Host),
		credsSrc:  credsSrc,
		Transport: httpTransport,
	}
}
func (d *Disco) SetUserAgent(uaString string) {
	d.Transport = &userAgentRoundTripper{
		innerRt:   d.Transport,
		userAgent: uaString,
	}
}
func (d *Disco) SetCredentialsSource(src auth.CredentialsSource) {
	d.credsSrc = src
}
func (d *Disco) CredentialsSource() auth.CredentialsSource {
	if d.credsSrc == nil {
		return auth.StaticCredentialsSource(nil)
	}
	return d.credsSrc
}
func (d *Disco) CredentialsForHost(hostname svchost.Hostname) (auth.HostCredentials, error) {
	if d.credsSrc == nil {
		return nil, nil
	}
	return d.credsSrc.ForHost(hostname)
}
func (d *Disco) ForceHostServices(hostname svchost.Hostname, services map[string]interface{}) {
	if services == nil {
		services = map[string]interface{}{}
	}
	d.hostCache[hostname] = &Host{
		discoURL: &url.URL{
			Scheme: "https",
			Host:   string(hostname),
			Path:   discoPath,
		},
		hostname:  hostname.ForDisplay(),
		services:  services,
		transport: d.Transport,
	}
}
func (d *Disco) Discover(hostname svchost.Hostname) (*Host, error) {
	if host, cached := d.hostCache[hostname]; cached {
		return host, nil
	}
	host, err := d.discover(hostname)
	if err != nil {
		return nil, err
	}
	d.hostCache[hostname] = host
	return host, nil
}
func (d *Disco) DiscoverServiceURL(hostname svchost.Hostname, serviceID string) (*url.URL, error) {
	host, err := d.Discover(hostname)
	if err != nil {
		return nil, err
	}
	return host.ServiceURL(serviceID)
}