From f93cd44399aeb76d3aa78e338d3009db1a2c194a Mon Sep 17 00:00:00 2001 From: Richard Park <51494936+richardpark-msft@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:13:56 -0700 Subject: [PATCH] [azeventgrid] Adding in a Publisher client to use with Event Grid topics (#21521) * Event Grid publisher client - can publish to topics. * Fixing some issues in the publisher client and adding in examples. * Add function specific examples. * Adding a very basic readme for the publisher package. * Updating changelog * Bump the minor revision as well. * Disable live tests if we're in a CI environment. * Plumb in recording infra * Rearrange client constructors. * Test resources should work now * Fixing tests to just get skipped in non-recording mode. * Remove unused fakeVars * Remove en-us * Temp replace dead links. * Another one failing the dead link checker. * (comparing with others) Looks like I was using an incorrect inputSchema for the topic. * The error code when releasing an expired lock token has changed. Documenting for now, will pose a question for an ongoing discussion. * Skipping tests that are related to #21530 * Making our template look more like Python's with the scope/role assignment. * The CloudEvent part of this test was getting set at too high a level and would affect other parts of the HTTP pipeline (including token authentication). * Was missing an explicit set of variables so the EnvironmentCredential works. * testing * EnvVars should be _under_ parameters! * A little too much copying... * Remove the old content-header code now that we're auto-generating it in the right spot. --------- Co-authored-by: Richard Park --- sdk/messaging/azeventgrid/CHANGELOG.md | 8 +- sdk/messaging/azeventgrid/README.md | 27 ++- sdk/messaging/azeventgrid/ci.yml | 15 +- sdk/messaging/azeventgrid/client_custom.go | 5 +- sdk/messaging/azeventgrid/client_test.go | 12 ++ sdk/messaging/azeventgrid/example_test.go | 121 +++++++++++- sdk/messaging/azeventgrid/go.mod | 16 +- sdk/messaging/azeventgrid/go.sum | 35 +++- .../azeventgrid/{ => internal}/version.go | 10 +- sdk/messaging/azeventgrid/publisher/README.md | 134 +++++++++++++ .../azeventgrid/publisher/autorest.md | 76 ++++++++ sdk/messaging/azeventgrid/publisher/build.go | 10 + sdk/messaging/azeventgrid/publisher/client.go | 147 +++++++++++++++ .../azeventgrid/publisher/client_custom.go | 122 ++++++++++++ .../azeventgrid/publisher/client_test.go | 129 +++++++++++++ .../publisher/example_newclient_test.go | 98 ++++++++++ .../publisher/example_publish_topic_test.go | 105 +++++++++++ .../azeventgrid/publisher/main_test.go | 28 +++ sdk/messaging/azeventgrid/publisher/models.go | 38 ++++ .../azeventgrid/publisher/models_serde.go | 102 ++++++++++ .../azeventgrid/publisher/options.go | 25 +++ .../azeventgrid/publisher/response_types.go | 24 +++ .../azeventgrid/publisher/shared_test.go | 177 ++++++++++++++++++ .../azeventgrid/publisher/time_rfc3339.go | 87 +++++++++ .../azeventgrid/test-resources.bicep | 82 +++++++- 25 files changed, 1589 insertions(+), 44 deletions(-) rename sdk/messaging/azeventgrid/{ => internal}/version.go (64%) create mode 100644 sdk/messaging/azeventgrid/publisher/README.md create mode 100644 sdk/messaging/azeventgrid/publisher/autorest.md create mode 100644 sdk/messaging/azeventgrid/publisher/build.go create mode 100644 sdk/messaging/azeventgrid/publisher/client.go create mode 100644 sdk/messaging/azeventgrid/publisher/client_custom.go create mode 100644 sdk/messaging/azeventgrid/publisher/client_test.go create mode 100644 sdk/messaging/azeventgrid/publisher/example_newclient_test.go create mode 100644 sdk/messaging/azeventgrid/publisher/example_publish_topic_test.go create mode 100644 sdk/messaging/azeventgrid/publisher/main_test.go create mode 100644 sdk/messaging/azeventgrid/publisher/models.go create mode 100644 sdk/messaging/azeventgrid/publisher/models_serde.go create mode 100644 sdk/messaging/azeventgrid/publisher/options.go create mode 100644 sdk/messaging/azeventgrid/publisher/response_types.go create mode 100644 sdk/messaging/azeventgrid/publisher/shared_test.go create mode 100644 sdk/messaging/azeventgrid/publisher/time_rfc3339.go diff --git a/sdk/messaging/azeventgrid/CHANGELOG.md b/sdk/messaging/azeventgrid/CHANGELOG.md index b37a093a1937..869874e1cdf2 100644 --- a/sdk/messaging/azeventgrid/CHANGELOG.md +++ b/sdk/messaging/azeventgrid/CHANGELOG.md @@ -1,15 +1,15 @@ # Release History -## 0.1.1 (Unreleased) +## 0.2.0 (Unreleased) ### Features Added -### Breaking Changes - -### Bugs Fixed +- The publisher client for Event Grid topics has been added as a sub-package under `publisher`. ### Other Changes +- Documentation and examples added for Event Grid namespace client. + ## 0.1.0 (2023-07-11) ### Features Added diff --git a/sdk/messaging/azeventgrid/README.md b/sdk/messaging/azeventgrid/README.md index cc633144c593..cf13777619e3 100644 --- a/sdk/messaging/azeventgrid/README.md +++ b/sdk/messaging/azeventgrid/README.md @@ -4,6 +4,8 @@ This client module allows you to publish events and receive events using the [Pull delivery](https://learn.microsoft.com/azure/event-grid/pull-delivery-overview) API. +> NOTE: This client does not work with Event Grid topics. Use the [publisher.Client][godoc_publisher_client] in the `publisher` sub-package instead. + Key links: - [Source code][source] - [API Reference Documentation][godoc] @@ -24,18 +26,20 @@ go get github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid - Go, version 1.18 or higher - An [Azure subscription](https://azure.microsoft.com/free/) -- An [Event Grid namespace](https://learn.microsoft.com/azure/event-grid/). -- An Event Grid namespace. You can create an Event Grid Namespace using the [Azure Portal](https://learn.microsoft.com/azure/event-grid/create-view-manage-namespaces). +- An [Event Grid namespace][ms_namespace]. You can create an Event Grid namespace using the [Azure Portal][ms_create_namespace]. +- An [Event Grid namespace topic][ms_topic]. You can create an Event Grid namespace topic using the [Azure Portal][ms_create_topic]. ### Authenticate the client -Event Grid clients authenticate using a shared key credential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclient]. +Event Grid namespace clients authenticate using a shared key credential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclient]. # Key concepts -An Azure Event Grid [**namespace**](https://learn.microsoft.com/azure/event-grid/mqtt-event-grid-namespace-terminology#namespace) can contain multiple **namespace topics**. Publishers publish events to namespace topics. More on creating namespace topics can be found here: [link](https://learn.microsoft.com/azure/event-grid/create-view-manage-namespace-topics). +An Event Grid namespace is a container for multiple types of resources, including [**namespace topics**][ms_topic]: +- A [**namespace topic**][ms_topic] contains CloudEvents that you publish, via [Client.PublishCloudEvents][godoc_client_publish]. +- A [**topic subscription**][ms_subscription], associated with a single topic, can be used to receive events via [Client.ReceiveEvents][godoc_client_receive]. -To receive events, you must create an **event subscription**, which is associated with a topic. More on creating topic subscriptions can be found here: [link](https://learn.microsoft.com/azure/event-grid/create-view-manage-event-subscriptions). +Namespaces also offer access using MQTT, although that is not covered in this package. # Examples @@ -110,5 +114,18 @@ Azure SDK for Go is licensed under the [MIT](https://github.com/Azure/azure-sdk- [cloud_shell_bash]: https://shell.azure.com/bash [source]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/messaging/azeventgrid [godoc]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid +[godoc_client]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/#Client +[godoc_client_publish]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid#Client.PublishCloudEvents +[godoc_client_receive]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid#Client.ReceiveCloudEvents [godoc_examples]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid#pkg-examples [godoc_example_newclient]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid#example-NewClientWithSharedKeyCredential +[ms_pulldelivery]: https://learn.microsoft.com/azure/event-grid/concepts-pull-delivery +[ms_namespace]: https://learn.microsoft.com/azure/event-grid/concepts-pull-delivery#namespaces +[ms_topic]: https://learn.microsoft.com/azure/event-grid/concepts-pull-delivery#namespace-topics +[ms_subscription]: https://learn.microsoft.com/azure/event-grid/concepts-pull-delivery#event-subscriptions +[ms_create_namespace]: https://learn.microsoft.com/azure/event-grid/create-view-manage-namespaces +[ms_create_topic]: https://learn.microsoft.com/azure/event-grid/create-view-manage-namespace-topics + + +[godoc_publisher_client]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/#Client + diff --git a/sdk/messaging/azeventgrid/ci.yml b/sdk/messaging/azeventgrid/ci.yml index b3cfa6b86fd7..ee4ef0cbf4e3 100644 --- a/sdk/messaging/azeventgrid/ci.yml +++ b/sdk/messaging/azeventgrid/ci.yml @@ -23,8 +23,13 @@ pr: - sdk/messaging/azeventgrid stages: - - template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml - parameters: - ServiceDirectory: "messaging/azeventgrid" - RunLiveTests: true - Location: westus2 +- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml + parameters: + ServiceDirectory: "messaging/azeventgrid" + RunLiveTests: true + Location: westus2 + EnvVars: + AZURE_CLIENT_ID: $(AZEVENTGRID_CLIENT_ID) + AZURE_TENANT_ID: $(AZEVENTGRID_TENANT_ID) + AZURE_CLIENT_SECRET: $(AZEVENTGRID_CLIENT_SECRET) + AZURE_SUBSCRIPTION_ID: $(AZEVENTGRID_SUBSCRIPTION_ID) diff --git a/sdk/messaging/azeventgrid/client_custom.go b/sdk/messaging/azeventgrid/client_custom.go index cba474c18d50..792880944132 100644 --- a/sdk/messaging/azeventgrid/client_custom.go +++ b/sdk/messaging/azeventgrid/client_custom.go @@ -1,5 +1,3 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. //go:build go1.18 // +build go1.18 @@ -16,6 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/internal" ) // ClientOptions contains optional settings for [Client] @@ -30,7 +29,7 @@ func NewClientWithSharedKeyCredential(endpoint string, key string, options *Clie } // TODO: I believe we're supposed to allow for dynamically updating the key at any time as well. - azc, err := azcore.NewClient(moduleName+".Client", moduleVersion, runtime.PipelineOptions{ + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ PerRetry: []policy.Policy{ &skpolicy{Key: key}, }, diff --git a/sdk/messaging/azeventgrid/client_test.go b/sdk/messaging/azeventgrid/client_test.go index 98d8ed0235ca..75768f9798d7 100644 --- a/sdk/messaging/azeventgrid/client_test.go +++ b/sdk/messaging/azeventgrid/client_test.go @@ -75,6 +75,8 @@ func TestFailedAck(t *testing.T) { }) t.Run("ReleaseCloudEvents", func(t *testing.T) { + t.Skipf("Skipping, server-bug preventing release from working properly. https://github.com/Azure/azure-sdk-for-go/issues/21530") + resp, err := c.ReleaseCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, azeventgrid.ReleaseOptions{ LockTokens: []string{*recvResp.Value[0].BrokerProperties.LockToken}, }, nil) @@ -169,6 +171,8 @@ func TestReject(t *testing.T) { } func TestRelease(t *testing.T) { + t.Skipf("Skipping, server-bug preventing release from working properly. https://github.com/Azure/azure-sdk-for-go/issues/21530") + c := newClientWrapper(t, nil) ce, err := messaging.NewCloudEvent("TestAbandon", "world", []byte("event one"), nil) @@ -188,6 +192,14 @@ func TestRelease(t *testing.T) { LockTokens: []string{*events.Value[0].BrokerProperties.LockToken}, }, nil) require.NoError(t, err) + + if len(rejectResp.FailedLockTokens) > 0 { + for _, flt := range rejectResp.FailedLockTokens { + t.Logf("FailedLockToken:\n ec: %s\n desc: %s\n locktoken:%s", *flt.ErrorCode, *flt.ErrorDescription, *flt.LockToken) + } + require.Fail(t, "Failed to release events") + } + require.Empty(t, rejectResp.FailedLockTokens) events, err = c.ReceiveCloudEvents(context.Background(), c.TestVars.Topic, c.TestVars.Subscription, nil) diff --git a/sdk/messaging/azeventgrid/example_test.go b/sdk/messaging/azeventgrid/example_test.go index cffb5045ffaa..b4eb649f3aa4 100644 --- a/sdk/messaging/azeventgrid/example_test.go +++ b/sdk/messaging/azeventgrid/example_test.go @@ -4,8 +4,13 @@ package azeventgrid_test import ( + "context" + "fmt" + "log" "os" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid" ) @@ -13,11 +18,125 @@ func ExampleNewClientWithSharedKeyCredential() { endpoint := os.Getenv("EVENTGRID_ENDPOINT") sharedKey := os.Getenv("EVENTGRID_KEY") + if endpoint == "" || sharedKey == "" { + return + } + client, err := azeventgrid.NewClientWithSharedKeyCredential(endpoint, sharedKey, nil) if err != nil { - panic(err) + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) } _ = client // ignore + + // Output: +} + +func ExampleClient_PublishCloudEvents() { + client := getEventGridClient() + + if client == nil { + return + } + + topic := os.Getenv("EVENTGRID_TOPIC") + + // CloudEvent is in github.com/Azure/azure-sdk-for-go/azcore/messaging and can be + // used to transport + + // you can send a variety of different payloads, all of which can be encoded by messaging.CloudEvent + var payloads = []any{ + []byte{1, 2, 3}, + "hello world", + struct{ Value string }{Value: "hello world"}, + } + + var eventsToSend []messaging.CloudEvent + + for _, payload := range payloads { + event, err := messaging.NewCloudEvent("source", "eventType", payload, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + eventsToSend = append(eventsToSend, event) + } + + _, err := client.PublishCloudEvents(context.TODO(), topic, eventsToSend, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + // Output: +} + +func ExampleClient_ReceiveCloudEvents() { + client := getEventGridClient() + + if client == nil { + return + } + + topic := os.Getenv("EVENTGRID_TOPIC") + subscription := os.Getenv("EVENTGRID_SUBSCRIPTION") + + resp, err := client.ReceiveCloudEvents(context.TODO(), topic, subscription, &azeventgrid.ReceiveCloudEventsOptions{ + MaxEvents: to.Ptr[int32](1), + MaxWaitTime: to.Ptr[int32](10), // in seconds + }) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + for _, rd := range resp.Value { + lockToken := rd.BrokerProperties.LockToken + + // NOTE: See the documentation for CloudEvent.Data on how your data + // is deserialized. + data := rd.Event.Data + + fmt.Fprintf(os.Stderr, "Event ID:%s, data: %#v, lockToken: %s\n", rd.Event.ID, data, *lockToken) + + // This will complete the message, deleting it from the subscription. + resp, err := client.AcknowledgeCloudEvents(context.TODO(), topic, subscription, azeventgrid.AcknowledgeOptions{ + LockTokens: []string{*lockToken}, + }, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + if len(resp.FailedLockTokens) > 0 { + log.Fatalf("ERROR: %d events were not acknowledged", len(resp.FailedLockTokens)) + } + } + + // Output: +} + +func getEventGridClient() *azeventgrid.Client { + endpoint := os.Getenv("EVENTGRID_ENDPOINT") + sharedKey := os.Getenv("EVENTGRID_KEY") + + if endpoint == "" || sharedKey == "" { + return nil + } + + client, err := azeventgrid.NewClientWithSharedKeyCredential(endpoint, sharedKey, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + return client } diff --git a/sdk/messaging/azeventgrid/go.mod b/sdk/messaging/azeventgrid/go.mod index cb97f6ef0718..54b5b8130d15 100644 --- a/sdk/messaging/azeventgrid/go.mod +++ b/sdk/messaging/azeventgrid/go.mod @@ -4,17 +4,25 @@ go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.2 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.7.0 ) require ( + github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dnaeon/go-vcr v1.1.0 // indirect + github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/sdk/messaging/azeventgrid/go.sum b/sdk/messaging/azeventgrid/go.sum index 7ee44ebcb89d..ef899bd51d95 100644 --- a/sdk/messaging/azeventgrid/go.sum +++ b/sdk/messaging/azeventgrid/go.sum @@ -1,29 +1,46 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.2 h1:klbj076a7qZCoxAMac7nv/6RTLGDgojM6trEFcYQUyI= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.2/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdk/messaging/azeventgrid/version.go b/sdk/messaging/azeventgrid/internal/version.go similarity index 64% rename from sdk/messaging/azeventgrid/version.go rename to sdk/messaging/azeventgrid/internal/version.go index 774168cbcd39..52321e86dadc 100644 --- a/sdk/messaging/azeventgrid/version.go +++ b/sdk/messaging/azeventgrid/internal/version.go @@ -6,13 +6,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package azeventgrid +package internal // Constants to identify the module const ( - // moduleName is the module name that shows in telemetry. - moduleName = "azeventgrid" + // ModuleName is the module name that shows in telemetry. + ModuleName = "azeventgrid" - // moduleVersion is the semantic version (see http://semver.org) of this module. - moduleVersion = "v0.1.1" + // ModuleVersion is the semantic version (see http://semver.org) of this module. + ModuleVersion = "v0.2.0" ) diff --git a/sdk/messaging/azeventgrid/publisher/README.md b/sdk/messaging/azeventgrid/publisher/README.md new file mode 100644 index 000000000000..873680e7be8d --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/README.md @@ -0,0 +1,134 @@ +# Azure Event Grid Publisher Client Module for Go + +[Azure Event Grid](https://learn.microsoft.com/azure/event-grid/overview) is a highly scalable, fully managed Pub Sub message distribution service that offers flexible message consumption patterns. For more information about Event Grid see: [link](https://learn.microsoft.com/azure/event-grid/overview). + +The client in this package can publish events to [Event Grid topics](https://learn.microsoft.com/azure/event-grid/concepts). + +> NOTE: This client does NOT work with Event Grid namespaces. Use the [Client][godoc_client] in the root package of this module instead. + +Key links: +- [Source code][source] +- [API Reference Documentation][godoc] +- [Product documentation](https://azure.microsoft.com/services/event-grid/) +- [Samples][godoc_examples] + +## Getting started + +### Install the package + +Install the Azure Event Grid client module for Go with `go get`: + +```bash +go get github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid +``` + +### Prerequisites + +- Go, version 1.18 or higher +- An [Azure subscription](https://azure.microsoft.com/free/) +- An Event Grid topic. You can create an Event Grid topic using the [Azure Portal](https://learn.microsoft.com/azure/event-grid/custom-event-quickstart-portal). + +### Authenticate the client + +Event Grid publisher clients authenticate using either: +- A TokenCredential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclient]. +- A shared key credential. An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclientsk] +- A Shared Access Signature (SAS). An example of that can be viewed here: [ExampleNewClientWithSharedKeyCredential][godoc_example_newclientsas]. + +# Key concepts + +The client in this package can publish events to Azure Event Grid topics. Topics are published to using the [publisher.Client][godoc_publisher_client]. The topic +you publish to will be configured to accept events of a certain format: EventGrid, CloudEvent or Custom. Separate functions are available on the publisher client for each format. + +# Examples + +Examples for various scenarios can be found on [pkg.go.dev][godoc_examples] or in the example*_test.go files in our GitHub repo for [azeventgrid][gh]. + +# Troubleshooting + +### Logging + +This module uses the classification-based logging implementation in `azcore`. To enable console logging for all SDK modules, set the environment variable `AZURE_SDK_GO_LOGGING` to `all`. + +Use the `azcore/log` package to control log event output. + +```go +import ( + "fmt" + azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" +) + +// print log output to stdout +azlog.SetListener(func(event azlog.Event, s string) { + fmt.Printf("[%s] %s\n", event, s) +}) +``` + +# Next steps + +More sample code should go here, along with links out to the appropriate example tests. + +## Contributing +For details on contributing to this repository, see the [contributing guide][azure_sdk_for_go_contributing]. + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +### Additional Helpful Links for Contributors +Many people all over the world have helped make this project better. You'll want to check out: + +* [What are some good first issues for new contributors to the repo?](https://github.com/azure/azure-sdk-for-go/issues?q=is%3Aopen+is%3Aissue+label%3A%22up+for+grabs%22) +* [How to build and test your change][azure_sdk_for_go_contributing_developer_guide] +* [How you can make a change happen!][azure_sdk_for_go_contributing_pull_requests] +* Frequently Asked Questions (FAQ) and Conceptual Topics in the detailed [Azure SDK for Go wiki](https://github.com/azure/azure-sdk-for-go/wiki). + + +### Reporting security issues and security bugs + +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) . You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue). + +### License + +Azure SDK for Go is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/template/aztemplate/LICENSE.txt) license. + + +[azure_sdk_for_go_contributing]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md +[azure_sdk_for_go_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md#developer-guide +[azure_sdk_for_go_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md#pull-requests +[azure_cli]: https://docs.microsoft.com/cli/azure +[azure_pattern_circuit_breaker]: https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker +[azure_pattern_retry]: https://docs.microsoft.com/azure/architecture/patterns/retry +[azure_portal]: https://portal.azure.com +[azure_sub]: https://azure.microsoft.com/free/ +[cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview +[cloud_shell_bash]: https://shell.azure.com/bash +[source]: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/messaging/azeventgrid +[godoc_client]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid#Client + + +[godoc]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[godoc_examples]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[godoc_publisher_client]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[godoc_example_newclient]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[godoc_example_newclientsk]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[godoc_example_newclientsas]: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/ +[gh]: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventgrid/ + + diff --git a/sdk/messaging/azeventgrid/publisher/autorest.md b/sdk/messaging/azeventgrid/publisher/autorest.md new file mode 100644 index 000000000000..40d584e8aa39 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/autorest.md @@ -0,0 +1,76 @@ +## Go + +``` yaml +title: EventGridPublisherClient +description: Azure Event Grid client +generated-metadata: false +clear-output-folder: false +go: true +input-file: + - https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/eventgrid/data-plane/Microsoft.EventGrid/stable/2018-01-01/EventGrid.json +license-header: MICROSOFT_MIT_NO_VERSION +openapi-type: "data-plane" +output-folder: ../publisher +override-client-name: Client +security: "AADToken" +use: "@autorest/go@4.0.0-preview.52" +version: "^3.0.0" +slice-elements-byval: true +remove-non-reference-schema: true +directive: + # make the endpoint a parameter of the client constructor + - from: swagger-document + where: $["x-ms-parameterized-host"] + transform: $.parameters[0]["x-ms-parameter-location"] = "client" + # reference azcore/messaging/CloudEvent + - from: client.go + where: $ + transform: return $.replace(/\[\]CloudEvent/g, "[]messaging.CloudEvent"); + - from: client.go + where: $ + transform: return $.replace(/func \(client \*Client\) PublishCloudEventEvents\(/g, "func (client *Client) internalPublishCloudEventEvents("); + - from: swagger-document + where: $.definitions.CloudEventEvent + transform: $["x-ms-external"] = true + # delete client name prefix from method options and response types + - from: + - client.go + - models.go + - response_types.go + - options.go + where: $ + transform: return $.replace(/Client(\w+)((?:Options|Response))/g, "$1$2"); + # delete some models that look like they're system events... + - from: models.go + where: $ + transform: return $.replace(/\/\/ (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, "") + - from: models_serde.go + where: $ + transform: | + return $ + .replace(/\/\/ MarshalJSON implements the json.Marshaller interface for type (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, "") + .replace(/\/\/ UnmarshalJSON implements the json.Unmarshaller interface for type (SubscriptionDeletedEventData|SubscriptionValidationEventData|SubscriptionValidationResponse).+?\n}/gs, ""); + - from: + - models.go + - client.go + - response_types.go + - options.go + where: $ + transform: return $.replace(/CloudEventEvent/g, "CloudEvent"); + - from: + - models.go + - models_serde.go + - client.go + - response_types.go + - options.go + where: $ + transform: return $.replace(/EventGridEvent/g, "Event"); + - from: + - client.go + where: $ + transform: | + return $.replace( + /(func \(client \*Client\) publishCloudEventsCreateRequest.+?)return req, nil/s, + '$1\nreq.Raw().Header.Set("Content-type", "application/cloudevents-batch+json; charset=utf-8")\nreturn req, nil'); + +``` diff --git a/sdk/messaging/azeventgrid/publisher/build.go b/sdk/messaging/azeventgrid/publisher/build.go new file mode 100644 index 000000000000..f7ba4c25d332 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/build.go @@ -0,0 +1,10 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +//go:generate autorest ./autorest.md +//go:generate goimports -w . + +package publisher diff --git a/sdk/messaging/azeventgrid/publisher/client.go b/sdk/messaging/azeventgrid/publisher/client.go new file mode 100644 index 000000000000..fe49d37e78f2 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/client.go @@ -0,0 +1,147 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" +) + +// Client contains the methods for the Client group. +// Don't use this type directly, use a constructor function instead. +type Client struct { + internal *azcore.Client + endpoint string +} + +// PublishCloudEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents +// method. +func (client *Client) internalPublishCloudEvents(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + var err error + req, err := client.publishCloudEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishCloudEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishCloudEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishCloudEventsResponse{}, err + } + return PublishCloudEventsResponse{}, nil +} + +// publishCloudEventsCreateRequest creates the PublishCloudEvents request. +func (client *Client) publishCloudEventsCreateRequest(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if options != nil && options.AegChannelName != nil { + req.Raw().Header["aeg-channel-name"] = []string{*options.AegChannelName} + } + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + + req.Raw().Header.Set("Content-type", "application/cloudevents-batch+json; charset=utf-8") + return req, nil +} + +// PublishCustomEventEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishCustomEventEventsOptions contains the optional parameters for the Client.PublishCustomEventEvents +// method. +func (client *Client) PublishCustomEventEvents(ctx context.Context, events []any, options *PublishCustomEventEventsOptions) (PublishCustomEventEventsResponse, error) { + var err error + req, err := client.publishCustomEventEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishCustomEventEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishCustomEventEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishCustomEventEventsResponse{}, err + } + return PublishCustomEventEventsResponse{}, nil +} + +// publishCustomEventEventsCreateRequest creates the PublishCustomEventEvents request. +func (client *Client) publishCustomEventEventsCreateRequest(ctx context.Context, events []any, options *PublishCustomEventEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + return req, nil +} + +// PublishEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - PublishEventsOptions contains the optional parameters for the Client.PublishEvents method. +func (client *Client) PublishEvents(ctx context.Context, events []Event, options *PublishEventsOptions) (PublishEventsResponse, error) { + var err error + req, err := client.publishEventsCreateRequest(ctx, events, options) + if err != nil { + return PublishEventsResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return PublishEventsResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return PublishEventsResponse{}, err + } + return PublishEventsResponse{}, nil +} + +// publishEventsCreateRequest creates the PublishEvents request. +func (client *Client) publishEventsCreateRequest(ctx context.Context, events []Event, options *PublishEventsOptions) (*policy.Request, error) { + req, err := runtime.NewRequest(ctx, http.MethodPost, client.endpoint) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2018-01-01") + req.Raw().URL.RawQuery = reqQP.Encode() + if err := runtime.MarshalAsJSON(req, events); err != nil { + return nil, err + } + return req, nil +} diff --git a/sdk/messaging/azeventgrid/publisher/client_custom.go b/sdk/messaging/azeventgrid/publisher/client_custom.go new file mode 100644 index 000000000000..6db2c74a2bae --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/client_custom.go @@ -0,0 +1,122 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +package publisher + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/internal" +) + +// ClientOptions contains optional settings for [Client] +type ClientOptions struct { + azcore.ClientOptions +} + +var tokenScopes = []string{"https://eventgrid.azure.net/.default"} + +// NewClient creates a [Client] that authenticates using a TokenCredential. +func NewClient(endpoint string, tokenCredential azcore.TokenCredential, options *ClientOptions) (*Client, error) { + if options == nil { + options = &ClientOptions{} + } + + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + runtime.NewBearerTokenPolicy(tokenCredential, tokenScopes, nil), + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// NewClientWithSharedKeyCredential creates a [Client] using a shared key. +func NewClientWithSharedKeyCredential(endpoint string, key string, options *ClientOptions) (*Client, error) { + const sasKeyHeader = "aeg-sas-key" + + if options == nil { + options = &ClientOptions{} + } + + // TODO: I believe we're supposed to allow for dynamically updating the key at any time as well. + azc, err := azcore.NewClient(internal.ModuleName+".Client", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + // TODO: Java has a specific policy for this kind of authentication. + // AzureKeyCredentialPolicy + &skpolicy{HeaderName: sasKeyHeader, Key: key}, + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// NewClientWithSharedKeyCredential creates a [Client] using a shared key. +func NewClientWithSAS(endpoint string, sas string, options *ClientOptions) (*Client, error) { + const sasTokenHeader = "aeg-sas-token" + + if options == nil { + options = &ClientOptions{} + } + + // TODO: I believe we're supposed to allow for dynamically updating the key at any time as well. + azc, err := azcore.NewClient(internal.ModuleName+".PublisherClient", internal.ModuleVersion, runtime.PipelineOptions{ + PerRetry: []policy.Policy{ + // TODO: Java has a specific policy for this kind of authentication. + // AzureKeyCredentialPolicy + &skpolicy{HeaderName: sasTokenHeader, Key: sas}, + }, + }, &options.ClientOptions) + + if err != nil { + return nil, err + } + + return &Client{ + internal: azc, + endpoint: endpoint, + }, nil +} + +// TODO: remove in favor of a common policy instead? +type skpolicy struct { + Key string + HeaderName string +} + +func (p *skpolicy) Do(req *policy.Request) (*http.Response, error) { + req.Raw().Header.Add(p.HeaderName, p.Key) + return req.Next() +} + +// PublishCloudEvents - Publishes a batch of events to an Azure Event Grid topic. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2018-01-01 +// - events - An array of events to be published to Event Grid. +// - options - ClientPublishCloudEventEventsOptions contains the optional parameters for the Client.PublishCloudEvents +// method. +func (client *Client) PublishCloudEvents(ctx context.Context, events []messaging.CloudEvent, options *PublishCloudEventsOptions) (PublishCloudEventsResponse, error) { + return client.internalPublishCloudEvents(ctx, events, options) +} diff --git a/sdk/messaging/azeventgrid/publisher/client_test.go b/sdk/messaging/azeventgrid/publisher/client_test.go new file mode 100644 index 000000000000..071cd323f1c3 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/client_test.go @@ -0,0 +1,129 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package publisher_test + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/url" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/publisher" + "github.com/stretchr/testify/require" +) + +// TestPublishEvent publishes an event using the EventGrid format. +func TestPublishEvent(t *testing.T) { + vars := newTestVars(t) + + testPublish := func(t *testing.T, client *publisher.Client) { + _, err := client.PublishEvents(context.Background(), []publisher.Event{ + { + Data: map[string]string{ + "hello": "world", + }, + Subject: to.Ptr("subjectA"), + EventType: to.Ptr("eventType"), + ID: to.Ptr("id"), + EventTime: to.Ptr(time.Now()), + DataVersion: to.Ptr("1.0"), + }, + }, nil) + require.NoError(t, err) + } + + t.Run("sas", func(t *testing.T) { + sas := generateSAS(vars.EG.Endpoint, vars.EG.Key) + client, err := publisher.NewClientWithSAS(vars.EG.Endpoint, sas, newClientOptionsForTest(t, vars.EG)) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("sharedkey", func(t *testing.T) { + client, err := publisher.NewClientWithSharedKeyCredential(vars.EG.Endpoint, vars.EG.Key, newClientOptionsForTest(t, vars.EG)) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("tokencredential", func(t *testing.T) { + // note you need the "Event Grid sender" role. + cred, err := azidentity.NewDefaultAzureCredential(nil) + require.NoError(t, err) + + client, err := publisher.NewClient(vars.EG.Endpoint, cred, newClientOptionsForTest(t, vars.EG)) + require.NoError(t, err) + testPublish(t, client) + }) +} + +// TestPublishCloudEvent publishes an event using the CloudEvent format. +func TestPublishCloudEvent(t *testing.T) { + vars := newTestVars(t) + + testPublish := func(t *testing.T, client *publisher.Client) { + ce, err := messaging.NewCloudEvent("source", "eventType", map[string]string{ + "hello": "world", + }, nil) + require.NoError(t, err) + + _, err = client.PublishCloudEvents(context.Background(), []messaging.CloudEvent{ce}, nil) + require.NoError(t, err) + } + + t.Run("sas", func(t *testing.T) { + sas := generateSAS(vars.CE.Endpoint, vars.CE.Key) + client, err := publisher.NewClientWithSAS(vars.CE.Endpoint, sas, newClientOptionsForTest(t, vars.CE)) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("sharedkey", func(t *testing.T) { + client, err := publisher.NewClientWithSharedKeyCredential(vars.CE.Endpoint, vars.CE.Key, newClientOptionsForTest(t, vars.CE)) + require.NoError(t, err) + testPublish(t, client) + }) + + t.Run("tokencredential", func(t *testing.T) { + tokenCred, err := azidentity.NewDefaultAzureCredential(nil) + require.NoError(t, err) + + client, err := publisher.NewClient(vars.CE.Endpoint, tokenCred, newClientOptionsForTest(t, vars.CE)) + require.NoError(t, err) + testPublish(t, client) + }) +} + +func generateSAS(endpoint string, sharedKey string) string { + ttl := time.Now().UTC().Add(time.Hour).Format(time.RFC3339) + text := fmt.Sprintf("r=%s&e=%s", url.QueryEscape(endpoint), url.QueryEscape(ttl)) + + decodedKey, err := base64.StdEncoding.DecodeString(sharedKey) + + if err != nil { + panic(err) + } + + h := hmac.New(sha256.New, []byte(decodedKey)) + _, err = h.Write([]byte(text)) + + if err != nil { + panic(err) + } + + b64Sig := base64.StdEncoding.EncodeToString(h.Sum(nil)) + sig := url.QueryEscape(b64Sig) + + sas := fmt.Sprintf("%s&s=%s", text, sig) + return sas +} diff --git a/sdk/messaging/azeventgrid/publisher/example_newclient_test.go b/sdk/messaging/azeventgrid/publisher/example_newclient_test.go new file mode 100644 index 000000000000..6da65335039d --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/example_newclient_test.go @@ -0,0 +1,98 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package publisher_test + +import ( + "fmt" + "log" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/publisher" +) + +func ExampleNewClient() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + + if endpoint == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // DefaultAzureCredential is a simplified credential type that tries to authenticate via several + // different authentication mechanisms. For more control (or more credential types) see the documentation + // for the azidentity module: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity + tokenCred, err := azidentity.NewDefaultAzureCredential(nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + client, err := publisher.NewClient(endpoint, tokenCred, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} + +func ExampleNewClientWithSAS() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + client, err := publisher.NewClientWithSharedKeyCredential(endpoint, key, &publisher.ClientOptions{ + ClientOptions: policy.ClientOptions{ + PerCallPolicies: []policy.Policy{ + dumpFullPolicy{"EventGridEvent"}, + }, + }, + }) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} + +func ExampleNewClientWithSharedKeyCredential() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + client, err := publisher.NewClientWithSharedKeyCredential(endpoint, key, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + _ = client + + // Output: +} diff --git a/sdk/messaging/azeventgrid/publisher/example_publish_topic_test.go b/sdk/messaging/azeventgrid/publisher/example_publish_topic_test.go new file mode 100644 index 000000000000..324eefea2005 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/example_publish_topic_test.go @@ -0,0 +1,105 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package publisher_test + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/messaging" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/publisher" +) + +// PublishEvents publishes events using the EventGrid schema to a topic. The +// topic must be configured to use the EventGrid schema or this will fail. +func ExampleClient_PublishEvents() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // Other authentication methods: + // - publisher.NewClient(): authenticate using a TokenCredential from azidentity. + // - publisher.NewClientWithSAS(): authenticate using a SAS token. + client, err := publisher.NewClientWithSharedKeyCredential(endpoint, key, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + events := []publisher.Event{ + { + Data: "data for this event", + DataVersion: to.Ptr("1.0"), + EventType: to.Ptr("event-type"), + EventTime: to.Ptr(time.Now()), + ID: to.Ptr("unique-id"), + Subject: to.Ptr("subject"), + }, + } + + _, err = client.PublishEvents(context.TODO(), events, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + // Output: +} + +// PublishCloudEvents publishes events using the CloudEvent schema to a topic. The +// topic must be configured to use the CloudEvent schema or this will fail. +func ExampleClient_PublishCloudEvents() { + // ex: https://..eventgrid.azure.net/api/events + endpoint := os.Getenv("EVENTGRID_CE_TOPIC_ENDPOINT") + key := os.Getenv("EVENTGRID_CE_TOPIC_KEY") + + if endpoint == "" || key == "" { + fmt.Fprintf(os.Stderr, "Skipping example, environment variables missing\n") + return + } + + // Other authentication methods: + // - publisher.NewClient(): authenticate using a TokenCredential from azidentity. + // - publisher.NewClientWithSAS(): authenticate using a SAS token. + client, err := publisher.NewClientWithSharedKeyCredential(endpoint, key, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + cloudEvent, err := messaging.NewCloudEvent("source", "eventtype", "data", nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + events := []messaging.CloudEvent{ + cloudEvent, + } + + _, err = client.PublishCloudEvents(context.TODO(), events, nil) + + if err != nil { + // TODO: Update the following line with your application specific error handling logic + log.Fatalf("ERROR: %s", err) + } + + // Output: +} diff --git a/sdk/messaging/azeventgrid/publisher/main_test.go b/sdk/messaging/azeventgrid/publisher/main_test.go new file mode 100644 index 000000000000..e9a7d8f3b9c7 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/main_test.go @@ -0,0 +1,28 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package publisher_test + +import ( + "log" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/joho/godotenv" +) + +func TestMain(m *testing.M) { + if err := recording.ResetProxy(nil); err != nil { + panic(err) + } + + if err := godotenv.Load("../.env"); err != nil { + log.Printf("Failed to load .env file, no integration tests will run: %s", err) + } + + os.Exit(m.Run()) +} diff --git a/sdk/messaging/azeventgrid/publisher/models.go b/sdk/messaging/azeventgrid/publisher/models.go new file mode 100644 index 000000000000..e5f6cb73bc6d --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/models.go @@ -0,0 +1,38 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +import "time" + +// Event - Properties of an event published to an Event Grid topic using the EventGrid Schema. +type Event struct { + // REQUIRED; Event data specific to the event type. + Data any + + // REQUIRED; The schema version of the data object. + DataVersion *string + + // REQUIRED; The time (in UTC) the event was generated. + EventTime *time.Time + + // REQUIRED; The type of the event that occurred. + EventType *string + + // REQUIRED; An unique identifier for the event. + ID *string + + // REQUIRED; A resource path relative to the topic path. + Subject *string + + // The resource path of the event source. + Topic *string + + // READ-ONLY; The schema version of the event metadata. + MetadataVersion *string +} diff --git a/sdk/messaging/azeventgrid/publisher/models_serde.go b/sdk/messaging/azeventgrid/publisher/models_serde.go new file mode 100644 index 000000000000..a869e292026a --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/models_serde.go @@ -0,0 +1,102 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +// MarshalJSON implements the json.Marshaller interface for type Event. +func (e Event) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populateAny(objectMap, "data", e.Data) + populate(objectMap, "dataVersion", e.DataVersion) + populateTimeRFC3339(objectMap, "eventTime", e.EventTime) + populate(objectMap, "eventType", e.EventType) + populate(objectMap, "id", e.ID) + populate(objectMap, "metadataVersion", e.MetadataVersion) + populate(objectMap, "subject", e.Subject) + populate(objectMap, "topic", e.Topic) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type Event. +func (e *Event) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "data": + err = unpopulate(val, "Data", &e.Data) + delete(rawMsg, key) + case "dataVersion": + err = unpopulate(val, "DataVersion", &e.DataVersion) + delete(rawMsg, key) + case "eventTime": + err = unpopulateTimeRFC3339(val, "EventTime", &e.EventTime) + delete(rawMsg, key) + case "eventType": + err = unpopulate(val, "EventType", &e.EventType) + delete(rawMsg, key) + case "id": + err = unpopulate(val, "ID", &e.ID) + delete(rawMsg, key) + case "metadataVersion": + err = unpopulate(val, "MetadataVersion", &e.MetadataVersion) + delete(rawMsg, key) + case "subject": + err = unpopulate(val, "Subject", &e.Subject) + delete(rawMsg, key) + case "topic": + err = unpopulate(val, "Topic", &e.Topic) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", e, err) + } + } + return nil +} + +func populate(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else if !reflect.ValueOf(v).IsNil() { + m[k] = v + } +} + +func populateAny(m map[string]any, k string, v any) { + if v == nil { + return + } else if azcore.IsNullValue(v) { + m[k] = nil + } else { + m[k] = v + } +} + +func unpopulate(data json.RawMessage, fn string, v any) error { + if data == nil { + return nil + } + if err := json.Unmarshal(data, v); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + return nil +} diff --git a/sdk/messaging/azeventgrid/publisher/options.go b/sdk/messaging/azeventgrid/publisher/options.go new file mode 100644 index 000000000000..6921d3122e70 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/options.go @@ -0,0 +1,25 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +// PublishCloudEventsOptions contains the optional parameters for the Client.PublishCloudEvents method. +type PublishCloudEventsOptions struct { + // Required only when publishing to partner namespaces with partner topic routing mode ChannelNameHeader. + AegChannelName *string +} + +// PublishCustomEventEventsOptions contains the optional parameters for the Client.PublishCustomEventEvents method. +type PublishCustomEventEventsOptions struct { + // placeholder for future optional parameters +} + +// PublishEventsOptions contains the optional parameters for the Client.PublishEvents method. +type PublishEventsOptions struct { + // placeholder for future optional parameters +} diff --git a/sdk/messaging/azeventgrid/publisher/response_types.go b/sdk/messaging/azeventgrid/publisher/response_types.go new file mode 100644 index 000000000000..6d7800606036 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/response_types.go @@ -0,0 +1,24 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +// PublishCloudEventsResponse contains the response from method Client.PublishCloudEvents. +type PublishCloudEventsResponse struct { + // placeholder for future response values +} + +// PublishCustomEventEventsResponse contains the response from method Client.PublishCustomEventEvents. +type PublishCustomEventEventsResponse struct { + // placeholder for future response values +} + +// PublishEventsResponse contains the response from method Client.PublishEvents. +type PublishEventsResponse struct { + // placeholder for future response values +} diff --git a/sdk/messaging/azeventgrid/publisher/shared_test.go b/sdk/messaging/azeventgrid/publisher/shared_test.go new file mode 100644 index 000000000000..0b75560c07b2 --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/shared_test.go @@ -0,0 +1,177 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package publisher_test + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httputil" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventgrid/publisher" +) + +type topicVars struct { + Name string + Key string + Endpoint string +} + +type eventGridVars struct { + // EG are connection variables for an EventGrid encoded topic. + EG topicVars + // CE are connection variables for a CloudEvent encoded topic. + CE topicVars +} + +// TODO: will get back to this when adding in recordings. +// var fakeVars topicVars = topicVars{ +// Name: "faketopic", +// Key: "fakekey", +// Endpoint: "https://localhost/fake-endpoint", +// } + +func newTestVars(t *testing.T) eventGridVars { + // TODO: will get back to this when adding in recordings. + // if recording.GetRecordMode() != recording.LiveMode { + // return eventGridVars{ + // EG: fakeVars, + // CE: fakeVars, + // } + // } + + egVars := eventGridVars{ + EG: topicVars{Name: os.Getenv("EVENTGRID_TOPIC_NAME"), + Key: os.Getenv("EVENTGRID_TOPIC_KEY"), + Endpoint: os.Getenv("EVENTGRID_TOPIC_ENDPOINT"), + }, + CE: topicVars{Name: os.Getenv("EVENTGRID_CE_TOPIC_NAME"), + Key: os.Getenv("EVENTGRID_CE_TOPIC_KEY"), + Endpoint: os.Getenv("EVENTGRID_CE_TOPIC_ENDPOINT"), + }, + } + + for _, v := range []topicVars{egVars.EG, egVars.CE} { + if v.Endpoint == "" || v.Key == "" || v.Name == "" { + t.Logf("WARNING: not enabling `publisher` integration tests, environment variables not set") + t.Skip() + break + } + } + + return egVars +} + +func newClientOptionsForTest(t *testing.T, tv topicVars) *publisher.ClientOptions { + // TODO: will get back to this when adding in recordings. + // if recording.GetRecordMode() != recording.LiveMode { + // return &publisher.ClientOptions{ + // ClientOptions: azcore.ClientOptions{ + // Transport: newRecordingTransporter(t, tv), + // }, + // } + // } + + return nil +} + +type dumpFullPolicy struct { + Prefix string +} + +func (p dumpFullPolicy) Do(req *policy.Request) (*http.Response, error) { + fmt.Printf("\n\n===> BEGIN: REQUEST (%s) <===\n\n", p.Prefix) + + requestBytes, err := httputil.DumpRequestOut(req.Raw(), false) + + if err != nil { + return nil, err + } + + fmt.Println(string(requestBytes)) + fmt.Printf("Body: %s\n", string(FormatRequestBytes(req.Raw()))) + fmt.Printf("\n\n===> END: REQUEST (%s)<===\n\n", p.Prefix) + + resp, err := req.Next() + + if err != nil { + return nil, err + } + + fmt.Printf("\n\n===> BEGIN: RESPONSE (%s) <===\n\n", p.Prefix) + + responseBytes, err := httputil.DumpResponse(resp, false) + + if err != nil { + return nil, err + } + + fmt.Println(string(responseBytes)) + fmt.Printf("Body: %s\n", string(FormatResponseBytes(resp))) + + fmt.Printf("\n\n===> END: RESPONSE (%s) <===\n\n", p.Prefix) + return resp, err +} + +func FormatRequestBytes(req *http.Request) []byte { + if req.Body == nil { + return nil + } + + requestBytes, err := io.ReadAll(req.Body) + + if err != nil { + panic(err) + } + + req.Body = io.NopCloser(bytes.NewBuffer(requestBytes)) + return FormatBytes(requestBytes) +} + +func FormatResponseBytes(resp *http.Response) []byte { + requestBytes, err := io.ReadAll(resp.Body) + + if err != nil { + panic(err) + } + + resp.Body = io.NopCloser(bytes.NewBuffer(requestBytes)) + return FormatBytes(requestBytes) +} + +func FormatBytes(body []byte) []byte { + var m *map[string]any + var l *[]any + + candidates := []any{&m, &l} + + for _, v := range candidates { + err := json.Unmarshal(body, v) + + if err != nil { + continue + } + + if err == nil { + formattedBytes, err := json.MarshalIndent(v, " ", " ") + + if err != nil { + continue + } + + return formattedBytes + } + } + + // if we can't format it we'll just give it back. + return body +} diff --git a/sdk/messaging/azeventgrid/publisher/time_rfc3339.go b/sdk/messaging/azeventgrid/publisher/time_rfc3339.go new file mode 100644 index 000000000000..687ac8c2f8ea --- /dev/null +++ b/sdk/messaging/azeventgrid/publisher/time_rfc3339.go @@ -0,0 +1,87 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package publisher + +import ( + "encoding/json" + "fmt" + "reflect" + "regexp" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" +) + +const ( + utcLayoutJSON = `"2006-01-02T15:04:05.999999999"` + utcLayout = "2006-01-02T15:04:05.999999999" + rfc3339JSON = `"` + time.RFC3339Nano + `"` +) + +// Azure reports time in UTC but it doesn't include the 'Z' time zone suffix in some cases. +var tzOffsetRegex = regexp.MustCompile(`(Z|z|\+|-)(\d+:\d+)*"*$`) + +type timeRFC3339 time.Time + +func (t timeRFC3339) MarshalJSON() (json []byte, err error) { + tt := time.Time(t) + return tt.MarshalJSON() +} + +func (t timeRFC3339) MarshalText() (text []byte, err error) { + tt := time.Time(t) + return tt.MarshalText() +} + +func (t *timeRFC3339) UnmarshalJSON(data []byte) error { + layout := utcLayoutJSON + if tzOffsetRegex.Match(data) { + layout = rfc3339JSON + } + return t.Parse(layout, string(data)) +} + +func (t *timeRFC3339) UnmarshalText(data []byte) (err error) { + layout := utcLayout + if tzOffsetRegex.Match(data) { + layout = time.RFC3339Nano + } + return t.Parse(layout, string(data)) +} + +func (t *timeRFC3339) Parse(layout, value string) error { + p, err := time.Parse(layout, strings.ToUpper(value)) + *t = timeRFC3339(p) + return err +} + +func populateTimeRFC3339(m map[string]any, k string, t *time.Time) { + if t == nil { + return + } else if azcore.IsNullValue(t) { + m[k] = nil + return + } else if reflect.ValueOf(t).IsNil() { + return + } + m[k] = (*timeRFC3339)(t) +} + +func unpopulateTimeRFC3339(data json.RawMessage, fn string, t **time.Time) error { + if data == nil || strings.EqualFold(string(data), "null") { + return nil + } + var aux timeRFC3339 + if err := json.Unmarshal(data, &aux); err != nil { + return fmt.Errorf("struct field %s: %v", fn, err) + } + *t = (*time.Time)(&aux) + return nil +} diff --git a/sdk/messaging/azeventgrid/test-resources.bicep b/sdk/messaging/azeventgrid/test-resources.bicep index 7297d1b5ba56..5459b26f6ba7 100644 --- a/sdk/messaging/azeventgrid/test-resources.bicep +++ b/sdk/messaging/azeventgrid/test-resources.bicep @@ -4,9 +4,19 @@ param baseName string = resourceGroup().name @description('The resource location') param location string = resourceGroup().location +@description('The client OID to grant access to test resources.') +param testApplicationOid string + +output RESOURCE_GROUP string = resourceGroup().name +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId + +// +// [BEGIN] Event Grid namespace +// + var namespaceName = '${baseName}-2' -var topicName = 'testtopic1' -var subscriptionName = 'testsubscription1' +var nsTopicName = 'testtopic1' +var nsSubscriptionName = 'testsubscription1' resource ns_resource 'Microsoft.EventGrid/namespaces@2023-06-01-preview' = { name: namespaceName @@ -23,7 +33,7 @@ resource ns_resource 'Microsoft.EventGrid/namespaces@2023-06-01-preview' = { resource ns_testtopic1 'Microsoft.EventGrid/namespaces/topics@2023-06-01-preview' = { parent: ns_resource - name: topicName + name: nsTopicName properties: { publisherType: 'Custom' inputSchema: 'CloudEventSchemaV1_0' @@ -33,7 +43,7 @@ resource ns_testtopic1 'Microsoft.EventGrid/namespaces/topics@2023-06-01-preview resource ns_testtopic1_testsubscription1 'Microsoft.EventGrid/namespaces/topics/eventSubscriptions@2023-06-01-preview' = { parent: ns_testtopic1 - name: subscriptionName + name: nsSubscriptionName properties: { deliveryConfiguration: { deliveryMode: 'Queue' @@ -51,11 +61,67 @@ resource ns_testtopic1_testsubscription1 'Microsoft.EventGrid/namespaces/topics/ } // https://learn.microsoft.com/en-us/rest/api/eventgrid/controlplane-version2023-06-01-preview/namespaces/list-shared-access-keys?tabs=HTTP +#disable-next-line outputs-should-not-contain-secrets // (this is just how our test deployments work) output EVENTGRID_KEY string = listKeys(resourceId('Microsoft.EventGrid/namespaces', namespaceName), '2023-06-01-preview').key1 // TODO: get this formatted properly output EVENTGRID_ENDPOINT string = 'https://${ns_resource.properties.topicsConfiguration.hostname}' -output EVENTGRID_TOPIC string = topicName -output EVENTGRID_SUBSCRIPTION string = subscriptionName -output RESOURCE_GROUP string = resourceGroup().name -output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId +output EVENTGRID_TOPIC string = nsTopicName +output EVENTGRID_SUBSCRIPTION string = nsSubscriptionName + +// [END] Event Grid namespace + +// +// [BEGIN] Event Grid topics (publisher) +// + +resource egTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = { + name: '${baseName}-eg' + location: location + kind: 'Azure' + properties: { + inputSchema: 'EventGridSchema' + } +} + +resource ceTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = { + name: '${baseName}-ce' + location: location + kind: 'Azure' + properties: { + inputSchema: 'CloudEventSchemaV1_0' + } +} + +resource egContributorRole 'Microsoft.Authorization/roleAssignments@2018-01-01-preview' = { + name: guid('egContributorRoleId${baseName}') + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1e241071-0855-49ea-94dc-649edcd759de') + // roleDefinitionId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/1e241071-0855-49ea-94dc-649edcd759de' + principalId: testApplicationOid + } +} + +resource egDataSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('egSenderRoleId${baseName}') + scope: resourceGroup() + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7') + principalId: testApplicationOid + } +} + +output EVENTGRID_TOPIC_NAME string = egTopic.name +#disable-next-line outputs-should-not-contain-secrets // (this is just how our test deployments work) +output EVENTGRID_TOPIC_KEY string = egTopic.listKeys().key1 +output EVENTGRID_TOPIC_ENDPOINT string = egTopic.properties.endpoint + +output EVENTGRID_CE_TOPIC_NAME string = ceTopic.name +#disable-next-line outputs-should-not-contain-secrets // (this is just how our test deployments work) +output EVENTGRID_CE_TOPIC_KEY string = ceTopic.listKeys().key1 +output EVENTGRID_CE_TOPIC_ENDPOINT string = ceTopic.properties.endpoint + +// +// [END] Event Grid topics (publisher) +//