Skip to content

Commit

Permalink
webhook: use new json package
Browse files Browse the repository at this point in the history
Signed-off-by: Hank Donnay <[email protected]>
  • Loading branch information
hdonnay committed Apr 10, 2024
1 parent d74fb15 commit a8a4a81
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 10 deletions.
72 changes: 72 additions & 0 deletions notifier/webhook/benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package webhook

import (
stdjson "encoding/json"
"io"
"net/url"
"testing"

"github.com/google/uuid"

"github.com/quay/clair/v4/internal/codec"
"github.com/quay/clair/v4/internal/json"
"github.com/quay/clair/v4/notifier"
)

func BenchmarkEncodingJSON(b *testing.B) {
enc := stdjson.NewEncoder(io.Discard)
obj := notifier.Callback{
NotificationID: uuid.New(),
}
if err := obj.Callback.UnmarshalBinary([]byte("http://example.com/")); err != nil {
b.Fatal(err)
}

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := enc.Encode(&obj)
if err != nil {
b.Error(err)
}
}
}

func BenchmarkCodecJSON(b *testing.B) {
enc := codec.GetEncoder(io.Discard)
obj := notifier.Callback{
NotificationID: uuid.New(),
}
if err := obj.Callback.UnmarshalBinary([]byte("http://example.com/")); err != nil {
b.Fatal(err)
}

b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := enc.Encode(&obj)
if err != nil {
b.Error(err)
}
}
}

func BenchmarkExperimentalJSON(b *testing.B) {
id := uuid.New()
url, err := url.Parse("http://example.com")
if err != nil {
b.Fatal(err)
}
obj := callbackRequest{
ID: &id,
URL: url,
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := json.MarshalWrite(io.Discard, &obj, options())
if err != nil {
b.Error(err)
}
}
}
55 changes: 45 additions & 10 deletions notifier/webhook/deliverer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package webhook
import (
"context"
"errors"
"io"
"net/http"
"net/url"
"sync"
Expand All @@ -12,9 +13,9 @@ import (
"github.com/quay/zlog"

clairerror "github.com/quay/clair/v4/clair-error"
"github.com/quay/clair/v4/internal/codec"
"github.com/quay/clair/v4/internal/httputil"
"github.com/quay/clair/v4/notifier"
"github.com/quay/clair/v4/internal/json"
"github.com/quay/clair/v4/internal/json/jsontext"
)

// SignedOnce is used to print a deprecation notice, but only once per run.
Expand Down Expand Up @@ -67,26 +68,60 @@ func (d *Deliverer) Name() string {
return "webhook"
}

var options = sync.OnceValue(func() json.Options {
return json.WithMarshalers(json.MarshalFuncV2(marshalCallback))
})

func marshalCallback(enc *jsontext.Encoder, cb *callbackRequest, opts json.Options) error {
if err := enc.WriteToken(jsontext.ObjectStart); err != nil {
return err
}
if err := enc.WriteToken(jsontext.String(`callback`)); err != nil {
return err
}
if err := enc.WriteToken(jsontext.String(cb.URL.String())); err != nil {
return err
}
if err := enc.WriteToken(jsontext.String(`notification_id`)); err != nil {
return err
}
if err := enc.WriteToken(jsontext.String(cb.ID.String())); err != nil {
return err
}
return enc.WriteToken(jsontext.ObjectEnd)
}

type callbackRequest struct {
ID *uuid.UUID
URL *url.URL
}

// Deliver implements the notifier.Deliverer interface.
//
// Deliver POSTS a webhook data structure to the configured target.
// Deliver POSTs a webhook data structure to the configured target.
func (d *Deliverer) Deliver(ctx context.Context, nID uuid.UUID) error {
ctx = zlog.ContextWithValues(ctx,
"component", "notifier/webhook/Deliverer.Deliver",
"notification_id", nID.String(),
)

callback, err := d.callback.Parse(nID.String())
url, err := d.callback.Parse(nID.String())
if err != nil {
return err
}

wh := notifier.Callback{
NotificationID: nID,
Callback: *callback,
cb := callbackRequest{
ID: &nID,
URL: url,
}

req, err := httputil.NewRequestWithContext(ctx, http.MethodPost, d.target.String(), codec.JSONReader(&wh))
rd, wr := io.Pipe()
defer rd.Close()
go func() {
err := json.MarshalWrite(wr, &cb, options())
wr.CloseWithError(err)
}()

req, err := httputil.NewRequestWithContext(ctx, http.MethodPost, d.target.String(), rd)
if err != nil {
return err
}
Expand All @@ -102,7 +137,7 @@ func (d *Deliverer) Deliver(ctx context.Context, nID uuid.UUID) error {
}

zlog.Info(ctx).
Stringer("callback", callback).
Stringer("callback", url).
Stringer("target", d.target).
Msg("dispatching webhook")

Expand Down
47 changes: 47 additions & 0 deletions notifier/webhook/deliverer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http/httptest"
"net/url"
"path"
"strings"
"sync"
"testing"

Expand All @@ -15,6 +16,7 @@ import (
"github.com/quay/clair/config"
"github.com/quay/zlog"

json2 "github.com/quay/clair/v4/internal/json"
"github.com/quay/clair/v4/notifier"
)

Expand Down Expand Up @@ -81,3 +83,48 @@ func TestDeliverer(t *testing.T) {
t.Fatalf("got: %v, wanted: %v", got, want)
}
}

func TestMarshal(t *testing.T) {
// Check that the "v0" format (no specific media type) is identical to
// stdlib output.
t.Run("V0", func(t *testing.T) {
var (
callback = "http://clair-notifier/notifier/api/v1/notification/"
noteID = uuid.New()
)

want := func() string {
v := notifier.Callback{
NotificationID: noteID,
}
if err := v.Callback.UnmarshalBinary([]byte(callback)); err != nil {
t.Error(err)
}
b, err := json.Marshal(&v)
if err != nil {
t.Error(err)
}
return string(b)
}()

got := func() string {
url, err := url.Parse(callback)
if err != nil {
t.Error(err)
}
var b strings.Builder
cb := callbackRequest{
ID: &noteID,
URL: url,
}
if err := json2.MarshalWrite(&b, &cb, options()); err != nil {
t.Error(err)
}
return b.String()
}()

if !cmp.Equal(got, want) {
t.Error(cmp.Diff(got, want))
}
})
}

0 comments on commit a8a4a81

Please sign in to comment.