provider/openstack: Enable HTTP Logging (#12089)

This commit adds the ability to log all requests and responses
between Terraform and the OpenStack cloud. To enable, set the
OS_DEBUG environment variable to 1.
This commit is contained in:
Joe Topjian 2017-02-20 05:36:05 -07:00 committed by Paul Stack
parent 6d4fc8d21a
commit 1b5694b7f4
3 changed files with 153 additions and 1 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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