diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index eb5333f26..651b66783 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "fmt" "net/http" + "os" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" @@ -103,8 +104,19 @@ func (c *Config) loadAndValidate() error { config.BuildNameToCertificate() } + // if OS_DEBUG is set, log the requests and responses + var osDebug bool + if os.Getenv("OS_DEBUG") != "" { + osDebug = true + } + transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} - client.HTTPClient.Transport = transport + client.HTTPClient = http.Client{ + Transport: &LogRoundTripper{ + rt: transport, + osDebug: osDebug, + }, + } // If using Swift Authentication, there's no need to validate authentication normally. if !c.Swauth { diff --git a/builtin/providers/openstack/types.go b/builtin/providers/openstack/types.go index 417364b93..7bcc75560 100644 --- a/builtin/providers/openstack/types.go +++ b/builtin/providers/openstack/types.go @@ -1,6 +1,14 @@ package openstack import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "log" + "net/http" + "strings" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies" @@ -12,6 +20,122 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" ) +// LogRoundTripper satisfies the http.RoundTripper interface and is used to +// customize the default http client RoundTripper to allow for logging. +type LogRoundTripper struct { + rt http.RoundTripper + osDebug bool +} + +// RoundTrip performs a round-trip HTTP request and logs relevant information about it. +func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { + defer func() { + if request.Body != nil { + request.Body.Close() + } + }() + + // for future reference, this is how to access the Transport struct: + //tlsconfig := lrt.rt.(*http.Transport).TLSClientConfig + + var err error + + if lrt.osDebug { + log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL) + + if request.Body != nil { + request.Body, err = lrt.logRequestBody(request.Body, request.Header) + if err != nil { + return nil, err + } + } + } + + response, err := lrt.rt.RoundTrip(request) + if response == nil { + return nil, err + } + + if lrt.osDebug { + response.Body, err = lrt.logResponseBody(response.Body, response.Header) + } + + return response, err +} + +// logRequestBody will log the HTTP Request body. +// If the body is JSON, it will attempt to be pretty-formatted. +func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) { + defer original.Close() + + var bs bytes.Buffer + _, err := io.Copy(&bs, original) + if err != nil { + return nil, err + } + + contentType := headers.Get("Content-Type") + if strings.HasPrefix(contentType, "application/json") { + debugInfo := lrt.formatJSON(bs.Bytes()) + log.Printf("[DEBUG] OpenStack Request Options: %s", debugInfo) + } else { + log.Printf("[DEBUG] OpenStack Request Options: %s", bs.String()) + } + + return ioutil.NopCloser(strings.NewReader(bs.String())), nil +} + +// logResponseBody will log the HTTP Response body. +// If the body is JSON, it will attempt to be pretty-formatted. +func (lrt *LogRoundTripper) logResponseBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) { + contentType := headers.Get("Content-Type") + if strings.HasPrefix(contentType, "application/json") { + var bs bytes.Buffer + defer original.Close() + _, err := io.Copy(&bs, original) + if err != nil { + return nil, err + } + debugInfo := lrt.formatJSON(bs.Bytes()) + log.Printf("[DEBUG] OpenStack Response Body: %s", debugInfo) + return ioutil.NopCloser(strings.NewReader(bs.String())), nil + } + + log.Printf("[DEBUG] Not logging because OpenStack response body isn't JSON") + return original, nil +} + +// formatJSON will try to pretty-format a JSON body. +// It will also mask known fields which contain sensitive information. +func (lrt *LogRoundTripper) formatJSON(raw []byte) string { + var data map[string]interface{} + + err := json.Unmarshal(raw, &data) + if err != nil { + log.Printf("[DEBUG] Unable to parse OpenStack JSON: %s", err) + return string(raw) + } + + // Mask known password fields + if v, ok := data["auth"].(map[string]interface{}); ok { + if v, ok := v["identity"].(map[string]interface{}); ok { + if v, ok := v["password"].(map[string]interface{}); ok { + if v, ok := v["user"].(map[string]interface{}); ok { + v["password"] = "***" + } + } + } + } + + pretty, err := json.MarshalIndent(data, "", " ") + if err != nil { + log.Printf("[DEBUG] Unable to re-marshal OpenStack JSON: %s", err) + return string(raw) + } + + return string(pretty) +} + // FirewallCreateOpts represents the attributes used when creating a new firewall. type FirewallCreateOpts struct { firewalls.CreateOpts diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index 5396426e7..cb4bbddb3 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -96,6 +96,22 @@ The following arguments are supported: Finally, set `auth_url` as the location of the Swift service. Note that this will only work when used with the OpenStack Object Storage resources. +## Additional Logging + +This provider has the ability to log all HTTP requests and responses between +Terraform and the OpenStack cloud which is useful for troubleshooting and +debugging. + +To enable these logs, set the `OS_DEBUG` environment variable to `1` along +with the usual `TF_LOG=DEBUG` environment variable: + +```shell +$ OS_DEBUG=1 TF_LOG=DEBUG terraform apply +``` + +If you submit these logs with a bug report, please ensure any sensitive +information has been scrubbed first! + ## Rackspace Compatibility Using this OpenStack provider with Rackspace is not supported and not