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"
2016-07-26 11:18:38 -04:00
"fmt"
2024-05-08 08:59:49 -04:00
"io"
2016-07-26 11:18:38 -04:00
"net/http"
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
2022-07-22 05:19:15 -04:00
authConfig , err := getAuthConfigForRegistry ( pullOpts . Registry , meta . ( * ProviderConfig ) )
if err != nil {
// The user did not provide a credential for this registry.
// But there are many registries where you can pull without a credential.
// We are setting default values for the authConfig here.
authConfig . Username = ""
authConfig . Password = ""
authConfig . ServerAddress = "https://" + pullOpts . Registry
2017-11-21 04:14:07 -05:00
}
2021-05-31 03:11:49 -04:00
insecureSkipVerify := d . Get ( "insecure_skip_verify" ) . ( bool )
2022-07-22 05:19:15 -04:00
digest , err := getImageDigest ( pullOpts . Registry , authConfig . ServerAddress , pullOpts . Repository , pullOpts . Tag , authConfig . Username , authConfig . Password , insecureSkipVerify , false )
2016-07-26 11:18:38 -04:00
if err != nil {
2022-07-22 05:19:15 -04:00
digest , err = getImageDigest ( pullOpts . Registry , authConfig . ServerAddress , pullOpts . Repository , pullOpts . Tag , authConfig . Username , authConfig . Password , insecureSkipVerify , true )
2018-02-09 14:11:30 -05:00
if err != nil {
2021-06-21 03:52:34 -04:00
return diag . Errorf ( "Got error when attempting to fetch image version %s:%s from registry: %s" , pullOpts . Repository , pullOpts . Tag , 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
}
2022-07-22 05:19:15 -04:00
func getImageDigest ( registry string , registryWithProtocol string , image , tag , username , password string , insecureSkipVerify , fallback bool ) ( string , error ) {
client := buildHttpClientForRegistry ( registryWithProtocol , insecureSkipVerify )
2016-07-26 11:18:38 -04:00
2025-04-29 00:19:37 -04:00
req , err := setupHTTPRequestForRegistry ( "HEAD" , registry , registryWithProtocol , image , tag , username , password , fallback )
2016-07-26 11:18:38 -04:00
if err != nil {
2025-04-29 00:19:37 -04:00
return "" , err
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 :
2025-04-29 00:19:37 -04:00
auth , err := parseAuthHeader ( resp . Header . Get ( "www-authenticate" ) )
if err != nil {
return "" , fmt . Errorf ( "bad credentials: %s" , resp . Status )
2022-08-29 09:53:40 -04:00
}
2016-07-26 11:18:38 -04:00
2025-04-29 10:21:08 -04:00
token , err := getAuthToken ( auth , username , password , "repository:" + image + ":push,pull" , client )
2022-08-29 09:53:40 -04:00
if err != nil {
return "" , err
}
2016-07-26 11:18:38 -04:00
2022-08-29 09:53:40 -04:00
req . Header . Set ( "Authorization" , "Bearer " + token )
2016-07-26 11:18:38 -04:00
2022-08-29 09:53:40 -04:00
// Do a HEAD request to docker registry first (avoiding Docker Hub rate limiting)
digestResponse , err := doDigestRequest ( req , client )
if err != nil {
return "" , err
}
2016-07-26 11:18:38 -04:00
2022-08-29 09:53:40 -04:00
digest , err := getDigestFromResponse ( digestResponse )
if err == nil {
return digest , nil
}
2016-07-26 11:18:38 -04:00
2022-08-29 09:53:40 -04:00
// If previous HEAD request does not contain required info, do a GET request
req . Method = "GET"
digestResponse , err = doDigestRequest ( req , client )
2016-07-26 11:18:38 -04:00
2022-08-29 09:53:40 -04:00
if err != nil {
return "" , err
2016-07-26 11:18:38 -04:00
}
2022-08-29 09:53:40 -04:00
return getDigestFromResponse ( digestResponse )
2018-07-03 11:30:53 -04:00
2022-08-29 09:53:40 -04: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 )
}
}
2019-10-06 05:25:02 -04:00
func getDigestFromResponse ( response * http . Response ) ( string , error ) {
header := response . Header . Get ( "Docker-Content-Digest" )
if header == "" {
2024-05-08 08:59:49 -04:00
body , err := io . ReadAll ( response . Body )
2022-08-29 09:53:40 -04:00
if err != nil || len ( body ) == 0 {
2019-10-06 05:25:02 -04:00
return "" , fmt . Errorf ( "Error reading registry response body: %s" , err )
}
return fmt . Sprintf ( "sha256:%x" , sha256 . Sum256 ( body ) ) , nil
}
return header , nil
}
2022-08-29 09:53:40 -04:00
func doDigestRequest ( req * http . Request , client * http . Client ) ( * http . Response , error ) {
digestResponse , err := client . Do ( req )
if err != nil {
2024-05-08 08:59:49 -04:00
return nil , fmt . Errorf ( "error during registry request: %s" , err )
2022-08-29 09:53:40 -04:00
}
if digestResponse . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "Got bad response from registry: " + digestResponse . Status )
}
return digestResponse , nil
}