-
-
Notifications
You must be signed in to change notification settings - Fork 115
/
exporter.go
119 lines (105 loc) · 3.02 KB
/
exporter.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
package webhookexporter
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"sync"
"github.com/thomaspoignant/go-feature-flag/exporter"
"github.com/thomaspoignant/go-feature-flag/internal"
"github.com/thomaspoignant/go-feature-flag/internal/signer"
)
// Exporter is the exporter of your data to a webhook.
// It calls the EndpointURL with a POST request with the following format:
//
// {
// "meta": {
// "hostname": "server01",
// },
// "events": [
// {
// "kind": "feature",
// "contextKind": "anonymousUser",
// "userKey": "14613538188334553206",
// "creationDate": 1618909178,
// "key": "test-flag",
// "variation": "Default",
// "value": false,
// "default": false
// },
// ]
// }
type Exporter struct {
// EndpointURL of your webhook
EndpointURL string
// Secret used to sign your request body.
Secret string
// Meta information that you want to send to your webhook (not mandatory)
Meta map[string]string
httpClient internal.HTTPClient
init sync.Once
}
// webhookPayload contains the body of the webhook.
type webhookPayload struct {
// Meta are the extra information added during the configuration
Meta map[string]string `json:"meta"`
// events is the list of the event we send in the payload
Events []exporter.FeatureEvent `json:"events"`
}
// Export is sending a collection of events in a webhook call.
func (f *Exporter) Export(ctx context.Context, logger *log.Logger, featureEvents []exporter.FeatureEvent) error {
f.init.Do(func() {
if f.httpClient == nil {
f.httpClient = internal.DefaultHTTPClient()
}
if f.Meta == nil {
f.Meta = make(map[string]string)
}
// if no hostname provided we return the hostname of the current machine
if _, ok := f.Meta["hostname"]; !ok {
hostname, _ := os.Hostname()
f.Meta["hostname"] = hostname
}
})
body := webhookPayload{
Meta: f.Meta,
Events: featureEvents,
}
payload, err := json.Marshal(body)
if err != nil {
return err
}
headers := http.Header{
"Content-Type": []string{"application/json"},
}
// if a secret is provided we sign the body and add this signature as a header.
if f.Secret != "" {
headers["X-Hub-Signature-256"] = []string{signer.Sign(payload, []byte(f.Secret))}
}
request, err := http.NewRequestWithContext(
ctx, http.MethodPost, f.EndpointURL, io.NopCloser(bytes.NewReader(payload)))
if err != nil {
return err
}
request.Header = headers
response, err := f.httpClient.Do(request)
// Log if something went wrong while calling the webhook.
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode > 399 {
return fmt.Errorf(
"error while calling the webhook, HTTP Code %d received, response: %v", response.StatusCode, response.Body)
}
return nil
}
// IsBulk return false if we should directly send the data as soon as it is produce
// and true if we collect the data to send them in bulk.
func (f *Exporter) IsBulk() bool {
return true
}