mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-22 07:39:35 -05:00
* chore: format test configs for datasources * chore: outlines load test config helper and structure * docs(contributing): add command for resouce tests to have an example of the regex * refactor: move container test configs into separate files * fix: add insecure_skip_verify for image pulls to fix the local test setup with invalid certs * chore(ci): remove insecure registry adaption * chore: regenerate website * chore: update gitignore for scipts/testing dir * fix: replace nodejs services with go versions * fix: move testing program versions in separate files * test: reactivate flaky test from travis * chore: fix linter on all go files * fix(linter): testing go servers * chore(ci): add env for go version * chore(ci): name workflow steps also moves description of available docker versions in to acc dockerfile * Revert "test: reactivate flaky test from travis" This reverts commit b02654acc4d6b7d02c8f3ba090e6a3f248741b10. * docs: fix provider-ssh example * chore: use alpine als final image for tests * refactor: move test configs from folder into testname.tf files * refactor: image delete log is now debug and indented * refactor: image test config into seprate files * refactor: move network test config into seperate files * refactor: move plugin test config into seperate files * chore: rename registry image test file * refactor: move registry_image test config into seperate files * chore: format secret test configs * refactor: inline volume test configs * fix: remove unused volume label test function * refactor: move service test configs into seperate files * test: reactivate and fix service test * chore: simplify insecure skip verify add to http client * chore(ci): debug into service test * chore(ci): add testacc setup * chore: format tf config for provider test * chore(ci): add debug output for config.json * fix: check service auth for emptyness * fix: remove re-read of provider auth config because the bug occured only in CI as the meta object might be GCd * test: pass auth to service instead of provider * chore: reactivate all acc tests * test: outlines service inspect json check for full spec * test: add service inspect json checks * test: finish service inspect json checks * chore(service): move test helper to end to of the file * chore: move mapEquals to test helpers * test: add json inspect for config * chore: add debug inspect log for plugin, secret and volume * test: add json inspect for secret * test: add json inspect for image * test: add json inspect for network * test: add json inspect for plugin * test: add json inspect for volume * test: inline ds plugin test configs * test: inline network configs * test: move ds reg image configs into separate files * test: reactivates container upload checks * chore: adapt issues ref from old to new xw repo * fix: reactivate network ingress test and provide helpers for removing the default ingress network and leaving the swamr * docs: rerun website gen * test: fix reg image build and keep test * chore: add name to todo * chore: move ds network and plugin specs to file * chore: format provider test spec * chore: use simpler error message for empty strings
218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
)
|
|
|
|
func dataSourceDockerRegistryImage() *schema.Resource {
|
|
return &schema.Resource{
|
|
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.",
|
|
|
|
ReadContext: dataSourceDockerRegistryImageRead,
|
|
|
|
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,
|
|
},
|
|
|
|
"sha256_digest": {
|
|
Type: schema.TypeString,
|
|
Description: "The content digest of the image, as stored in the registry.",
|
|
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 dataSourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
pullOpts := parseImageOptions(d.Get("name").(string))
|
|
authConfig := meta.(*ProviderConfig).AuthConfigs
|
|
|
|
// 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)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
if pullOpts.Tag == "" {
|
|
pullOpts.Tag = "latest"
|
|
}
|
|
|
|
username := ""
|
|
password := ""
|
|
|
|
if auth, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok {
|
|
username = auth.Username
|
|
password = auth.Password
|
|
}
|
|
|
|
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
|
|
digest, err := getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, username, password, insecureSkipVerify, false)
|
|
if err != nil {
|
|
digest, err = getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, username, password, insecureSkipVerify, true)
|
|
if err != nil {
|
|
return diag.Errorf("Got error when attempting to fetch image version from registry: %s", err)
|
|
}
|
|
}
|
|
|
|
d.SetId(digest)
|
|
d.Set("sha256_digest", digest)
|
|
|
|
return nil
|
|
}
|
|
|
|
func getImageDigest(registry, image, tag, username, password string, insecureSkipVerify, fallback bool) (string, error) {
|
|
client := http.DefaultClient
|
|
// DevSkim: ignore DS440000
|
|
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}}
|
|
|
|
req, err := http.NewRequest("GET", "https://"+registry+"/v2/"+image+"/manifests/"+tag, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Error creating registry request: %s", err)
|
|
}
|
|
|
|
if username != "" {
|
|
req.SetBasicAuth(username, password)
|
|
}
|
|
|
|
// 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")
|
|
|
|
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")
|
|
}
|
|
|
|
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:
|
|
return getDigestFromResponse(resp)
|
|
|
|
// 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)
|
|
}
|
|
|
|
return getDigestFromResponse(digestResponse)
|
|
}
|
|
|
|
return "", fmt.Errorf("Bad credentials: " + resp.Status)
|
|
|
|
// Some unexpected status was given, return an error
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|