2021-03-18 03:30:54 -04:00
package provider
2016-07-26 11:18:38 -04:00
import (
2021-03-18 03:30:54 -04:00
"context"
2019-10-06 05:25:02 -04:00
"crypto/sha256"
2018-02-09 14:11:30 -05:00
"crypto/tls"
2016-07-26 11:18:38 -04:00
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
2021-03-18 03:30:54 -04:00
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2016-07-26 11:18:38 -04:00
)
func dataSourceDockerRegistryImage ( ) * schema . Resource {
return & schema . Resource {
2021-05-21 08:30:56 -04:00
Description : "Reads the image metadata from a Docker Registry. Used in conjunction with the [docker_image](../resources/image.md) resource to keep an image up to date on the latest available version of the tag." ,
2021-03-18 03:30:54 -04:00
ReadContext : dataSourceDockerRegistryImageRead ,
2016-07-26 11:18:38 -04:00
Schema : map [ string ] * schema . Schema {
2019-03-01 16:02:17 -05:00
"name" : {
2021-05-21 08:30:56 -04:00
Type : schema . TypeString ,
Description : "The name of the Docker image, including any tags. e.g. `alpine:latest`" ,
Required : true ,
2016-07-26 11:18:38 -04:00
} ,
2019-03-01 16:02:17 -05:00
"sha256_digest" : {
2021-05-21 08:30:56 -04:00
Type : schema . TypeString ,
Description : "The content digest of the image, as stored in the registry." ,
Computed : true ,
2016-07-26 11:18:38 -04:00
} ,
2021-05-31 03:11:49 -04:00
"insecure_skip_verify" : {
Type : schema . TypeBool ,
Description : "If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false`" ,
Optional : true ,
Default : false ,
} ,
2016-07-26 11:18:38 -04:00
} ,
}
}
2021-03-18 03:30:54 -04:00
func dataSourceDockerRegistryImageRead ( ctx context . Context , d * schema . ResourceData , meta interface { } ) diag . Diagnostics {
2016-07-26 11:18:38 -04:00
pullOpts := parseImageOptions ( d . Get ( "name" ) . ( string ) )
2017-11-21 04:14:07 -05:00
authConfig := meta . ( * ProviderConfig ) . AuthConfigs
2016-07-26 11:18:38 -04:00
// Use the official Docker Hub if a registry isn't specified
if pullOpts . Registry == "" {
pullOpts . Registry = "registry.hub.docker.com"
} else {
// Otherwise, filter the registry name out of the repo name
pullOpts . Repository = strings . Replace ( pullOpts . Repository , pullOpts . Registry + "/" , "" , 1 )
}
2018-01-02 04:31:06 -05:00
if pullOpts . Registry == "registry.hub.docker.com" {
// Docker prefixes 'library' to official images in the path; 'consul' becomes 'library/consul'
if ! strings . Contains ( pullOpts . Repository , "/" ) {
pullOpts . Repository = "library/" + pullOpts . Repository
}
2016-07-26 11:18:38 -04:00
}
if pullOpts . Tag == "" {
pullOpts . Tag = "latest"
}
2017-11-21 04:14:07 -05:00
username := ""
password := ""
if auth , ok := authConfig . Configs [ normalizeRegistryAddress ( pullOpts . Registry ) ] ; ok {
username = auth . Username
password = auth . Password
}
2021-05-31 03:11:49 -04:00
insecureSkipVerify := d . Get ( "insecure_skip_verify" ) . ( bool )
digest , err := getImageDigest ( pullOpts . Registry , pullOpts . Repository , pullOpts . Tag , username , password , insecureSkipVerify , false )
2016-07-26 11:18:38 -04:00
if err != nil {
2021-05-31 03:11:49 -04:00
digest , err = getImageDigest ( pullOpts . Registry , pullOpts . Repository , pullOpts . Tag , username , password , insecureSkipVerify , true )
2018-02-09 14:11:30 -05:00
if err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Got error when attempting to fetch image version from registry: %s" , err )
2018-02-09 14:11:30 -05:00
}
2016-07-26 11:18:38 -04:00
}
d . SetId ( digest )
d . Set ( "sha256_digest" , digest )
return nil
}
2021-05-31 03:11:49 -04:00
func getImageDigest ( registry , image , tag , username , password string , insecureSkipVerify , fallback bool ) ( string , error ) {
2016-07-26 11:18:38 -04:00
client := http . DefaultClient
2021-05-31 03:11:49 -04:00
// DevSkim: ignore DS440000
client . Transport = & http . Transport { TLSClientConfig : & tls . Config { InsecureSkipVerify : insecureSkipVerify } }
2016-07-26 11:18:38 -04:00
2018-02-09 14:11:30 -05:00
req , err := http . NewRequest ( "GET" , "https://" + registry + "/v2/" + image + "/manifests/" + tag , nil )
2016-07-26 11:18:38 -04:00
if err != nil {
return "" , fmt . Errorf ( "Error creating registry request: %s" , err )
}
if username != "" {
req . SetBasicAuth ( username , password )
}
2020-12-01 08:06:27 -05:00
// We accept schema v2 manifests and manifest lists, and also OCI types
req . Header . Add ( "Accept" , "application/vnd.docker.distribution.manifest.v2+json" )
req . Header . Add ( "Accept" , "application/vnd.docker.distribution.manifest.list.v2+json" )
req . Header . Add ( "Accept" , "application/vnd.oci.image.manifest.v1+json" )
req . Header . Add ( "Accept" , "application/vnd.oci.image.index.v1+json" )
2018-02-09 14:11:30 -05:00
if fallback {
// Fallback to this header if the registry does not support the v2 manifest like gcr.io
req . Header . Set ( "Accept" , "application/vnd.docker.distribution.manifest.v1+prettyjws" )
}
2017-11-21 04:14:07 -05:00
2016-07-26 11:18:38 -04:00
resp , err := client . Do ( req )
if err != nil {
return "" , fmt . Errorf ( "Error during registry request: %s" , err )
}
switch resp . StatusCode {
// Basic auth was valid or not needed
case http . StatusOK :
2019-10-06 05:25:02 -04:00
return getDigestFromResponse ( resp )
2016-07-26 11:18:38 -04:00
// Either OAuth is required or the basic auth creds were invalid
case http . StatusUnauthorized :
if strings . HasPrefix ( resp . Header . Get ( "www-authenticate" ) , "Bearer" ) {
auth := parseAuthHeader ( resp . Header . Get ( "www-authenticate" ) )
params := url . Values { }
params . Set ( "service" , auth [ "service" ] )
params . Set ( "scope" , auth [ "scope" ] )
tokenRequest , err := http . NewRequest ( "GET" , auth [ "realm" ] + "?" + params . Encode ( ) , nil )
if err != nil {
return "" , fmt . Errorf ( "Error creating registry request: %s" , err )
}
if username != "" {
tokenRequest . SetBasicAuth ( username , password )
}
tokenResponse , err := client . Do ( tokenRequest )
if err != nil {
return "" , fmt . Errorf ( "Error during registry request: %s" , err )
}
if tokenResponse . StatusCode != http . StatusOK {
return "" , fmt . Errorf ( "Got bad response from registry: " + tokenResponse . Status )
}
body , err := ioutil . ReadAll ( tokenResponse . Body )
if err != nil {
return "" , fmt . Errorf ( "Error reading response body: %s" , err )
}
token := & TokenResponse { }
err = json . Unmarshal ( body , token )
if err != nil {
return "" , fmt . Errorf ( "Error parsing OAuth token response: %s" , err )
}
req . Header . Set ( "Authorization" , "Bearer " + token . Token )
digestResponse , err := client . Do ( req )
if err != nil {
return "" , fmt . Errorf ( "Error during registry request: %s" , err )
}
if digestResponse . StatusCode != http . StatusOK {
return "" , fmt . Errorf ( "Got bad response from registry: " + digestResponse . Status )
}
2019-10-06 05:25:02 -04:00
return getDigestFromResponse ( digestResponse )
2016-07-26 11:18:38 -04:00
}
2018-07-03 11:30:53 -04:00
return "" , fmt . Errorf ( "Bad credentials: " + resp . Status )
2018-02-09 14:11:30 -05:00
// Some unexpected status was given, return an error
2016-07-26 11:18:38 -04:00
default :
return "" , fmt . Errorf ( "Got bad response from registry: " + resp . Status )
}
}
type TokenResponse struct {
Token string
}
// Parses key/value pairs from a WWW-Authenticate header
func parseAuthHeader ( header string ) map [ string ] string {
parts := strings . SplitN ( header , " " , 2 )
parts = strings . Split ( parts [ 1 ] , "," )
opts := make ( map [ string ] string )
for _ , part := range parts {
vals := strings . SplitN ( part , "=" , 2 )
key := vals [ 0 ]
val := strings . Trim ( vals [ 1 ] , "\", " )
opts [ key ] = val
}
return opts
}
2019-10-06 05:25:02 -04:00
func getDigestFromResponse ( response * http . Response ) ( string , error ) {
header := response . Header . Get ( "Docker-Content-Digest" )
if header == "" {
body , err := ioutil . ReadAll ( response . Body )
if err != nil {
return "" , fmt . Errorf ( "Error reading registry response body: %s" , err )
}
return fmt . Sprintf ( "sha256:%x" , sha256 . Sum256 ( body ) ) , nil
}
return header , nil
}