-
Notifications
You must be signed in to change notification settings - Fork 441
/
Copy pathcivisibility_transport.go
201 lines (181 loc) · 6.25 KB
/
civisibility_transport.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024 Datadog, Inc.
package tracer
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
)
// Constants for CI Visibility API paths and subdomains.
const (
TestCycleSubdomain = "citestcycle-intake" // Subdomain for test cycle intake.
TestCyclePath = "api/v2/citestcycle" // API path for test cycle.
EvpProxyPath = "evp_proxy/v2" // Path for EVP proxy.
)
// Ensure that civisibilityTransport implements the transport interface.
var _ transport = (*ciVisibilityTransport)(nil)
// ciVisibilityTransport is a structure that handles sending CI Visibility payloads
// to the Datadog endpoint, either in agentless mode or through the EVP proxy.
type ciVisibilityTransport struct {
config *config // Configuration for the tracer.
testCycleURLPath string // URL path for the test cycle endpoint.
headers map[string]string // HTTP headers to be included in the requests.
agentless bool // Gets if the transport is configured in agentless mode (eg: Gzip support)
}
// newCiVisibilityTransport creates and initializes a new civisibilityTransport
// based on the provided tracer configuration. It sets up the appropriate headers
// and determines the URL path based on whether agentless mode is enabled.
//
// Parameters:
//
// config - The tracer configuration.
//
// Returns:
//
// A pointer to an initialized civisibilityTransport instance.
func newCiVisibilityTransport(config *config) *ciVisibilityTransport {
// Initialize the default headers with encoder metadata.
defaultHeaders := map[string]string{
"Datadog-Meta-Lang": "go",
"Datadog-Meta-Lang-Version": strings.TrimPrefix(runtime.Version(), "go"),
"Datadog-Meta-Lang-Interpreter": runtime.Compiler + "-" + runtime.GOARCH + "-" + runtime.GOOS,
"Datadog-Meta-Tracer-Version": version.Tag,
"Content-Type": "application/msgpack",
}
if cid := internal.ContainerID(); cid != "" {
defaultHeaders["Datadog-Container-ID"] = cid
}
if eid := internal.EntityID(); eid != "" {
defaultHeaders["Datadog-Entity-ID"] = eid
}
// Determine if agentless mode is enabled through an environment variable.
agentlessEnabled := internal.BoolEnv(constants.CiVisibilityAgentlessEnabledEnvironmentVariable, false)
testCycleURL := ""
if agentlessEnabled {
// Agentless mode is enabled.
APIKeyValue := os.Getenv(constants.APIKeyEnvironmentVariable)
if APIKeyValue == "" {
log.Error("An API key is required for agentless mode. Use the DD_API_KEY env variable to set it")
os.Exit(1)
}
defaultHeaders["dd-api-key"] = APIKeyValue
// Check for a custom agentless URL.
agentlessURL := ""
if v := os.Getenv(constants.CiVisibilityAgentlessURLEnvironmentVariable); v != "" {
agentlessURL = v
}
if agentlessURL == "" {
// Use the standard agentless URL format.
site := "datadoghq.com"
if v := os.Getenv("DD_SITE"); v != "" {
site = v
}
testCycleURL = fmt.Sprintf("https://%s.%s/%s", TestCycleSubdomain, site, TestCyclePath)
} else {
// Use the custom agentless URL.
testCycleURL = fmt.Sprintf("%s/%s", agentlessURL, TestCyclePath)
}
} else {
// Use agent mode with the EVP proxy.
defaultHeaders["X-Datadog-EVP-Subdomain"] = TestCycleSubdomain
testCycleURL = fmt.Sprintf("%s/%s/%s", config.agentURL.String(), EvpProxyPath, TestCyclePath)
}
return &ciVisibilityTransport{
config: config,
testCycleURLPath: testCycleURL,
headers: defaultHeaders,
agentless: agentlessEnabled,
}
}
// send sends the CI Visibility payload to the Datadog endpoint.
// It prepares the payload, creates the HTTP request, and handles the response.
//
// Parameters:
//
// p - The payload to be sent.
//
// Returns:
//
// An io.ReadCloser for reading the response body, and an error if the operation fails.
func (t *ciVisibilityTransport) send(p *payload) (body io.ReadCloser, err error) {
ciVisibilityPayload := &ciVisibilityPayload{p}
buffer, bufferErr := ciVisibilityPayload.getBuffer(t.config)
if bufferErr != nil {
return nil, fmt.Errorf("cannot create buffer payload: %v", bufferErr)
}
if t.agentless {
// Compress payload
var gzipBuffer bytes.Buffer
gzipWriter := gzip.NewWriter(&gzipBuffer)
_, err = io.Copy(gzipWriter, buffer)
if err != nil {
return nil, fmt.Errorf("cannot compress request body: %v", err)
}
err = gzipWriter.Close()
if err != nil {
return nil, fmt.Errorf("cannot compress request body: %v", err)
}
buffer = &gzipBuffer
}
req, err := http.NewRequest("POST", t.testCycleURLPath, buffer)
if err != nil {
return nil, fmt.Errorf("cannot create http request: %v", err)
}
for header, value := range t.headers {
req.Header.Set(header, value)
}
req.Header.Set("Content-Length", strconv.Itoa(buffer.Len()))
if t.agentless {
req.Header.Set("Content-Encoding", "gzip")
}
response, err := t.config.httpClient.Do(req)
if err != nil {
return nil, err
}
if code := response.StatusCode; code >= 400 {
// error, check the body for context information and
// return a nice error.
msg := make([]byte, 1000)
n, _ := response.Body.Read(msg)
_ = response.Body.Close()
txt := http.StatusText(code)
if n > 0 {
return nil, fmt.Errorf("%s (Status: %s)", msg[:n], txt)
}
return nil, fmt.Errorf("%s", txt)
}
return response.Body, nil
}
// sendStats is a no-op for CI Visibility transport as it does not support sending stats payloads.
//
// Parameters:
//
// payload - The stats payload to be sent.
//
// Returns:
//
// An error indicating that stats are not supported.
func (t *ciVisibilityTransport) sendStats(*statsPayload) error {
// Stats are not supported by CI Visibility agentless / EVP proxy.
return nil
}
// endpoint returns the URL path of the test cycle endpoint.
//
// Returns:
//
// The URL path as a string.
func (t *ciVisibilityTransport) endpoint() string {
return t.testCycleURLPath
}