github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/docker/data_source_docker_registry_image.go (about)

     1  package docker
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  )
    13  
    14  func dataSourceDockerRegistryImage() *schema.Resource {
    15  	return &schema.Resource{
    16  		Read: dataSourceDockerRegistryImageRead,
    17  
    18  		Schema: map[string]*schema.Schema{
    19  			"name": &schema.Schema{
    20  				Type:     schema.TypeString,
    21  				Optional: true,
    22  			},
    23  
    24  			"sha256_digest": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Computed: true,
    27  			},
    28  		},
    29  	}
    30  }
    31  
    32  func dataSourceDockerRegistryImageRead(d *schema.ResourceData, meta interface{}) error {
    33  	pullOpts := parseImageOptions(d.Get("name").(string))
    34  
    35  	// Use the official Docker Hub if a registry isn't specified
    36  	if pullOpts.Registry == "" {
    37  		pullOpts.Registry = "registry.hub.docker.com"
    38  	} else {
    39  		// Otherwise, filter the registry name out of the repo name
    40  		pullOpts.Repository = strings.Replace(pullOpts.Repository, pullOpts.Registry+"/", "", 1)
    41  	}
    42  
    43  	// Docker prefixes 'library' to official images in the path; 'consul' becomes 'library/consul'
    44  	if !strings.Contains(pullOpts.Repository, "/") {
    45  		pullOpts.Repository = "library/" + pullOpts.Repository
    46  	}
    47  
    48  	if pullOpts.Tag == "" {
    49  		pullOpts.Tag = "latest"
    50  	}
    51  
    52  	digest, err := getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, "", "")
    53  
    54  	if err != nil {
    55  		return fmt.Errorf("Got error when attempting to fetch image version from registry: %s", err)
    56  	}
    57  
    58  	d.SetId(digest)
    59  	d.Set("sha256_digest", digest)
    60  
    61  	return nil
    62  }
    63  
    64  func getImageDigest(registry, image, tag, username, password string) (string, error) {
    65  	client := http.DefaultClient
    66  
    67  	req, err := http.NewRequest("GET", "https://"+registry+"/v2/"+image+"/manifests/"+tag, nil)
    68  
    69  	if err != nil {
    70  		return "", fmt.Errorf("Error creating registry request: %s", err)
    71  	}
    72  
    73  	if username != "" {
    74  		req.SetBasicAuth(username, password)
    75  	}
    76  
    77  	resp, err := client.Do(req)
    78  
    79  	if err != nil {
    80  		return "", fmt.Errorf("Error during registry request: %s", err)
    81  	}
    82  
    83  	switch resp.StatusCode {
    84  	// Basic auth was valid or not needed
    85  	case http.StatusOK:
    86  		return resp.Header.Get("Docker-Content-Digest"), nil
    87  
    88  	// Either OAuth is required or the basic auth creds were invalid
    89  	case http.StatusUnauthorized:
    90  		if strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") {
    91  			auth := parseAuthHeader(resp.Header.Get("www-authenticate"))
    92  			params := url.Values{}
    93  			params.Set("service", auth["service"])
    94  			params.Set("scope", auth["scope"])
    95  			tokenRequest, err := http.NewRequest("GET", auth["realm"]+"?"+params.Encode(), nil)
    96  
    97  			if err != nil {
    98  				return "", fmt.Errorf("Error creating registry request: %s", err)
    99  			}
   100  
   101  			if username != "" {
   102  				tokenRequest.SetBasicAuth(username, password)
   103  			}
   104  
   105  			tokenResponse, err := client.Do(tokenRequest)
   106  
   107  			if err != nil {
   108  				return "", fmt.Errorf("Error during registry request: %s", err)
   109  			}
   110  
   111  			if tokenResponse.StatusCode != http.StatusOK {
   112  				return "", fmt.Errorf("Got bad response from registry: " + tokenResponse.Status)
   113  			}
   114  
   115  			body, err := ioutil.ReadAll(tokenResponse.Body)
   116  			if err != nil {
   117  				return "", fmt.Errorf("Error reading response body: %s", err)
   118  			}
   119  
   120  			token := &TokenResponse{}
   121  			err = json.Unmarshal(body, token)
   122  			if err != nil {
   123  				return "", fmt.Errorf("Error parsing OAuth token response: %s", err)
   124  			}
   125  
   126  			req.Header.Set("Authorization", "Bearer "+token.Token)
   127  			digestResponse, err := client.Do(req)
   128  
   129  			if err != nil {
   130  				return "", fmt.Errorf("Error during registry request: %s", err)
   131  			}
   132  
   133  			if digestResponse.StatusCode != http.StatusOK {
   134  				return "", fmt.Errorf("Got bad response from registry: " + digestResponse.Status)
   135  			}
   136  
   137  			return digestResponse.Header.Get("Docker-Content-Digest"), nil
   138  		} else {
   139  			return "", fmt.Errorf("Bad credentials: " + resp.Status)
   140  		}
   141  
   142  	// Some unexpected status was given, return an error
   143  	default:
   144  		return "", fmt.Errorf("Got bad response from registry: " + resp.Status)
   145  	}
   146  }
   147  
   148  type TokenResponse struct {
   149  	Token string
   150  }
   151  
   152  // Parses key/value pairs from a WWW-Authenticate header
   153  func parseAuthHeader(header string) map[string]string {
   154  	parts := strings.SplitN(header, " ", 2)
   155  	parts = strings.Split(parts[1], ",")
   156  	opts := make(map[string]string)
   157  
   158  	for _, part := range parts {
   159  		vals := strings.SplitN(part, "=", 2)
   160  		key := vals[0]
   161  		val := strings.Trim(vals[1], "\", ")
   162  		opts[key] = val
   163  	}
   164  
   165  	return opts
   166  }