mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-18 23:06:10 -05:00
201 lines
6.3 KiB
Go
201 lines
6.3 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/docker/docker/api/types/registry"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
)
|
|
|
|
func dataSourceDockerRegistryImageManifests() *schema.Resource {
|
|
return &schema.Resource{
|
|
Description: "Reads the image metadata for each manifest in a Docker multi-arch image from a Docker Registry.",
|
|
|
|
ReadContext: dataSourceDockerRegistryImageManifestsRead,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Description: "The name of the Docker image, including any tags. e.g. `alpine:latest`",
|
|
Required: true,
|
|
},
|
|
|
|
"auth_config": AuthConfigSchema,
|
|
|
|
"manifests": {
|
|
Type: schema.TypeSet,
|
|
Description: "The metadata for each manifest in the image",
|
|
Computed: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"media_type": {
|
|
Type: schema.TypeString,
|
|
Description: "The media type of the manifest.",
|
|
Computed: true,
|
|
},
|
|
"sha256_digest": {
|
|
Type: schema.TypeString,
|
|
Description: "The content digest of the manifest, as stored in the registry.",
|
|
Computed: true,
|
|
},
|
|
"architecture": {
|
|
Type: schema.TypeString,
|
|
Description: "The platform architecture supported by the manifest.",
|
|
Computed: true,
|
|
},
|
|
"os": {
|
|
Type: schema.TypeString,
|
|
Description: "The operating system supported by the manifest.",
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"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,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func dataSourceDockerRegistryImageManifestsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
pullOpts := parseImageOptions(d.Get("name").(string))
|
|
|
|
var authConfig registry.AuthConfig
|
|
if v, ok := d.GetOk("auth_config"); ok {
|
|
log.Printf("[INFO] Using auth config from resource: %s", v)
|
|
authConfig = buildAuthConfigFromResource(v)
|
|
} else {
|
|
log.Printf("[INFO] Using auth config from provider: %s", v)
|
|
var err error
|
|
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
|
|
}
|
|
}
|
|
|
|
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
|
|
manifest, err := getImageManifest(pullOpts.Registry, authConfig.ServerAddress, pullOpts.Repository, pullOpts.Tag, authConfig.Username, authConfig.Password, insecureSkipVerify, false)
|
|
if err != nil {
|
|
manifest, err = getImageManifest(pullOpts.Registry, authConfig.ServerAddress, pullOpts.Repository, pullOpts.Tag, authConfig.Username, authConfig.Password, insecureSkipVerify, true)
|
|
if err != nil {
|
|
return diag.Errorf("Got error when attempting to fetch image version %s:%s from registry: %s", pullOpts.Repository, pullOpts.Tag, err)
|
|
}
|
|
}
|
|
|
|
d.SetId(fmt.Sprintf("%s:%s", pullOpts.Repository, pullOpts.Tag))
|
|
if err = d.Set("manifests", flattenManifests(manifest.Manifests)); err != nil {
|
|
log.Printf("[WARN] failed to set manifests from API: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getImageManifest(registry, registryWithProtocol, image, tag, username, password string, insecureSkipVerify, fallback bool) (*ManifestResponse, error) {
|
|
client := buildHttpClientForRegistry(registryWithProtocol, insecureSkipVerify)
|
|
|
|
req, err := setupHTTPRequestForRegistry("GET", registry, registryWithProtocol, image, tag, username, password, fallback)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return doManifestRequest(req, client, username, password, "repository:"+image+":push,pull", true)
|
|
}
|
|
|
|
func doManifestRequest(req *http.Request, client *http.Client, username string, password string, fallbackScope string, retryUnauthorized bool) (*ManifestResponse, error) {
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error during registry request: %s", err)
|
|
}
|
|
|
|
switch resp.StatusCode {
|
|
// Basic auth was valid or not needed
|
|
case http.StatusOK:
|
|
return getManifestsFromResponse(resp)
|
|
|
|
default:
|
|
if resp.StatusCode == http.StatusUnauthorized && retryUnauthorized {
|
|
auth, err := parseAuthHeader(resp.Header.Get("www-authenticate"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bad credentials: %s", resp.Status)
|
|
}
|
|
|
|
token, err := getAuthToken(auth, username, password, fallbackScope, client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
return doManifestRequest(req, client, username, password, fallbackScope, false)
|
|
}
|
|
|
|
return nil, fmt.Errorf("got bad response from registry: %s", resp.Status)
|
|
}
|
|
}
|
|
|
|
func getManifestsFromResponse(response *http.Response) (*ManifestResponse, error) {
|
|
body, err := io.ReadAll(response.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error reading response body: %s", err)
|
|
}
|
|
|
|
manifest := &ManifestResponse{}
|
|
err = json.Unmarshal(body, manifest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error parsing manifest response: %s", err)
|
|
}
|
|
|
|
if len(manifest.Manifests) == 0 {
|
|
log.Printf("[DEBUG] Manifest response was not for list: %s", string(body))
|
|
return nil, fmt.Errorf("Error unsupported manifest response")
|
|
}
|
|
|
|
return manifest, nil
|
|
}
|
|
|
|
func flattenManifests(in []Manifest) []manifestMap {
|
|
manifests := make([]manifestMap, len(in))
|
|
for i, m := range in {
|
|
manifests[i] = manifestMap{
|
|
"media_type": m.MediaType,
|
|
"sha256_digest": m.Digest,
|
|
"architecture": m.Platform.Architecture,
|
|
"os": m.Platform.OS,
|
|
}
|
|
}
|
|
|
|
return manifests
|
|
}
|
|
|
|
type ManifestResponse struct {
|
|
Manifests []Manifest `json:"manifests"`
|
|
}
|
|
|
|
type Manifest struct {
|
|
MediaType string `json:"mediaType"`
|
|
Digest string `json:"digest"`
|
|
Platform ManifestPlatform `json:"platform"`
|
|
}
|
|
|
|
type ManifestPlatform struct {
|
|
Architecture string `json:"architecture"`
|
|
OS string `json:"os"`
|
|
}
|
|
|
|
type manifestMap map[string]interface{}
|