Skip to content

Commit 9196166

Browse files
authored
internal/output/webhook: add server probe configuration (#153)
Setting the STREAM_PROBE env var or the webhook-probe options can now be used to modify the behaviour of the webhook output to choose whether to perform an initial server probe and which method to use in the probe. * empty/unset, 1, true or HEAD: current behaviour * 0, false: no probe * GET, CONNECT, OPTIONS, PATCH, POST and PUT: an empty-bodied request using that method.
1 parent 481eed0 commit 9196166

File tree

5 files changed

+72
-2
lines changed

5 files changed

+72
-2
lines changed

.changelog/153.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
webhook output: Add support for server probing behavior configuration.
3+
```

internal/command/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func ExecuteContext(ctx context.Context) error {
7575
rootCmd.PersistentFlags().StringVar(&opts.WebhookOptions.Password, "webhook-password", "", "webhook password for basic authentication")
7676
rootCmd.PersistentFlags().StringVar(&opts.WebhookOptions.Username, "webhook-username", "", "webhook username for basic authentication")
7777
rootCmd.PersistentFlags().DurationVar(&opts.WebhookOptions.Timeout, "webhook-timeout", time.Second, "webhook request timeout (zero is no timeout)")
78+
rootCmd.PersistentFlags().StringVar(&opts.WebhookOptions.Probe, "webhook-probe", "", "webhook server probe request method (''/1/true/HEAD, CONNECT, GET, ..., or 0/false for no probe)")
7879

7980
// GCP Pubsub output flags.
8081
rootCmd.PersistentFlags().StringVar(&opts.GCPPubsubOptions.Project, "gcppubsub-project", "test", "GCP Pubsub project name")

internal/output/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type WebhookOptions struct {
3131
Username string // Basic auth username.
3232
Password string // Basic auth password.
3333
Timeout time.Duration // Timeout for request handling.
34+
Probe string // Server probe behavior.
3435
}
3536

3637
type GCPPubsubOptions struct {

internal/output/webhook/webhook.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,22 @@ func New(opts *output.Options) (output.Output, error) {
4747
}
4848

4949
func (o *Output) DialContext(ctx context.Context) error {
50-
// Use a HEAD request to check if the service is ready.
51-
req, err := http.NewRequestWithContext(ctx, http.MethodHead, o.opts.Addr, nil)
50+
method := o.opts.WebhookOptions.Probe
51+
switch method {
52+
case "", "1", "true", http.MethodHead:
53+
// Default behaviour is to do a HEAD probe.
54+
method = http.MethodHead
55+
case "0", "false":
56+
// Don't probe.
57+
return nil
58+
case http.MethodGet, http.MethodConnect, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut:
59+
// Fall through with the option that the env var specifies.
60+
default:
61+
return fmt.Errorf("unknown probe behavior option: %q", method)
62+
}
63+
64+
// Use a request to check if the service is ready.
65+
req, err := http.NewRequestWithContext(ctx, method, o.opts.Addr, nil)
5266
if err != nil {
5367
return err
5468
}

internal/output/webhook/webhook_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"net/http"
1212
"net/http/httptest"
13+
"sync/atomic"
1314
"testing"
1415
"time"
1516

@@ -76,3 +77,53 @@ func TestWebhook(t *testing.T) {
7677
require.NoError(t, err)
7778
assert.Equal(t, len(data), n)
7879
}
80+
81+
func TestWebhookProbe(t *testing.T) {
82+
for _, test := range []struct {
83+
name string
84+
method string
85+
probed bool
86+
ok bool
87+
}{
88+
{name: "unset", method: "", probed: true, ok: false},
89+
{name: "true", method: "true", probed: true, ok: false},
90+
{name: "HEAD", method: "HEAD", probed: true, ok: false},
91+
92+
{name: "zero", method: "0", probed: false, ok: true},
93+
{name: "false", method: "false", probed: false, ok: true},
94+
95+
{name: "CONNECT", method: "CONNECT", probed: true, ok: false},
96+
{name: "GET", method: "GET", probed: true, ok: false},
97+
{name: "OPTIONS", method: "OPTIONS", probed: true, ok: false},
98+
{name: "POST", method: "POST", probed: true, ok: true}, // There can be only one.
99+
{name: "PUT", method: "PUT", probed: true, ok: false},
100+
} {
101+
t.Run(test.name, func(t *testing.T) {
102+
var probed atomic.Bool
103+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
104+
probed.Store(true)
105+
if test.ok {
106+
assert.Equal(t, http.MethodPost, r.Method)
107+
} else {
108+
assert.NotEqual(t, http.MethodPost, r.Method)
109+
}
110+
}))
111+
defer ts.Close()
112+
113+
out, err := New(&output.Options{
114+
Addr: ts.URL + "/logs",
115+
WebhookOptions: output.WebhookOptions{
116+
Timeout: time.Second,
117+
Probe: test.method,
118+
},
119+
})
120+
require.NoError(t, err)
121+
122+
err = out.DialContext(context.Background())
123+
require.NoError(t, err)
124+
125+
assert.Equal(t, test.probed, probed.Load())
126+
})
127+
}
128+
129+
}

0 commit comments

Comments
 (0)