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 }