• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Scalr / terraform-provider-scalr / 13494954504

24 Feb 2025 09:37AM UTC coverage: 69.72% (-1.2%) from 70.967%
13494954504

Pull #397

github

denkl
SCALRCORE-30924 Manage environment access to Infracost via Terraform provider
Pull Request #397: SCALRCORE-30924 Manage environment access to Infracost via Terraform …

85 of 305 new or added lines in 3 files covered. (27.87%)

2 existing lines in 1 file now uncovered.

7460 of 10700 relevant lines covered (69.72%)

173.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.51
/internal/logging/http.go
1
package logging
2

3
import (
4
        "bytes"
5
        "io"
6
        "net/http"
7

8
        "github.com/hashicorp/terraform-plugin-log/tflog"
9
)
10

11
// NewLoggingTransport returns a wrapper around http.RoundTripper
12
// that logs HTTP requests and responses using the `tflog` package.
13
// The context.Context of the underlying http.Request is passed to the logger.
14
func NewLoggingTransport(transport http.RoundTripper) http.RoundTripper {
3,230✔
15
        return &loggingTransport{transport: transport}
3,230✔
16
}
3,230✔
17

18
// loggingTransport is a http.RoundTripper that logs HTTP requests and responses.
19
type loggingTransport struct {
20
        transport http.RoundTripper
21
}
22

23
func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
3,271✔
24
        ctx := req.Context()
3,271✔
25

3,271✔
26
        fields, err := collectRequestFields(req)
3,271✔
27
        if err != nil {
3,271✔
28
                tflog.Error(ctx, "Failed to parse request for logging", map[string]interface{}{
×
29
                        "error": err,
×
30
                })
×
31
        } else {
3,271✔
32
                tflog.Debug(ctx, "Sending HTTP Request", fields)
3,271✔
33
        }
3,271✔
34

35
        resp, err := t.transport.RoundTrip(req)
3,271✔
36
        if err != nil {
3,271✔
UNCOV
37
                return resp, err
×
UNCOV
38
        }
×
39

40
        fields, err = collectResponseFields(resp)
3,271✔
41
        if err != nil {
3,271✔
42
                tflog.Error(ctx, "Failed to parse response for logging", map[string]interface{}{
×
43
                        "error": err,
×
44
                })
×
45
        } else {
3,271✔
46
                tflog.Debug(ctx, "Received HTTP Response", fields)
3,271✔
47
        }
3,271✔
48
        return resp, nil
3,271✔
49
}
50

51
func collectRequestFields(req *http.Request) (map[string]interface{}, error) {
3,271✔
52
        fields := make(map[string]interface{})
3,271✔
53

3,271✔
54
        fields["http_op"] = "request"
3,271✔
55
        fields["http_url"] = req.URL.String()
3,271✔
56
        fields["http_method"] = req.Method
3,271✔
57

3,271✔
58
        // Collect request headers
3,271✔
59
        for name, values := range req.Header {
10,300✔
60
                if len(values) == 1 {
14,058✔
61
                        fields[name] = values[0]
7,029✔
62
                } else {
7,029✔
63
                        fields[name] = values
×
64
                }
×
65
        }
66

67
        // Collect the request body
68
        body, err := bodyFromRequest(req)
3,271✔
69
        if err != nil {
3,271✔
70
                return nil, err
×
71
        }
×
72
        fields["http_req_body"] = body
3,271✔
73

3,271✔
74
        return fields, nil
3,271✔
75
}
76

77
func bodyFromRequest(req *http.Request) (string, error) {
3,271✔
78
        if req.Body == nil {
6,285✔
79
                return "", nil
3,014✔
80
        }
3,014✔
81

82
        // Read and log the body without consuming it
83
        var buf bytes.Buffer
257✔
84
        tee := io.TeeReader(req.Body, &buf)
257✔
85

257✔
86
        // Read the body into a byte slice
257✔
87
        bodyBytes, err := io.ReadAll(tee)
257✔
88
        if err != nil {
257✔
89
                return "", err
×
90
        }
×
91

92
        // Restore the original request body for the actual request
93
        req.Body = io.NopCloser(&buf)
257✔
94

257✔
95
        return string(bodyBytes), nil
257✔
96
}
97

98
func collectResponseFields(resp *http.Response) (map[string]interface{}, error) {
3,271✔
99
        fields := make(map[string]interface{})
3,271✔
100

3,271✔
101
        fields["http_op"] = "response"
3,271✔
102
        fields["http_status"] = resp.StatusCode
3,271✔
103

3,271✔
104
        // Collect response headers
3,271✔
105
        for name, values := range resp.Header {
37,100✔
106
                if len(values) == 1 {
67,658✔
107
                        fields[name] = values[0]
33,829✔
108
                } else {
33,829✔
109
                        fields[name] = values
×
110
                }
×
111
        }
112

113
        // Collect the response body
114
        body, err := bodyFromResponse(resp)
3,271✔
115
        if err != nil {
3,271✔
116
                return nil, err
×
117
        }
×
118
        fields["http_resp_body"] = body
3,271✔
119

3,271✔
120
        return fields, nil
3,271✔
121
}
122

123
func bodyFromResponse(resp *http.Response) (string, error) {
3,271✔
124
        if resp.Body == nil {
3,271✔
125
                return "", nil
×
126
        }
×
127

128
        // Read the response body
129
        bodyBytes, err := io.ReadAll(resp.Body)
3,271✔
130
        if err != nil {
3,271✔
131
                return "", err
×
132
        }
×
133

134
        // Close the original response body
135
        err = resp.Body.Close()
3,271✔
136
        if err != nil {
3,271✔
137
                return "", err
×
138
        }
×
139

140
        // Restore the response body for the client
141
        resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
3,271✔
142

3,271✔
143
        return string(bodyBytes), nil
3,271✔
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc