Skip to content

Commit 1b5694b

Browse files
jtopjianstack72
authored andcommitted
provider/openstack: Enable HTTP Logging (hashicorp#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.
1 parent 6d4fc8d commit 1b5694b

3 files changed

Lines changed: 153 additions & 1 deletion

File tree

builtin/providers/openstack/config.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/x509"
66
"fmt"
77
"net/http"
8+
"os"
89

910
"github.com/gophercloud/gophercloud"
1011
"github.com/gophercloud/gophercloud/openstack"
@@ -103,8 +104,19 @@ func (c *Config) loadAndValidate() error {
103104
config.BuildNameToCertificate()
104105
}
105106

107+
// if OS_DEBUG is set, log the requests and responses
108+
var osDebug bool
109+
if os.Getenv("OS_DEBUG") != "" {
110+
osDebug = true
111+
}
112+
106113
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
107-
client.HTTPClient.Transport = transport
114+
client.HTTPClient = http.Client{
115+
Transport: &LogRoundTripper{
116+
rt: transport,
117+
osDebug: osDebug,
118+
},
119+
}
108120

109121
// If using Swift Authentication, there's no need to validate authentication normally.
110122
if !c.Swauth {

builtin/providers/openstack/types.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
package openstack
22

33
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"io/ioutil"
8+
"log"
9+
"net/http"
10+
"strings"
11+
412
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
513
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls"
614
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/fwaas/policies"
@@ -12,6 +20,122 @@ import (
1220
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
1321
)
1422

23+
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
24+
// customize the default http client RoundTripper to allow for logging.
25+
type LogRoundTripper struct {
26+
rt http.RoundTripper
27+
osDebug bool
28+
}
29+
30+
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
31+
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
32+
defer func() {
33+
if request.Body != nil {
34+
request.Body.Close()
35+
}
36+
}()
37+
38+
// for future reference, this is how to access the Transport struct:
39+
//tlsconfig := lrt.rt.(*http.Transport).TLSClientConfig
40+
41+
var err error
42+
43+
if lrt.osDebug {
44+
log.Printf("[DEBUG] OpenStack Request URL: %s %s", request.Method, request.URL)
45+
46+
if request.Body != nil {
47+
request.Body, err = lrt.logRequestBody(request.Body, request.Header)
48+
if err != nil {
49+
return nil, err
50+
}
51+
}
52+
}
53+
54+
response, err := lrt.rt.RoundTrip(request)
55+
if response == nil {
56+
return nil, err
57+
}
58+
59+
if lrt.osDebug {
60+
response.Body, err = lrt.logResponseBody(response.Body, response.Header)
61+
}
62+
63+
return response, err
64+
}
65+
66+
// logRequestBody will log the HTTP Request body.
67+
// If the body is JSON, it will attempt to be pretty-formatted.
68+
func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
69+
defer original.Close()
70+
71+
var bs bytes.Buffer
72+
_, err := io.Copy(&bs, original)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
contentType := headers.Get("Content-Type")
78+
if strings.HasPrefix(contentType, "application/json") {
79+
debugInfo := lrt.formatJSON(bs.Bytes())
80+
log.Printf("[DEBUG] OpenStack Request Options: %s", debugInfo)
81+
} else {
82+
log.Printf("[DEBUG] OpenStack Request Options: %s", bs.String())
83+
}
84+
85+
return ioutil.NopCloser(strings.NewReader(bs.String())), nil
86+
}
87+
88+
// logResponseBody will log the HTTP Response body.
89+
// If the body is JSON, it will attempt to be pretty-formatted.
90+
func (lrt *LogRoundTripper) logResponseBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
91+
contentType := headers.Get("Content-Type")
92+
if strings.HasPrefix(contentType, "application/json") {
93+
var bs bytes.Buffer
94+
defer original.Close()
95+
_, err := io.Copy(&bs, original)
96+
if err != nil {
97+
return nil, err
98+
}
99+
debugInfo := lrt.formatJSON(bs.Bytes())
100+
log.Printf("[DEBUG] OpenStack Response Body: %s", debugInfo)
101+
return ioutil.NopCloser(strings.NewReader(bs.String())), nil
102+
}
103+
104+
log.Printf("[DEBUG] Not logging because OpenStack response body isn't JSON")
105+
return original, nil
106+
}
107+
108+
// formatJSON will try to pretty-format a JSON body.
109+
// It will also mask known fields which contain sensitive information.
110+
func (lrt *LogRoundTripper) formatJSON(raw []byte) string {
111+
var data map[string]interface{}
112+
113+
err := json.Unmarshal(raw, &data)
114+
if err != nil {
115+
log.Printf("[DEBUG] Unable to parse OpenStack JSON: %s", err)
116+
return string(raw)
117+
}
118+
119+
// Mask known password fields
120+
if v, ok := data["auth"].(map[string]interface{}); ok {
121+
if v, ok := v["identity"].(map[string]interface{}); ok {
122+
if v, ok := v["password"].(map[string]interface{}); ok {
123+
if v, ok := v["user"].(map[string]interface{}); ok {
124+
v["password"] = "***"
125+
}
126+
}
127+
}
128+
}
129+
130+
pretty, err := json.MarshalIndent(data, "", " ")
131+
if err != nil {
132+
log.Printf("[DEBUG] Unable to re-marshal OpenStack JSON: %s", err)
133+
return string(raw)
134+
}
135+
136+
return string(pretty)
137+
}
138+
15139
// FirewallCreateOpts represents the attributes used when creating a new firewall.
16140
type FirewallCreateOpts struct {
17141
firewalls.CreateOpts

website/source/docs/providers/openstack/index.html.markdown

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ The following arguments are supported:
9696
Finally, set `auth_url` as the location of the Swift service. Note that this
9797
will only work when used with the OpenStack Object Storage resources.
9898

99+
## Additional Logging
100+
101+
This provider has the ability to log all HTTP requests and responses between
102+
Terraform and the OpenStack cloud which is useful for troubleshooting and
103+
debugging.
104+
105+
To enable these logs, set the `OS_DEBUG` environment variable to `1` along
106+
with the usual `TF_LOG=DEBUG` environment variable:
107+
108+
```shell
109+
$ OS_DEBUG=1 TF_LOG=DEBUG terraform apply
110+
```
111+
112+
If you submit these logs with a bug report, please ensure any sensitive
113+
information has been scrubbed first!
114+
99115
## Rackspace Compatibility
100116

101117
Using this OpenStack provider with Rackspace is not supported and not

0 commit comments

Comments
 (0)