mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
fix: oauth authorization support for azurecr (#451)
* fix: oauth authorization support for azurecr * fix: oauth when destroying images * refactor: run make fmt
This commit is contained in:
parent
0d82189137
commit
d0eae33123
9 changed files with 62 additions and 71 deletions
|
|
@ -50,6 +50,12 @@ func isECRRepositoryURL(url string) bool {
|
||||||
return ecrRexp.MatchString(url)
|
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) {
|
func setupHTTPHeadersForRegistryRequests(req *http.Request, fallback bool) {
|
||||||
// We accept schema v2 manifests and manifest lists, and also OCI types
|
// 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.v2+json")
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us
|
||||||
}
|
}
|
||||||
|
|
||||||
if username != "" {
|
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)
|
req.SetBasicAuth(username, password)
|
||||||
} else {
|
} else {
|
||||||
if isECRRepositoryURL(registry) {
|
if isECRRepositoryURL(registry) {
|
||||||
|
|
@ -146,7 +146,8 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenResponse struct {
|
type TokenResponse struct {
|
||||||
Token string
|
Token string
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses key/value pairs from a WWW-Authenticate header
|
// 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 "", 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) {
|
func doDigestRequest(req *http.Request, client *http.Client) (*http.Response, error) {
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,9 @@ func TestAccDockerConfig_basicUpdatable(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////
|
// ///////////
|
||||||
// Helpers
|
// Helpers
|
||||||
/////////////
|
// ///////////
|
||||||
func testCheckDockerConfigDestroy(ctx context.Context, s *terraform.State) error {
|
func testCheckDockerConfigDestroy(ctx context.Context, s *terraform.State) error {
|
||||||
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
|
|
||||||
|
|
@ -818,7 +818,6 @@ func TestAccDockerContainer_uploadSource(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
func TestAccDockerContainer_uploadSourceHash(t *testing.T) {
|
func TestAccDockerContainer_uploadSourceHash(t *testing.T) {
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
var firstRunId string
|
var firstRunId string
|
||||||
|
|
@ -1606,9 +1605,9 @@ func TestAccDockerContainer_dualstackaddress(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////
|
// /////////
|
||||||
// HELPERS
|
// HELPERS
|
||||||
///////////
|
// /////////
|
||||||
func testAccContainerRunning(resourceName string, container *types.ContainerJSON) resource.TestCheckFunc {
|
func testAccContainerRunning(resourceName string, container *types.ContainerJSON) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -483,7 +482,16 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr
|
||||||
}
|
}
|
||||||
|
|
||||||
if username != "" {
|
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)
|
setupHTTPHeadersForRegistryRequests(req, fallback)
|
||||||
|
|
@ -500,56 +508,26 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr
|
||||||
|
|
||||||
// Either OAuth is required or the basic auth creds were invalid
|
// Either OAuth is required or the basic auth creds were invalid
|
||||||
case http.StatusUnauthorized:
|
case http.StatusUnauthorized:
|
||||||
if strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") {
|
if !strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") {
|
||||||
auth := parseAuthHeader(resp.Header.Get("www-authenticate"))
|
return fmt.Errorf("Bad credentials: " + resp.Status)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Some unexpected status was given, return an error
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Got bad response from registry: " + resp.Status)
|
return fmt.Errorf("Got bad response from registry: " + resp.Status)
|
||||||
|
|
|
||||||
|
|
@ -136,9 +136,9 @@ func TestAccDockerSecret_labels(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////
|
// ///////////
|
||||||
// Helpers
|
// Helpers
|
||||||
/////////////
|
// ///////////
|
||||||
func testCheckDockerSecretDestroy(ctx context.Context, s *terraform.State) error {
|
func testCheckDockerSecretDestroy(ctx context.Context, s *terraform.State) error {
|
||||||
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ type convergeConfig struct {
|
||||||
delay time.Duration
|
delay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////
|
// ///////////////
|
||||||
// TF CRUD funcs
|
// TF CRUD funcs
|
||||||
/////////////////
|
// ///////////////
|
||||||
func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
var err error
|
var err error
|
||||||
client := meta.(*ProviderConfig).DockerClient
|
client := meta.(*ProviderConfig).DockerClient
|
||||||
|
|
@ -226,9 +226,9 @@ func resourceDockerServiceDelete(ctx context.Context, d *schema.ResourceData, me
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////
|
// ///////////////
|
||||||
// Helpers
|
// Helpers
|
||||||
/////////////////
|
// ///////////////
|
||||||
// fetchDockerService fetches a service by its name or id
|
// fetchDockerService fetches a service by its name or id
|
||||||
func fetchDockerService(ctx context.Context, ID string, name string, client *client.Client) (*swarm.Service, error) {
|
func fetchDockerService(ctx context.Context, ID string, name string, client *client.Client) (*swarm.Service, error) {
|
||||||
apiServices, err := client.ServiceList(ctx, types.ServiceListOptions{})
|
apiServices, err := client.ServiceList(ctx, types.ServiceListOptions{})
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ import (
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
//////////////
|
// ////////////
|
||||||
// flatteners
|
// flatteners
|
||||||
// flatten API objects to the terraform schema
|
// flatten API objects to the terraform schema
|
||||||
//////////////
|
// ////////////
|
||||||
// see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions
|
// see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions
|
||||||
func flattenTaskSpec(in swarm.TaskSpec) []interface{} {
|
func flattenTaskSpec(in swarm.TaskSpec) []interface{} {
|
||||||
m := make(map[string]interface{})
|
m := make(map[string]interface{})
|
||||||
|
|
@ -110,7 +110,7 @@ func flattenServiceEndpointSpec(in *swarm.EndpointSpec) []interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
///// start TaskSpec
|
// /// start TaskSpec
|
||||||
func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} {
|
func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} {
|
||||||
out := make([]interface{}, 0)
|
out := make([]interface{}, 0)
|
||||||
m := make(map[string]interface{})
|
m := make(map[string]interface{})
|
||||||
|
|
@ -525,8 +525,8 @@ func flattenTaskLogDriver(in *swarm.Driver) []interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
///// end TaskSpec
|
// /// end TaskSpec
|
||||||
///// start EndpointSpec
|
// /// start EndpointSpec
|
||||||
func flattenServicePorts(in []swarm.PortConfig) []interface{} {
|
func flattenServicePorts(in []swarm.PortConfig) []interface{} {
|
||||||
out := make([]interface{}, len(in))
|
out := make([]interface{}, len(in))
|
||||||
for i, v := range in {
|
for i, v := range in {
|
||||||
|
|
@ -543,10 +543,10 @@ func flattenServicePorts(in []swarm.PortConfig) []interface{} {
|
||||||
|
|
||||||
///// end EndpointSpec
|
///// end EndpointSpec
|
||||||
|
|
||||||
//////////////
|
// ////////////
|
||||||
// Mappers
|
// Mappers
|
||||||
// create API object from the terraform resource schema
|
// create API object from the terraform resource schema
|
||||||
//////////////
|
// ////////////
|
||||||
// createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate
|
// createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate
|
||||||
func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) {
|
func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) {
|
||||||
serviceSpec := swarm.ServiceSpec{
|
serviceSpec := swarm.ServiceSpec{
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ const (
|
||||||
// As a convention the test configurations are in
|
// As a convention the test configurations are in
|
||||||
// 'testdata/<resourceType>/<resourceName>/<testName>.tf', e.g.
|
// 'testdata/<resourceType>/<resourceName>/<testName>.tf', e.g.
|
||||||
// 'testdata/resources/docker_container/testAccDockerContainerPrivateImage.tf'
|
// 'testdata/resources/docker_container/testAccDockerContainerPrivateImage.tf'
|
||||||
//
|
|
||||||
func loadTestConfiguration(t *testing.T, resourceType resourceType, resourceName, testName string) string {
|
func loadTestConfiguration(t *testing.T, resourceType resourceType, resourceName, testName string) string {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue