diff --git a/internal/provider/authentication_helpers.go b/internal/provider/authentication_helpers.go index d737663b..50662020 100644 --- a/internal/provider/authentication_helpers.go +++ b/internal/provider/authentication_helpers.go @@ -50,6 +50,12 @@ func isECRRepositoryURL(url string) bool { return ecrRexp.MatchString(url) } +func isAzureCRRepositoryURL(url string) bool { + // Regexp is based on the azurecr urls shown https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal?tabs=azure-cli#push-image-to-registry + var azurecrRexp = regexp.MustCompile(`^.*\.azurecr\.io$`) + return azurecrRexp.MatchString(url) +} + func setupHTTPHeadersForRegistryRequests(req *http.Request, fallback bool) { // We accept schema v2 manifests and manifest lists, and also OCI types req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json") diff --git a/internal/provider/data_source_docker_registry_image.go b/internal/provider/data_source_docker_registry_image.go index cf15623b..d565d53c 100644 --- a/internal/provider/data_source_docker_registry_image.go +++ b/internal/provider/data_source_docker_registry_image.go @@ -81,7 +81,7 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us } if username != "" { - if registry != "ghcr.io" && !isECRRepositoryURL(registry) && registry != "gcr.io" { + if registry != "ghcr.io" && !isECRRepositoryURL(registry) && !isAzureCRRepositoryURL(registry) && registry != "gcr.io" { req.SetBasicAuth(username, password) } else { if isECRRepositoryURL(registry) { @@ -146,7 +146,8 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us } type TokenResponse struct { - Token string + Token string + AccessToken string `json:"access_token"` } // Parses key/value pairs from a WWW-Authenticate header @@ -214,7 +215,15 @@ func getAuthToken(authHeader string, username string, password string, client *h return "", fmt.Errorf("Error parsing OAuth token response: %s", err) } - return token.Token, nil + if token.Token != "" { + return token.Token, nil + } + + if token.AccessToken != "" { + return token.AccessToken, nil + } + + return "", fmt.Errorf("Error unsupported OAuth response") } func doDigestRequest(req *http.Request, client *http.Client) (*http.Response, error) { diff --git a/internal/provider/resource_docker_config_test.go b/internal/provider/resource_docker_config_test.go index b0e69a8c..d169f4ff 100644 --- a/internal/provider/resource_docker_config_test.go +++ b/internal/provider/resource_docker_config_test.go @@ -111,9 +111,9 @@ func TestAccDockerConfig_basicUpdatable(t *testing.T) { }) } -///////////// +// /////////// // Helpers -///////////// +// /////////// func testCheckDockerConfigDestroy(ctx context.Context, s *terraform.State) error { client := testAccProvider.Meta().(*ProviderConfig).DockerClient for _, rs := range s.RootModule().Resources { diff --git a/internal/provider/resource_docker_container_test.go b/internal/provider/resource_docker_container_test.go index d94738f4..79c23a32 100644 --- a/internal/provider/resource_docker_container_test.go +++ b/internal/provider/resource_docker_container_test.go @@ -818,7 +818,6 @@ func TestAccDockerContainer_uploadSource(t *testing.T) { }) } -// func TestAccDockerContainer_uploadSourceHash(t *testing.T) { var c types.ContainerJSON var firstRunId string @@ -1606,9 +1605,9 @@ func TestAccDockerContainer_dualstackaddress(t *testing.T) { }) } -/////////// +// ///////// // HELPERS -/////////// +// ///////// func testAccContainerRunning(resourceName string, container *types.ContainerJSON) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := context.Background() diff --git a/internal/provider/resource_docker_registry_image_funcs.go b/internal/provider/resource_docker_registry_image_funcs.go index d0926655..7e50e169 100644 --- a/internal/provider/resource_docker_registry_image_funcs.go +++ b/internal/provider/resource_docker_registry_image_funcs.go @@ -14,7 +14,6 @@ import ( "io/ioutil" "log" "net/http" - "net/url" "os" "path/filepath" "strings" @@ -483,7 +482,16 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr } if username != "" { - req.SetBasicAuth(username, password) + if pushOpts.Registry != "ghcr.io" && !isECRRepositoryURL(pushOpts.Registry) && !isAzureCRRepositoryURL(pushOpts.Registry) && pushOpts.Registry != "gcr.io" { + req.SetBasicAuth(username, password) + } else { + if isECRRepositoryURL(pushOpts.Registry) { + password = normalizeECRPasswordForHTTPUsage(password) + req.Header.Add("Authorization", "Basic "+password) + } else { + req.Header.Add("Authorization", "Bearer "+base64.StdEncoding.EncodeToString([]byte(password))) + } + } } setupHTTPHeadersForRegistryRequests(req, fallback) @@ -500,56 +508,26 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr // 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) - oauthResp, err := client.Do(req) - if err != nil { - return err - } - switch oauthResp.StatusCode { - case http.StatusOK, http.StatusAccepted, http.StatusNotFound: - return nil - default: - return fmt.Errorf("Got bad response from registry: " + resp.Status) - } - + if !strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") { + return fmt.Errorf("Bad credentials: " + resp.Status) } - return fmt.Errorf("Bad credentials: " + resp.Status) + token, err := getAuthToken(resp.Header.Get("www-authenticate"), username, password, client) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+token) + oauthResp, err := client.Do(req) + if err != nil { + return err + } + switch oauthResp.StatusCode { + case http.StatusOK, http.StatusAccepted, http.StatusNotFound: + return nil + default: + return fmt.Errorf("Got bad response from registry: " + resp.Status) + } // Some unexpected status was given, return an error default: return fmt.Errorf("Got bad response from registry: " + resp.Status) diff --git a/internal/provider/resource_docker_secret_test.go b/internal/provider/resource_docker_secret_test.go index 93a33fa1..32876b06 100644 --- a/internal/provider/resource_docker_secret_test.go +++ b/internal/provider/resource_docker_secret_test.go @@ -136,9 +136,9 @@ func TestAccDockerSecret_labels(t *testing.T) { }) } -///////////// +// /////////// // Helpers -///////////// +// /////////// func testCheckDockerSecretDestroy(ctx context.Context, s *terraform.State) error { client := testAccProvider.Meta().(*ProviderConfig).DockerClient for _, rs := range s.RootModule().Resources { diff --git a/internal/provider/resource_docker_service_funcs.go b/internal/provider/resource_docker_service_funcs.go index 628d1733..ca3fd813 100644 --- a/internal/provider/resource_docker_service_funcs.go +++ b/internal/provider/resource_docker_service_funcs.go @@ -25,9 +25,9 @@ type convergeConfig struct { delay time.Duration } -///////////////// +// /////////////// // TF CRUD funcs -///////////////// +// /////////////// func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var err error client := meta.(*ProviderConfig).DockerClient @@ -226,9 +226,9 @@ func resourceDockerServiceDelete(ctx context.Context, d *schema.ResourceData, me return nil } -///////////////// +// /////////////// // Helpers -///////////////// +// /////////////// // fetchDockerService fetches a service by its name or id func fetchDockerService(ctx context.Context, ID string, name string, client *client.Client) (*swarm.Service, error) { apiServices, err := client.ServiceList(ctx, types.ServiceListOptions{}) diff --git a/internal/provider/resource_docker_service_structures.go b/internal/provider/resource_docker_service_structures.go index 3995abe7..7f0718e3 100644 --- a/internal/provider/resource_docker_service_structures.go +++ b/internal/provider/resource_docker_service_structures.go @@ -13,10 +13,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -////////////// +// //////////// // flatteners // flatten API objects to the terraform schema -////////////// +// //////////// // see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions func flattenTaskSpec(in swarm.TaskSpec) []interface{} { m := make(map[string]interface{}) @@ -110,7 +110,7 @@ func flattenServiceEndpointSpec(in *swarm.EndpointSpec) []interface{} { return out } -///// start TaskSpec +// /// start TaskSpec func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { out := make([]interface{}, 0) m := make(map[string]interface{}) @@ -525,8 +525,8 @@ func flattenTaskLogDriver(in *swarm.Driver) []interface{} { return out } -///// end TaskSpec -///// start EndpointSpec +// /// end TaskSpec +// /// start EndpointSpec func flattenServicePorts(in []swarm.PortConfig) []interface{} { out := make([]interface{}, len(in)) for i, v := range in { @@ -543,10 +543,10 @@ func flattenServicePorts(in []swarm.PortConfig) []interface{} { ///// end EndpointSpec -////////////// +// //////////// // Mappers // create API object from the terraform resource schema -////////////// +// //////////// // createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) { serviceSpec := swarm.ServiceSpec{ diff --git a/internal/provider/test_helpers.go b/internal/provider/test_helpers.go index 43c2b0f0..23f5742f 100644 --- a/internal/provider/test_helpers.go +++ b/internal/provider/test_helpers.go @@ -38,7 +38,6 @@ const ( // As a convention the test configurations are in // 'testdata///.tf', e.g. // 'testdata/resources/docker_container/testAccDockerContainerPrivateImage.tf' -// func loadTestConfiguration(t *testing.T, resourceType resourceType, resourceName, testName string) string { wd, err := os.Getwd() if err != nil {