From b5d8cc0eebd524860aac8ec1ded2c5fcf4e276b3 Mon Sep 17 00:00:00 2001 From: Andrea Frittoli Date: Wed, 17 Jul 2019 02:57:32 +0900 Subject: [PATCH] Add a cloud event helper Add a cloud event helper that is going to be used by the CloudEventPipelineResource to send cloud events. This includes an initial list of the type of events that can be generated by Tekton. Signed-off-by: Andrea Frittoli --- Gopkg.lock | 33 ++ .../resources/cloudevent/cloudevent.go | 113 +++++ .../resources/cloudevent/cloudevent_test.go | 208 ++++++++ .../resources/cloudevent/cloudeventclient.go | 52 ++ .../cloudevent/cloudeventsfakeclient.go | 64 +++ third_party/VENDOR-LICENSE | 414 ++++++++++++++++ vendor/github.com/cloudevents/sdk-go/LICENSE | 201 ++++++++ .../sdk-go/pkg/cloudevents/client/client.go | 184 +++++++ .../pkg/cloudevents/client/defaulters.go | 35 ++ .../sdk-go/pkg/cloudevents/client/doc.go | 6 + .../pkg/cloudevents/client/observability.go | 68 +++ .../sdk-go/pkg/cloudevents/client/options.go | 37 ++ .../sdk-go/pkg/cloudevents/client/receiver.go | 187 +++++++ .../sdk-go/pkg/cloudevents/codec/doc.go | 5 + .../sdk-go/pkg/cloudevents/codec/jsoncodec.go | 258 ++++++++++ .../pkg/cloudevents/codec/observability.go | 90 ++++ .../sdk-go/pkg/cloudevents/content_type.go | 35 ++ .../sdk-go/pkg/cloudevents/context/context.go | 30 ++ .../sdk-go/pkg/cloudevents/context/doc.go | 5 + .../sdk-go/pkg/cloudevents/context/logger.go | 42 ++ .../pkg/cloudevents/data_content_encoding.go | 11 + .../sdk-go/pkg/cloudevents/datacodec/codec.go | 94 ++++ .../sdk-go/pkg/cloudevents/datacodec/doc.go | 5 + .../pkg/cloudevents/datacodec/json/data.go | 98 ++++ .../pkg/cloudevents/datacodec/json/doc.go | 4 + .../datacodec/json/observability.go | 63 +++ .../cloudevents/datacodec/observability.go | 63 +++ .../pkg/cloudevents/datacodec/xml/data.go | 91 ++++ .../pkg/cloudevents/datacodec/xml/doc.go | 4 + .../datacodec/xml/observability.go | 63 +++ .../cloudevents/sdk-go/pkg/cloudevents/doc.go | 4 + .../sdk-go/pkg/cloudevents/event.go | 97 ++++ .../sdk-go/pkg/cloudevents/event_data.go | 96 ++++ .../sdk-go/pkg/cloudevents/event_interface.go | 72 +++ .../sdk-go/pkg/cloudevents/event_reader.go | 58 +++ .../sdk-go/pkg/cloudevents/event_response.go | 37 ++ .../sdk-go/pkg/cloudevents/event_writer.go | 92 ++++ .../sdk-go/pkg/cloudevents/eventcontext.go | 105 ++++ .../pkg/cloudevents/eventcontext_v01.go | 267 ++++++++++ .../cloudevents/eventcontext_v01_reader.go | 86 ++++ .../cloudevents/eventcontext_v01_writer.go | 103 ++++ .../pkg/cloudevents/eventcontext_v02.go | 273 ++++++++++ .../cloudevents/eventcontext_v02_reader.go | 86 ++++ .../cloudevents/eventcontext_v02_writer.go | 103 ++++ .../pkg/cloudevents/eventcontext_v03.go | 283 +++++++++++ .../cloudevents/eventcontext_v03_reader.go | 81 +++ .../cloudevents/eventcontext_v03_writer.go | 107 ++++ .../sdk-go/pkg/cloudevents/extensions.go | 13 + .../pkg/cloudevents/observability/doc.go | 4 + .../pkg/cloudevents/observability/keys.go | 19 + .../pkg/cloudevents/observability/observer.go | 92 ++++ .../sdk-go/pkg/cloudevents/transport/codec.go | 10 + .../sdk-go/pkg/cloudevents/transport/doc.go | 5 + .../pkg/cloudevents/transport/http/codec.go | 226 +++++++++ .../cloudevents/transport/http/codec_v01.go | 244 +++++++++ .../cloudevents/transport/http/codec_v02.go | 295 +++++++++++ .../cloudevents/transport/http/codec_v03.go | 323 ++++++++++++ .../pkg/cloudevents/transport/http/context.go | 158 ++++++ .../pkg/cloudevents/transport/http/doc.go | 4 + .../cloudevents/transport/http/encoding.go | 120 +++++ .../pkg/cloudevents/transport/http/message.go | 79 +++ .../transport/http/observability.go | 109 ++++ .../pkg/cloudevents/transport/http/options.go | 175 +++++++ .../cloudevents/transport/http/transport.go | 466 ++++++++++++++++++ .../pkg/cloudevents/transport/message.go | 9 + .../pkg/cloudevents/transport/transport.go | 21 + .../sdk-go/pkg/cloudevents/types/allocate.go | 36 ++ .../sdk-go/pkg/cloudevents/types/doc.go | 4 + .../sdk-go/pkg/cloudevents/types/timestamp.go | 83 ++++ .../sdk-go/pkg/cloudevents/types/urlref.go | 78 +++ .../knative/eventing-sources/AUTHORS | 6 + .../knative/eventing-sources/LICENSE | 201 ++++++++ .../pkg/kncloudevents/good_client.go | 28 ++ .../test/test_images/k8sevents/kodata/LICENSE | 1 + .../k8sevents/kodata/VENDOR-LICENSE | 1 + 75 files changed, 7323 insertions(+) create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent.go create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent_test.go create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventclient.go create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventsfakeclient.go create mode 100644 vendor/github.com/cloudevents/sdk-go/LICENSE create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/client.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/defaulters.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/options.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/receiver.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/jsoncodec.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/content_type.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/context.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/logger.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/data_content_encoding.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/codec.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/data.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/data.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_data.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_interface.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_reader.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_response.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_writer.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/extensions.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/keys.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/observer.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/codec.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/message.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/transport.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/allocate.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/doc.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/timestamp.go create mode 100644 vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/urlref.go create mode 100644 vendor/github.com/knative/eventing-sources/AUTHORS create mode 100644 vendor/github.com/knative/eventing-sources/LICENSE create mode 100644 vendor/github.com/knative/eventing-sources/pkg/kncloudevents/good_client.go create mode 120000 vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/LICENSE create mode 120000 vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/VENDOR-LICENSE diff --git a/Gopkg.lock b/Gopkg.lock index a7d32a87d98..f237994b11e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -122,6 +122,26 @@ revision = "7f2434bc10da710debe5c4315ed6d4df454b4024" version = "v0.1.0" +[[projects]] + digest = "1:fa1c3e6de410f74eb102fd927c838a66feb5b825fdf63d0e82cbbfd1a16db8a1" + name = "github.com/cloudevents/sdk-go" + packages = [ + "pkg/cloudevents", + "pkg/cloudevents/client", + "pkg/cloudevents/codec", + "pkg/cloudevents/context", + "pkg/cloudevents/datacodec", + "pkg/cloudevents/datacodec/json", + "pkg/cloudevents/datacodec/xml", + "pkg/cloudevents/observability", + "pkg/cloudevents/transport", + "pkg/cloudevents/transport/http", + "pkg/cloudevents/types", + ] + pruneopts = "NUT" + revision = "51b1fb67ea02b6edb9c158592f4c996d6edd1493" + version = "0.6.0" + [[projects]] digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" @@ -397,6 +417,14 @@ pruneopts = "T" revision = "3fc06fd3c9880a9ebb5c401f4b20cf6666cc7bc0" +[[projects]] + digest = "1:067a241f3d8511e43ae1df08337a3cf2475d82b90c31cb4c43e4936cd5808d3c" + name = "github.com/knative/eventing-sources" + packages = ["pkg/kncloudevents"] + pruneopts = "NUT" + revision = "32ce3778fa1bc46ed0b0ade16d5c57f343cafa4d" + version = "v0.5.0" + [[projects]] digest = "1:33afd816eb96416e5d7c0a64c1c6412e277720faa03c90739ce1d1fb1764b8de" name = "github.com/knative/pkg" @@ -1253,6 +1281,10 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/cloudevents/sdk-go/pkg/cloudevents", + "github.com/cloudevents/sdk-go/pkg/cloudevents/client", + "github.com/cloudevents/sdk-go/pkg/cloudevents/context", + "github.com/cloudevents/sdk-go/pkg/cloudevents/types", "github.com/ghodss/yaml", "github.com/google/go-cmp/cmp", "github.com/google/go-cmp/cmp/cmpopts", @@ -1270,6 +1302,7 @@ "github.com/hashicorp/golang-lru", "github.com/knative/caching/pkg/apis/caching", "github.com/knative/caching/pkg/client/clientset/versioned", + "github.com/knative/eventing-sources/pkg/kncloudevents", "github.com/knative/pkg/apis", "github.com/knative/pkg/apis/duck/v1beta1", "github.com/knative/pkg/codegen/cmd/injection-gen", diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent.go b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent.go new file mode 100644 index 00000000000..ae47b42ce8e --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloudevent + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/client" + cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "github.com/knative/eventing-sources/pkg/kncloudevents" + "github.com/knative/pkg/apis" + "go.uber.org/zap" + + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" +) + +// TektonEventType holds the types of cloud events sent by Tekton +type TektonEventType string + +const ( + // TektonTaskRunUnknown is sent for TaskRuns with "ConditionSucceeded" "Unknown" + TektonTaskRunUnknown TektonEventType = "dev.tekton.event.task.unknown" + // TektonTaskRunSuccessful is sent for TaskRuns with "ConditionSucceeded" "True" + TektonTaskRunSuccessful TektonEventType = "dev.tekton.event.task.successful" + // TektonTaskRunFailed is sent for TaskRuns with "ConditionSucceeded" "False" + TektonTaskRunFailed TektonEventType = "dev.tekton.event.task.failed" +) + +// CEClient matches the `Client` interface from github.com/cloudevents/sdk-go/pkg/cloudevents +type CEClient client.Client + +// SendCloudEvent sends a Cloud Event to the specified SinkURI +func SendCloudEvent(sinkURI, eventID, eventSourceURI string, data []byte, eventType TektonEventType, logger *zap.SugaredLogger, cloudEventClient CEClient) (cloudevents.Event, error) { + var event cloudevents.Event + + cloudEventSource := types.ParseURLRef(eventSourceURI) + if cloudEventSource == nil { + logger.Errorf("Invalid eventSourceURI: %s", eventSourceURI) + return event, fmt.Errorf("Invalid eventSourceURI: %s", eventSourceURI) + } + + event = cloudevents.Event{ + Context: cloudevents.EventContextV02{ + ID: eventID, + Type: string(eventType), + Source: *cloudEventSource, + Time: &types.Timestamp{Time: time.Now()}, + Extensions: nil, + }.AsV02(), + Data: data, + } + ctxt := cecontext.WithTarget(context.TODO(), sinkURI) + _, err := cloudEventClient.Send(ctxt, event) + if err != nil { + logger.Errorf("Error sending the cloud-event: %s", err) + return event, err + } + return event, nil +} + +// SendTaskRunCloudEvent sends a cloud event for a TaskRun +func SendTaskRunCloudEvent(sinkURI string, taskRun *v1alpha1.TaskRun, logger *zap.SugaredLogger, cloudEventClient CEClient) (cloudevents.Event, error) { + var event cloudevents.Event + var err error + // Check if a client was provided, if not build one on the fly + if cloudEventClient == nil { + cloudEventClient, err = kncloudevents.NewDefaultClient() + if err != nil { + logger.Errorf("Error creating the cloud-event client: %s", err) + return event, err + } + } + // Check if the TaskRun is defined + if taskRun == nil { + return event, errors.New("Cannot send an event for an empty TaskRun") + } + eventID := taskRun.ObjectMeta.Name + taskRunStatus := taskRun.Status.GetCondition(apis.ConditionSucceeded) + var eventType TektonEventType + if taskRunStatus.IsUnknown() { + eventType = TektonTaskRunUnknown + } else if taskRunStatus.IsFalse() { + eventType = TektonTaskRunFailed + } else if taskRunStatus.IsTrue() { + eventType = TektonTaskRunSuccessful + } else { + return event, fmt.Errorf("Unknown condition for in TaskRun.Status %s", taskRunStatus.Status) + } + eventSourceURI := taskRun.ObjectMeta.SelfLink + data, _ := json.Marshal(taskRun) + event, err = SendCloudEvent(sinkURI, eventID, eventSourceURI, data, eventType, logger, cloudEventClient) + return event, err +} diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent_test.go b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent_test.go new file mode 100644 index 00000000000..746755c45aa --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudevent_test.go @@ -0,0 +1,208 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloudevent + +import ( + "encoding/json" + "fmt" + "regexp" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/knative/eventing-sources/pkg/kncloudevents" + "github.com/knative/pkg/apis" + duckv1beta1 "github.com/knative/pkg/apis/duck/v1beta1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/logging" + "github.com/tektoncd/pipeline/test/names" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + defaultSinkURI = "http://sink" + invalidSinkURI = "invalid_URI" + defaultEventID = "event1234" + defaultEventSourceURI = "/taskrun/1234" + invalidEventSourceURI = "htt%23p://_invalid_URI##" + defaultEventType = TektonTaskRunUnknown + taskRunName = "faketaskrunname" + invalidConditionSuccessStatus = "foobar" +) + +var ( + defaultRawData = []byte(`{"metadata": {"name":"faketaskrun"}}`) + nilEventType TektonEventType + defaultCloudEventClient, _ = kncloudevents.NewDefaultClient() + happyClientBehaviour = FakeClientBehaviour{SendSuccessfully: true} + failingClientBehaviour = FakeClientBehaviour{SendSuccessfully: false} +) + +func TestSendCloudEvent(t *testing.T) { + for _, c := range []struct { + desc string + sinkURI string + eventSourceURI string + cloudEventClient CEClient + wantErr bool + errRegexp string + }{{ + desc: "send a cloud event when all inputs are valid", + sinkURI: defaultSinkURI, + eventSourceURI: defaultEventSourceURI, + cloudEventClient: NewFakeClient(&happyClientBehaviour), + wantErr: false, + errRegexp: "", + }, { + desc: "send a cloud event with invalid sink URI", + sinkURI: invalidSinkURI, + eventSourceURI: defaultEventSourceURI, + cloudEventClient: defaultCloudEventClient, + wantErr: true, + errRegexp: fmt.Sprintf("%s: unsupported protocol scheme", invalidSinkURI), + }, { + desc: "send a cloud event, fail to send", + sinkURI: defaultSinkURI, + eventSourceURI: defaultEventSourceURI, + cloudEventClient: NewFakeClient(&failingClientBehaviour), + wantErr: true, + errRegexp: fmt.Sprintf("%s had to fail", defaultEventID), + }, { + desc: "send a cloud event with invalid source URI", + sinkURI: defaultSinkURI, + eventSourceURI: invalidEventSourceURI, + cloudEventClient: defaultCloudEventClient, + wantErr: true, + errRegexp: fmt.Sprintf("Invalid eventSourceURI: %s", invalidEventSourceURI), + }} { + t.Run(c.desc, func(t *testing.T) { + logger, _ := logging.NewLogger("", "") + names.TestingSeed() + _, err := SendCloudEvent(c.sinkURI, defaultEventID, c.eventSourceURI, defaultRawData, defaultEventType, logger, c.cloudEventClient) + if c.wantErr != (err != nil) { + if c.wantErr { + t.Fatalf("I expected an error but I got nil") + } else { + t.Fatalf("I did not expect an error but I got %s", err) + } + } else { + if c.wantErr { + match, _ := regexp.Match(c.errRegexp, []byte(err.Error())) + if !match { + t.Fatalf("I expected an error like %s, but I got %s", c.errRegexp, err) + } + } + } + }) + } +} + +func getTaskRunByCondition(status corev1.ConditionStatus) *v1alpha1.TaskRun { + return &v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: taskRunName, + Namespace: "marshmallow", + SelfLink: defaultEventSourceURI, + }, + Spec: v1alpha1.TaskRunSpec{}, + Status: v1alpha1.TaskRunStatus{ + Status: duckv1beta1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: status, + }}, + }, + }, + } +} + +func TestSendTaskRunCloudEvent(t *testing.T) { + for _, c := range []struct { + desc string + taskRun *v1alpha1.TaskRun + wantEventType TektonEventType + wantErr bool + errRegexp string + }{{ + desc: "send a cloud event with a nil taskrun", + taskRun: nil, + wantEventType: nilEventType, + wantErr: true, + errRegexp: "Cannot send an event for an empty TaskRun", + }, { + desc: "send a cloud event with unknown status taskrun", + taskRun: getTaskRunByCondition(corev1.ConditionUnknown), + wantEventType: TektonTaskRunUnknown, + wantErr: false, + errRegexp: "", + }, { + desc: "send a cloud event with successful status taskrun", + taskRun: getTaskRunByCondition(corev1.ConditionTrue), + wantEventType: TektonTaskRunSuccessful, + wantErr: false, + errRegexp: "", + }, { + desc: "send a cloud event with unknown status taskrun", + taskRun: getTaskRunByCondition(corev1.ConditionFalse), + wantEventType: TektonTaskRunFailed, + wantErr: false, + errRegexp: "", + }, { + desc: "send a cloud event with invalid status taskrun", + taskRun: getTaskRunByCondition(invalidConditionSuccessStatus), + wantEventType: nilEventType, + wantErr: true, + errRegexp: fmt.Sprintf("Unknown condition for in TaskRun.Status %s", invalidConditionSuccessStatus), + }} { + t.Run(c.desc, func(t *testing.T) { + logger, _ := logging.NewLogger("", "") + names.TestingSeed() + event, err := SendTaskRunCloudEvent(defaultSinkURI, c.taskRun, logger, NewFakeClient(&happyClientBehaviour)) + if c.wantErr != (err != nil) { + if c.wantErr { + t.Fatalf("I expected an error but I got nil") + } else { + t.Fatalf("I did not expect an error but I got %s", err) + } + } else { + if c.wantErr { + match, _ := regexp.Match(c.errRegexp, []byte(err.Error())) + if !match { + t.Fatalf("I expected an error like %s, but I got %s", c.errRegexp, err) + } + } else { + wantEventID := taskRunName + if diff := cmp.Diff(wantEventID, event.Context.GetID()); diff != "" { + t.Errorf("Wrong Event ID (-want +got) = %s", diff) + } + gotEventType := event.Context.GetType() + if diff := cmp.Diff(string(c.wantEventType), gotEventType); diff != "" { + t.Errorf("Wrong Event Type (-want +got) = %s", diff) + } + wantData, _ := json.Marshal(c.taskRun) + gotData, err := event.DataBytes() + if err != nil { + t.Fatalf("Could not get data from event %v: %v", event, err) + } + if diff := cmp.Diff(wantData, gotData); diff != "" { + t.Errorf("Wrong Event data (-want +got) = %s", diff) + } + } + } + }) + } +} diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventclient.go b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventclient.go new file mode 100644 index 00000000000..2bca242d788 --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventclient.go @@ -0,0 +1,52 @@ +/* +Copyright 2019 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloudevent + +import ( + "context" + + "github.com/knative/eventing-sources/pkg/kncloudevents" + injection "github.com/knative/pkg/injection" + logging "github.com/knative/pkg/logging" + rest "k8s.io/client-go/rest" +) + +func init() { + injection.Default.RegisterClient(withCloudEventClient) +} + +// CECKey is used to associate the CloudEventClient inside the context.Context +type CECKey struct{} + +func withCloudEventClient(ctx context.Context, cfg *rest.Config) context.Context { + cloudEventClient, err := kncloudevents.NewDefaultClient() + if err != nil { + // If we cannot setup a client, die + panic(err) + } + return context.WithValue(ctx, CECKey{}, cloudEventClient) +} + +// Get extracts the cloudEventClient client from the context. +func Get(ctx context.Context) CEClient { + untyped := ctx.Value(CECKey{}) + if untyped == nil { + logging.FromContext(ctx).Fatalf( + "Unable to fetch %T from context.", (CEClient)(nil)) + } + return untyped.(CEClient) +} diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventsfakeclient.go b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventsfakeclient.go new file mode 100644 index 00000000000..af9e9023969 --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/cloudevent/cloudeventsfakeclient.go @@ -0,0 +1,64 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cloudevent + +import ( + "context" + "fmt" + + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/client" +) + +// FakeClientBehaviour defines how the client will behave +type FakeClientBehaviour struct { + SendSuccessfully bool +} + +// FakeClient is a fake CloudEvent client for unit testing +// Holding a pointer to the behaviour allows to change the behaviour of a client +type FakeClient struct { + behaviour *FakeClientBehaviour + event cloudevents.Event +} + +// NewFakeClient is a FakeClient factory, it returns a client for the target +func NewFakeClient(behaviour *FakeClientBehaviour) (client.Client) { + c := FakeClient{ + behaviour: behaviour, + } + return c +} + +// Send fakes the Send method from kncloudevents.NewDefaultClient +func (c FakeClient) Send(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) { + c.event = event + if c.behaviour.SendSuccessfully { + return &event, nil + } + return nil, fmt.Errorf("%s had to fail", event.Context.GetID()) +} + +// StartReceiver fakes the StartReceiver method from kncloudevents.NewDefaultClient +func (c FakeClient) StartReceiver(ctx context.Context, fn interface{}) error { + return nil +} + +// WithClient adds to the context a fake client with the desired behaviour +func WithClient(ctx context.Context, behaviour *FakeClientBehaviour) context.Context { + return context.WithValue(ctx, CECKey{}, NewFakeClient(behaviour)) +} diff --git a/third_party/VENDOR-LICENSE b/third_party/VENDOR-LICENSE index 093404f0866..3823d33184e 100644 --- a/third_party/VENDOR-LICENSE +++ b/third_party/VENDOR-LICENSE @@ -1469,6 +1469,213 @@ Import: github.com/tektoncd/pipeline/vendor/github.com/census-instrumentation/op +=========================================================== +Import: github.com/tektoncd/pipeline/vendor/github.com/cloudevents/sdk-go + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + =========================================================== Import: github.com/tektoncd/pipeline/vendor/github.com/davecgh/go-spew @@ -4520,6 +4727,213 @@ Import: github.com/tektoncd/pipeline/vendor/github.com/knative/caching +=========================================================== +Import: github.com/tektoncd/pipeline/vendor/github.com/knative/eventing-sources + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + =========================================================== Import: github.com/tektoncd/pipeline/vendor/github.com/knative/pkg diff --git a/vendor/github.com/cloudevents/sdk-go/LICENSE b/vendor/github.com/cloudevents/sdk-go/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/client.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/client.go new file mode 100644 index 00000000000..9eefa27c8ec --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/client.go @@ -0,0 +1,184 @@ +package client + +import ( + "context" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http" + "sync" +) + +// Client interface defines the runtime contract the CloudEvents client supports. +type Client interface { + // Send will transmit the given event over the client's configured transport. + Send(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) + + // StartReceiver will register the provided function for callback on receipt + // of a cloudevent. It will also start the underlying transport as it has + // been configured. + // This call is blocking. + // Valid fn signatures are: + // * func() + // * func() error + // * func(context.Context) + // * func(context.Context) error + // * func(cloudevents.Event) + // * func(cloudevents.Event) error + // * func(context.Context, cloudevents.Event) + // * func(context.Context, cloudevents.Event) error + // * func(cloudevents.Event, *cloudevents.EventResponse) + // * func(cloudevents.Event, *cloudevents.EventResponse) error + // * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) + // * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error + // Note: if fn returns an error, it is treated as a critical and + // EventResponse will not be processed. + StartReceiver(ctx context.Context, fn interface{}) error +} + +// New produces a new client with the provided transport object and applied +// client options. +func New(t transport.Transport, opts ...Option) (Client, error) { + c := &ceClient{ + transport: t, + } + if err := c.applyOptions(opts...); err != nil { + return nil, err + } + t.SetReceiver(c) + return c, nil +} + +// NewDefault provides the good defaults for the common case using an HTTP +// Transport client. The http transport has had WithBinaryEncoding http +// transport option applied to it. The client will always send Binary +// encoding but will inspect the outbound event context and match the version. +// The WithtimeNow and WithUUIDs client options are also applied to the client, +// all outbound events will have a time and id set if not already present. +func NewDefault() (Client, error) { + t, err := http.New(http.WithBinaryEncoding()) + if err != nil { + return nil, err + } + c, err := New(t, WithTimeNow(), WithUUIDs()) + if err != nil { + return nil, err + } + return c, nil +} + +type ceClient struct { + transport transport.Transport + fn *receiverFn + + receiverMu sync.Mutex + eventDefaulterFns []EventDefaulter +} + +// Send transmits the provided event on a preconfigured Transport. +// Send returns a response event if there is a response or an error if there +// was an an issue validating the outbound event or the transport returns an +// error. +func (c *ceClient) Send(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) { + ctx, r := observability.NewReporter(ctx, reportSend) + resp, err := c.obsSend(ctx, event) + if err != nil { + r.Error() + } else { + r.OK() + } + return resp, err +} + +func (c *ceClient) obsSend(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) { + // Confirm we have a transport set. + if c.transport == nil { + return nil, fmt.Errorf("client not ready, transport not initialized") + } + // Apply the defaulter chain to the incoming event. + if len(c.eventDefaulterFns) > 0 { + for _, fn := range c.eventDefaulterFns { + event = fn(event) + } + } + + // Validate the event conforms to the CloudEvents Spec. + if err := event.Validate(); err != nil { + return nil, err + } + // Send the event over the transport. + return c.transport.Send(ctx, event) +} + +// Receive is called from from the transport on event delivery. +func (c *ceClient) Receive(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error { + ctx, r := observability.NewReporter(ctx, reportReceive) + err := c.obsReceive(ctx, event, resp) + if err != nil { + r.Error() + } else { + r.OK() + } + return err +} + +func (c *ceClient) obsReceive(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error { + if c.fn != nil { + ctx, rFn := observability.NewReporter(ctx, reportReceiveFn) + err := c.fn.invoke(ctx, event, resp) + if err != nil { + rFn.Error() + } else { + rFn.OK() + } + + // Apply the defaulter chain to the outgoing event. + if err == nil && resp != nil && resp.Event != nil && len(c.eventDefaulterFns) > 0 { + for _, fn := range c.eventDefaulterFns { + *resp.Event = fn(*resp.Event) + } + // Validate the event conforms to the CloudEvents Spec. + if err := resp.Event.Validate(); err != nil { + return fmt.Errorf("cloudevent validation failed on response event: %v", err) + } + } + return err + } + return nil +} + +// StartReceiver sets up the given fn to handle Receive. +// See Client.StartReceiver for details. This is a blocking call. +func (c *ceClient) StartReceiver(ctx context.Context, fn interface{}) error { + c.receiverMu.Lock() + defer c.receiverMu.Unlock() + + if c.transport == nil { + return fmt.Errorf("client not ready, transport not initialized") + } + if c.fn != nil { + return fmt.Errorf("client already has a receiver") + } + + if fn, err := receiver(fn); err != nil { + return err + } else { + c.fn = fn + } + + defer func() { + c.fn = nil + }() + + return c.transport.StartReceiver(ctx) +} + +func (c *ceClient) applyOptions(opts ...Option) error { + for _, fn := range opts { + if err := fn(c); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/defaulters.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/defaulters.go new file mode 100644 index 00000000000..a5cd591c740 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/defaulters.go @@ -0,0 +1,35 @@ +package client + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/google/uuid" + "time" +) + +// EventDefaulter is the function signature for extensions that are able +// to perform event defaulting. +type EventDefaulter func(event cloudevents.Event) cloudevents.Event + +// DefaultIDToUUIDIfNotSet will inspect the provided event and assign a UUID to +// context.ID if it is found to be empty. +func DefaultIDToUUIDIfNotSet(event cloudevents.Event) cloudevents.Event { + if event.Context != nil { + if event.ID() == "" { + event.Context = event.Context.Clone() + event.SetID(uuid.New().String()) + } + } + return event +} + +// DefaultTimeToNowIfNotSet will inspect the provided event and assign a new +// Timestamp to context.Time if it is found to be nil or zero. +func DefaultTimeToNowIfNotSet(event cloudevents.Event) cloudevents.Event { + if event.Context != nil { + if event.Time().IsZero() { + event.Context = event.Context.Clone() + event.SetTime(time.Now()) + } + } + return event +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/doc.go new file mode 100644 index 00000000000..a6a602bb410 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/doc.go @@ -0,0 +1,6 @@ +/* +Package client holds the recommended entry points for interacting with the CloudEvents Golang SDK. The client wraps +a selected transport. The client adds validation and defaulting for sending events, and flexible receiver method +registration. For full details, read the `client.Client` documentation. +*/ +package client diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/observability.go new file mode 100644 index 00000000000..b844c19a86e --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/observability.go @@ -0,0 +1,68 @@ +package client + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the CloudEvents + // client methods. + LatencyMs = stats.Float64("cloudevents.io/sdk-go/client/latency", "The latency in milliseconds for the CloudEvents client methods.", "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows client method latency. + LatencyView = &view.View{ + Name: "client/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of client for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportSend observed = iota + reportReceive + reportReceiveFn +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportSend: + return "client/send" + case reportReceive: + return "client/receive" + case reportReceiveFn: + return "client/receive/fn" + default: + return "client/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportSend: + return "send" + case reportReceive: + return "receive" + case reportReceiveFn: + return "receive/fn" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/options.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/options.go new file mode 100644 index 00000000000..ec4ac7842f8 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/options.go @@ -0,0 +1,37 @@ +package client + +import ( + "fmt" +) + +// Option is the function signature required to be considered an client.Option. +type Option func(*ceClient) error + +// WithEventDefaulter adds an event defaulter to the end of the defaulter chain. +func WithEventDefaulter(fn EventDefaulter) Option { + return func(c *ceClient) error { + if fn == nil { + return fmt.Errorf("client option was given an nil event defaulter") + } + c.eventDefaulterFns = append(c.eventDefaulterFns, fn) + return nil + } +} + +// WithUUIDs adds DefaultIDToUUIDIfNotSet event defaulter to the end of the +// defaulter chain. +func WithUUIDs() Option { + return func(c *ceClient) error { + c.eventDefaulterFns = append(c.eventDefaulterFns, DefaultIDToUUIDIfNotSet) + return nil + } +} + +// WithTimeNow adds DefaultTimeToNowIfNotSet event defaulter to the end of the +// defaulter chain. +func WithTimeNow() Option { + return func(c *ceClient) error { + c.eventDefaulterFns = append(c.eventDefaulterFns, DefaultTimeToNowIfNotSet) + return nil + } +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/receiver.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/receiver.go new file mode 100644 index 00000000000..6264a3f79d0 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/client/receiver.go @@ -0,0 +1,187 @@ +package client + +import ( + "context" + "errors" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "reflect" +) + +// Receive is the signature of a fn to be invoked for incoming cloudevents. +// If fn returns an error, EventResponse will not be considered by the client or +// or transport. +// This is just an FYI: +type ReceiveFull func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error + +type receiverFn struct { + numIn int + fnValue reflect.Value + + hasContextIn bool + hasEventIn bool + hasEventResponseIn bool + + hasErrorOut bool +} + +const ( + inParamUsage = "expected a function taking either no parameters, one or more of (context.Context, cloudevents.Event, *cloudevents.EventResponse) ordered" + outParamUsage = "expected a function returning either nothing or an error" +) + +var ( + contextType = reflect.TypeOf((*context.Context)(nil)).Elem() + eventType = reflect.TypeOf((*cloudevents.Event)(nil)).Elem() + eventResponseType = reflect.TypeOf((*cloudevents.EventResponse)(nil)) // want the ptr type + errorType = reflect.TypeOf((*error)(nil)).Elem() +) + +// receiver creates a receiverFn wrapper class that is used by the client to +// validate and invoke the provided function. +// Valid fn signatures are: +// * func() +// * func() error +// * func(context.Context) +// * func(context.Context) error +// * func(cloudevents.Event) +// * func(cloudevents.Event) error +// * func(context.Context, cloudevents.Event) +// * func(context.Context, cloudevents.Event) error +// * func(cloudevents.Event, *cloudevents.EventResponse) +// * func(cloudevents.Event, *cloudevents.EventResponse) error +// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) +// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error +// +func receiver(fn interface{}) (*receiverFn, error) { + fnType := reflect.TypeOf(fn) + if fnType.Kind() != reflect.Func { + return nil, errors.New("must pass a function to handle events") + } + + r := &receiverFn{ + fnValue: reflect.ValueOf(fn), + numIn: fnType.NumIn(), + } + if err := r.validate(fnType); err != nil { + return nil, err + } + + return r, nil +} + +func (r *receiverFn) invoke(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error { + args := make([]reflect.Value, 0, r.numIn) + + if r.numIn > 0 { + if r.hasContextIn { + args = append(args, reflect.ValueOf(ctx)) + } + if r.hasEventIn { + args = append(args, reflect.ValueOf(event)) + } + if r.hasEventResponseIn { + args = append(args, reflect.ValueOf(resp)) + } + } + v := r.fnValue.Call(args) + if r.hasErrorOut && len(v) >= 1 { + if err, ok := v[0].Interface().(error); ok { + return err + } + } + return nil +} + +// Verifies that the inputs to a function have a valid signature +// Valid input is to be [0, all] of +// context.Context, cloudevents.Event, *cloudevents.EventResponse in this order. +func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error { + r.hasContextIn = false + r.hasEventIn = false + r.hasEventResponseIn = false + + switch fnType.NumIn() { + case 3: + // has to be cloudevents.Event, *cloudevents.EventResponse + if !fnType.In(2).ConvertibleTo(eventResponseType) { + return fmt.Errorf("%s; cannot convert parameter 2 from %s to *cloudevents.EventResponse", inParamUsage, fnType.In(2)) + } else { + r.hasEventResponseIn = true + } + fallthrough + case 2: + // can be cloudevents.Event or *cloudevents.EventResponse + if !fnType.In(1).ConvertibleTo(eventResponseType) { + if !fnType.In(1).ConvertibleTo(eventType) { + return fmt.Errorf("%s; cannot convert parameter 1 from %s to cloudevents.Event or *cloudevents.EventResponse", inParamUsage, fnType.In(1)) + } else { + r.hasEventIn = true + } + } else if r.hasEventResponseIn { + return fmt.Errorf("%s; duplicate parameter of type *cloudevents.EventResponse", inParamUsage) + } else { + r.hasEventResponseIn = true + } + fallthrough + case 1: + if !fnType.In(0).ConvertibleTo(contextType) { + if !fnType.In(0).ConvertibleTo(eventResponseType) { + if !fnType.In(0).ConvertibleTo(eventType) { + return fmt.Errorf("%s; cannot convert parameter 0 from %s to context.Context, cloudevents.Event or *cloudevents.EventResponse", inParamUsage, fnType.In(0)) + } else if r.hasEventIn { + return fmt.Errorf("%s; duplicate parameter of type cloudevents.Event", inParamUsage) + } else { + r.hasEventIn = true + } + } else if r.hasEventResponseIn { + return fmt.Errorf("%s; duplicate parameter of type *cloudevents.EventResponse", inParamUsage) + } else if r.hasEventIn { + return fmt.Errorf("%s; out of order parameter 0 for %s", inParamUsage, fnType.In(1)) + } else { + r.hasEventResponseIn = true + } + } else { + r.hasContextIn = true + } + fallthrough + case 0: + return nil + default: + return fmt.Errorf("%s; function has too many parameters (%d)", inParamUsage, fnType.NumIn()) + } +} + +// Verifies that the outputs of a function have a valid signature +// Valid output signatures: +// (), (error) +func (r *receiverFn) validateOutParamSignature(fnType reflect.Type) error { + r.hasErrorOut = false + switch fnType.NumOut() { + case 1: + paramNo := fnType.NumOut() - 1 + paramType := fnType.Out(paramNo) + if !paramType.ConvertibleTo(errorType) { + return fmt.Errorf("%s; cannot convert return type %d from %s to error", outParamUsage, paramNo, paramType) + } else { + r.hasErrorOut = true + } + fallthrough + case 0: + return nil + default: + return fmt.Errorf("%s; function has too many return types (%d)", outParamUsage, fnType.NumOut()) + } +} + +// validateReceiverFn validates that a function has the right number of in and +// out params and that they are of allowed types. +func (r *receiverFn) validate(fnType reflect.Type) error { + if err := r.validateInParamSignature(fnType); err != nil { + return err + } + if err := r.validateOutParamSignature(fnType); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/doc.go new file mode 100644 index 00000000000..f6028398f5a --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/doc.go @@ -0,0 +1,5 @@ +/* +Package codec holds the encoder/decoder implementation for structured encodings using `application/json` of the +whole CloudEvent. +*/ +package codec diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/jsoncodec.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/jsoncodec.go new file mode 100644 index 00000000000..ea607f3f9f1 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/jsoncodec.go @@ -0,0 +1,258 @@ +package codec + +import ( + "context" + "encoding/json" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "strconv" +) + +// JsonEncodeV01 takes in a cloudevent.Event and outputs the byte representation of that event using CloudEvents +// version 0.1 structured json formatting rules. +func JsonEncodeV01(e cloudevents.Event) ([]byte, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportEncode, v: "v0.1"}) + b, err := obsJsonEncodeV01(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsJsonEncodeV01(e cloudevents.Event) ([]byte, error) { + ctx := e.Context.AsV01() + if ctx.ContentType == nil { + ctx.ContentType = cloudevents.StringOfApplicationJSON() + } + data, err := e.DataBytes() + if err != nil { + return nil, err + } + return jsonEncode(ctx, data) +} + +// JsonEncodeV02 takes in a cloudevent.Event and outputs the byte representation of that event using CloudEvents +// version 0.2 structured json formatting rules. +func JsonEncodeV02(e cloudevents.Event) ([]byte, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportEncode, v: "v0.2"}) + b, err := obsJsonEncodeV02(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsJsonEncodeV02(e cloudevents.Event) ([]byte, error) { + ctx := e.Context.AsV02() + if ctx.ContentType == nil { + ctx.ContentType = cloudevents.StringOfApplicationJSON() + } + data, err := e.DataBytes() + if err != nil { + return nil, err + } + return jsonEncode(ctx, data) +} + +// JsonEncodeV03 takes in a cloudevent.Event and outputs the byte representation of that event using CloudEvents +// version 0.3 structured json formatting rules. +func JsonEncodeV03(e cloudevents.Event) ([]byte, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportEncode, v: "v0.3"}) + b, err := obsJsonEncodeV03(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsJsonEncodeV03(e cloudevents.Event) ([]byte, error) { + ctx := e.Context.AsV03() + if ctx.DataContentType == nil { + ctx.DataContentType = cloudevents.StringOfApplicationJSON() + } + + data, err := e.DataBytes() + if err != nil { + return nil, err + } + return jsonEncode(ctx, data) +} + +func jsonEncode(ctx cloudevents.EventContextReader, data []byte) ([]byte, error) { + ctxb, err := marshalEvent(ctx) + if err != nil { + return nil, err + } + + var body []byte + + b := map[string]json.RawMessage{} + if err := json.Unmarshal(ctxb, &b); err != nil { + return nil, err + } + + if data != nil { + // data is passed in as an encoded []byte. That slice might be any + // number of things but for json encoding of the envelope all we care + // is if the payload is either a string or a json object. If it is a + // json object, it can be inserted into the body without modification. + // Otherwise we need to quote it if not already quoted. + mediaType, err := ctx.GetDataMediaType() + if err != nil { + return nil, err + } + isBase64 := ctx.GetDataContentEncoding() == cloudevents.Base64 + isJson := mediaType == "" || mediaType == cloudevents.ApplicationJSON || mediaType == cloudevents.TextJSON + // TODO(#60): we do not support json values at the moment, only objects and lists. + if isJson && !isBase64 { + b["data"] = data + } else if data[0] != byte('"') { + b["data"] = []byte(strconv.QuoteToASCII(string(data))) + } else { + // already quoted + b["data"] = data + } + } + + body, err = json.Marshal(b) + if err != nil { + return nil, err + } + + return body, nil +} + +// JsonDecodeV01 takes in the byte representation of a version 0.1 structured json CloudEvent and returns a +// cloudevent.Event or an error if there are parsing errors. +func JsonDecodeV01(body []byte) (*cloudevents.Event, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportDecode, v: "v0.1"}) + e, err := obsJsonDecodeV01(body) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func obsJsonDecodeV01(body []byte) (*cloudevents.Event, error) { + ec := cloudevents.EventContextV01{} + if err := json.Unmarshal(body, &ec); err != nil { + return nil, err + } + + raw := make(map[string]json.RawMessage) + + if err := json.Unmarshal(body, &raw); err != nil { + return nil, err + } + var data interface{} + if d, ok := raw["data"]; ok { + data = []byte(d) + } + + return &cloudevents.Event{ + Context: &ec, + Data: data, + DataEncoded: true, + }, nil +} + +// JsonDecodeV02 takes in the byte representation of a version 0.2 structured json CloudEvent and returns a +// cloudevent.Event or an error if there are parsing errors. +func JsonDecodeV02(body []byte) (*cloudevents.Event, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportDecode, v: "v0.2"}) + e, err := obsJsonDecodeV02(body) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func obsJsonDecodeV02(body []byte) (*cloudevents.Event, error) { + ec := cloudevents.EventContextV02{} + if err := json.Unmarshal(body, &ec); err != nil { + return nil, err + } + + raw := make(map[string]json.RawMessage) + + if err := json.Unmarshal(body, &raw); err != nil { + return nil, err + } + var data interface{} + if d, ok := raw["data"]; ok { + data = []byte(d) + } + + return &cloudevents.Event{ + Context: &ec, + Data: data, + DataEncoded: true, + }, nil +} + +// JsonDecodeV03 takes in the byte representation of a version 0.3 structured json CloudEvent and returns a +// cloudevent.Event or an error if there are parsing errors. +func JsonDecodeV03(body []byte) (*cloudevents.Event, error) { + _, r := observability.NewReporter(context.Background(), codecObserved{o: reportDecode, v: "v0.3"}) + e, err := obsJsonDecodeV03(body) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func obsJsonDecodeV03(body []byte) (*cloudevents.Event, error) { + ec := cloudevents.EventContextV03{} + if err := json.Unmarshal(body, &ec); err != nil { + return nil, err + } + + raw := make(map[string]json.RawMessage) + + if err := json.Unmarshal(body, &raw); err != nil { + return nil, err + } + var data interface{} + if d, ok := raw["data"]; ok { + data = []byte(d) + } + + return &cloudevents.Event{ + Context: &ec, + Data: data, + DataEncoded: true, + }, nil +} + +func marshalEvent(event interface{}) ([]byte, error) { + b, err := json.Marshal(event) + if err != nil { + return nil, err + } + return b, nil +} + +// TODO: not sure about this location for eventdata. +func marshalEventData(encoding string, data interface{}) ([]byte, error) { + if data == nil { + return []byte(nil), nil + } + // already encoded? + if b, ok := data.([]byte); ok { + return b, nil + } + return datacodec.Encode(encoding, data) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/observability.go new file mode 100644 index 00000000000..49bcb793d81 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/codec/observability.go @@ -0,0 +1,90 @@ +package codec + +import ( + "fmt" + + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the CloudEvents json codec methods. + LatencyMs = stats.Float64("cloudevents.io/sdk-go/codec/json/latency", "The latency in milliseconds for the CloudEvents json codec methods.", "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows codec/json method latency. + LatencyView = &view.View{ + Name: "codec/json/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of the json codec for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportEncode observed = iota + reportDecode +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportEncode: + return "codec/json/encode" + case reportDecode: + return "codec/json/decode" + default: + return "codec/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportEncode: + return "encode" + case reportDecode: + return "decode" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} + +// codecObserved is a wrapper to append version to observed. +type codecObserved struct { + // Method + o observed + // Version + v string +} + +// Adheres to Observable +var _ observability.Observable = (*codecObserved)(nil) + +// TraceName implements Observable.TraceName +func (c codecObserved) TraceName() string { + return fmt.Sprintf("%s/%s", c.o.TraceName(), c.v) +} + +// MethodName implements Observable.MethodName +func (c codecObserved) MethodName() string { + return fmt.Sprintf("%s/%s", c.o.MethodName(), c.v) +} + +// LatencyMs implements Observable.LatencyMs +func (c codecObserved) LatencyMs() *stats.Float64Measure { + return c.o.LatencyMs() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/content_type.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/content_type.go new file mode 100644 index 00000000000..e4e0e17f2b7 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/content_type.go @@ -0,0 +1,35 @@ +package cloudevents + +const ( + TextJSON = "text/json" + ApplicationJSON = "application/json" + ApplicationXML = "application/xml" + ApplicationCloudEventsJSON = "application/cloudevents+json" + ApplicationCloudEventsBatchJSON = "application/cloudevents-batch+json" +) + +// StringOfApplicationJSON returns a string pointer to "application/json" +func StringOfApplicationJSON() *string { + a := ApplicationJSON + return &a +} + +// StringOfApplicationXML returns a string pointer to "application/xml" +func StringOfApplicationXML() *string { + a := ApplicationXML + return &a +} + +// StringOfApplicationCloudEventsJSON returns a string pointer to +// "application/cloudevents+json" +func StringOfApplicationCloudEventsJSON() *string { + a := ApplicationCloudEventsJSON + return &a +} + +// StringOfApplicationCloudEventsBatchJSON returns a string pointer to +// "application/cloudevents-batch+json" +func StringOfApplicationCloudEventsBatchJSON() *string { + a := ApplicationCloudEventsBatchJSON + return &a +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/context.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/context.go new file mode 100644 index 00000000000..18afb6282b6 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/context.go @@ -0,0 +1,30 @@ +package context + +import ( + "context" + "net/url" +) + +// Opaque key type used to store target +type targetKeyType struct{} + +var targetKey = targetKeyType{} + +// WithTarget returns back a new context with the given target. Target is intended to be transport dependent. +// For http transport, `target` should be a full URL and will be injected into the outbound http request. +func WithTarget(ctx context.Context, target string) context.Context { + return context.WithValue(ctx, targetKey, target) +} + +// TargetFrom looks in the given context and returns `target` as a parsed url if found and valid, otherwise nil. +func TargetFrom(ctx context.Context) *url.URL { + c := ctx.Value(targetKey) + if c != nil { + if s, ok := c.(string); ok && s != "" { + if target, err := url.Parse(s); err == nil { + return target + } + } + } + return nil +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/doc.go new file mode 100644 index 00000000000..377cab850fc --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/doc.go @@ -0,0 +1,5 @@ +/* +Package context holds the last resort overrides and fyi objects that can be passed to clients and transports added to +context.Context objects. +*/ +package context diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/logger.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/logger.go new file mode 100644 index 00000000000..38d46069664 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/context/logger.go @@ -0,0 +1,42 @@ +package context + +import ( + "context" + "go.uber.org/zap" +) + +// Opaque key type used to store logger +type loggerKeyType struct{} + +var loggerKey = loggerKeyType{} + +// fallbackLogger is the logger is used when there is no logger attached to the context. +var fallbackLogger *zap.SugaredLogger + +func init() { + if logger, err := zap.NewProduction(); err != nil { + // We failed to create a fallback logger. + fallbackLogger = zap.NewNop().Sugar() + } else { + fallbackLogger = logger.Named("fallback").Sugar() + } +} + +// WithLogger returns a new context with the logger injected into the given context. +func WithLogger(ctx context.Context, logger *zap.SugaredLogger) context.Context { + if logger == nil { + return context.WithValue(ctx, loggerKey, fallbackLogger) + } + return context.WithValue(ctx, loggerKey, logger) +} + +// LoggerFrom returns the logger stored in context. +func LoggerFrom(ctx context.Context) *zap.SugaredLogger { + l := ctx.Value(loggerKey) + if l != nil { + if logger, ok := l.(*zap.SugaredLogger); ok { + return logger + } + } + return fallbackLogger +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/data_content_encoding.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/data_content_encoding.go new file mode 100644 index 00000000000..180102ee3fa --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/data_content_encoding.go @@ -0,0 +1,11 @@ +package cloudevents + +const ( + Base64 = "base64" +) + +// StringOfBase64 returns a string pointer to "Base64" +func StringOfBase64() *string { + a := Base64 + return &a +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/codec.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/codec.go new file mode 100644 index 00000000000..7b88e627ccd --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/codec.go @@ -0,0 +1,94 @@ +package datacodec + +import ( + "context" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json" + "github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" +) + +// Decoder is the expected function signature for decoding `in` to `out`. What +// `in` is could be decoder dependent. For example, `in` could be bytes, or a +// base64 string. +type Decoder func(in, out interface{}) error + +// Encoder is the expected function signature for encoding `in` to bytes. +// Returns an error if the encoder has an issue encoding `in`. +type Encoder func(in interface{}) ([]byte, error) + +var decoder map[string]Decoder +var encoder map[string]Encoder + +func init() { + decoder = make(map[string]Decoder, 10) + encoder = make(map[string]Encoder, 10) + + AddDecoder("", json.Decode) + AddDecoder("application/json", json.Decode) + AddDecoder("text/json", json.Decode) + AddDecoder("application/xml", xml.Decode) + AddDecoder("text/xml", xml.Decode) + + AddEncoder("", json.Encode) + AddEncoder("application/json", json.Encode) + AddEncoder("text/json", json.Encode) + AddEncoder("application/xml", xml.Encode) + AddEncoder("text/xml", xml.Encode) +} + +// AddDecoder registers a decoder for a given content type. The codecs will use +// these to decode the data payload from a cloudevent.Event object. +func AddDecoder(contentType string, fn Decoder) { + decoder[contentType] = fn +} + +// AddEncoder registers an encoder for a given content type. The codecs will +// use these to encode the data payload for a cloudevent.Event object. +func AddEncoder(contentType string, fn Encoder) { + encoder[contentType] = fn +} + +// Decode looks up and invokes the decoder registered for the given content +// type. An error is returned if no decoder is registered for the given +// content type. +func Decode(contentType string, in, out interface{}) error { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportDecode) + err := obsDecode(contentType, in, out) + if err != nil { + r.Error() + } else { + r.OK() + } + return err +} + +func obsDecode(contentType string, in, out interface{}) error { + if fn, ok := decoder[contentType]; ok { + return fn(in, out) + } + return fmt.Errorf("[decode] unsupported content type: %q", contentType) +} + +// Encode looks up and invokes the encoder registered for the given content +// type. An error is returned if no encoder is registered for the given +// content type. +func Encode(contentType string, in interface{}) ([]byte, error) { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportEncode) + b, err := obsEncode(contentType, in) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsEncode(contentType string, in interface{}) ([]byte, error) { + if fn, ok := encoder[contentType]; ok { + return fn(in) + } + return nil, fmt.Errorf("[encode] unsupported content type: %q", contentType) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/doc.go new file mode 100644 index 00000000000..9e401534e27 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/doc.go @@ -0,0 +1,5 @@ +/* +Package datacodec holds the data codec registry and adds known encoders and decoders supporting media types such as +`application/json` and `application/xml`. +*/ +package datacodec diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/data.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/data.go new file mode 100644 index 00000000000..ef2a69eb78c --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/data.go @@ -0,0 +1,98 @@ +package json + +import ( + "context" + "encoding/json" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "reflect" + "strconv" +) + +// Decode takes `in` as []byte, or base64 string, normalizes in to unquoted and +// base64 decoded []byte if required, and then attempts to use json.Unmarshal +// to convert those bytes to `out`. Returns and error if this process fails. +func Decode(in, out interface{}) error { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportDecode) + err := obsDecode(in, out) + if err != nil { + r.Error() + } else { + r.OK() + } + return err +} + +func obsDecode(in, out interface{}) error { + if in == nil { + return nil + } + if out == nil { + return fmt.Errorf("out is nil") + } + + b, ok := in.([]byte) // TODO: I think there is fancy marshaling happening here. Fix with reflection? + if !ok { + var err error + b, err = json.Marshal(in) + if err != nil { + return fmt.Errorf("[json] failed to marshal in: %s", err.Error()) + } + } + + // TODO: the spec says json could be just data... At the moment we expect wrapped. + if len(b) > 1 && (b[0] == byte('"') || (b[0] == byte('\\') && b[1] == byte('"'))) { + s, err := strconv.Unquote(string(b)) + if err != nil { + return fmt.Errorf("[json] failed to unquote in: %s", err.Error()) + } + if len(s) > 0 && (s[0] == '{' || s[0] == '[') { + // looks like json, use it + b = []byte(s) + } + } + + if err := json.Unmarshal(b, out); err != nil { + return fmt.Errorf("[json] found bytes \"%s\", but failed to unmarshal: %s", string(b), err.Error()) + } + return nil +} + +// Encode attempts to json.Marshal `in` into bytes. Encode will inspect `in` +// and returns `in` unmodified if it is detected that `in` is already a []byte; +// Or json.Marshal errors. +func Encode(in interface{}) ([]byte, error) { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportEncode) + b, err := obsEncode(in) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsEncode(in interface{}) ([]byte, error) { + if in == nil { + return nil, nil + } + + it := reflect.TypeOf(in) + switch it.Kind() { + case reflect.Slice: + if it.Elem().Kind() == reflect.Uint8 { + + if b, ok := in.([]byte); ok && len(b) > 0 { + // check to see if it is a pre-encoded byte string. + if b[0] == byte('"') || b[0] == byte('{') || b[0] == byte('[') { + return b, nil + } + } + + } + } + + return json.Marshal(in) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/doc.go new file mode 100644 index 00000000000..86772c2e339 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/doc.go @@ -0,0 +1,4 @@ +/* +Package json holds the encoder/decoder implementation for `application/json`. +*/ +package json diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/observability.go new file mode 100644 index 00000000000..d38a4b7d250 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json/observability.go @@ -0,0 +1,63 @@ +package json + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the CloudEvents json + // data codec methods. + LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/json/latency", "The latency in milliseconds for the CloudEvents json data codec methods.", "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows data codec json method latency. + LatencyView = &view.View{ + Name: "datacodec/json/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of the json data codec for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportEncode observed = iota + reportDecode +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportEncode: + return "datacodec/json/encode" + case reportDecode: + return "datacodec/json/decode" + default: + return "datacodec/json/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportEncode: + return "encode" + case reportDecode: + return "decode" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/observability.go new file mode 100644 index 00000000000..a51e05eb9fc --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/observability.go @@ -0,0 +1,63 @@ +package datacodec + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the CloudEvents generic + // codec data methods. + LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/latency", "The latency in milliseconds for the CloudEvents generic data codec methods.", "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows data codec method latency. + LatencyView = &view.View{ + Name: "datacodec/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of the generic data codec for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportEncode observed = iota + reportDecode +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportEncode: + return "datacodec/encode" + case reportDecode: + return "datacodec/decode" + default: + return "datacodec/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportEncode: + return "encode" + case reportDecode: + return "decode" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/data.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/data.go new file mode 100644 index 00000000000..047a961e0d2 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/data.go @@ -0,0 +1,91 @@ +package xml + +import ( + "context" + "encoding/base64" + "encoding/xml" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "strconv" +) + +// Decode takes `in` as []byte, or base64 string, normalizes in to unquoted and +// base64 decoded []byte if required, and then attempts to use xml.Unmarshal +// to convert those bytes to `out`. Returns and error if this process fails. +func Decode(in, out interface{}) error { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportDecode) + err := obsDecode(in, out) + if err != nil { + r.Error() + } else { + r.OK() + } + return err +} + +func obsDecode(in, out interface{}) error { + if in == nil { + return nil + } + + b, ok := in.([]byte) + if !ok { + var err error + b, err = xml.Marshal(in) + if err != nil { + return fmt.Errorf("[xml] failed to marshal in: %s", err.Error()) + } + } + + // If the message is encoded as a base64 block as a string, we need to + // decode that first before trying to unmarshal the bytes + if len(b) > 1 && (b[0] == byte('"') || (b[0] == byte('\\') && b[1] == byte('"'))) { + s, err := strconv.Unquote(string(b)) + if err != nil { + return fmt.Errorf("[xml] failed to unquote quoted data: %s", err.Error()) + } + if len(s) > 0 && s[0] == '<' { + // looks like xml, use it + b = []byte(s) + } else if len(s) > 0 { + // looks like base64, decode + bs, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return fmt.Errorf("[xml] failed to decode base64 encoded string: %s", err.Error()) + } + b = bs + } + } + + if err := xml.Unmarshal(b, out); err != nil { + return fmt.Errorf("[xml] found bytes, but failed to unmarshal: %s %s", err.Error(), string(b)) + } + return nil +} + +// Encode attempts to xml.Marshal `in` into bytes. Encode will inspect `in` +// and returns `in` unmodified if it is detected that `in` is already a []byte; +// Or xml.Marshal errors. +func Encode(in interface{}) ([]byte, error) { + // TODO: wire in context. + _, r := observability.NewReporter(context.Background(), reportEncode) + b, err := obsEncode(in) + if err != nil { + r.Error() + } else { + r.OK() + } + return b, err +} + +func obsEncode(in interface{}) ([]byte, error) { + if b, ok := in.([]byte); ok { + // check to see if it is a pre-encoded byte string. + if len(b) > 0 && b[0] == byte('"') { + return b, nil + } + } + + return xml.Marshal(in) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/doc.go new file mode 100644 index 00000000000..d90b7c444da --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/doc.go @@ -0,0 +1,4 @@ +/* +Package xml holds the encoder/decoder implementation for `application/xml`. +*/ +package xml diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/observability.go new file mode 100644 index 00000000000..31b0bb26998 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml/observability.go @@ -0,0 +1,63 @@ +package xml + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the CloudEvents xml data + // codec methods. + LatencyMs = stats.Float64("cloudevents.io/sdk-go/datacodec/xml/latency", "The latency in milliseconds for the CloudEvents xml data codec methods.", "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows data codec xml method latency. + LatencyView = &view.View{ + Name: "datacodec/xml/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of the xml data codec for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportEncode observed = iota + reportDecode +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportEncode: + return "datacodec/xml/encode" + case reportDecode: + return "datacodec/xml/decode" + default: + return "datacodec/xml/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportEncode: + return "encode" + case reportDecode: + return "decode" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/doc.go new file mode 100644 index 00000000000..cc2201da915 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/doc.go @@ -0,0 +1,4 @@ +/* +Package cloudevents provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec. +*/ +package cloudevents diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event.go new file mode 100644 index 00000000000..0f81258d209 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event.go @@ -0,0 +1,97 @@ +package cloudevents + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// Event represents the canonical representation of a CloudEvent. +type Event struct { + Context EventContext + Data interface{} + DataEncoded bool +} + +const ( + defaultEventVersion = CloudEventsVersionV02 +) + +// New returns a new Event, an optional version can be passed to change the +// default spec version from 0.2 to the provided version. +func New(version ...string) Event { + specVersion := defaultEventVersion // TODO: should there be a default? or set a default? + if len(version) >= 1 { + specVersion = version[0] + } + e := &Event{} + e.SetSpecVersion(specVersion) + return *e +} + +// ExtensionAs returns Context.ExtensionAs(name, obj) +func (e Event) ExtensionAs(name string, obj interface{}) error { + return e.Context.ExtensionAs(name, obj) +} + +// Validate performs a spec based validation on this event. +// Validation is dependent on the spec version specified in the event context. +func (e Event) Validate() error { + if e.Context == nil { + return fmt.Errorf("every event conforming to the CloudEvents specification MUST include a context") + } + + if err := e.Context.Validate(); err != nil { + return err + } + + // TODO: validate data. + + return nil +} + +// String returns a pretty-printed representation of the Event. +func (e Event) String() string { + b := strings.Builder{} + + b.WriteString("Validation: ") + + valid := e.Validate() + if valid == nil { + b.WriteString("valid\n") + } else { + b.WriteString("invalid\n") + } + if valid != nil { + b.WriteString(fmt.Sprintf("Validation Error: \n%s\n", valid.Error())) + } + + b.WriteString(e.Context.String()) + + if e.Data != nil { + b.WriteString("Data,\n ") + if strings.HasPrefix(e.DataContentType(), "application/json") { + var prettyJSON bytes.Buffer + + data, ok := e.Data.([]byte) + if !ok { + var err error + data, err = json.Marshal(e.Data) + if err != nil { + data = []byte(err.Error()) + } + } + err := json.Indent(&prettyJSON, data, " ", " ") + if err != nil { + b.Write(e.Data.([]byte)) + } else { + b.Write(prettyJSON.Bytes()) + } + } else { + b.Write(e.Data.([]byte)) + } + b.WriteString("\n") + } + return b.String() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_data.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_data.go new file mode 100644 index 00000000000..d8929ccde15 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_data.go @@ -0,0 +1,96 @@ +package cloudevents + +import ( + "encoding/base64" + "errors" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec" + "strconv" +) + +// Data is special. Break it out into it's own file. + +// SetData implements EventWriter.SetData +func (e *Event) SetData(obj interface{}) error { + data, err := datacodec.Encode(e.DataMediaType(), obj) + if err != nil { + return err + } + if e.DataContentEncoding() == Base64 { + buf := make([]byte, base64.StdEncoding.EncodedLen(len(data))) + base64.StdEncoding.Encode(buf, data) + e.Data = string(buf) + } else { + e.Data = data + } + e.DataEncoded = true + return nil +} + +func (e *Event) DataBytes() ([]byte, error) { + if !e.DataEncoded { + if err := e.SetData(e.Data); err != nil { + return nil, err + } + } + + b, ok := e.Data.([]byte) + if !ok { + if s, ok := e.Data.(string); ok { + b = []byte(s) + } else { + return nil, errors.New("data was not a byte slice or string") + } + } + return b, nil +} + +const ( + quotes = `"'` +) + +// DataAs attempts to populate the provided data object with the event payload. +// data should be a pointer type. +func (e Event) DataAs(data interface{}) error { // TODO: Clean this function up + if e.Data == nil { + return nil + } + obj, ok := e.Data.([]byte) + if !ok { + if s, ok := e.Data.(string); ok { + obj = []byte(s) + } else { + return errors.New("data was not a byte slice or string") + } + } + if len(obj) == 0 { + // no data. + return nil + } + if e.Context.GetDataContentEncoding() == Base64 { + var bs []byte + // test to see if we need to unquote the data. + if obj[0] == quotes[0] || obj[0] == quotes[1] { + str, err := strconv.Unquote(string(obj)) + if err != nil { + return err + } + bs = []byte(str) + } else { + bs = obj + } + + buf := make([]byte, base64.StdEncoding.DecodedLen(len(bs))) + n, err := base64.StdEncoding.Decode(buf, bs) + if err != nil { + return fmt.Errorf("failed to decode data from base64: %s", err.Error()) + } + obj = buf[:n] + } + + mediaType, err := e.Context.GetDataMediaType() + if err != nil { + return err + } + return datacodec.Decode(mediaType, obj, data) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_interface.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_interface.go new file mode 100644 index 00000000000..da3890e9399 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_interface.go @@ -0,0 +1,72 @@ +package cloudevents + +import ( + "time" +) + +// EventWriter is the interface for reading through an event from attributes. +type EventReader interface { + // SpecVersion returns event.Context.GetSpecVersion(). + SpecVersion() string + // Type returns event.Context.GetType(). + Type() string + // Source returns event.Context.GetSource(). + Source() string + // Subject returns event.Context.GetSubject(). + Subject() string + // ID returns event.Context.GetID(). + ID() string + // Time returns event.Context.GetTime(). + Time() time.Time + // SchemaURL returns event.Context.GetSchemaURL(). + SchemaURL() string + // DataContentType returns event.Context.GetDataContentType(). + DataContentType() string + // DataMediaType returns event.Context.GetDataMediaType(). + DataMediaType() string + // DataContentEncoding returns event.Context.GetDataContentEncoding(). + DataContentEncoding() string + + // Extension Attributes + + // ExtensionAs returns event.Context.ExtensionAs(name, obj). + ExtensionAs(string, interface{}) error + + // Data Attribute + + // ExtensionAs returns event.Context.ExtensionAs(name, obj). + DataAs(interface{}) error +} + +// EventWriter is the interface for writing through an event onto attributes. +// If an error is thrown by a sub-component, EventWriter panics. +type EventWriter interface { + // Context Attributes + + // SetSpecVersion performs event.Context.SetSpecVersion. + SetSpecVersion(string) + // SetType performs event.Context.SetType. + SetType(string) + // SetSource performs event.Context.SetSource. + SetSource(string) + // SetSubject( performs event.Context.SetSubject. + SetSubject(string) + // SetID performs event.Context.SetID. + SetID(string) + // SetTime performs event.Context.SetTime. + SetTime(time.Time) + // SetSchemaURL performs event.Context.SetSchemaURL. + SetSchemaURL(string) + // SetDataContentType performs event.Context.SetDataContentType. + SetDataContentType(string) + // SetDataContentEncoding performs event.Context.SetDataContentEncoding. + SetDataContentEncoding(string) + + // Extension Attributes + + // SetExtension performs event.Context.SetExtension. + SetExtension(string, interface{}) + + // SetData encodes the given payload with the current encoding settings. + SetData(interface{}) error +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_reader.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_reader.go new file mode 100644 index 00000000000..73beb626304 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_reader.go @@ -0,0 +1,58 @@ +package cloudevents + +import ( + "time" +) + +var _ EventReader = (*Event)(nil) + +// SpecVersion implements EventReader.SpecVersion +func (e Event) SpecVersion() string { + return e.Context.GetSpecVersion() +} + +// Type implements EventReader.Type +func (e Event) Type() string { + return e.Context.GetType() +} + +// Source implements EventReader.Source +func (e Event) Source() string { + return e.Context.GetSource() +} + +// Subject implements EventReader.Subject +func (e Event) Subject() string { + return e.Context.GetSubject() +} + +// ID implements EventReader.ID +func (e Event) ID() string { + return e.Context.GetID() +} + +// Time implements EventReader.Time +func (e Event) Time() time.Time { + return e.Context.GetTime() +} + +// SchemaURL implements EventReader.SchemaURL +func (e Event) SchemaURL() string { + return e.Context.GetSchemaURL() +} + +// DataContentType implements EventReader.DataContentType +func (e Event) DataContentType() string { + return e.Context.GetDataContentType() +} + +// DataMediaType implements EventReader.DataMediaType +func (e Event) DataMediaType() string { + mediaType, _ := e.Context.GetDataMediaType() + return mediaType +} + +// DataContentEncoding implements EventReader.DataContentEncoding +func (e Event) DataContentEncoding() string { + return e.Context.GetDataContentEncoding() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_response.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_response.go new file mode 100644 index 00000000000..0e5f7ce75d4 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_response.go @@ -0,0 +1,37 @@ +package cloudevents + +// EventResponse represents the canonical representation of a Response to a +// CloudEvent from a receiver. Response implementation is Transport dependent. +type EventResponse struct { + Status int + Event *Event + Reason string + // Context is transport specific struct to allow for controlling transport + // response details. + // For example, see http.TransportResponseContext. + Context interface{} +} + +// RespondWith sets up the instance of EventResponse to be set with status and +// an event. Response implementation is Transport dependent. +func (e *EventResponse) RespondWith(status int, event *Event) { + if e == nil { + // if nil, response not supported + return + } + e.Status = status + if event != nil { + e.Event = event + } +} + +// Error sets the instance of EventResponse to be set with an error code and +// reason string. Response implementation is Transport dependent. +func (e *EventResponse) Error(status int, reason string) { + if e == nil { + // if nil, response not supported + return + } + e.Status = status + e.Reason = reason +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_writer.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_writer.go new file mode 100644 index 00000000000..ce5b3e87673 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/event_writer.go @@ -0,0 +1,92 @@ +package cloudevents + +import ( + "fmt" + "time" +) + +var _ EventWriter = (*Event)(nil) + +// SetSpecVersion implements EventWriter.SetSpecVersion +func (e *Event) SetSpecVersion(v string) { + if e.Context == nil { + switch v { + case CloudEventsVersionV01: + e.Context = EventContextV01{}.AsV01() + case CloudEventsVersionV02: + e.Context = EventContextV02{}.AsV02() + case CloudEventsVersionV03: + e.Context = EventContextV03{}.AsV03() + default: + panic(fmt.Errorf("a valid spec version is required: [%s, %s, %s]", + CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03)) + } + return + } + if err := e.Context.SetSpecVersion(v); err != nil { + panic(err) + } +} + +// SetType implements EventWriter.SetType +func (e *Event) SetType(t string) { + if err := e.Context.SetType(t); err != nil { + panic(err) + } +} + +// SetSource implements EventWriter.SetSource +func (e *Event) SetSource(s string) { + if err := e.Context.SetSource(s); err != nil { + panic(err) + } +} + +// SetSubject implements EventWriter.SetSubject +func (e *Event) SetSubject(s string) { + if err := e.Context.SetSubject(s); err != nil { + panic(err) + } +} + +// SetID implements EventWriter.SetID +func (e *Event) SetID(id string) { + if err := e.Context.SetID(id); err != nil { + panic(err) + } +} + +// SetTime implements EventWriter.SetTime +func (e *Event) SetTime(t time.Time) { + if err := e.Context.SetTime(t); err != nil { + panic(err) + } +} + +// SetSchemaURL implements EventWriter.SetSchemaURL +func (e *Event) SetSchemaURL(s string) { + if err := e.Context.SetSchemaURL(s); err != nil { + panic(err) + } +} + +// SetDataContentType implements EventWriter.SetDataContentType +func (e *Event) SetDataContentType(ct string) { + if err := e.Context.SetDataContentType(ct); err != nil { + panic(err) + } +} + +// SetDataContentEncoding implements EventWriter.SetDataContentEncoding +func (e *Event) SetDataContentEncoding(enc string) { + if err := e.Context.SetDataContentEncoding(enc); err != nil { + panic(err) + } +} + +// SetDataContentEncoding implements EventWriter.SetDataContentEncoding +func (e *Event) SetExtension(name string, obj interface{}) { + if err := e.Context.SetExtension(name, obj); err != nil { + panic(err) + } +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext.go new file mode 100644 index 00000000000..7354d25b1f3 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext.go @@ -0,0 +1,105 @@ +package cloudevents + +import "time" + +// EventContextReader are the methods required to be a reader of context +// attributes. +type EventContextReader interface { + // GetSpecVersion returns the native CloudEvents Spec version of the event + // context. + GetSpecVersion() string + // GetType returns the CloudEvents type from the context. + GetType() string + // GetSource returns the CloudEvents source from the context. + GetSource() string + // GetSubject returns the CloudEvents subject from the context. + GetSubject() string + // GetID returns the CloudEvents ID from the context. + GetID() string + // GetTime returns the CloudEvents creation time from the context. + GetTime() time.Time + // GetSchemaURL returns the CloudEvents schema URL (if any) from the + // context. + GetSchemaURL() string + // GetDataContentType returns content type on the context. + GetDataContentType() string + // GetDataContentEncoding returns content encoding on the context. + GetDataContentEncoding() string + + // GetDataMediaType returns the MIME media type for encoded data, which is + // needed by both encoding and decoding. This is a processed form of + // GetDataContentType and it may return an error. + GetDataMediaType() (string, error) + + // ExtensionAs populates the given interface with the CloudEvents extension + // of the given name from the extension attributes. It returns an error if + // the extension does not exist, the extension's type does not match the + // provided type, or if the type is not a supported. + ExtensionAs(string, interface{}) error +} + +// EventContextWriter are the methods required to be a writer of context +// attributes. +type EventContextWriter interface { + // SetSpecVersion sets the spec version of the context. + SetSpecVersion(string) error + // SetType sets the type of the context. + SetType(string) error + // SetSource sets the source of the context. + SetSource(string) error + // SetSubject sets the subject of the context. + SetSubject(string) error + // SetID sets the ID of the context. + SetID(string) error + // SetTime sets the time of the context. + SetTime(time time.Time) error + // SetSchemaURL sets the schema url of the context. + SetSchemaURL(string) error + // SetDataContentType sets the data content type of the context. + SetDataContentType(string) error + // SetDataContentEncoding sets the data context encoding of the context. + SetDataContentEncoding(string) error + + // SetExtension sets the given interface onto the extension attributes + // determined by the provided name. + SetExtension(string, interface{}) error +} + +type EventContextConverter interface { + // AsV01 provides a translation from whatever the "native" encoding of the + // CloudEvent was to the equivalent in v0.1 field names, moving fields to or + // from extensions as necessary. + AsV01() *EventContextV01 + + // AsV02 provides a translation from whatever the "native" encoding of the + // CloudEvent was to the equivalent in v0.2 field names, moving fields to or + // from extensions as necessary. + AsV02() *EventContextV02 + + // AsV03 provides a translation from whatever the "native" encoding of the + // CloudEvent was to the equivalent in v0.3 field names, moving fields to or + // from extensions as necessary. + AsV03() *EventContextV03 +} + +// EventContext is conical interface for a CloudEvents Context. +type EventContext interface { + // EventContextConverter allows for conversion between versions. + EventContextConverter + + // EventContextReader adds methods for reading context. + EventContextReader + + // EventContextWriter adds methods for writing to context. + EventContextWriter + + // Validate the event based on the specifics of the CloudEvents spec version + // represented by this event context. + Validate() error + + // Clone clones the event context. + Clone() EventContext + + // String returns a pretty-printed representation of the EventContext. + String() string +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01.go new file mode 100644 index 00000000000..b8f9ce570d5 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01.go @@ -0,0 +1,267 @@ +package cloudevents + +import ( + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "sort" + "strings" +) + +const ( + // CloudEventsVersionV01 represents the version 0.1 of the CloudEvents spec. + CloudEventsVersionV01 = "0.1" +) + +// EventContextV01 holds standard metadata about an event. See +// https://github.com/cloudevents/spec/blob/v0.1/spec.md#context-attributes for +// details on these fields. +type EventContextV01 struct { + // The version of the CloudEvents specification used by the event. + CloudEventsVersion string `json:"cloudEventsVersion,omitempty"` + // ID of the event; must be non-empty and unique within the scope of the producer. + EventID string `json:"eventID"` + // Timestamp when the event happened. + EventTime *types.Timestamp `json:"eventTime,omitempty"` + // Type of occurrence which has happened. + EventType string `json:"eventType"` + // The version of the `eventType`; this is producer-specific. + EventTypeVersion *string `json:"eventTypeVersion,omitempty"` + // A link to the schema that the `data` attribute adheres to. + SchemaURL *types.URLRef `json:"schemaURL,omitempty"` + // A MIME (RFC 2046) string describing the media type of `data`. + // TODO: Should an empty string assume `application/json`, or auto-detect the content? + ContentType *string `json:"contentType,omitempty"` + // A URI describing the event producer. + Source types.URLRef `json:"source"` + // Additional metadata without a well-defined structure. + Extensions map[string]interface{} `json:"extensions,omitempty"` +} + +// Adhere to EventContext +var _ EventContext = (*EventContextV01)(nil) + +// ExtensionAs implements EventContextReader.ExtensionAs +func (ec EventContextV01) ExtensionAs(name string, obj interface{}) error { + value, ok := ec.Extensions[name] + if !ok { + return fmt.Errorf("extension %q does not exist", name) + } + // Only support *string for now. + switch v := obj.(type) { + case *string: + if valueAsString, ok := value.(string); ok { + *v = valueAsString + return nil + } else { + return fmt.Errorf("invalid type for extension %q", name) + } + default: + return fmt.Errorf("unkown extension type %T", obj) + } +} + +// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context. +func (ec *EventContextV01) SetExtension(name string, value interface{}) error { + if ec.Extensions == nil { + ec.Extensions = make(map[string]interface{}) + } + if value == nil { + delete(ec.Extensions, name) + } else { + ec.Extensions[name] = value + } + return nil +} + +// Clone implements EventContextConverter.Clone +func (ec EventContextV01) Clone() EventContext { + return ec.AsV01() +} + +// AsV01 implements EventContextConverter.AsV01 +func (ec EventContextV01) AsV01() *EventContextV01 { + ec.CloudEventsVersion = CloudEventsVersionV01 + return &ec +} + +// AsV02 implements EventContextConverter.AsV02 +func (ec EventContextV01) AsV02() *EventContextV02 { + ret := EventContextV02{ + SpecVersion: CloudEventsVersionV02, + Type: ec.EventType, + Source: ec.Source, + ID: ec.EventID, + Time: ec.EventTime, + SchemaURL: ec.SchemaURL, + ContentType: ec.ContentType, + Extensions: make(map[string]interface{}), + } + + // eventTypeVersion was retired in v0.2, so put it in an extension. + if ec.EventTypeVersion != nil { + ret.SetExtension(EventTypeVersionKey, *ec.EventTypeVersion) + } + if ec.Extensions != nil { + for k, v := range ec.Extensions { + ret.Extensions[k] = v + } + } + if len(ret.Extensions) == 0 { + ret.Extensions = nil + } + return &ret +} + +// AsV03 implements EventContextConverter.AsV03 +func (ec EventContextV01) AsV03() *EventContextV03 { + ecv2 := ec.AsV02() + return ecv2.AsV03() +} + +// Validate returns errors based on requirements from the CloudEvents spec. +// For more details, see https://github.com/cloudevents/spec/blob/v0.1/spec.md +func (ec EventContextV01) Validate() error { + errors := []string(nil) + + // eventType + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type. + eventType := strings.TrimSpace(ec.EventType) + if eventType == "" { + errors = append(errors, "eventType: MUST be a non-empty string") + } + + // eventTypeVersion + // Type: String + // Constraints: + // OPTIONAL + // If present, MUST be a non-empty string + if ec.EventTypeVersion != nil { + eventTypeVersion := strings.TrimSpace(*ec.EventTypeVersion) + if eventTypeVersion == "" { + errors = append(errors, "eventTypeVersion: if present, MUST be a non-empty string") + } + } + + // cloudEventsVersion + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + cloudEventsVersion := strings.TrimSpace(ec.CloudEventsVersion) + if cloudEventsVersion == "" { + errors = append(errors, "cloudEventsVersion: MUST be a non-empty string") + } + + // source + // Type: URI + // Constraints: + // REQUIRED + source := strings.TrimSpace(ec.Source.String()) + if source == "" { + errors = append(errors, "source: REQUIRED") + } + + // eventID + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // MUST be unique within the scope of the producer + eventID := strings.TrimSpace(ec.EventID) + if eventID == "" { + errors = append(errors, "eventID: MUST be a non-empty string") + + // no way to test "MUST be unique within the scope of the producer" + } + + // eventTime + // Type: Timestamp + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3339 + // --> no need to test this, no way to set the eventTime without it being valid. + + // schemaURL + // Type: URI + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3986 + if ec.SchemaURL != nil { + schemaURL := strings.TrimSpace(ec.SchemaURL.String()) + // empty string is not RFC 3986 compatible. + if schemaURL == "" { + errors = append(errors, "schemaURL: if present, MUST adhere to the format specified in RFC 3986") + } + } + + // contentType + // Type: String per RFC 2046 + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 2046 + if ec.ContentType != nil { + contentType := strings.TrimSpace(*ec.ContentType) + if contentType == "" { + // TODO: need to test for RFC 2046 + errors = append(errors, "contentType: if present, MUST adhere to the format specified in RFC 2046") + } + } + + // extensions + // Type: Map + // Constraints: + // OPTIONAL + // If present, MUST contain at least one entry + if ec.Extensions != nil { + if len(ec.Extensions) == 0 { + errors = append(errors, "extensions: if present, MUST contain at least one entry") + } + } + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } + return nil +} + +// String returns a pretty-printed representation of the EventContext. +func (ec EventContextV01) String() string { + b := strings.Builder{} + + b.WriteString("Context Attributes,\n") + + b.WriteString(" cloudEventsVersion: " + ec.CloudEventsVersion + "\n") + b.WriteString(" eventType: " + ec.EventType + "\n") + if ec.EventTypeVersion != nil { + b.WriteString(" eventTypeVersion: " + *ec.EventTypeVersion + "\n") + } + b.WriteString(" source: " + ec.Source.String() + "\n") + b.WriteString(" eventID: " + ec.EventID + "\n") + if ec.EventTime != nil { + b.WriteString(" eventTime: " + ec.EventTime.String() + "\n") + } + if ec.SchemaURL != nil { + b.WriteString(" schemaURL: " + ec.SchemaURL.String() + "\n") + } + if ec.ContentType != nil { + b.WriteString(" contentType: " + *ec.ContentType + "\n") + } + + if ec.Extensions != nil && len(ec.Extensions) > 0 { + b.WriteString("Extensions,\n") + keys := make([]string, 0, len(ec.Extensions)) + for k := range ec.Extensions { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key])) + } + } + + return b.String() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go new file mode 100644 index 00000000000..f355c3677be --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go @@ -0,0 +1,86 @@ +package cloudevents + +import ( + "mime" + "time" +) + +// Adhere to EventContextReader +var _ EventContextReader = (*EventContextV01)(nil) + +// GetSpecVersion implements EventContextReader.GetSpecVersion +func (ec EventContextV01) GetSpecVersion() string { + if ec.CloudEventsVersion != "" { + return ec.CloudEventsVersion + } + return CloudEventsVersionV01 +} + +// GetDataContentType implements EventContextReader.GetDataContentType +func (ec EventContextV01) GetDataContentType() string { + if ec.ContentType != nil { + return *ec.ContentType + } + return "" +} + +// GetDataMediaType implements EventContextReader.GetDataMediaType +func (ec EventContextV01) GetDataMediaType() (string, error) { + if ec.ContentType != nil { + mediaType, _, err := mime.ParseMediaType(*ec.ContentType) + if err != nil { + return "", err + } + return mediaType, nil + } + return "", nil +} + +// GetType implements EventContextReader.GetType +func (ec EventContextV01) GetType() string { + return ec.EventType +} + +// GetSource implements EventContextReader.GetSource +func (ec EventContextV01) GetSource() string { + return ec.Source.String() +} + +// GetSubject implements EventContextReader.GetSubject +func (ec EventContextV01) GetSubject() string { + var sub string + if err := ec.ExtensionAs(SubjectKey, &sub); err != nil { + return "" + } + return sub +} + +// GetID implements EventContextReader.GetID +func (ec EventContextV01) GetID() string { + return ec.EventID +} + +// GetTime implements EventContextReader.GetTime +func (ec EventContextV01) GetTime() time.Time { + if ec.EventTime != nil { + return ec.EventTime.Time + } + return time.Time{} +} + +// GetSchemaURL implements EventContextReader.GetSchemaURL +func (ec EventContextV01) GetSchemaURL() string { + if ec.SchemaURL != nil { + return ec.SchemaURL.String() + } + return "" +} + +// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding +func (ec EventContextV01) GetDataContentEncoding() string { + var enc string + if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil { + return "" + } + return enc +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go new file mode 100644 index 00000000000..f3594815614 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go @@ -0,0 +1,103 @@ +package cloudevents + +import ( + "errors" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "net/url" + "strings" + "time" +) + +// Adhere to EventContextWriter +var _ EventContextWriter = (*EventContextV01)(nil) + +// SetSpecVersion implements EventContextWriter.SetSpecVersion +func (ec *EventContextV01) SetSpecVersion(v string) error { + if v != CloudEventsVersionV01 { + return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV01) + } + ec.CloudEventsVersion = CloudEventsVersionV01 + return nil +} + +// SetDataContentType implements EventContextWriter.SetDataContentType +func (ec *EventContextV01) SetDataContentType(ct string) error { + ct = strings.TrimSpace(ct) + if ct == "" { + ec.ContentType = nil + } else { + ec.ContentType = &ct + } + return nil +} + +// SetType implements EventContextWriter.SetType +func (ec *EventContextV01) SetType(t string) error { + t = strings.TrimSpace(t) + ec.EventType = t + return nil +} + +// SetSource implements EventContextWriter.SetSource +func (ec *EventContextV01) SetSource(u string) error { + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.Source = types.URLRef{URL: *pu} + return nil +} + +// SetSubject implements EventContextWriter.SetSubject +func (ec *EventContextV01) SetSubject(s string) error { + s = strings.TrimSpace(s) + if s == "" { + return ec.SetExtension(SubjectKey, nil) + } + return ec.SetExtension(SubjectKey, s) +} + +// SetID implements EventContextWriter.SetID +func (ec *EventContextV01) SetID(id string) error { + id = strings.TrimSpace(id) + if id == "" { + return errors.New("event id is required to be a non-empty string") + } + ec.EventID = id + return nil +} + +// SetTime implements EventContextWriter.SetTime +func (ec *EventContextV01) SetTime(t time.Time) error { + if t.IsZero() { + ec.EventTime = nil + } else { + ec.EventTime = &types.Timestamp{Time: t} + } + return nil +} + +// SetSchemaURL implements EventContextWriter.SetSchemaURL +func (ec *EventContextV01) SetSchemaURL(u string) error { + u = strings.TrimSpace(u) + if u == "" { + ec.SchemaURL = nil + return nil + } + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.SchemaURL = &types.URLRef{URL: *pu} + return nil +} + +// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding +func (ec *EventContextV01) SetDataContentEncoding(e string) error { + e = strings.ToLower(strings.TrimSpace(e)) + if e == "" { + return ec.SetExtension(DataContentEncodingKey, nil) + } + return ec.SetExtension(DataContentEncodingKey, e) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02.go new file mode 100644 index 00000000000..332dbff498c --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02.go @@ -0,0 +1,273 @@ +package cloudevents + +import ( + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "sort" + "strings" +) + +const ( + // CloudEventsVersionV02 represents the version 0.2 of the CloudEvents spec. + CloudEventsVersionV02 = "0.2" +) + +// EventContextV02 represents the non-data attributes of a CloudEvents v0.2 +// event. +type EventContextV02 struct { + // The version of the CloudEvents specification used by the event. + SpecVersion string `json:"specversion"` + // The type of the occurrence which has happened. + Type string `json:"type"` + // A URI describing the event producer. + Source types.URLRef `json:"source"` + // ID of the event; must be non-empty and unique within the scope of the producer. + ID string `json:"id"` + // Timestamp when the event happened. + Time *types.Timestamp `json:"time,omitempty"` + // A link to the schema that the `data` attribute adheres to. + SchemaURL *types.URLRef `json:"schemaurl,omitempty"` + // A MIME (RFC2046) string describing the media type of `data`. + // TODO: Should an empty string assume `application/json`, `application/octet-stream`, or auto-detect the content? + ContentType *string `json:"contenttype,omitempty"` + // Additional extension metadata beyond the base spec. + Extensions map[string]interface{} `json:"-,omitempty"` // TODO: decide how we want extensions to be inserted +} + +// Adhere to EventContext +var _ EventContext = (*EventContextV02)(nil) + +// ExtensionAs implements EventContext.ExtensionAs +func (ec EventContextV02) ExtensionAs(name string, obj interface{}) error { + value, ok := ec.Extensions[name] + if !ok { + return fmt.Errorf("extension %q does not exist", name) + } + // Only support *string for now. + switch v := obj.(type) { + case *string: + if valueAsString, ok := value.(string); ok { + *v = valueAsString + return nil + } else { + return fmt.Errorf("invalid type for extension %q", name) + } + default: + return fmt.Errorf("unkown extension type %T", obj) + } +} + +// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context. +func (ec *EventContextV02) SetExtension(name string, value interface{}) error { + if ec.Extensions == nil { + ec.Extensions = make(map[string]interface{}) + } + if value == nil { + delete(ec.Extensions, name) + } else { + ec.Extensions[name] = value + } + return nil +} + +// Clone implements EventContextConverter.Clone +func (ec EventContextV02) Clone() EventContext { + return ec.AsV02() +} + +// AsV01 implements EventContextConverter.AsV01 +func (ec EventContextV02) AsV01() *EventContextV01 { + ret := EventContextV01{ + CloudEventsVersion: CloudEventsVersionV01, + EventID: ec.ID, + EventTime: ec.Time, + EventType: ec.Type, + SchemaURL: ec.SchemaURL, + Source: ec.Source, + ContentType: ec.ContentType, + Extensions: make(map[string]interface{}), + } + + for k, v := range ec.Extensions { + // eventTypeVersion was retired in v0.2 + if strings.EqualFold(k, EventTypeVersionKey) { + etv, ok := v.(string) + if ok && etv != "" { + ret.EventTypeVersion = &etv + } + continue + } + ret.Extensions[k] = v + } + if len(ret.Extensions) == 0 { + ret.Extensions = nil + } + return &ret +} + +// AsV02 implements EventContextConverter.AsV02 +func (ec EventContextV02) AsV02() *EventContextV02 { + ec.SpecVersion = CloudEventsVersionV02 + return &ec +} + +// AsV03 implements EventContextConverter.AsV03 +func (ec EventContextV02) AsV03() *EventContextV03 { + ret := EventContextV03{ + SpecVersion: CloudEventsVersionV03, + ID: ec.ID, + Time: ec.Time, + Type: ec.Type, + SchemaURL: ec.SchemaURL, + DataContentType: ec.ContentType, + Source: ec.Source, + Extensions: make(map[string]interface{}), + } + + for k, v := range ec.Extensions { + // Subject was introduced in 0.3 + if strings.EqualFold(k, SubjectKey) { + sub, ok := v.(string) + if ok && sub != "" { + ret.Subject = &sub + } + continue + } + // DataContentEncoding was introduced in 0.3 + if strings.EqualFold(k, DataContentEncodingKey) { + etv, ok := v.(string) + if ok && etv != "" { + ret.DataContentEncoding = &etv + } + continue + } + ret.Extensions[k] = v + } + if len(ret.Extensions) == 0 { + ret.Extensions = nil + } + + return &ret +} + +// Validate returns errors based on requirements from the CloudEvents spec. +// For more details, see https://github.com/cloudevents/spec/blob/v0.2/spec.md +func (ec EventContextV02) Validate() error { + errors := []string(nil) + + // type + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type. + eventType := strings.TrimSpace(ec.Type) + if eventType == "" { + errors = append(errors, "type: MUST be a non-empty string") + } + + // specversion + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + specVersion := strings.TrimSpace(ec.SpecVersion) + if specVersion == "" { + errors = append(errors, "specversion: MUST be a non-empty string") + } + + // source + // Type: URI-reference + // Constraints: + // REQUIRED + source := strings.TrimSpace(ec.Source.String()) + if source == "" { + errors = append(errors, "source: REQUIRED") + } + + // id + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // MUST be unique within the scope of the producer + id := strings.TrimSpace(ec.ID) + if id == "" { + errors = append(errors, "id: MUST be a non-empty string") + + // no way to test "MUST be unique within the scope of the producer" + } + + // time + // Type: Timestamp + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3339 + // --> no need to test this, no way to set the time without it being valid. + + // schemaurl + // Type: URI + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3986 + if ec.SchemaURL != nil { + schemaURL := strings.TrimSpace(ec.SchemaURL.String()) + // empty string is not RFC 3986 compatible. + if schemaURL == "" { + errors = append(errors, "schemaurl: if present, MUST adhere to the format specified in RFC 3986") + } + } + + // contenttype + // Type: String per RFC 2046 + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 2046 + if ec.ContentType != nil { + contentType := strings.TrimSpace(*ec.ContentType) + if contentType == "" { + // TODO: need to test for RFC 2046 + errors = append(errors, "contenttype: if present, MUST adhere to the format specified in RFC 2046") + } + } + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } + return nil +} + +// String returns a pretty-printed representation of the EventContext. +func (ec EventContextV02) String() string { + b := strings.Builder{} + + b.WriteString("Context Attributes,\n") + + b.WriteString(" specversion: " + ec.SpecVersion + "\n") + b.WriteString(" type: " + ec.Type + "\n") + b.WriteString(" source: " + ec.Source.String() + "\n") + b.WriteString(" id: " + ec.ID + "\n") + if ec.Time != nil { + b.WriteString(" time: " + ec.Time.String() + "\n") + } + if ec.SchemaURL != nil { + b.WriteString(" schemaurl: " + ec.SchemaURL.String() + "\n") + } + if ec.ContentType != nil { + b.WriteString(" contenttype: " + *ec.ContentType + "\n") + } + + if ec.Extensions != nil && len(ec.Extensions) > 0 { + b.WriteString("Extensions,\n") + keys := make([]string, 0, len(ec.Extensions)) + for k := range ec.Extensions { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key])) + } + } + + return b.String() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go new file mode 100644 index 00000000000..72772eb5307 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go @@ -0,0 +1,86 @@ +package cloudevents + +import ( + "mime" + "time" +) + +// Adhere to EventContextReader +var _ EventContextReader = (*EventContextV02)(nil) + +// GetSpecVersion implements EventContextReader.GetSpecVersion +func (ec EventContextV02) GetSpecVersion() string { + if ec.SpecVersion != "" { + return ec.SpecVersion + } + return CloudEventsVersionV02 +} + +// GetType implements EventContextReader.GetType +func (ec EventContextV02) GetType() string { + return ec.Type +} + +// GetSource implements EventContextReader.GetSource +func (ec EventContextV02) GetSource() string { + return ec.Source.String() +} + +// GetSubject implements EventContextReader.GetSubject +func (ec EventContextV02) GetSubject() string { + var sub string + if err := ec.ExtensionAs(SubjectKey, &sub); err != nil { + return "" + } + return sub +} + +// GetID implements EventContextReader.GetID +func (ec EventContextV02) GetID() string { + return ec.ID +} + +// GetTime implements EventContextReader.GetTime +func (ec EventContextV02) GetTime() time.Time { + if ec.Time != nil { + return ec.Time.Time + } + return time.Time{} +} + +// GetSchemaURL implements EventContextReader.GetSchemaURL +func (ec EventContextV02) GetSchemaURL() string { + if ec.SchemaURL != nil { + return ec.SchemaURL.String() + } + return "" +} + +// GetDataContentType implements EventContextReader.GetDataContentType +func (ec EventContextV02) GetDataContentType() string { + if ec.ContentType != nil { + return *ec.ContentType + } + return "" +} + +// GetDataMediaType implements EventContextReader.GetDataMediaType +func (ec EventContextV02) GetDataMediaType() (string, error) { + if ec.ContentType != nil { + mediaType, _, err := mime.ParseMediaType(*ec.ContentType) + if err != nil { + return "", err + } + return mediaType, nil + } + return "", nil +} + +// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding +func (ec EventContextV02) GetDataContentEncoding() string { + var enc string + if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil { + return "" + } + return enc +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go new file mode 100644 index 00000000000..a67cff01e66 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go @@ -0,0 +1,103 @@ +package cloudevents + +import ( + "errors" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "net/url" + "strings" + "time" +) + +// Adhere to EventContextWriter +var _ EventContextWriter = (*EventContextV02)(nil) + +// SetSpecVersion implements EventContextWriter.SetSpecVersion +func (ec *EventContextV02) SetSpecVersion(v string) error { + if v != CloudEventsVersionV02 { + return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV02) + } + ec.SpecVersion = CloudEventsVersionV02 + return nil +} + +// SetDataContentType implements EventContextWriter.SetDataContentType +func (ec *EventContextV02) SetDataContentType(ct string) error { + ct = strings.TrimSpace(ct) + if ct == "" { + ec.ContentType = nil + } else { + ec.ContentType = &ct + } + return nil +} + +// SetType implements EventContextWriter.SetType +func (ec *EventContextV02) SetType(t string) error { + t = strings.TrimSpace(t) + ec.Type = t + return nil +} + +// SetSource implements EventContextWriter.SetSource +func (ec *EventContextV02) SetSource(u string) error { + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.Source = types.URLRef{URL: *pu} + return nil +} + +// SetSubject implements EventContextWriter.SetSubject +func (ec *EventContextV02) SetSubject(s string) error { + s = strings.TrimSpace(s) + if s == "" { + return ec.SetExtension(SubjectKey, nil) + } + return ec.SetExtension(SubjectKey, s) +} + +// SetID implements EventContextWriter.SetID +func (ec *EventContextV02) SetID(id string) error { + id = strings.TrimSpace(id) + if id == "" { + return errors.New("id is required to be a non-empty string") + } + ec.ID = id + return nil +} + +// SetTime implements EventContextWriter.SetTime +func (ec *EventContextV02) SetTime(t time.Time) error { + if t.IsZero() { + ec.Time = nil + } else { + ec.Time = &types.Timestamp{Time: t} + } + return nil +} + +// SetSchemaURL implements EventContextWriter.SetSchemaURL +func (ec *EventContextV02) SetSchemaURL(u string) error { + u = strings.TrimSpace(u) + if u == "" { + ec.SchemaURL = nil + return nil + } + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.SchemaURL = &types.URLRef{URL: *pu} + return nil +} + +// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding +func (ec *EventContextV02) SetDataContentEncoding(e string) error { + e = strings.ToLower(strings.TrimSpace(e)) + if e == "" { + return ec.SetExtension(DataContentEncodingKey, nil) + } + return ec.SetExtension(DataContentEncodingKey, e) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03.go new file mode 100644 index 00000000000..67af3a00e7a --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03.go @@ -0,0 +1,283 @@ +package cloudevents + +import ( + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "sort" + "strings" +) + +// WIP: AS OF FEB 19, 2019 + +const ( + // CloudEventsVersionV03 represents the version 0.3 of the CloudEvents spec. + CloudEventsVersionV03 = "0.3" +) + +// EventContextV03 represents the non-data attributes of a CloudEvents v0.3 +// event. +type EventContextV03 struct { + // SpecVersion - The version of the CloudEvents specification used by the event. + SpecVersion string `json:"specversion"` + // Type - The type of the occurrence which has happened. + Type string `json:"type"` + // Source - A URI describing the event producer. + Source types.URLRef `json:"source"` + // Subject - The subject of the event in the context of the event producer + // (identified by `source`). + Subject *string `json:"subject,omitempty"` + // ID of the event; must be non-empty and unique within the scope of the producer. + ID string `json:"id"` + // Time - A Timestamp when the event happened. + Time *types.Timestamp `json:"time,omitempty"` + // SchemaURL - A link to the schema that the `data` attribute adheres to. + SchemaURL *types.URLRef `json:"schemaurl,omitempty"` + // GetDataMediaType - A MIME (RFC2046) string describing the media type of `data`. + // TODO: Should an empty string assume `application/json`, `application/octet-stream`, or auto-detect the content? + DataContentType *string `json:"datacontenttype,omitempty"` + // DataContentEncoding describes the content encoding for the `data` attribute. Valid: nil, `Base64`. + DataContentEncoding *string `json:"datacontentencoding,omitempty"` + // Extensions - Additional extension metadata beyond the base spec. + Extensions map[string]interface{} `json:"-,omitempty"` // TODO: decide how we want extensions to be inserted +} + +// Adhere to EventContext +var _ EventContext = (*EventContextV03)(nil) + +// ExtensionAs implements EventContext.ExtensionAs +func (ec EventContextV03) ExtensionAs(name string, obj interface{}) error { + value, ok := ec.Extensions[name] + if !ok { + return fmt.Errorf("extension %q does not exist", name) + } + // Only support *string for now. + switch v := obj.(type) { + case *string: + if valueAsString, ok := value.(string); ok { + *v = valueAsString + return nil + } else { + return fmt.Errorf("invalid type for extension %q", name) + } + default: + return fmt.Errorf("unkown extension type %T", obj) + } +} + +// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context. +func (ec *EventContextV03) SetExtension(name string, value interface{}) error { + if ec.Extensions == nil { + ec.Extensions = make(map[string]interface{}) + } + if value == nil { + delete(ec.Extensions, name) + } else { + ec.Extensions[name] = value + } + return nil +} + +// Clone implements EventContextConverter.Clone +func (ec EventContextV03) Clone() EventContext { + return ec.AsV03() +} + +// AsV01 implements EventContextConverter.AsV01 +func (ec EventContextV03) AsV01() *EventContextV01 { + ecv2 := ec.AsV02() + return ecv2.AsV01() +} + +// AsV02 implements EventContextConverter.AsV02 +func (ec EventContextV03) AsV02() *EventContextV02 { + ret := EventContextV02{ + SpecVersion: CloudEventsVersionV02, + ID: ec.ID, + Time: ec.Time, + Type: ec.Type, + SchemaURL: ec.SchemaURL, + ContentType: ec.DataContentType, + Source: ec.Source, + Extensions: make(map[string]interface{}), + } + // Subject was introduced in 0.3, so put it in an extension for 0.2. + if ec.Subject != nil { + ret.SetExtension(SubjectKey, *ec.Subject) + } + // DataContentEncoding was introduced in 0.3, so put it in an extension for 0.2. + if ec.DataContentEncoding != nil { + ret.SetExtension(DataContentEncodingKey, *ec.DataContentEncoding) + } + if ec.Extensions != nil { + for k, v := range ec.Extensions { + ret.Extensions[k] = v + } + } + if len(ret.Extensions) == 0 { + ret.Extensions = nil + } + return &ret +} + +// AsV03 implements EventContextConverter.AsV03 +func (ec EventContextV03) AsV03() *EventContextV03 { + ec.SpecVersion = CloudEventsVersionV03 + return &ec +} + +// Validate returns errors based on requirements from the CloudEvents spec. +// For more details, see https://github.com/cloudevents/spec/blob/master/spec.md +// As of Feb 26, 2019, commit 17c32ea26baf7714ad027d9917d03d2fff79fc7e +// + https://github.com/cloudevents/spec/pull/387 -> datacontentencoding +// + https://github.com/cloudevents/spec/pull/406 -> subject +func (ec EventContextV03) Validate() error { + errors := []string(nil) + + // type + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type. + eventType := strings.TrimSpace(ec.Type) + if eventType == "" { + errors = append(errors, "type: MUST be a non-empty string") + } + + // specversion + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + specVersion := strings.TrimSpace(ec.SpecVersion) + if specVersion == "" { + errors = append(errors, "specversion: MUST be a non-empty string") + } + + // source + // Type: URI-reference + // Constraints: + // REQUIRED + source := strings.TrimSpace(ec.Source.String()) + if source == "" { + errors = append(errors, "source: REQUIRED") + } + + // subject + // Type: String + // Constraints: + // OPTIONAL + // MUST be a non-empty string + if ec.Subject != nil { + subject := strings.TrimSpace(*ec.Subject) + if subject == "" { + errors = append(errors, "subject: if present, MUST be a non-empty string") + } + } + + // id + // Type: String + // Constraints: + // REQUIRED + // MUST be a non-empty string + // MUST be unique within the scope of the producer + id := strings.TrimSpace(ec.ID) + if id == "" { + errors = append(errors, "id: MUST be a non-empty string") + + // no way to test "MUST be unique within the scope of the producer" + } + + // time + // Type: Timestamp + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3339 + // --> no need to test this, no way to set the time without it being valid. + + // schemaurl + // Type: URI + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 3986 + if ec.SchemaURL != nil { + schemaURL := strings.TrimSpace(ec.SchemaURL.String()) + // empty string is not RFC 3986 compatible. + if schemaURL == "" { + errors = append(errors, "schemaurl: if present, MUST adhere to the format specified in RFC 3986") + } + } + + // datacontenttype + // Type: String per RFC 2046 + // Constraints: + // OPTIONAL + // If present, MUST adhere to the format specified in RFC 2046 + if ec.DataContentType != nil { + dataContentType := strings.TrimSpace(*ec.DataContentType) + if dataContentType == "" { + // TODO: need to test for RFC 2046 + errors = append(errors, "datacontenttype: if present, MUST adhere to the format specified in RFC 2046") + } + } + + // datacontentencoding + // Type: String per RFC 2045 Section 6.1 + // Constraints: + // The attribute MUST be set if the data attribute contains string-encoded binary data. + // Otherwise the attribute MUST NOT be set. + // If present, MUST adhere to RFC 2045 Section 6.1 + if ec.DataContentEncoding != nil { + dataContentEncoding := strings.ToLower(strings.TrimSpace(*ec.DataContentEncoding)) + if dataContentEncoding != Base64 { + // TODO: need to test for RFC 2046 + errors = append(errors, "datacontentencoding: if present, MUST adhere to RFC 2045 Section 6.1") + } + } + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } + return nil +} + +// String returns a pretty-printed representation of the EventContext. +func (ec EventContextV03) String() string { + b := strings.Builder{} + + b.WriteString("Context Attributes,\n") + + b.WriteString(" specversion: " + ec.SpecVersion + "\n") + b.WriteString(" type: " + ec.Type + "\n") + b.WriteString(" source: " + ec.Source.String() + "\n") + if ec.Subject != nil { + b.WriteString(" subject: " + *ec.Subject + "\n") + } + b.WriteString(" id: " + ec.ID + "\n") + if ec.Time != nil { + b.WriteString(" time: " + ec.Time.String() + "\n") + } + if ec.SchemaURL != nil { + b.WriteString(" schemaurl: " + ec.SchemaURL.String() + "\n") + } + if ec.DataContentType != nil { + b.WriteString(" datacontenttype: " + *ec.DataContentType + "\n") + } + if ec.DataContentEncoding != nil { + b.WriteString(" datacontentencoding: " + *ec.DataContentEncoding + "\n") + } + + if ec.Extensions != nil && len(ec.Extensions) > 0 { + b.WriteString("Extensions,\n") + keys := make([]string, 0, len(ec.Extensions)) + for k := range ec.Extensions { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key])) + } + } + + return b.String() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go new file mode 100644 index 00000000000..0aa8d74a384 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go @@ -0,0 +1,81 @@ +package cloudevents + +import ( + "mime" + "time" +) + +// GetSpecVersion implements EventContextReader.GetSpecVersion +func (ec EventContextV03) GetSpecVersion() string { + if ec.SpecVersion != "" { + return ec.SpecVersion + } + return CloudEventsVersionV03 +} + +// GetDataContentType implements EventContextReader.GetDataContentType +func (ec EventContextV03) GetDataContentType() string { + if ec.DataContentType != nil { + return *ec.DataContentType + } + return "" +} + +// GetDataMediaType implements EventContextReader.GetDataMediaType +func (ec EventContextV03) GetDataMediaType() (string, error) { + if ec.DataContentType != nil { + mediaType, _, err := mime.ParseMediaType(*ec.DataContentType) + if err != nil { + return "", err + } + return mediaType, nil + } + return "", nil +} + +// GetType implements EventContextReader.GetType +func (ec EventContextV03) GetType() string { + return ec.Type +} + +// GetSource implements EventContextReader.GetSource +func (ec EventContextV03) GetSource() string { + return ec.Source.String() +} + +// GetSubject implements EventContextReader.GetSubject +func (ec EventContextV03) GetSubject() string { + if ec.Subject != nil { + return *ec.Subject + } + return "" +} + +// GetTime implements EventContextReader.GetTime +func (ec EventContextV03) GetTime() time.Time { + if ec.Time != nil { + return ec.Time.Time + } + return time.Time{} +} + +// GetID implements EventContextReader.GetID +func (ec EventContextV03) GetID() string { + return ec.ID +} + +// GetSchemaURL implements EventContextReader.GetSchemaURL +func (ec EventContextV03) GetSchemaURL() string { + if ec.SchemaURL != nil { + return ec.SchemaURL.String() + } + return "" +} + +// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding +func (ec EventContextV03) GetDataContentEncoding() string { + if ec.DataContentEncoding != nil { + return *ec.DataContentEncoding + } + return "" +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go new file mode 100644 index 00000000000..16a06803043 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go @@ -0,0 +1,107 @@ +package cloudevents + +import ( + "errors" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "net/url" + "strings" + "time" +) + +// Adhere to EventContextWriter +var _ EventContextWriter = (*EventContextV03)(nil) + +// SetSpecVersion implements EventContextWriter.SetSpecVersion +func (ec *EventContextV03) SetSpecVersion(v string) error { + if v != CloudEventsVersionV03 { + return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV03) + } + ec.SpecVersion = CloudEventsVersionV03 + return nil +} + +// SetDataContentType implements EventContextWriter.SetDataContentType +func (ec *EventContextV03) SetDataContentType(ct string) error { + ct = strings.TrimSpace(ct) + if ct == "" { + ec.DataContentType = nil + } else { + ec.DataContentType = &ct + } + return nil +} + +// SetType implements EventContextWriter.SetType +func (ec *EventContextV03) SetType(t string) error { + t = strings.TrimSpace(t) + ec.Type = t + return nil +} + +// SetSource implements EventContextWriter.SetSource +func (ec *EventContextV03) SetSource(u string) error { + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.Source = types.URLRef{URL: *pu} + return nil +} + +// SetSubject implements EventContextWriter.SetSubject +func (ec *EventContextV03) SetSubject(s string) error { + s = strings.TrimSpace(s) + if s == "" { + ec.Subject = nil + } else { + ec.Subject = &s + } + return nil +} + +// SetID implements EventContextWriter.SetID +func (ec *EventContextV03) SetID(id string) error { + id = strings.TrimSpace(id) + if id == "" { + return errors.New("id is required to be a non-empty string") + } + ec.ID = id + return nil +} + +// SetTime implements EventContextWriter.SetTime +func (ec *EventContextV03) SetTime(t time.Time) error { + if t.IsZero() { + ec.Time = nil + } else { + ec.Time = &types.Timestamp{Time: t} + } + return nil +} + +// SetSchemaURL implements EventContextWriter.SetSchemaURL +func (ec *EventContextV03) SetSchemaURL(u string) error { + u = strings.TrimSpace(u) + if u == "" { + ec.SchemaURL = nil + return nil + } + pu, err := url.Parse(u) + if err != nil { + return err + } + ec.SchemaURL = &types.URLRef{URL: *pu} + return nil +} + +// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding +func (ec *EventContextV03) SetDataContentEncoding(e string) error { + e = strings.ToLower(strings.TrimSpace(e)) + if e == "" { + ec.DataContentEncoding = nil + } else { + ec.DataContentEncoding = &e + } + return nil +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/extensions.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/extensions.go new file mode 100644 index 00000000000..e33205fc8b5 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/extensions.go @@ -0,0 +1,13 @@ +package cloudevents + +const ( + // DataContentEncodingKey is the key to DataContentEncoding for versions that do not support data content encoding + // directly. + DataContentEncodingKey = "datacontentencoding" + + // EventTypeVersionKey is the key to EventTypeVersion for versions that do not support event type version directly. + EventTypeVersionKey = "eventTypeVersion" + + // SubjectKey is the key to Subject for versions that do not support subject directly. + SubjectKey = "subject" +) diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/doc.go new file mode 100644 index 00000000000..3067ebe7e5d --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/doc.go @@ -0,0 +1,4 @@ +/* +Package observability holds metrics and tracing recording implementations. +*/ +package observability diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/keys.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/keys.go new file mode 100644 index 00000000000..f032b10ecf7 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/keys.go @@ -0,0 +1,19 @@ +package observability + +import ( + "go.opencensus.io/tag" +) + +var ( + // KeyMethod is the tag used for marking method on a metric. + KeyMethod, _ = tag.NewKey("method") + // KeyResult is the tag used for marking result on a metric. + KeyResult, _ = tag.NewKey("result") +) + +const ( + // ResultError is a shared result tag value for error. + ResultError = "error" + // ResultOK is a shared result tag value for success. + ResultOK = "success" +) diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/observer.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/observer.go new file mode 100644 index 00000000000..a7936f4b987 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/observability/observer.go @@ -0,0 +1,92 @@ +package observability + +import ( + "context" + "sync" + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.opencensus.io/trace" +) + +// Observable represents the the customization used by the Reporter for a given +// measurement and trace for a single method. +type Observable interface { + TraceName() string + MethodName() string + LatencyMs() *stats.Float64Measure +} + +// Reporter represents a running latency counter and trace span. When Error or +// OK are called, the latency is calculated and the trace space is ended. Error +// or OK are only allowed to be called once. +type Reporter interface { + Error() + OK() +} + +type reporter struct { + ctx context.Context + span *trace.Span + on Observable + start time.Time + measure stats.Measure + once sync.Once +} + +// All tags used for Latency measurements. +func LatencyTags() []tag.Key { + return []tag.Key{KeyMethod, KeyResult} +} + +// NewReporter creates and returns a reporter wrapping the provided Observable, +// and injects a trace span into the context. +func NewReporter(ctx context.Context, on Observable) (context.Context, Reporter) { + ctx, span := trace.StartSpan(ctx, on.TraceName()) + r := &reporter{ + ctx: ctx, + on: on, + span: span, + start: time.Now(), + } + r.tagMethod() + return ctx, r +} + +func (r *reporter) tagMethod() { + var err error + r.ctx, err = tag.New(r.ctx, tag.Insert(KeyMethod, r.on.MethodName())) + if err != nil { + panic(err) // or ignore? + } +} + +func (r *reporter) record() { + ms := float64(time.Since(r.start) / time.Millisecond) + stats.Record(r.ctx, r.on.LatencyMs().M(ms)) + r.span.End() +} + +// Error records the result as an error. +func (r *reporter) Error() { + r.once.Do(func() { + r.result(ResultError) + }) +} + +// OK records the result as a success. +func (r *reporter) OK() { + r.once.Do(func() { + r.result(ResultOK) + }) +} + +func (r *reporter) result(v string) { + var err error + r.ctx, err = tag.New(r.ctx, tag.Insert(KeyResult, v)) + if err != nil { + panic(err) // or ignore? + } + r.record() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/codec.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/codec.go new file mode 100644 index 00000000000..6564dc9e502 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/codec.go @@ -0,0 +1,10 @@ +package transport + +import "github.com/cloudevents/sdk-go/pkg/cloudevents" + +// Codec is the interface for transport codecs to convert between transport +// specific payloads and the Message interface. +type Codec interface { + Encode(cloudevents.Event) (Message, error) + Decode(Message) (*cloudevents.Event, error) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/doc.go new file mode 100644 index 00000000000..b93cd60a8cf --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/doc.go @@ -0,0 +1,5 @@ +/* +Package transport is the toplevel package to define interfaces that the client and codec packages use to decouple from +the transport implementations. +*/ +package transport diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec.go new file mode 100644 index 00000000000..4ef207ccb05 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec.go @@ -0,0 +1,226 @@ +package http + +import ( + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" +) + +// Codec is the wrapper for all versions of codecs supported by the http +// transport. +type Codec struct { + // Encoding is the setting to inform the DefaultEncodingSelectionFn for + // selecting a codec. + Encoding Encoding + + // DefaultEncodingSelectionFn allows for encoding selection strategies to be injected. + DefaultEncodingSelectionFn EncodingSelector + + v01 *CodecV01 + v02 *CodecV02 + v03 *CodecV03 +} + +// Adheres to Codec +var _ transport.Codec = (*Codec)(nil) + +// DefaultBinaryEncodingSelectionStrategy implements a selection process for +// which binary encoding to use based on spec version of the event. +func DefaultBinaryEncodingSelectionStrategy(e cloudevents.Event) Encoding { + switch e.SpecVersion() { + case cloudevents.CloudEventsVersionV01: + return BinaryV01 + case cloudevents.CloudEventsVersionV02: + return BinaryV02 + case cloudevents.CloudEventsVersionV03: + return BinaryV03 + } + // Unknown version, return Default. + return Default +} + +// DefaultStructuredEncodingSelectionStrategy implements a selection process +// for which structured encoding to use based on spec version of the event. +func DefaultStructuredEncodingSelectionStrategy(e cloudevents.Event) Encoding { + switch e.SpecVersion() { + case cloudevents.CloudEventsVersionV01: + return StructuredV01 + case cloudevents.CloudEventsVersionV02: + return StructuredV02 + case cloudevents.CloudEventsVersionV03: + return StructuredV03 + } + // Unknown version, return Default. + return Default +} + +// Encode encodes the provided event into a transport message. +func (c *Codec) Encode(e cloudevents.Event) (transport.Message, error) { + encoding := c.Encoding + + if encoding == Default && c.DefaultEncodingSelectionFn != nil { + encoding = c.DefaultEncodingSelectionFn(e) + } + + switch encoding { + case Default: + fallthrough + case BinaryV01: + fallthrough + case StructuredV01: + if c.v01 == nil { + c.v01 = &CodecV01{Encoding: encoding} + } + return c.v01.Encode(e) + case BinaryV02: + fallthrough + case StructuredV02: + if c.v02 == nil { + c.v02 = &CodecV02{Encoding: encoding} + } + return c.v02.Encode(e) + case BinaryV03: + fallthrough + case StructuredV03: + if c.v03 == nil { + c.v03 = &CodecV03{Encoding: encoding} + } + return c.v03.Encode(e) + default: + return nil, fmt.Errorf("unknown encoding: %s", encoding) + } +} + +// Decode converts a provided transport message into an Event, or error. +func (c *Codec) Decode(msg transport.Message) (*cloudevents.Event, error) { + switch c.inspectEncoding(msg) { + case BinaryV01: + fallthrough + case StructuredV01: + if c.v01 == nil { + c.v01 = &CodecV01{Encoding: c.Encoding} + } + if event, err := c.v01.Decode(msg); err != nil { + return nil, err + } else { + return c.convertEvent(event), nil + } + case BinaryV02: + fallthrough + case StructuredV02: + if c.v02 == nil { + c.v02 = &CodecV02{Encoding: c.Encoding} + } + if event, err := c.v02.Decode(msg); err != nil { + return nil, err + } else { + return c.convertEvent(event), nil + } + case BinaryV03: + fallthrough + case StructuredV03: + fallthrough + case BatchedV03: + if c.v03 == nil { + c.v03 = &CodecV03{Encoding: c.Encoding} + } + if event, err := c.v03.Decode(msg); err != nil { + return nil, err + } else { + return c.convertEvent(event), nil + } + default: + return nil, fmt.Errorf("unknown encoding") + } +} + +// Give the context back as the user expects +func (c *Codec) convertEvent(event *cloudevents.Event) *cloudevents.Event { + if event == nil { + return nil + } + switch c.Encoding { + case Default: + return event + case BinaryV01: + fallthrough + case StructuredV01: + if c.v01 == nil { + c.v01 = &CodecV01{Encoding: c.Encoding} + } + ctx := event.Context.AsV01() + event.Context = ctx + return event + case BinaryV02: + fallthrough + case StructuredV02: + if c.v02 == nil { + c.v02 = &CodecV02{Encoding: c.Encoding} + } + ctx := event.Context.AsV02() + event.Context = ctx + return event + case BinaryV03: + fallthrough + case StructuredV03: + fallthrough + case BatchedV03: + if c.v03 == nil { + c.v03 = &CodecV03{Encoding: c.Encoding} + } + ctx := event.Context.AsV03() + event.Context = ctx + return event + default: + return nil + } +} + +func (c *Codec) inspectEncoding(msg transport.Message) Encoding { + // TODO: there should be a better way to make the version codecs on demand. + if c.v01 == nil { + c.v01 = &CodecV01{Encoding: c.Encoding} + } + // Try v0.1 first. + encoding := c.v01.inspectEncoding(msg) + if encoding != Unknown { + return encoding + } + + if c.v02 == nil { + c.v02 = &CodecV02{Encoding: c.Encoding} + } + // Try v0.2. + encoding = c.v02.inspectEncoding(msg) + if encoding != Unknown { + return encoding + } + + if c.v03 == nil { + c.v03 = &CodecV03{Encoding: c.Encoding} + } + // Try v0.3. + encoding = c.v03.inspectEncoding(msg) + if encoding != Unknown { + return encoding + } + + // We do not understand the message encoding. + return Unknown +} + +// --------- +// TODO: Should move these somewhere else. the methods are shared for all versions. + +func marshalEventData(encoding string, data interface{}) ([]byte, error) { + if data == nil { + return []byte(nil), nil + } + // already encoded? + if b, ok := data.([]byte); ok { + return b, nil + } + + return datacodec.Encode(encoding, data) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go new file mode 100644 index 00000000000..93eb3539c9d --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go @@ -0,0 +1,244 @@ +package http + +import ( + "context" + "encoding/json" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/codec" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "net/http" + "net/textproto" + "strings" +) + +// CodecV01 represents a http transport codec that uses CloudEvents spec v0.3 +type CodecV01 struct { + Encoding Encoding +} + +// Adheres to Codec +var _ transport.Codec = (*CodecV01)(nil) + +// Encode implements Codec.Encode +func (v CodecV01) Encode(e cloudevents.Event) (transport.Message, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportEncode, c: v.Encoding.Codec()}) + m, err := v.obsEncode(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return m, err +} + +func (v CodecV01) obsEncode(e cloudevents.Event) (transport.Message, error) { + switch v.Encoding { + case Default: + fallthrough + case BinaryV01: + return v.encodeBinary(e) + case StructuredV01: + return v.encodeStructured(e) + default: + return nil, fmt.Errorf("unknown encoding: %d", v.Encoding) + } +} + +// Decode implements Codec.Decode +func (v CodecV01) Decode(msg transport.Message) (*cloudevents.Event, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportDecode, c: v.inspectEncoding(msg).Codec()}) // TODO: inspectEncoding is not free. + e, err := v.obsDecode(msg) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func (v CodecV01) obsDecode(msg transport.Message) (*cloudevents.Event, error) { + switch v.inspectEncoding(msg) { + case BinaryV01: + return v.decodeBinary(msg) + case StructuredV01: + return v.decodeStructured(msg) + default: + return nil, fmt.Errorf("unknown encoding") + } +} + +func (v CodecV01) encodeBinary(e cloudevents.Event) (transport.Message, error) { + header, err := v.toHeaders(e.Context.AsV01()) + if err != nil { + return nil, err + } + + body, err := e.DataBytes() + if err != nil { + panic("encode") + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV01) toHeaders(ec *cloudevents.EventContextV01) (http.Header, error) { + // Preserve case in v0.1, even though HTTP headers are case-insensitive. + h := http.Header{} + h["CE-CloudEventsVersion"] = []string{ec.CloudEventsVersion} + h["CE-EventID"] = []string{ec.EventID} + h["CE-EventType"] = []string{ec.EventType} + h["CE-Source"] = []string{ec.Source.String()} + if ec.EventTime != nil && !ec.EventTime.IsZero() { + h["CE-EventTime"] = []string{ec.EventTime.String()} + } + if ec.EventTypeVersion != nil { + h["CE-EventTypeVersion"] = []string{*ec.EventTypeVersion} + } + if ec.SchemaURL != nil { + h["CE-SchemaURL"] = []string{ec.SchemaURL.String()} + } + if ec.ContentType != nil { + h.Set("Content-Type", *ec.ContentType) + } else if v.Encoding == Default || v.Encoding == BinaryV01 { + // in binary v0.1, the Content-Type header is tied to ec.ContentType + // This was later found to be an issue with the spec, but yolo. + // TODO: not sure what the default should be? + h.Set("Content-Type", cloudevents.ApplicationJSON) + } + + // Regarding Extensions, v0.1 Spec says the following: + // * Each map entry name MUST be prefixed with "CE-X-" + // * Each map entry name's first character MUST be capitalized + for k, v := range ec.Extensions { + encoded, err := json.Marshal(v) + if err != nil { + return nil, err + } + h["CE-X-"+strings.Title(k)] = []string{string(encoded)} + } + return h, nil +} + +func (v CodecV01) encodeStructured(e cloudevents.Event) (transport.Message, error) { + header := http.Header{} + header.Set("Content-Type", cloudevents.ApplicationCloudEventsJSON) + + body, err := codec.JsonEncodeV01(e) + if err != nil { + return nil, err + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV01) decodeBinary(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + ctx, err := v.fromHeaders(m.Header) + if err != nil { + return nil, err + } + var body interface{} + if len(m.Body) > 0 { + body = m.Body + } + return &cloudevents.Event{ + Context: &ctx, + Data: body, + DataEncoded: true, + }, nil +} + +func (v CodecV01) fromHeaders(h http.Header) (cloudevents.EventContextV01, error) { + // Normalize headers. + for k, v := range h { + ck := textproto.CanonicalMIMEHeaderKey(k) + if k != ck { + h[ck] = v + } + } + + ec := cloudevents.EventContextV01{} + ec.CloudEventsVersion = h.Get("CE-CloudEventsVersion") + h.Del("CE-CloudEventsVersion") + ec.EventID = h.Get("CE-EventID") + h.Del("CE-EventID") + ec.EventType = h.Get("CE-EventType") + h.Del("CE-EventType") + source := types.ParseURLRef(h.Get("CE-Source")) + h.Del("CE-Source") + if source != nil { + ec.Source = *source + } + ec.EventTime = types.ParseTimestamp(h.Get("CE-EventTime")) + h.Del("CE-EventTime") + etv := h.Get("CE-EventTypeVersion") + h.Del("CE-EventTypeVersion") + if etv != "" { + ec.EventTypeVersion = &etv + } + ec.SchemaURL = types.ParseURLRef(h.Get("CE-SchemaURL")) + h.Del("CE-SchemaURL") + et := h.Get("Content-Type") + ec.ContentType = &et + + extensions := make(map[string]interface{}) + for k, v := range h { + if len(k) > len("CE-X-") && strings.EqualFold(k[:len("CE-X-")], "CE-X-") { + key := k[len("CE-X-"):] + var tmp interface{} + if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil { + extensions[key] = tmp + } else { + // If we can't unmarshal the data, treat it as a string. + extensions[key] = v[0] + } + h.Del(k) + } + } + if len(extensions) > 0 { + ec.Extensions = extensions + } + return ec, nil +} + +func (v CodecV01) decodeStructured(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + return codec.JsonDecodeV01(m.Body) +} + +func (v CodecV01) inspectEncoding(msg transport.Message) Encoding { + version := msg.CloudEventsVersion() + if version != cloudevents.CloudEventsVersionV01 { + return Unknown + } + m, ok := msg.(*Message) + if !ok { + return Unknown + } + contentType := m.Header.Get("Content-Type") + if contentType == cloudevents.ApplicationCloudEventsJSON { + return StructuredV01 + } + return BinaryV01 +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go new file mode 100644 index 00000000000..1e1d9996d9b --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go @@ -0,0 +1,295 @@ +package http + +import ( + "context" + "encoding/json" + "fmt" + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/codec" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" + "net/http" + "net/textproto" + "strings" +) + +// CodecV02 represents a http transport codec that uses CloudEvents spec v0.2 +type CodecV02 struct { + Encoding Encoding +} + +// Adheres to Codec +var _ transport.Codec = (*CodecV02)(nil) + +// Encode implements Codec.Encode +func (v CodecV02) Encode(e cloudevents.Event) (transport.Message, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportEncode, c: v.Encoding.Codec()}) + m, err := v.obsEncode(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return m, err +} + +func (v CodecV02) obsEncode(e cloudevents.Event) (transport.Message, error) { + switch v.Encoding { + case Default: + fallthrough + case BinaryV02: + return v.encodeBinary(e) + case StructuredV02: + return v.encodeStructured(e) + default: + return nil, fmt.Errorf("unknown encoding: %d", v.Encoding) + } +} + +// Decode implements Codec.Decode +func (v CodecV02) Decode(msg transport.Message) (*cloudevents.Event, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportDecode, c: v.inspectEncoding(msg).Codec()}) // TODO: inspectEncoding is not free. + e, err := v.obsDecode(msg) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func (v CodecV02) obsDecode(msg transport.Message) (*cloudevents.Event, error) { + switch v.inspectEncoding(msg) { + case BinaryV02: + return v.decodeBinary(msg) + case StructuredV02: + return v.decodeStructured(msg) + default: + return nil, fmt.Errorf("unknown encoding") + } +} + +func (v CodecV02) encodeBinary(e cloudevents.Event) (transport.Message, error) { + header, err := v.toHeaders(e.Context.AsV02()) + if err != nil { + return nil, err + } + body, err := e.DataBytes() + if err != nil { + return nil, err + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV02) toHeaders(ec *cloudevents.EventContextV02) (http.Header, error) { + h := http.Header{} + h.Set("ce-specversion", ec.SpecVersion) + h.Set("ce-type", ec.Type) + h.Set("ce-source", ec.Source.String()) + h.Set("ce-id", ec.ID) + if ec.Time != nil && !ec.Time.IsZero() { + h.Set("ce-time", ec.Time.String()) + } + if ec.SchemaURL != nil { + h.Set("ce-schemaurl", ec.SchemaURL.String()) + } + if ec.ContentType != nil { + h.Set("Content-Type", *ec.ContentType) + } else if v.Encoding == Default || v.Encoding == BinaryV02 { + // in binary v0.2, the Content-Type header is tied to ec.ContentType + // This was later found to be an issue with the spec, but yolo. + // TODO: not sure what the default should be? + h.Set("Content-Type", cloudevents.ApplicationJSON) + } + for k, v := range ec.Extensions { + // Per spec, map-valued extensions are converted to a list of headers as: + // CE-attrib-key + if mapVal, ok := v.(map[string]interface{}); ok { + for subkey, subval := range mapVal { + encoded, err := json.Marshal(subval) + if err != nil { + return nil, err + } + h.Set("ce-"+k+"-"+subkey, string(encoded)) + } + continue + } + encoded, err := json.Marshal(v) + if err != nil { + return nil, err + } + h.Set("ce-"+k, string(encoded)) + } + + return h, nil +} + +func (v CodecV02) encodeStructured(e cloudevents.Event) (transport.Message, error) { + header := http.Header{} + header.Set("Content-Type", "application/cloudevents+json") + + body, err := codec.JsonEncodeV02(e) + if err != nil { + return nil, err + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV02) decodeBinary(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + ctx, err := v.fromHeaders(m.Header) + if err != nil { + return nil, err + } + var body interface{} + if len(m.Body) > 0 { + body = m.Body + } + return &cloudevents.Event{ + Context: &ctx, + Data: body, + DataEncoded: true, + }, nil +} + +func (v CodecV02) fromHeaders(h http.Header) (cloudevents.EventContextV02, error) { + // Normalize headers. + for k, v := range h { + ck := textproto.CanonicalMIMEHeaderKey(k) + if k != ck { + delete(h, k) + h[ck] = v + } + } + + ec := cloudevents.EventContextV02{} + + ec.SpecVersion = h.Get("ce-specversion") + h.Del("ce-specversion") + + ec.ID = h.Get("ce-id") + h.Del("ce-id") + + ec.Type = h.Get("ce-type") + h.Del("ce-type") + + source := types.ParseURLRef(h.Get("ce-source")) + if source != nil { + ec.Source = *source + } + h.Del("ce-source") + + ec.Time = types.ParseTimestamp(h.Get("ce-time")) + h.Del("ce-time") + + ec.SchemaURL = types.ParseURLRef(h.Get("ce-schemaurl")) + h.Del("ce-schemaurl") + + contentType := h.Get("Content-Type") + if contentType != "" { + ec.ContentType = &contentType + } + h.Del("Content-Type") + + // At this point, we have deleted all the known headers. + // Everything left is assumed to be an extension. + + extensions := make(map[string]interface{}) + for k, v := range h { + if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") { + ak := strings.ToLower(k[len("ce-"):]) + if i := strings.Index(ak, "-"); i > 0 { + // attrib-key + attrib := ak[:i] + key := ak[(i + 1):] + if xv, ok := extensions[attrib]; ok { + if m, ok := xv.(map[string]interface{}); ok { + m[key] = v + continue + } + // TODO: revisit how we want to bubble errors up. + return ec, fmt.Errorf("failed to process map type extension") + } else { + m := make(map[string]interface{}) + m[key] = v + extensions[attrib] = m + } + } else { + // key + var tmp interface{} + if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil { + extensions[ak] = tmp + } else { + // If we can't unmarshal the data, treat it as a string. + extensions[ak] = v[0] + } + } + } + } + if len(extensions) > 0 { + ec.Extensions = extensions + } + return ec, nil +} + +func (v CodecV02) decodeStructured(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + + ec := cloudevents.EventContextV02{} + if err := json.Unmarshal(m.Body, &ec); err != nil { + return nil, err + } + + raw := make(map[string]json.RawMessage) + + if err := json.Unmarshal(m.Body, &raw); err != nil { + return nil, err + } + var data interface{} + if d, ok := raw["data"]; ok { + data = []byte(d) + } + + return &cloudevents.Event{ + Context: &ec, + Data: data, + DataEncoded: true, + }, nil +} + +func (v CodecV02) inspectEncoding(msg transport.Message) Encoding { + version := msg.CloudEventsVersion() + if version != cloudevents.CloudEventsVersionV02 { + return Unknown + } + m, ok := msg.(*Message) + if !ok { + return Unknown + } + contentType := m.Header.Get("Content-Type") + if contentType == cloudevents.ApplicationCloudEventsJSON { + return StructuredV02 + } + return BinaryV02 +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go new file mode 100644 index 00000000000..e932dc740bf --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go @@ -0,0 +1,323 @@ +package http + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/textproto" + "strings" + + "github.com/cloudevents/sdk-go/pkg/cloudevents" + "github.com/cloudevents/sdk-go/pkg/cloudevents/codec" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" + "github.com/cloudevents/sdk-go/pkg/cloudevents/types" +) + +// CodecV03 represents a http transport codec that uses CloudEvents spec v0.3 +type CodecV03 struct { + Encoding Encoding +} + +// Adheres to Codec +var _ transport.Codec = (*CodecV03)(nil) + +// Encode implements Codec.Encode +func (v CodecV03) Encode(e cloudevents.Event) (transport.Message, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportEncode, c: v.Encoding.Codec()}) + m, err := v.obsEncode(e) + if err != nil { + r.Error() + } else { + r.OK() + } + return m, err +} + +func (v CodecV03) obsEncode(e cloudevents.Event) (transport.Message, error) { + switch v.Encoding { + case Default: + fallthrough + case BinaryV03: + return v.encodeBinary(e) + case StructuredV03: + return v.encodeStructured(e) + case BatchedV03: + return nil, fmt.Errorf("not implemented") + default: + return nil, fmt.Errorf("unknown encoding: %d", v.Encoding) + } +} + +// Decode implements Codec.Decode +func (v CodecV03) Decode(msg transport.Message) (*cloudevents.Event, error) { + // TODO: wire context + _, r := observability.NewReporter(context.Background(), CodecObserved{o: reportDecode, c: v.inspectEncoding(msg).Codec()}) // TODO: inspectEncoding is not free. + e, err := v.obsDecode(msg) + if err != nil { + r.Error() + } else { + r.OK() + } + return e, err +} + +func (v CodecV03) obsDecode(msg transport.Message) (*cloudevents.Event, error) { + switch v.inspectEncoding(msg) { + case BinaryV03: + return v.decodeBinary(msg) + case StructuredV03: + return v.decodeStructured(msg) + case BatchedV03: + return nil, fmt.Errorf("not implemented") + default: + return nil, fmt.Errorf("unknown encoding") + } +} + +func (v CodecV03) encodeBinary(e cloudevents.Event) (transport.Message, error) { + header, err := v.toHeaders(e.Context.AsV03()) + if err != nil { + return nil, err + } + + body, err := e.DataBytes() + if err != nil { + return nil, err + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV03) toHeaders(ec *cloudevents.EventContextV03) (http.Header, error) { + h := http.Header{} + h.Set("ce-specversion", ec.SpecVersion) + h.Set("ce-type", ec.Type) + h.Set("ce-source", ec.Source.String()) + if ec.Subject != nil { + h.Set("ce-subject", *ec.Subject) + } + h.Set("ce-id", ec.ID) + if ec.Time != nil && !ec.Time.IsZero() { + h.Set("ce-time", ec.Time.String()) + } + if ec.SchemaURL != nil { + h.Set("ce-schemaurl", ec.SchemaURL.String()) + } + if ec.DataContentType != nil { + h.Set("Content-Type", *ec.DataContentType) + } else if v.Encoding == Default || v.Encoding == BinaryV03 { + // in binary v0.2, the Content-Type header is tied to ec.ContentType + // This was later found to be an issue with the spec, but yolo. + // TODO: not sure what the default should be? + h.Set("Content-Type", cloudevents.ApplicationJSON) + } + if ec.DataContentEncoding != nil { + h.Set("ce-datacontentencoding", *ec.DataContentEncoding) + } + + for k, v := range ec.Extensions { + // Per spec, map-valued extensions are converted to a list of headers as: + // CE-attrib-key + if mapVal, ok := v.(map[string]interface{}); ok { + for subkey, subval := range mapVal { + encoded, err := json.Marshal(subval) + if err != nil { + return nil, err + } + h.Set("ce-"+k+"-"+subkey, string(encoded)) + } + continue + } + encoded, err := json.Marshal(v) + if err != nil { + return nil, err + } + h.Set("ce-"+k, string(encoded)) + } + + return h, nil +} + +func (v CodecV03) encodeStructured(e cloudevents.Event) (transport.Message, error) { + header := http.Header{} + header.Set("Content-Type", "application/cloudevents+json") + + body, err := codec.JsonEncodeV03(e) + if err != nil { + return nil, err + } + + msg := &Message{ + Header: header, + Body: body, + } + + return msg, nil +} + +func (v CodecV03) decodeBinary(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + ctx, err := v.fromHeaders(m.Header) + if err != nil { + return nil, err + } + var body interface{} + if len(m.Body) > 0 { + body = m.Body + } + return &cloudevents.Event{ + Context: &ctx, + Data: body, + DataEncoded: true, + }, nil +} + +func (v CodecV03) fromHeaders(h http.Header) (cloudevents.EventContextV03, error) { + // Normalize headers. + for k, v := range h { + ck := textproto.CanonicalMIMEHeaderKey(k) + if k != ck { + delete(h, k) + h[ck] = v + } + } + + ec := cloudevents.EventContextV03{} + + ec.SpecVersion = h.Get("ce-specversion") + h.Del("ce-specversion") + + ec.ID = h.Get("ce-id") + h.Del("ce-id") + + ec.Type = h.Get("ce-type") + h.Del("ce-type") + + source := types.ParseURLRef(h.Get("ce-source")) + if source != nil { + ec.Source = *source + } + h.Del("ce-source") + + subject := h.Get("ce-subject") + if subject != "" { + ec.Subject = &subject + } + h.Del("ce-subject") + + ec.Time = types.ParseTimestamp(h.Get("ce-time")) + h.Del("ce-time") + + ec.SchemaURL = types.ParseURLRef(h.Get("ce-schemaurl")) + h.Del("ce-schemaurl") + + contentType := h.Get("Content-Type") + if contentType != "" { + ec.DataContentType = &contentType + } + h.Del("Content-Type") + + dataContentEncoding := h.Get("ce-datacontentencoding") + if dataContentEncoding != "" { + ec.DataContentEncoding = &dataContentEncoding + } + h.Del("ce-datacontentencoding") + + // At this point, we have deleted all the known headers. + // Everything left is assumed to be an extension. + + extensions := make(map[string]interface{}) + for k, v := range h { + if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") { + ak := strings.ToLower(k[len("ce-"):]) + if i := strings.Index(ak, "-"); i > 0 { + // attrib-key + attrib := ak[:i] + key := ak[(i + 1):] + if xv, ok := extensions[attrib]; ok { + if m, ok := xv.(map[string]interface{}); ok { + m[key] = v + continue + } + // TODO: revisit how we want to bubble errors up. + return ec, fmt.Errorf("failed to process map type extension") + } else { + m := make(map[string]interface{}) + m[key] = v + extensions[attrib] = m + } + } else { + // key + var tmp interface{} + if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil { + extensions[ak] = tmp + } else { + // If we can't unmarshal the data, treat it as a string. + extensions[ak] = v[0] + } + } + } + } + if len(extensions) > 0 { + ec.Extensions = extensions + } + return ec, nil +} + +func (v CodecV03) decodeStructured(msg transport.Message) (*cloudevents.Event, error) { + m, ok := msg.(*Message) + if !ok { + return nil, fmt.Errorf("failed to convert transport.Message to http.Message") + } + + ec := cloudevents.EventContextV03{} + if err := json.Unmarshal(m.Body, &ec); err != nil { + return nil, err + } + + raw := make(map[string]json.RawMessage) + + if err := json.Unmarshal(m.Body, &raw); err != nil { + return nil, err + } + var data interface{} + if d, ok := raw["data"]; ok { + data = []byte(d) + } + + return &cloudevents.Event{ + Context: &ec, + Data: data, + DataEncoded: true, + }, nil +} + +func (v CodecV03) inspectEncoding(msg transport.Message) Encoding { + version := msg.CloudEventsVersion() + if version != cloudevents.CloudEventsVersionV03 { + return Unknown + } + m, ok := msg.(*Message) + if !ok { + return Unknown + } + contentType := m.Header.Get("Content-Type") + if contentType == cloudevents.ApplicationCloudEventsJSON { + return StructuredV03 + } + if contentType == cloudevents.ApplicationCloudEventsBatchJSON { + return BatchedV03 + } + return BinaryV03 +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go new file mode 100644 index 00000000000..9511ec6d239 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go @@ -0,0 +1,158 @@ +package http + +import ( + "context" + "fmt" + "net/http" + "strings" +) + +// TransportContext allows a Receiver to understand the context of a request. +type TransportContext struct { + URI string + Host string + Method string + Header http.Header + + // IgnoreHeaderPrefixes controls what comes back from AttendToHeaders. + // AttendToHeaders controls what is output for .String() + IgnoreHeaderPrefixes []string +} + +// NewTransportContext creates a new TransportContext from a http.Request. +func NewTransportContext(req *http.Request) TransportContext { + var tx *TransportContext + if req != nil { + tx = &TransportContext{ + URI: req.RequestURI, + Host: req.Host, + Method: req.Method, + Header: req.Header, + } + } else { + tx = &TransportContext{} + } + tx.AddIgnoreHeaderPrefix("accept-encoding", "user-agent", "connection", "content-type") + return *tx +} + +// TransportResponseContext allows a Receiver response with http transport specific fields. +type TransportResponseContext struct { + // Header will be merged with the response headers. + Header http.Header +} + +// AttendToHeaders returns the list of headers that exist in the TransportContext that are not currently in +// tx.IgnoreHeaderPrefix. +func (tx TransportContext) AttendToHeaders() []string { + a := []string(nil) + if tx.Header != nil && len(tx.Header) > 0 { + for k := range tx.Header { + if tx.shouldIgnoreHeader(k) { + continue + } + a = append(a, k) + } + } + return a +} + +func (tx TransportContext) shouldIgnoreHeader(h string) bool { + for _, v := range tx.IgnoreHeaderPrefixes { + if strings.HasPrefix(strings.ToLower(h), strings.ToLower(v)) { + return true + } + } + return false +} + +// String generates a pretty-printed version of the resource as a string. +func (tx TransportContext) String() string { + b := strings.Builder{} + + b.WriteString("Transport Context,\n") + + empty := b.Len() + + if tx.URI != "" { + b.WriteString(" URI: " + tx.URI + "\n") + } + if tx.Host != "" { + b.WriteString(" Host: " + tx.Host + "\n") + } + + if tx.Method != "" { + b.WriteString(" Method: " + tx.Method + "\n") + } + + if tx.Header != nil && len(tx.Header) > 0 { + b.WriteString(" Header:\n") + for _, k := range tx.AttendToHeaders() { + b.WriteString(fmt.Sprintf(" %s: %s\n", k, tx.Header.Get(k))) + } + } + + if b.Len() == empty { + b.WriteString(" nil\n") + } + + return b.String() +} + +// AddIgnoreHeaderPrefix controls what header key is to be attended to and/or printed. +func (tx *TransportContext) AddIgnoreHeaderPrefix(prefix ...string) { + if tx.IgnoreHeaderPrefixes == nil { + tx.IgnoreHeaderPrefixes = []string(nil) + } + tx.IgnoreHeaderPrefixes = append(tx.IgnoreHeaderPrefixes, prefix...) +} + +// Opaque key type used to store TransportContext +type transportContextKeyType struct{} + +var transportContextKey = transportContextKeyType{} + +// WithTransportContext return a context with the given TransportContext into the provided context object. +func WithTransportContext(ctx context.Context, tcxt TransportContext) context.Context { + return context.WithValue(ctx, transportContextKey, tcxt) +} + +// TransportContextFrom pulls a TransportContext out of a context. Always +// returns a non-nil object. +func TransportContextFrom(ctx context.Context) TransportContext { + tctx := ctx.Value(transportContextKey) + if tctx != nil { + if tx, ok := tctx.(TransportContext); ok { + return tx + } + if tx, ok := tctx.(*TransportContext); ok { + return *tx + } + } + return TransportContext{} +} + +// Opaque key type used to store Headers +type headerKeyType struct{} + +var headerKey = headerKeyType{} + +// ContextWithHeader returns a context with a header added to the given context. +// Can be called multiple times to set multiple header key/value pairs. +func ContextWithHeader(ctx context.Context, key, value string) context.Context { + header := HeaderFrom(ctx) + header.Add(key, value) + return context.WithValue(ctx, headerKey, header) +} + +// HeaderFrom extracts the header oject in the given context. Always returns a non-nil Header. +func HeaderFrom(ctx context.Context) http.Header { + ch := http.Header{} + header := ctx.Value(headerKey) + if header != nil { + if h, ok := header.(http.Header); ok { + copyHeaders(h, ch) + } + } + return ch +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/doc.go new file mode 100644 index 00000000000..1a171e46e1e --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/doc.go @@ -0,0 +1,4 @@ +/* +Package http implements the CloudEvent transport implementation using HTTP. +*/ +package http diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go new file mode 100644 index 00000000000..ca62e5b5df9 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go @@ -0,0 +1,120 @@ +package http + +// Encoding to use for HTTP transport. +type Encoding int32 + +const ( + // Default + Default Encoding = iota + // BinaryV01 is Binary CloudEvents spec v0.1. + BinaryV01 + // StructuredV01 is Structured CloudEvents spec v0.1. + StructuredV01 + // BinaryV02 is Binary CloudEvents spec v0.2. + BinaryV02 + // StructuredV02 is Structured CloudEvents spec v0.2. + StructuredV02 + // BinaryV03 is Binary CloudEvents spec v0.3. + BinaryV03 + // StructuredV03 is Structured CloudEvents spec v0.3. + StructuredV03 + // BatchedV03 is Batched CloudEvents spec v0.3. + BatchedV03 + // Unknown is unknown. + Unknown +) + +// String pretty-prints the encoding as a string. +func (e Encoding) String() string { + switch e { + case Default: + return "Default Encoding " + e.Version() + + // Binary + case BinaryV01: + fallthrough + case BinaryV02: + fallthrough + case BinaryV03: + return "Binary Encoding " + e.Version() + + // Structured + case StructuredV01: + fallthrough + case StructuredV02: + fallthrough + case StructuredV03: + return "Structured Encoding " + e.Version() + + // Batched + case BatchedV03: + return "Batched Encoding " + e.Version() + + default: + return "Unknown Encoding" + } +} + +// Version pretty-prints the encoding version as a string. +func (e Encoding) Version() string { + switch e { + case Default: + return "Default" + + // Version 0.1 + case BinaryV01: + fallthrough + case StructuredV01: + return "v0.1" + + // Version 0.2 + case BinaryV02: + fallthrough + case StructuredV02: + return "v0.2" + + // Version 0.3 + case BinaryV03: + fallthrough + case StructuredV03: + fallthrough + case BatchedV03: + return "v0.3" + + // Unknown + default: + return "Unknown" + } +} + +// Codec creates a structured string to represent the the codec version. +func (e Encoding) Codec() string { + switch e { + case Default: + return "default" + + // Version 0.1 + case BinaryV01: + return "binary/v0.1" + case StructuredV01: + return "structured/v0.1" + + // Version 0.2 + case BinaryV02: + return "binary/v0.3" + case StructuredV02: + return "structured/v0.2" + + // Version 0.3 + case BinaryV03: + return "binary/v0.3" + case StructuredV03: + return "structured/v0.3" + case BatchedV03: + return "batched/v0.3" + + // Unknown + default: + return "unknown" + } +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go new file mode 100644 index 00000000000..0c5748c0b94 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go @@ -0,0 +1,79 @@ +package http + +import ( + "encoding/json" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" + "net/http" +) + +// type check that this transport message impl matches the contract +var _ transport.Message = (*Message)(nil) + +// Message is an http transport message. +type Message struct { + Header http.Header + Body []byte +} + +// Response is an http transport response. +type Response struct { + StatusCode int + Header http.Header + Body []byte +} + +// CloudEventsVersion inspects a message and tries to discover and return the +// CloudEvents spec version. +func (m Message) CloudEventsVersion() string { + + // TODO: the impl of this method needs to move into the codec. + + if m.Header != nil { + // Try headers first. + // v0.1, cased from the spec + if v := m.Header["CE-CloudEventsVersion"]; len(v) == 1 { + return v[0] + } + // v0.2, canonical casing + if ver := m.Header.Get("CE-CloudEventsVersion"); ver != "" { + return ver + } + + // v0.2, cased from the spec + if v := m.Header["ce-specversion"]; len(v) == 1 { + return v[0] + } + // v0.2, canonical casing + if ver := m.Header.Get("ce-specversion"); ver != "" { + return ver + } + } + + // Then try the data body. + // TODO: we need to use the correct decoding based on content type. + + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(m.Body, &raw); err != nil { + return "" + } + + // v0.1 + if v, ok := raw["cloudEventsVersion"]; ok { + var version string + if err := json.Unmarshal(v, &version); err != nil { + return "" + } + return version + } + + // v0.2 + if v, ok := raw["specversion"]; ok { + var version string + if err := json.Unmarshal(v, &version); err != nil { + return "" + } + return version + } + + return "" +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go new file mode 100644 index 00000000000..1da56dc2ad5 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go @@ -0,0 +1,109 @@ +package http + +import ( + "fmt" + + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +var ( + // LatencyMs measures the latency in milliseconds for the http transport + // methods for CloudEvents. + LatencyMs = stats.Float64( + "cloudevents.io/sdk-go/transport/http/latency", + "The latency in milliseconds for the http transport methods for CloudEvents.", + "ms") +) + +var ( + // LatencyView is an OpenCensus view that shows http transport method latency. + LatencyView = &view.View{ + Name: "transport/http/latency", + Measure: LatencyMs, + Description: "The distribution of latency inside of http transport for CloudEvents.", + Aggregation: view.Distribution(0, .01, .1, 1, 10, 100, 1000, 10000), + TagKeys: observability.LatencyTags(), + } +) + +type observed int32 + +// Adheres to Observable +var _ observability.Observable = observed(0) + +const ( + reportSend observed = iota + reportReceive + reportServeHTTP + reportEncode + reportDecode +) + +// TraceName implements Observable.TraceName +func (o observed) TraceName() string { + switch o { + case reportSend: + return "transport/http/send" + case reportReceive: + return "transport/http/receive" + case reportServeHTTP: + return "transport/http/servehttp" + case reportEncode: + return "transport/http/encode" + case reportDecode: + return "transport/http/decode" + default: + return "transport/http/unknown" + } +} + +// MethodName implements Observable.MethodName +func (o observed) MethodName() string { + switch o { + case reportSend: + return "send" + case reportReceive: + return "receive" + case reportServeHTTP: + return "servehttp" + case reportEncode: + return "encode" + case reportDecode: + return "decode" + default: + return "unknown" + } +} + +// LatencyMs implements Observable.LatencyMs +func (o observed) LatencyMs() *stats.Float64Measure { + return LatencyMs +} + +// CodecObserved is a wrapper to append version to observed. +type CodecObserved struct { + // Method + o observed + // Codec + c string +} + +// Adheres to Observable +var _ observability.Observable = (*CodecObserved)(nil) + +// TraceName implements Observable.TraceName +func (c CodecObserved) TraceName() string { + return fmt.Sprintf("%s/%s", c.o.TraceName(), c.c) +} + +// MethodName implements Observable.MethodName +func (c CodecObserved) MethodName() string { + return fmt.Sprintf("%s/%s", c.o.MethodName(), c.c) +} + +// LatencyMs implements Observable.LatencyMs +func (c CodecObserved) LatencyMs() *stats.Float64Measure { + return c.o.LatencyMs() +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go new file mode 100644 index 00000000000..226bb92553f --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go @@ -0,0 +1,175 @@ +package http + +import ( + "fmt" + nethttp "net/http" + "net/url" + "strings" + "time" +) + +// Option is the function signature required to be considered an http.Option. +type Option func(*Transport) error + +// WithTarget sets the outbound recipient of cloudevents when using an HTTP +// request. +func WithTarget(targetUrl string) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http target option can not set nil transport") + } + targetUrl = strings.TrimSpace(targetUrl) + if targetUrl != "" { + var err error + var target *url.URL + target, err = url.Parse(targetUrl) + if err != nil { + return fmt.Errorf("http target option failed to parse target url: %s", err.Error()) + } + + if t.Req == nil { + t.Req = &nethttp.Request{ + Method: nethttp.MethodPost, + } + } + t.Req.URL = target + return nil + } + return fmt.Errorf("http target option was empty string") + } +} + +// WithMethod sets the outbound recipient of cloudevents when using an HTTP +// request. +func WithMethod(method string) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http method option can not set nil transport") + } + method = strings.TrimSpace(method) + if method != "" { + if t.Req == nil { + t.Req = &nethttp.Request{} + } + t.Req.Method = method + return nil + } + return fmt.Errorf("http method option was empty string") + } +} + +// WithHeader sets an additional default outbound header for all cloudevents +// when using an HTTP request. +func WithHeader(key, value string) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http header option can not set nil transport") + } + key = strings.TrimSpace(key) + if key != "" { + if t.Req == nil { + t.Req = &nethttp.Request{} + } + if t.Req.Header == nil { + t.Req.Header = nethttp.Header{} + } + t.Req.Header.Add(key, value) + return nil + } + return fmt.Errorf("http header option was empty string") + } +} + +// WithShutdownTimeout sets the shutdown timeout when the http server is being shutdown. +func WithShutdownTimeout(timeout time.Duration) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http shutdown timeout option can not set nil transport") + } + t.ShutdownTimeout = &timeout + return nil + } +} + +// WithEncoding sets the encoding for clients with HTTP transports. +func WithEncoding(encoding Encoding) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http encoding option can not set nil transport") + } + t.Encoding = encoding + return nil + } +} + +// WithDefaultEncodingSelector sets the encoding selection strategy for +// default encoding selections based on Event. +func WithDefaultEncodingSelector(fn EncodingSelector) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http default encoding selector option can not set nil transport") + } + if fn != nil { + t.DefaultEncodingSelectionFn = fn + return nil + } + return fmt.Errorf("http fn for DefaultEncodingSelector was nil") + } +} + +// WithBinaryEncoding sets the encoding selection strategy for +// default encoding selections based on Event, the encoded event will be the +// given version in Binary form. +func WithBinaryEncoding() Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http binary encoding option can not set nil transport") + } + + t.DefaultEncodingSelectionFn = DefaultBinaryEncodingSelectionStrategy + return nil + } +} + +// WithStructuredEncoding sets the encoding selection strategy for +// default encoding selections based on Event, the encoded event will be the +// given version in Structured form. +func WithStructuredEncoding() Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http structured encoding option can not set nil transport") + } + + t.DefaultEncodingSelectionFn = DefaultStructuredEncodingSelectionStrategy + return nil + } +} + +// WithPort sets the port for for clients with HTTP transports. +func WithPort(port int) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http port option can not set nil transport") + } + if port < 0 { + return fmt.Errorf("http port option was given an invalid port: %d", port) + } + t.Port = &port + return nil + } +} + +// WithPath sets the path to receive cloudevents on for HTTP transports. +func WithPath(path string) Option { + return func(t *Transport) error { + if t == nil { + return fmt.Errorf("http path option can not set nil transport") + } + path = strings.TrimSpace(path) + if len(path) == 0 { + return fmt.Errorf("http path option was given an invalid path: %q", path) + } + t.Path = path + return nil + } +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go new file mode 100644 index 00000000000..258691c4815 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go @@ -0,0 +1,466 @@ +package http + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "go.uber.org/zap" + + "github.com/cloudevents/sdk-go/pkg/cloudevents" + cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context" + "github.com/cloudevents/sdk-go/pkg/cloudevents/observability" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport" +) + +type EncodingSelector func(e cloudevents.Event) Encoding + +// Transport adheres to transport.Transport. +var _ transport.Transport = (*Transport)(nil) + +const ( + // DefaultShutdownTimeout defines the default timeout given to the http.Server when calling Shutdown. + DefaultShutdownTimeout = time.Minute * 1 +) + +// Transport acts as both a http client and a http handler. +type Transport struct { + // The encoding used to select the codec for outbound events. + Encoding Encoding + + // DefaultEncodingSelectionFn allows for other encoding selection strategies to be injected. + DefaultEncodingSelectionFn EncodingSelector + + // ShutdownTimeout defines the timeout given to the http.Server when calling Shutdown. + // If nil, DefaultShutdownTimeout is used. + ShutdownTimeout *time.Duration + + // Sending + + // Client is the http client that will be used to send requests. + // If nil, the Transport will create a one. + Client *http.Client + // Req is the base http request that is used for http.Do. + // Only .Method, .URL, .Close, and .Header is considered. + // If not set, Req.Method defaults to POST. + // Req.URL or context.WithTarget(url) are required for sending. + Req *http.Request + + // Receiving + + // Receiver is invoked target for incoming events. + Receiver transport.Receiver + // Port is the port to bind the receiver to. Defaults to 8080. + Port *int + // Path is the path to bind the receiver to. Defaults to "/". + Path string + // Handler is the handler the http Server will use. Use this to reuse the + // http server. If nil, the Transport will create a one. + Handler *http.ServeMux + + realPort int + server *http.Server + handlerRegistered bool + codec transport.Codec + // Create Mutex + crMu sync.Mutex + // Receive Mutex + reMu sync.Mutex +} + +func New(opts ...Option) (*Transport, error) { + t := &Transport{ + Req: &http.Request{ + Method: http.MethodPost, + }, + } + if err := t.applyOptions(opts...); err != nil { + return nil, err + } + return t, nil +} + +func (t *Transport) applyOptions(opts ...Option) error { + for _, fn := range opts { + if err := fn(t); err != nil { + return err + } + } + return nil +} + +func (t *Transport) loadCodec(ctx context.Context) bool { + if t.codec == nil { + t.crMu.Lock() + if t.DefaultEncodingSelectionFn != nil && t.Encoding != Default { + logger := cecontext.LoggerFrom(ctx) + logger.Warn("transport has a DefaultEncodingSelectionFn set but Encoding is not Default. DefaultEncodingSelectionFn will be ignored.") + + t.codec = &Codec{ + Encoding: t.Encoding, + } + } else { + t.codec = &Codec{ + Encoding: t.Encoding, + DefaultEncodingSelectionFn: t.DefaultEncodingSelectionFn, + } + } + t.crMu.Unlock() + } + return true +} + +func copyHeaders(from, to http.Header) { + if from == nil || to == nil { + return + } + for header, values := range from { + for _, value := range values { + to.Add(header, value) + } + } +} + +// Send implements Transport.Send +func (t *Transport) Send(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) { + ctx, r := observability.NewReporter(ctx, reportSend) + resp, err := t.obsSend(ctx, event) + if err != nil { + r.Error() + } else { + r.OK() + } + return resp, err +} + +func (t *Transport) obsSend(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) { + if t.Client == nil { + t.crMu.Lock() + t.Client = &http.Client{} + t.crMu.Unlock() + } + + req := http.Request{ + Header: HeaderFrom(ctx), + } + if t.Req != nil { + req.Method = t.Req.Method + req.URL = t.Req.URL + req.Close = t.Req.Close + copyHeaders(t.Req.Header, req.Header) + } + + // Override the default request with target from context. + if target := cecontext.TargetFrom(ctx); target != nil { + req.URL = target + } + + if ok := t.loadCodec(ctx); !ok { + return nil, fmt.Errorf("unknown encoding set on transport: %d", t.Encoding) + } + + msg, err := t.codec.Encode(event) + if err != nil { + return nil, err + } + + if m, ok := msg.(*Message); ok { + copyHeaders(m.Header, req.Header) + + req.Body = ioutil.NopCloser(bytes.NewBuffer(m.Body)) + req.ContentLength = int64(len(m.Body)) + + return httpDo(ctx, t.Client, &req, func(resp *http.Response, err error) (*cloudevents.Event, error) { + logger := cecontext.LoggerFrom(ctx) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + msg := &Message{ + Header: resp.Header, + Body: body, + } + + var respEvent *cloudevents.Event + if msg.CloudEventsVersion() != "" { + if ok := t.loadCodec(ctx); !ok { + err := fmt.Errorf("unknown encoding set on transport: %d", t.Encoding) + logger.Error("failed to load codec", zap.Error(err)) + } + if respEvent, err = t.codec.Decode(msg); err != nil { + logger.Error("failed to decode message", zap.Error(err)) + } + } + + if accepted(resp) { + return respEvent, nil + } + return respEvent, fmt.Errorf("error sending cloudevent: %s", resp.Status) + }) + } + return nil, fmt.Errorf("failed to encode Event into a Message") +} + +// SetReceiver implements Transport.SetReceiver +func (t *Transport) SetReceiver(r transport.Receiver) { + t.Receiver = r +} + +// StartReceiver implements Transport.StartReceiver +// NOTE: This is a blocking call. +func (t *Transport) StartReceiver(ctx context.Context) error { + t.reMu.Lock() + defer t.reMu.Unlock() + + if t.Handler == nil { + t.Handler = http.NewServeMux() + } + if !t.handlerRegistered { + // handler.Handle might panic if the user tries to use the same path as the sdk. + t.Handler.Handle(t.GetPath(), t) + t.handlerRegistered = true + } + + addr := fmt.Sprintf(":%d", t.GetPort()) + t.server = &http.Server{ + Addr: addr, + Handler: t.Handler, + } + + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + t.realPort = listener.Addr().(*net.TCPAddr).Port + + // Shutdown + defer func() { + t.realPort = 0 + t.server.Close() + t.server = nil + }() + + errChan := make(chan error, 1) + go func() { + errChan <- t.server.Serve(listener) + }() + + // wait for the server to return or ctx.Done(). + select { + case <-ctx.Done(): + // Try a gracefully shutdown. + timeout := DefaultShutdownTimeout + if t.ShutdownTimeout != nil { + timeout = *t.ShutdownTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return t.server.Shutdown(ctx) + case err := <-errChan: + return err + } +} + +type eventError struct { + event *cloudevents.Event + err error +} + +func httpDo(ctx context.Context, client *http.Client, req *http.Request, fn func(*http.Response, error) (*cloudevents.Event, error)) (*cloudevents.Event, error) { + // Run the HTTP request in a goroutine and pass the response to fn. + c := make(chan eventError, 1) + req = req.WithContext(ctx) + go func() { + event, err := fn(client.Do(req)) + c <- eventError{event: event, err: err} + }() + select { + case <-ctx.Done(): + return nil, ctx.Err() + case ee := <-c: + return ee.event, ee.err + } +} + +// accepted is a helper method to understand if the response from the target +// accepted the CloudEvent. +func accepted(resp *http.Response) bool { + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return true + } + return false +} + +func (t *Transport) invokeReceiver(ctx context.Context, event cloudevents.Event) (*Response, error) { + ctx, r := observability.NewReporter(ctx, reportReceive) + resp, err := t.obsInvokeReceiver(ctx, event) + if err != nil { + r.Error() + } else { + r.OK() + } + return resp, err +} + +func (t *Transport) obsInvokeReceiver(ctx context.Context, event cloudevents.Event) (*Response, error) { + logger := cecontext.LoggerFrom(ctx) + if t.Receiver != nil { + // Note: http does not use eventResp.Reason + eventResp := cloudevents.EventResponse{} + resp := Response{} + + err := t.Receiver.Receive(ctx, event, &eventResp) + if err != nil { + logger.Warnw("got an error from receiver fn: %s", zap.Error(err)) + resp.StatusCode = http.StatusInternalServerError + return &resp, err + } + + if eventResp.Event != nil { + if t.loadCodec(ctx) { + if m, err := t.codec.Encode(*eventResp.Event); err != nil { + logger.Errorw("failed to encode response from receiver fn", zap.Error(err)) + } else if msg, ok := m.(*Message); ok { + resp.Header = msg.Header + resp.Body = msg.Body + } + } else { + logger.Error("failed to load codec") + resp.StatusCode = http.StatusInternalServerError + return &resp, err + } + // Look for a transport response context + var trx *TransportResponseContext + if ptrTrx, ok := eventResp.Context.(*TransportResponseContext); ok { + // found a *TransportResponseContext, use it. + trx = ptrTrx + } else if realTrx, ok := eventResp.Context.(TransportResponseContext); ok { + // found a TransportResponseContext, make it a pointer. + trx = &realTrx + } + // If we found a TransportResponseContext, use it. + if trx != nil && trx.Header != nil && len(trx.Header) > 0 { + copyHeaders(trx.Header, resp.Header) + } + } + + if eventResp.Status != 0 { + resp.StatusCode = eventResp.Status + } else { + resp.StatusCode = http.StatusAccepted // default is 202 - Accepted + } + return &resp, err + } + return nil, nil +} + +// ServeHTTP implements http.Handler +func (t *Transport) ServeHTTP(w http.ResponseWriter, req *http.Request) { + ctx, r := observability.NewReporter(req.Context(), reportServeHTTP) + logger := cecontext.LoggerFrom(ctx) + + body, err := ioutil.ReadAll(req.Body) + if err != nil { + logger.Errorw("failed to handle request", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"error":"Invalid request"}`)) + r.Error() + return + } + msg := &Message{ + Header: req.Header, + Body: body, + } + + if ok := t.loadCodec(ctx); !ok { + err := fmt.Errorf("unknown encoding set on transport: %d", t.Encoding) + logger.Errorw("failed to load codec", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf(`{"error":%q}`, err.Error()))) + r.Error() + return + } + event, err := t.codec.Decode(msg) + if err != nil { + logger.Errorw("failed to decode message", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf(`{"error":%q}`, err.Error()))) + r.Error() + return + } + + if req != nil { + ctx = WithTransportContext(ctx, NewTransportContext(req)) + } + + resp, err := t.invokeReceiver(ctx, *event) + if err != nil { + logger.Warnw("error returned from invokeReceiver", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf(`{"error":%q}`, err.Error()))) + r.Error() + return + } + + if resp != nil { + if t.Req != nil { + copyHeaders(t.Req.Header, w.Header()) + } + if len(resp.Header) > 0 { + copyHeaders(resp.Header, w.Header()) + } + w.Header().Add("Content-Length", strconv.Itoa(len(resp.Body))) + if len(resp.Body) > 0 { + if _, err := w.Write(resp.Body); err != nil { + r.Error() + return + } + } + status := http.StatusAccepted + if resp.StatusCode >= 200 && resp.StatusCode < 600 { + status = resp.StatusCode + } + w.WriteHeader(status) + + r.OK() + return + } + + w.WriteHeader(http.StatusNoContent) + r.OK() +} + +// GetPort returns the port the transport is active on. +// .Port can be set to 0, which means the transport selects a port, GetPort +// allows the transport to report back the selected port. +func (t *Transport) GetPort() int { + if t.Port != nil && *t.Port == 0 && t.realPort != 0 { + return t.realPort + } + + if t.Port != nil && *t.Port >= 0 { // 0 means next open port + return *t.Port + } + return 8080 // default +} + +// GetPath returns the path the transport is hosted on. If the path is '/', +// the transport will handle requests on any URI. To discover the true path +// a request was received on, inspect the context from Receive(cxt, ...) with +// TransportContextFrom(ctx). +func (t *Transport) GetPath() string { + path := strings.TrimSpace(t.Path) + if len(path) > 0 { + return path + } + return "/" // default +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/message.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/message.go new file mode 100644 index 00000000000..e2ed55c970f --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/message.go @@ -0,0 +1,9 @@ +package transport + +// Message is the abstract transport message wrapper. +type Message interface { + // CloudEventsVersion returns the version of the CloudEvent. + CloudEventsVersion() string + + // TODO maybe get encoding +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/transport.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/transport.go new file mode 100644 index 00000000000..75c652b9233 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/transport.go @@ -0,0 +1,21 @@ +package transport + +import ( + "context" + "github.com/cloudevents/sdk-go/pkg/cloudevents" +) + +// Transport is the interface for transport sender to send the converted Message +// over the underlying transport. +type Transport interface { + Send(context.Context, cloudevents.Event) (*cloudevents.Event, error) + + SetReceiver(Receiver) + StartReceiver(context.Context) error +} + +// Receiver is an interface to define how a transport will invoke a listener +// of incoming events. +type Receiver interface { + Receive(context.Context, cloudevents.Event, *cloudevents.EventResponse) error +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/allocate.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/allocate.go new file mode 100644 index 00000000000..c38f7117701 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/allocate.go @@ -0,0 +1,36 @@ +package types + +import "reflect" + +// Allocate allocates a new instance of type t and returns: +// asPtr is of type t if t is a pointer type and of type &t otherwise +// asValue is a Value of type t pointing to the same data as asPtr +func Allocate(obj interface{}) (asPtr interface{}, asValue reflect.Value) { + if obj == nil { + return nil, reflect.Value{} + } + + switch t := reflect.TypeOf(obj); t.Kind() { + case reflect.Ptr: + reflectPtr := reflect.New(t.Elem()) + asPtr = reflectPtr.Interface() + asValue = reflectPtr + case reflect.Map: + reflectPtr := reflect.MakeMap(t) + asPtr = reflectPtr.Interface() + asValue = reflectPtr + case reflect.String: + reflectPtr := reflect.New(t) + asPtr = "" + asValue = reflectPtr.Elem() + case reflect.Slice: + reflectPtr := reflect.MakeSlice(t, 0, 0) + asPtr = reflectPtr.Interface() + asValue = reflectPtr + default: + reflectPtr := reflect.New(t) + asPtr = reflectPtr.Interface() + asValue = reflectPtr.Elem() + } + return +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/doc.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/doc.go new file mode 100644 index 00000000000..1019b4a2dd2 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/doc.go @@ -0,0 +1,4 @@ +/* +Package types provides custom types to support CloudEvents. +*/ +package types diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/timestamp.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/timestamp.go new file mode 100644 index 00000000000..c11e4e15a6d --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/timestamp.go @@ -0,0 +1,83 @@ +package types + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "time" +) + +// Timestamp wraps time.Time to normalize the time layout to RFC3339. It is +// intended to enforce compliance with the CloudEvents spec for their +// definition of Timestamp. Custom marshal methods are implemented to ensure +// the outbound Timestamp is a string in the RFC3339 layout. +type Timestamp struct { + time.Time +} + +// ParseTimestamp attempts to parse the given time assuming RFC3339 layout +func ParseTimestamp(t string) *Timestamp { + if t == "" { + return nil + } + timestamp, err := time.Parse(time.RFC3339Nano, t) + if err != nil { + return nil + } + return &Timestamp{Time: timestamp} +} + +// MarshalJSON implements a custom json marshal method used when this type is +// marshaled using json.Marshal. +func (t *Timestamp) MarshalJSON() ([]byte, error) { + if t == nil || t.IsZero() { + return []byte(`""`), nil + } + rfc3339 := fmt.Sprintf("%q", t.UTC().Format(time.RFC3339Nano)) + return []byte(rfc3339), nil +} + +// UnmarshalJSON implements the json unmarshal method used when this type is +// unmarshed using json.Unmarshal. +func (t *Timestamp) UnmarshalJSON(b []byte) error { + var timestamp string + if err := json.Unmarshal(b, ×tamp); err != nil { + return err + } + if pt := ParseTimestamp(timestamp); pt != nil { + *t = *pt + } + return nil +} + +// MarshalXML implements a custom xml marshal method used when this type is +// marshaled using xml.Marshal. +func (t *Timestamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if t == nil || t.IsZero() { + return e.EncodeElement(nil, start) + } + v := t.UTC().Format(time.RFC3339Nano) + return e.EncodeElement(v, start) +} + +// UnmarshalXML implements the xml unmarshal method used when this type is +// unmarshed using xml.Unmarshal. +func (t *Timestamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var timestamp string + if err := d.DecodeElement(×tamp, &start); err != nil { + return err + } + if pt := ParseTimestamp(timestamp); pt != nil { + *t = *pt + } + return nil +} + +// String outputs the time using layout RFC3339. +func (t *Timestamp) String() string { + if t == nil { + return time.Time{}.UTC().Format(time.RFC3339Nano) + } + + return t.UTC().Format(time.RFC3339Nano) +} diff --git a/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/urlref.go b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/urlref.go new file mode 100644 index 00000000000..d1cc2703645 --- /dev/null +++ b/vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/urlref.go @@ -0,0 +1,78 @@ +package types + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "net/url" +) + +// URLRef is a wrapper to url.URL. It is intended to enforce compliance with +// the CloudEvents spec for their definition of URI-Reference. Custom +// marshal methods are implemented to ensure the outbound URLRef object is +// is a flat string. +type URLRef struct { + url.URL +} + +// ParseURLRef attempts to parse the given string as a URI-Reference. +func ParseURLRef(u string) *URLRef { + if u == "" { + return nil + } + pu, err := url.Parse(u) + if err != nil { + return nil + } + return &URLRef{URL: *pu} +} + +// MarshalJSON implements a custom json marshal method used when this type is +// marshaled using json.Marshal. +func (u URLRef) MarshalJSON() ([]byte, error) { + b := fmt.Sprintf("%q", u.String()) + return []byte(b), nil +} + +// UnmarshalJSON implements the json unmarshal method used when this type is +// unmarshed using json.Unmarshal. +func (u *URLRef) UnmarshalJSON(b []byte) error { + var ref string + if err := json.Unmarshal(b, &ref); err != nil { + return err + } + r := ParseURLRef(ref) + if r != nil { + *u = *r + } + return nil +} + +// MarshalXML implements a custom xml marshal method used when this type is +// marshaled using xml.Marshal. +func (u URLRef) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + v := fmt.Sprintf("%s", u.String()) + return e.EncodeElement(v, start) +} + +// UnmarshalXML implements the xml unmarshal method used when this type is +// unmarshed using xml.Unmarshal. +func (u *URLRef) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var ref string + if err := d.DecodeElement(&ref, &start); err != nil { + return err + } + r := ParseURLRef(ref) + if r != nil { + *u = *r + } + return nil +} + +// String returns the full string representation of the URI-Reference. +func (u *URLRef) String() string { + if u == nil { + return "" + } + return u.URL.String() +} diff --git a/vendor/github.com/knative/eventing-sources/AUTHORS b/vendor/github.com/knative/eventing-sources/AUTHORS new file mode 100644 index 00000000000..9a8f2f769f4 --- /dev/null +++ b/vendor/github.com/knative/eventing-sources/AUTHORS @@ -0,0 +1,6 @@ +# This is the list of Knative authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. +Google LLC diff --git a/vendor/github.com/knative/eventing-sources/LICENSE b/vendor/github.com/knative/eventing-sources/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/knative/eventing-sources/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/knative/eventing-sources/pkg/kncloudevents/good_client.go b/vendor/github.com/knative/eventing-sources/pkg/kncloudevents/good_client.go new file mode 100644 index 00000000000..52384cc889e --- /dev/null +++ b/vendor/github.com/knative/eventing-sources/pkg/kncloudevents/good_client.go @@ -0,0 +1,28 @@ +package kncloudevents + +import ( + "github.com/cloudevents/sdk-go/pkg/cloudevents/client" + "github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http" +) + +func NewDefaultClient(target ...string) (client.Client, error) { + tOpts := []http.Option{http.WithBinaryEncoding()} + if len(target) > 0 && target[0] != "" { + tOpts = append(tOpts, http.WithTarget(target[0])) + } + + // Make an http transport for the CloudEvents client. + t, err := http.New(tOpts...) + if err != nil { + return nil, err + } + // Use the transport to make a new CloudEvents client. + c, err := client.New(t, + client.WithUUIDs(), + client.WithTimeNow(), + ) + if err != nil { + return nil, err + } + return c, nil +} diff --git a/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/LICENSE b/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/LICENSE new file mode 120000 index 00000000000..14776154326 --- /dev/null +++ b/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/LICENSE @@ -0,0 +1 @@ +../../../../LICENSE \ No newline at end of file diff --git a/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/VENDOR-LICENSE b/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/VENDOR-LICENSE new file mode 120000 index 00000000000..7322c09d957 --- /dev/null +++ b/vendor/github.com/knative/eventing-sources/test/test_images/k8sevents/kodata/VENDOR-LICENSE @@ -0,0 +1 @@ +../../../../third_party/VENDOR-LICENSE \ No newline at end of file