├── .github ├── CODEOWNERS └── policies │ └── resourceManagement.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── RELEASE_PROCESS.md ├── SECURITY.md ├── azure-pipelines.yml ├── eng ├── cd │ ├── official-release.yml │ └── templates │ │ ├── predeployment │ │ ├── approve.yml │ │ ├── validate-maven.yml │ │ └── validate-nuget.yml │ │ └── publish.yml └── ci │ ├── code-mirror.yml │ ├── official-build.yml │ ├── public-linux-build.yml │ ├── public-windows-build.yml │ └── templates │ ├── official │ ├── build-extension.yml │ └── build-java-library.yml │ └── public │ ├── build-java-library.yml │ └── build-test-extension.yml ├── extension ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ └── settings.json ├── WebJobs.Extensions.RabbitMQ.Samples │ ├── .editorconfig │ ├── Program.cs │ ├── RabbitMQSamples.cs │ ├── SamplesTypeLocator.cs │ └── WebJobs.Extensions.RabbitMQ.Samples.csproj ├── WebJobs.Extensions.RabbitMQ.Tests │ ├── .editorconfig │ ├── BasicDeliverEventArgsValueProviderTests.cs │ ├── PocoToBytesConverterTests.cs │ ├── RabbitMQAsyncCollectorTests.cs │ ├── RabbitMQClientBuilderTests.cs │ ├── RabbitMQConfigProviderTests.cs │ ├── RabbitMQExtensionConfigProviderTests.cs │ ├── RabbitMQOptionsTests.cs │ ├── RabbitMQTriggerBindingProviderTests.cs │ ├── RabbitMQTriggerBindingTests.cs │ ├── Trigger │ │ └── RabbitMQListenerTests.cs │ ├── UtilityTests.cs │ ├── WebJobs.Extensions.RabbitMQ.Tests.csproj │ └── testappsettings.json ├── WebJobs.Extensions.RabbitMQ.sln ├── WebJobs.Extensions.RabbitMQ │ ├── Bindings │ │ ├── RabbitMQAsyncCollector.cs │ │ └── RabbitMQClientBuilder.cs │ ├── Config │ │ ├── DefaultRabbitMQServiceFactory.cs │ │ ├── IRabbitMQServiceFactory.cs │ │ ├── PocoToBytesConverter.cs │ │ ├── RabbitMQExtensionConfigProvider.cs │ │ ├── RabbitMQOptions.cs │ │ └── RabbitMQWebJobsBuilderExtensions.cs │ ├── Constants.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── PublicKey.snk │ ├── RabbitMQAttribute.cs │ ├── RabbitMQContext.cs │ ├── RabbitMQWebJobsStartup.cs │ ├── Services │ │ ├── IRabbitMQService.cs │ │ └── RabbitMQService.cs │ ├── Trigger │ │ ├── BasicDeliverEventArgsValueProvider.cs │ │ ├── RabbitMQActivitySource.cs │ │ ├── RabbitMQListener.cs │ │ ├── RabbitMQMessageActions.cs │ │ ├── RabbitMQTriggerAttribute.cs │ │ ├── RabbitMQTriggerAttributeBindingProvider.cs │ │ ├── RabbitMQTriggerBinding.cs │ │ ├── RabbitMQTriggerMetrics.cs │ │ └── RabbitMQTriggerParameterDescriptor.cs │ ├── Utility.cs │ ├── WebJobs.Extensions.RabbitMQ.csproj │ └── webjobs.png └── stylecop.json ├── global.json └── java-library ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── mvnBuild.bat ├── pom.xml └── src ├── main └── java │ └── com │ └── microsoft │ └── azure │ └── functions │ └── rabbitmq │ └── annotation │ ├── RabbitMQOutput.java │ └── RabbitMQTrigger.java └── test └── java └── com └── microsoft └── azure └── functions └── rabbitmq └── annotation ├── RabbitMQOutputTests.java └── RabbitMQTriggerTests.java /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo. 2 | * @vivekjilla @aloiva @manikantanallagatla -------------------------------------------------------------------------------- /.github/policies/resourceManagement.yml: -------------------------------------------------------------------------------- 1 | id: 2 | name: GitOps.PullRequestIssueManagement 3 | description: GitOps.PullRequestIssueManagement primitive 4 | owner: 5 | resource: repository 6 | disabled: false 7 | where: 8 | configuration: 9 | resourceManagementConfiguration: 10 | scheduledSearches: 11 | - description: 12 | frequencies: 13 | - hourly: 14 | hour: 3 15 | filters: 16 | - isIssue 17 | - isOpen 18 | - hasLabel: 19 | label: 'Needs: Author Feedback' 20 | - hasLabel: 21 | label: no recent activity 22 | - noActivitySince: 23 | days: 3 24 | actions: 25 | - closeIssue 26 | - description: 27 | frequencies: 28 | - hourly: 29 | hour: 3 30 | filters: 31 | - isIssue 32 | - isOpen 33 | - hasLabel: 34 | label: 'Needs: Author Feedback' 35 | - noActivitySince: 36 | days: 4 37 | - isNotLabeledWith: 38 | label: no recent activity 39 | actions: 40 | - addLabel: 41 | label: no recent activity 42 | - addReply: 43 | reply: This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**. 44 | - description: 45 | frequencies: 46 | - hourly: 47 | hour: 3 48 | filters: 49 | - isIssue 50 | - isOpen 51 | - hasLabel: 52 | label: duplicate 53 | - noActivitySince: 54 | days: 3 55 | actions: 56 | - addReply: 57 | reply: This issue has been marked as duplicate and has not had any activity for **3 days**. It will be closed for housekeeping purposes. 58 | - closeIssue 59 | - description: 60 | frequencies: 61 | - hourly: 62 | hour: 3 63 | filters: 64 | - isPullRequest 65 | - isOpen 66 | - hasLabel: 67 | label: 'Needs: Author Feedback' 68 | - hasLabel: 69 | label: no recent activity 70 | - noActivitySince: 71 | days: 7 72 | actions: 73 | - closeIssue 74 | - description: 75 | frequencies: 76 | - hourly: 77 | hour: 3 78 | filters: 79 | - isPullRequest 80 | - isOpen 81 | - hasLabel: 82 | label: 'Needs: Author Feedback' 83 | - noActivitySince: 84 | days: 7 85 | - isNotLabeledWith: 86 | label: no recent activity 87 | actions: 88 | - addLabel: 89 | label: no recent activity 90 | - addReply: 91 | reply: This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **7 days**. It will be closed if no further activity occurs **within 7 days of this comment**. 92 | eventResponderTasks: 93 | - if: 94 | - payloadType: Issue_Comment 95 | - isAction: 96 | action: Created 97 | - isActivitySender: 98 | issueAuthor: True 99 | - hasLabel: 100 | label: 'Needs: Author Feedback' 101 | then: 102 | - addLabel: 103 | label: 'Needs: Attention :wave:' 104 | - removeLabel: 105 | label: 'Needs: Author Feedback' 106 | description: 107 | - if: 108 | - payloadType: Issues 109 | - not: 110 | isAction: 111 | action: Closed 112 | - hasLabel: 113 | label: no recent activity 114 | then: 115 | - removeLabel: 116 | label: no recent activity 117 | description: 118 | - if: 119 | - payloadType: Issues 120 | - isAction: 121 | action: Closed 122 | - hasLabel: 123 | label: 'Needs: Triage (Functions)' 124 | then: 125 | - removeLabel: 126 | label: 'Needs: Triage (Functions)' 127 | description: 128 | - if: 129 | - payloadType: Issue_Comment 130 | - hasLabel: 131 | label: no recent activity 132 | then: 133 | - removeLabel: 134 | label: no recent activity 135 | description: 136 | - if: 137 | - payloadType: Pull_Request_Review 138 | - isAction: 139 | action: Submitted 140 | - isReviewState: 141 | reviewState: Changes_requested 142 | then: 143 | - addLabel: 144 | label: 'Needs: Author Feedback' 145 | description: 146 | - if: 147 | - payloadType: Pull_Request 148 | - isActivitySender: 149 | issueAuthor: True 150 | - not: 151 | isAction: 152 | action: Closed 153 | - hasLabel: 154 | label: 'Needs: Author Feedback' 155 | then: 156 | - removeLabel: 157 | label: 'Needs: Author Feedback' 158 | description: 159 | - if: 160 | - payloadType: Issue_Comment 161 | - isActivitySender: 162 | issueAuthor: True 163 | - hasLabel: 164 | label: 'Needs: Author Feedback' 165 | then: 166 | - removeLabel: 167 | label: 'Needs: Author Feedback' 168 | description: 169 | - if: 170 | - payloadType: Pull_Request_Review 171 | - isActivitySender: 172 | issueAuthor: True 173 | - hasLabel: 174 | label: 'Needs: Author Feedback' 175 | then: 176 | - removeLabel: 177 | label: 'Needs: Author Feedback' 178 | description: 179 | - if: 180 | - payloadType: Pull_Request 181 | - not: 182 | isAction: 183 | action: Closed 184 | - hasLabel: 185 | label: no recent activity 186 | then: 187 | - removeLabel: 188 | label: no recent activity 189 | description: 190 | - if: 191 | - payloadType: Issue_Comment 192 | - hasLabel: 193 | label: no recent activity 194 | then: 195 | - removeLabel: 196 | label: no recent activity 197 | description: 198 | - if: 199 | - payloadType: Pull_Request_Review 200 | - hasLabel: 201 | label: no recent activity 202 | then: 203 | - removeLabel: 204 | label: no recent activity 205 | description: 206 | - if: 207 | - payloadType: Pull_Request 208 | - hasLabel: 209 | label: auto merge 210 | then: 211 | - enableAutoMerge: 212 | mergeMethod: Squash 213 | description: 214 | - if: 215 | - payloadType: Pull_Request 216 | - labelRemoved: 217 | label: auto merge 218 | then: 219 | - disableAutoMerge 220 | description: 221 | onFailure: 222 | onSuccess: 223 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ Extension for Azure Functions 2 | 3 | [![Build Status](https://azfunc.visualstudio.com/public/_apis/build/status%2Fazure%2Fazure-functions-rabbitmq-extension%2Frabbitmq-extension-linux.public?repoName=Azure%2Fazure-functions-rabbitmq-extension&branchName=dev)](https://azfunc.visualstudio.com/public/_build/latest?definitionId=807&repoName=Azure%2Fazure-functions-rabbitmq-extension&branchName=dev) 4 | 5 | This repository hosts RabbitMQ trigger and output bindings to interact with RabbitMQ in your [Azure Functions](https://azure.microsoft.com/services/functions/) 6 | and [WebJobs](https://learn.microsoft.com/azure/app-service/webjobs-sdk-how-to). More specifically, the trigger binding enables invoking a function when a message arrives at the RabbitMQ queue. The triggered function can consume this message and take required action. Similarly, the output binding facilitates publishing of messages on the RabbitMQ queue. 7 | 8 | ## Usage 9 | 10 | The following example shows a [C# function](https://learn.microsoft.com/azure/azure-functions/functions-dotnet-class-library) that gets invoked (by virtue of the trigger binding) when a message is added to a RabbitMQ queue named `inputQueue`. The function then logs the message string, composes an output message and returns it. This value is then published to the queue named `outputQueue` through the output binding. The example function dictates that the connection URI for the RabbitMQ service is the one with key `RabbitMqConnectionString` in the [Application Settings](https://learn.microsoft.com/azure/azure-functions/functions-develop-local#local-settings-file). 11 | 12 | ```cs 13 | [FunctionName("RabbitMqExample")] 14 | [return: RabbitMQ(QueueName = "outputQueue", ConnectionStringSetting = "RabbitMqConnectionString")] 15 | public static string Run( 16 | [RabbitMQTrigger(queueName: "inputQueue" ConnectionStringSetting = "RabbitMqConnectionString")] string name, 17 | ILogger logger) 18 | { 19 | logger.LogInformation($"Message received: {name}."); 20 | return $"Hello, {name}."; 21 | } 22 | ``` 23 | 24 | Along with `string` type, the extension also allows binding to the input arguments and returned values of `byte[]` type, POCO objects, and `BasicDeliverEventArgs` type. The last type is particularly useful for fetching of RabbitMQ message headers and other message properties. See the [repository wiki](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki) for detailed samples of bindings to different types. 25 | 26 | ## Getting Started 27 | 28 | Before working with the RabbitMQ extension, you must [set up your RabbitMQ endpoint](https://www.rabbitmq.com/download.html). Then you can get started by following the sample functions in [C#](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki/Samples-in-C%23), [C# Script](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki/Samples-in-CSX), [JavaScript](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki/Samples-in-JavaScript), [Python](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki/Samples-in-Python) or [Java](https://github.com/Azure/azure-functions-rabbitmq-extension/wiki/Samples-in-Java). 29 | 30 | To learn about creating an application that works with RabbitMQ, see the [getting started](https://www.rabbitmq.com/getstarted.html) page. For general documentation on .NET RabbitMQ client usage, see the [.NET/C# client API guide](https://www.rabbitmq.com/dotnet-api-guide.html). 31 | 32 | ## C# Attributes 33 | 34 | The following C# attributes are common to both RabbitMQ trigger and output bindings. 35 | 36 | | Attribute Name | Type | Description | 37 | |---|---|---| 38 | | `ConnectionStringSetting` | `string` | The setting name for RabbitMQ connection URI. An example setting value would be `amqp://user:pass@host:10000/vhost`. | 39 | | `DisableCertificateValidation` | `bool` | Indicates whether certificate validation should be disabled. Not recommended for production. Does not apply when SSL is disabled. | 40 | | `QueueName` | `string` | The RabbitMQ queue name. | 41 | 42 | ## Java Annotations 43 | 44 | The following Java annotations are common to both RabbitMQ trigger and output bindings. 45 | 46 | | Annotation Name | Type | Description | 47 | |---|---|---| 48 | | `connectionStringSetting` | `String` | The setting name for RabbitMQ connection URI. An example setting value would be `amqp://user:pass@host:10000/vhost`. | 49 | | `dataType` | `String` | Defines how the Functions runtime should treat the parameter value. Possible values are `""`, `"string"` and `"binary"`. | 50 | | `disableCertificateValidation` | `boolean` | Indicates whether certificate validation should be disabled. Not recommended for production. Does not apply when SSL is disabled. | 51 | | `queueName` | `String` | The RabbitMQ queue name. | 52 | 53 | ## Further Reading 54 | 55 | Please refer to the Microsoft Docs page on [RabbitMQ bindings for Azure Functions overview](https://learn.microsoft.com/azure/azure-functions/functions-bindings-rabbitmq). It contains install instructions for all the supported programming languages, information on setting up and configuring the function app, and the list of Azure App Service plans that support hosting of the function apps with RabbitMQ bindings. 56 | 57 | ## Contributing 58 | 59 | 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. 60 | 61 | 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 repositories using our CLA. 62 | 63 | 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. 64 | -------------------------------------------------------------------------------- /RELEASE_PROCESS.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | > **Note:** The steps below are only meant to be followed by contributors having write permission to the repository and having access to Microsoft internal resources. The tag name `v2.0.0-preview` and the version number `2.0.0-preview` should be replaced to match with the package that is about to be released. The commands were run from PowerShell 7.2.5. 4 | 5 | 1. Choose the branch/commit as per your requirement. Create a tag and push it to GitHub repository. The tag should match patterns like `v2.0.0`, `v2.0.0-beta`, `v2.0.0-preview2`, etc. Tags like `2.0.0`, `v2.0` or `v2.0.0beta` will result in failed builds. 6 | 7 | ```console 8 | > git fetch origin 9 | > git tag v2.0.0-preview origin/dev 10 | > git push origin v2.0.0-preview 11 | ``` 12 | 13 | 1. This should trigger a release build. The build progress can be viewed on [RabbitMQ Extension Build](https://dev.azure.com/azfunc/Azure%20Functions/_build?definitionId=48) page. 14 | 15 | 1. Ensure that the build completes successfully. Check that there are no warning messages and the build artifacts are created in below tree format. If you find any problems, please get them fixed first and restart the process. 16 | 17 | ```text 18 | / 19 | ├───drop-extension 20 | │ └───2.0.0-preview 21 | │ ├───Microsoft.Azure.WebJobs.Extensions.RabbitMQ.2.0.0-preview.nupkg 22 | │ ├───Microsoft.Azure.WebJobs.Extensions.RabbitMQ.2.0.0-preview.symbols.nupkg 23 | │ └───_manifest 24 | │ └─── 25 | └───drop-java-library 26 | └───2.0.0-preview 27 | ├───_manifest 28 | │ └─── 29 | ├───azure-functions-java-library-rabbitmq-2.0.0-preview-javadoc.jar 30 | ├───azure-functions-java-library-rabbitmq-2.0.0-preview-sources.jar 31 | ├───azure-functions-java-library-rabbitmq-2.0.0-preview.jar 32 | └───azure-functions-java-library-rabbitmq-2.0.0-preview.pom 33 | ``` 34 | 35 | 1. Download the build artifacts, including the NuGet and JAR file. 36 | 37 | 1. Test the NuGet package. 38 | 39 | 1. Add package to local NuGet feed. 40 | 41 | ```console 42 | > nuget sources Add -Name 'local' -Source 'C:\Source\nuget' 43 | > nuget add 'C:\Users\\Downloads\drop-extension\2.0.0-preview\Microsoft.Azure.WebJobs.Extensions.RabbitMQ.2.0.0-preview.nupkg' -Source 'C:\Source\nuget' 44 | ``` 45 | 46 | 1. Update the package reference in your test application project such as in [this sample](https://github.com/JatinSanghvi/rabbitmq-functionapp). 47 | 48 | ```xml 49 | 50 | ``` 51 | 52 | 1. Follow the steps in the sample repository's Readme file. Make sure that the assembly gets restored and the test application runs as expected. 53 | 54 | 1. Test the Java library. 55 | 56 | 1. Add library files to local Maven repository. 57 | 58 | ```console 59 | > Copy-Item 'C:\Users\\Downloads\drop-java-library\2.0.0-preview' 'C:\Users\\.m2\repository\com\microsoft\azure\functions\azure-functions-java-library-rabbitmq\' -Recurse 60 | ``` 61 | 62 | 1. Update the package reference in your test application project such as in [this sample](https://github.com/JatinSanghvi/rabbitmq-java-functionapp). 63 | 64 | - **`pom.xml`** 65 | 66 | ```xml 67 | 68 | com.microsoft.azure.functions 69 | azure-functions-java-library-rabbitmq 70 | 2.0.0-preview 71 | 72 | ``` 73 | 74 | - **`extensions.csproj`** 75 | 76 | ```xml 77 | 78 | ``` 79 | 80 | 1. Follow the steps in the sample repository's Readme file. Make sure that the assembly and java library gets restored and the test application runs as expected. 81 | 82 | 1. Perform cleanup. This will ensure that the tests to be run after publishing the packages will download the packages from public sources instead of copying them directly from the cache. 83 | 84 | ```console 85 | > Remove-Item 'C:\Users\\.m2\repository\com\microsoft\azure\functions\azure-functions-java-library-rabbitmq\2.0.0-preview' -Recurse 86 | > Remove-Item 'C:\Source\nuget\microsoft.azure.webjobs.extensions.rabbitmq\2.0.0-preview' -Recurse 87 | > nuget sources Remove -Name 'local' 88 | ``` 89 | 90 | 1. Create a new release using [RabbitMQ Extension Release](https://dev.azure.com/azfunc/Azure%20Functions/_release?definitionId=63) pipeline. 91 | 92 | 1. Make sure that the `Version` input matches the title of the release build whose artifacts you have tested. 93 | 1. The release will await manual approval before it can resume. The approvers are supposed to ensure that the build artifacts point to correct branch (e.g. `refs/tags/v2.0.0-preview`). 94 | 95 | The release pipeline has two stages: *NuGet Publish* and *Maven Publish*. Both of them leverage Azure SDK Partner Release Pipeline ([Wiki](https://dev.azure.com/azure-sdk/internal/_wiki/wikis/internal.wiki/1/Partner-Release-Pipeline)) to publish the packages to respective locations. Our release will trigger [net - partner-release](https://dev.azure.com/azure-sdk/internal/_build?definitionId=4442) pipeline in *NuGet Publish* stage and [java - partner-release](https://dev.azure.com/azure-sdk/internal/_build?definitionId=1809) pipeline in *Maven Publish* stage. Both stages will wait for the respective pipelines to complete successfully before proceeding. The stages should fail if the corresponding partner release pipeline has failed. 96 | 97 | 1. (Only if required) If the release fails, check the release logs to know if the failure is caused by expiration of the personal access token (PAT) used to initiate the partner release pipeline. If that is the case: 98 | 99 | 1. Make sure you have access to run the partner pipelines. The instructions can be found in the Partner Release Pipeline Wiki. 100 | 1. Open [Personal Access Tokens](https://dev.azure.com/azure-sdk/_usersSettings/tokens) page on Azure DevOps site for *azure-sdk* organization. 101 | 1. Generate a new token having access to *azure-sdk* organization and with *Read, write, execute & manage Release* scope. 102 | 1. Update the release pipeline's `Azure SDK Release PAT` variable value to the new token. 103 | 104 | 1. Ensure that the packages are published. 105 | 106 | 1. NuGet package should be available on [NuGet Gallery](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.RabbitMQ). 107 | 1. Maven artifacts should be available on [Nexus Repository Manager](https://oss.sonatype.org/#nexus-search;quick~azure-functions-java-library-rabbitmq). 108 | 109 | It may take few days for Maven artifacts to be listed on [MVN Repository](https://mvnrepository.com/artifact/com.microsoft.azure.functions/azure-functions-java-library-rabbitmq) and on [Maven Central Repository](https://search.maven.org/artifact/com.microsoft.azure.functions/azure-functions-java-library-rabbitmq) but that should not be a concern; the artifacts should be available for consumption. 110 | 111 | 1. Repeat the step to test the NuGet package, but with the package sourced from public NuGet gallery this time. 112 | 1. Repeat the step to test the Java library sourced from Maven Central this time. 113 | 114 | 1. Publish the release on GitHub. 115 | 116 | 1. Open [Draft a new release](https://github.com/Azure/azure-functions-rabbitmq-extension/releases/new) page. 117 | 1. Select the release tag. 118 | 1. Set the release title same as the tag name. 119 | 1. Click on the *Auto-generate release notes* to generate the bottom-section of the release description. 120 | 1. For the top-section, provide release summary and enlist the improvements, bug fixes and breaking changes that went into the release. 121 | 1. Try to maintain consistency with [v2.0.0-preview release notes](https://github.com/Azure/azure-functions-rabbitmq-extension/releases/tag/v2.0.0-preview). 122 | 1. If it is a pre-release, tick the *This is a pre-release* checkbox before publishing the release. 123 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | 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. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | branches: 3 | include: 4 | - dev 5 | - v*.x 6 | 7 | schedules: 8 | - cron: '0 12 * * 0' 9 | displayName: Weekly Sunday build 10 | branches: 11 | include: 12 | - dev 13 | always: true 14 | 15 | trigger: 16 | branches: 17 | include: 18 | - dev 19 | - v*.x 20 | tags: 21 | include: 22 | - v*.*.* 23 | 24 | variables: 25 | # It is a release build if it is triggered by pushing a tag. 26 | isReleaseBuild: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }} 27 | 28 | ${{ if eq(variables.isReleaseBuild, 'True') }}: 29 | agentOSes: Windows 30 | ${{ else }}: 31 | agentOSes: Windows,Linux 32 | 33 | prefix: $[format('0.{0:yyyy}.{0:MMdd}', pipeline.startTime)] 34 | version: $[format('{0}.{1}', variables.prefix, counter(variables.prefix, 1))] # e.g. 0.2001.0203.4 35 | fileVersion: $[variables.version] 36 | Codeql.Enabled: true 37 | 38 | jobs: 39 | - ${{ each agentOS in split(variables.agentOSes, ',') }}: 40 | - job: buildExtension${{ agentOS }} 41 | displayName: WebJobs Extension (${{ agentOS }}) 42 | 43 | pool: 44 | ${{ if eq(variables.isReleaseBuild, 'True') }}: 45 | name: 1ES-Hosted-AzFunc 46 | demands: 47 | - ImageOverride -equals MMS2022TLS 48 | ${{ elseif eq(agentOS, 'Windows') }}: 49 | vmImage: windows-latest 50 | ${{ elseif eq(agentOS, 'Linux') }}: 51 | vmImage: ubuntu-latest 52 | 53 | steps: 54 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 55 | - powershell: | # Allow tags matching v1.2.3 and v1.2.3-xyz1 56 | $found = '$(Build.SourceBranchName)' | Select-String -Pattern '^v(((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))(?:-\w+)?)$' 57 | if (-not $found) { 58 | Write-Error "Found unexpected tag name: $(Build.SourceBranchName)." 59 | exit 1 60 | } 61 | Write-Host "##vso[task.setvariable variable=version]$($found.Matches.Groups[1].Value)" 62 | Write-Host "##vso[task.setvariable variable=fileVersion]$($found.Matches.Groups[2].Value)" 63 | 64 | displayName: Extract version # e.g. 1.2.3 65 | 66 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 67 | # .NET Core 2.1 SDK is required by ESRP Code Signing tasks. 68 | - task: UseDotNet@2 69 | displayName: Acquire .NET Core 2.1 SDK 70 | inputs: 71 | packageType: sdk 72 | version: 2.1.x 73 | performMultiLevelLookup: true 74 | 75 | - task: UseDotNet@2 76 | displayName: Acquire .NET 7.0 SDK 77 | inputs: 78 | packageType: sdk 79 | version: 7.0.x 80 | performMultiLevelLookup: true 81 | 82 | - task: DotNetCoreCLI@2 83 | displayName: Build solution 84 | inputs: 85 | command: build 86 | workingDirectory: extension 87 | arguments: --configuration Release -property:Version=$(fileVersion) -property:CommitHash=$(Build.SourceVersion) 88 | 89 | - task: DotNetCoreCLI@2 90 | displayName: Test extension 91 | inputs: 92 | command: test 93 | workingDirectory: extension 94 | arguments: --configuration Debug 95 | 96 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 97 | - task: EsrpCodeSigning@1 98 | displayName: Sign extension assembly 99 | inputs: 100 | connectedServiceName: ESRP Service 101 | folderPath: extension/WebJobs.Extensions.RabbitMQ/bin/Release/netstandard2.0 102 | pattern: Microsoft.Azure.WebJobs.Extensions.RabbitMQ.dll 103 | signConfigType: inlineSignParams 104 | inlineOperation: | 105 | [ 106 | { 107 | "KeyCode": "CP-230012", 108 | "OperationCode": "SigntoolSign", 109 | "Parameters": { 110 | "OpusName": "Microsoft", 111 | "OpusInfo": "http://www.microsoft.com", 112 | "FileDigest": "/fd \"SHA256\"", 113 | "PageHash": "/NPH", 114 | "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" 115 | }, 116 | "ToolName": "sign", 117 | "ToolVersion": "1.0" 118 | }, 119 | { 120 | "KeyCode": "CP-230012", 121 | "OperationCode": "SigntoolVerify", 122 | "Parameters": {}, 123 | "ToolName": "sign", 124 | "ToolVersion": "1.0" 125 | } 126 | ] 127 | 128 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 129 | - task: DotNetCoreCLI@2 130 | displayName: Pack extension 131 | inputs: 132 | command: pack 133 | searchPatternPack: extension/WebJobs.Extensions.RabbitMQ/WebJobs.Extensions.RabbitMQ.csproj 134 | configurationToPack: Release 135 | buildProperties: Version=$(version);CommitHash=$(Build.SourceVersion) 136 | outputDir: $(Build.ArtifactStagingDirectory)/$(version) 137 | nobuild: true 138 | includesymbols: true 139 | verbosityPack: minimal 140 | 141 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 142 | - task: EsrpCodeSigning@1 143 | displayName: Sign extension package 144 | inputs: 145 | connectedServiceName: ESRP Service 146 | folderPath: $(Build.ArtifactStagingDirectory)/$(version) 147 | pattern: Microsoft.Azure.WebJobs.Extensions.RabbitMQ.*.nupkg 148 | signConfigType: inlineSignParams 149 | inlineOperation: | 150 | [ 151 | { 152 | "KeyCode": "CP-401405", 153 | "OperationCode": "NuGetSign", 154 | "Parameters": {}, 155 | "ToolName": "sign", 156 | "ToolVersion": "1.0" 157 | }, 158 | { 159 | "KeyCode": "CP-401405", 160 | "OperationCode": "NuGetVerify", 161 | "Parameters": {}, 162 | "ToolName": "sign", 163 | "ToolVersion": "1.0" 164 | } 165 | ] 166 | 167 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 168 | - task: DeleteFiles@1 169 | displayName: Cleanup staging directory 170 | inputs: 171 | sourceFolder: $(Build.ArtifactStagingDirectory)/$(version) 172 | # contents: '!(Microsoft.Azure.WebJobs.Extensions.RabbitMQ.*.nupkg)' 173 | contents: CodeSignSummary-*.md 174 | 175 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 176 | - task: ManifestGeneratorTask@0 177 | displayName: Generate SBOM manifest 178 | inputs: 179 | buildDropPath: $(Build.ArtifactStagingDirectory)/$(version) 180 | packageName: Azure Functions RabbitMQ Extension 181 | packageVersion: $(version) 182 | 183 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 184 | - publish: $(Build.ArtifactStagingDirectory) 185 | displayName: Publish extension package 186 | artifact: drop-extension 187 | 188 | - job: buildJavaLibrary${{ agentOS }} 189 | displayName: Java Library (${{ agentOS }}) 190 | 191 | pool: 192 | ${{ if eq(variables.isReleaseBuild, 'True') }}: 193 | name: 1ES-Hosted-AzFunc 194 | demands: 195 | - ImageOverride -equals MMS2022TLS 196 | ${{ elseif eq(agentOS, 'Windows') }}: 197 | vmImage: windows-latest 198 | ${{ elseif eq(agentOS, 'Linux') }}: 199 | vmImage: ubuntu-latest 200 | 201 | steps: 202 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 203 | - powershell: | # Allow tags matching v1.2.3 and v1.2.3-xyz1 204 | $found = '$(Build.SourceBranchName)' | Select-String -Pattern '^v((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-\w+)?)$' 205 | if (-not $found) { 206 | Write-Error "Found unexpected tag name: $(Build.SourceBranchName)." 207 | exit 1 208 | } 209 | Write-Host "##vso[task.setvariable variable=version]$($found.Matches.Groups[1].Value)" 210 | 211 | displayName: Extract version # e.g. 1.2.3 212 | 213 | # A combination of --batch-mode and --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 214 | # options prevents spurious logging about downloading of Maven packages. 215 | - task: Maven@3 216 | displayName: Set library version 217 | inputs: 218 | mavenPomFile: java-library/pom.xml 219 | goals: versions:set 220 | options: --batch-mode --define=newVersion=$(version) --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 221 | 222 | - task: Maven@3 223 | displayName: Build library 224 | inputs: 225 | mavenPomFile: java-library/pom.xml 226 | options: --batch-mode --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 227 | 228 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 229 | - powershell: | 230 | $prefix = 'azure-functions-java-library-rabbitmq-$(version)' 231 | $source = 'java-library' 232 | $destination = '$(Build.ArtifactStagingDirectory)/$(version)' 233 | 234 | New-Item $destination -ItemType Directory 235 | Copy-Item "$source/pom.xml" "$destination/$prefix.pom" 236 | Copy-Item "$source/target/$prefix.jar" "$destination/$prefix.jar" 237 | Copy-Item "$source/target/$prefix-javadoc.jar" "$destination/$prefix-javadoc.jar" 238 | Copy-Item "$source/target/$prefix-sources.jar" "$destination/$prefix-sources.jar" 239 | 240 | displayName: Copy output files 241 | 242 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 243 | - task: ManifestGeneratorTask@0 244 | displayName: Generate SBOM manifest 245 | inputs: 246 | buildDropPath: $(Build.ArtifactStagingDirectory)/$(version) 247 | packageName: Azure Functions RabbitMQ Java Bindings 248 | packageVersion: $(version) 249 | 250 | - ${{ if eq(variables.isReleaseBuild, 'True') }}: 251 | - publish: $(Build.ArtifactStagingDirectory) 252 | displayName: Publish library package 253 | artifact: drop-java-library 254 | -------------------------------------------------------------------------------- /eng/cd/official-release.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | 3 | parameters: 4 | - name: nugetPackageName 5 | type: string 6 | displayName: "Base name of the Nuget package" 7 | default: "Microsoft.Azure.WebJobs.Extensions.RabbitMQ" 8 | - name: mavenPackageName 9 | type: string 10 | displayName: "Base name of the Maven package" 11 | default: "azure-functions-java-library-rabbitmq" 12 | - name: packageVersion 13 | displayName: "Release version (ex. 2.0.0, 1.0.1-preview)" 14 | type: string 15 | default: "" 16 | 17 | resources: 18 | pipelines: 19 | - pipeline: "_rabbitmq-extensionofficial" 20 | project: "internal" 21 | source: 'azure\azure-functions-rabbitmq-extension\rabbitmq-extension.official' 22 | repositories: 23 | - repository: 1ESPipelineTemplates 24 | type: git 25 | name: 1ESPipelineTemplates/1ESPipelineTemplates 26 | ref: refs/tags/release 27 | 28 | extends: 29 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 30 | parameters: 31 | pool: 32 | name: 1es-pool-azfunc 33 | image: 1es-windows-2022 34 | os: windows 35 | stages: 36 | - stage: ValidateAndApprove 37 | displayName: Validate and Approve Release 38 | jobs: 39 | # validates that version.nupkg and version.symbols files under version folder in drop-extension exist 40 | - template: /eng/cd/templates/predeployment/validate-nuget.yml@self 41 | parameters: 42 | nugetPackageBaseName: "${{ format('{0}.{1}', parameters.nugetPackageName, parameters.packageVersion) }}" 43 | nugetArtifactPath: "$(Build.ArtifactStagingDirectory)\\drop-extension" 44 | packageVersion: "${{ parameters.packageVersion }}" 45 | # validates that version-jar, version-pom files under version folder in drop-java-library exist 46 | - template: /eng/cd/templates/predeployment/validate-maven.yml@self 47 | parameters: 48 | mavenPackageBaseName: "${{ format('{0}-{1}', parameters.mavenPackageName, parameters.packageVersion) }}" 49 | mavenArtifactPath: "$(Build.ArtifactStagingDirectory)\\drop-java-library" 50 | packageVersion: "${{ parameters.packageVersion }}" 51 | # approval step sent to Azure Functions IDC 52 | - template: /eng/cd/templates/predeployment/approve.yml@self 53 | # stage to copy all nuget package files in drop-extension to partner storage account 54 | - template: /eng/cd/templates/publish.yml@self 55 | parameters: 56 | stageName: "NugetPublishStage" 57 | displayName: "Publish Nuget Package" 58 | artifactPath: "$(Build.ArtifactStagingDirectory)\\drop-extension\\${{ parameters.packageVersion }}" 59 | blobPath: "azure-functions-rabbitmq-extension/net/${{ parameters.packageVersion }}" 60 | # stage to copy all maven package files in drop-java-library to partner storage account 61 | - template: /eng/cd/templates/publish.yml@self 62 | parameters: 63 | stageName: "MavenPublishStage" 64 | displayName: "Publish Maven Package" 65 | artifactPath: "$(Build.ArtifactStagingDirectory)\\drop-java-library\\${{ parameters.packageVersion }}" 66 | blobPath: "azure-functions-rabbitmq-extension/java/${{ parameters.packageVersion }}" 67 | -------------------------------------------------------------------------------- /eng/cd/templates/predeployment/approve.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: ApproveRelease 3 | displayName: Pre-Deployment Approval 4 | condition: succeeded() 5 | timeoutInMinutes: 1440 6 | pool: server 7 | steps: 8 | - task: ManualValidation@1 9 | inputs: 10 | notifyUsers: |- 11 | [TEAM FOUNDATION]\Azure Functions IDC 12 | approvers: |- 13 | [TEAM FOUNDATION]\Azure Functions IDC 14 | allowApproversToApproveTheirOwnRuns: false 15 | -------------------------------------------------------------------------------- /eng/cd/templates/predeployment/validate-maven.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: mavenPackageBaseName # (e.g. azure-functions-java-library-rabbitmq-2.0.0) 3 | type: string 4 | - name: mavenArtifactPath # path to which pipeline artifact is copied 5 | type: string 6 | - name: packageVersion 7 | type: string 8 | 9 | jobs: 10 | - job: ValidateCopyMaven 11 | displayName: Validate Maven package files 12 | variables: 13 | mavenJarPath: "${{ parameters.mavenArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.mavenPackageBaseName }}.jar" 14 | mavenPomPath: "${{ parameters.mavenArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.mavenPackageBaseName }}.pom" 15 | mavenSourcesPath: "${{ parameters.mavenArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.mavenPackageBaseName }}-sources.jar" 16 | mavenJavadocPath: "${{ parameters.mavenArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.mavenPackageBaseName }}-javadoc.jar" 17 | templateContext: 18 | type: releaseJob 19 | isProduction: true 20 | inputs: 21 | - input: pipelineArtifact 22 | pipeline: "_rabbitmq-extensionofficial" 23 | artifactName: "drop-java-library" 24 | targetPath: "${{ parameters.mavenArtifactPath }}" 25 | steps: 26 | - task: PowerShell@2 27 | displayName: Display artifacts 28 | inputs: 29 | targetType: inline 30 | script: |- 31 | Write-Host "##[debug]Listing files in the workspace folder:" 32 | Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)" -Recurse 33 | Write-Host "##[debug]Listing files in the drop-java-library folder:" 34 | Get-ChildItem -Path "${{ parameters.mavenArtifactPath }}" 35 | - task: PowerShell@2 36 | displayName: "Check Maven Files Exist" 37 | inputs: 38 | targetType: inline 39 | script: | 40 | Write-Host "##[debug]Checking for $(mavenJarPath)..." 41 | if (-not (Test-Path -Path $(mavenJarPath))) { 42 | Write-Host "[error]Maven JAR file '$(mavenJarPath)' is missing." 43 | exit 1 44 | } else { 45 | Write-Host "Maven JAR file '$(mavenJarPath)' found." 46 | } 47 | 48 | Write-Host "##[debug]Checking for $(mavenPomPath)..." 49 | if (-not (Test-Path -Path $(mavenPomPath))) { 50 | Write-Host "[error]Maven POM file '$(mavenPomPath)' is missing." 51 | exit 1 52 | } else { 53 | Write-Host "Maven POM file '$(mavenPomPath)' found." 54 | } 55 | 56 | Write-Host "##[debug]Checking for $(mavenSourcesPath)..." 57 | if (-not (Test-Path -Path $(mavenSourcesPath))) { 58 | Write-Host "##vso[task.logissue type=warning;]Maven sources file '$(mavenSourcesPath)' is missing." 59 | } else { 60 | Write-Host "Maven sources file '$(mavenSourcesPath)' found." 61 | } 62 | 63 | Write-Host "##[debug]Checking for $(mavenJavadocPath)..." 64 | if (-not (Test-Path -Path $(mavenJavadocPath))) { 65 | Write-Host "##vso[task.logissue type=warning;]Maven javadoc file '$(mavenJavadocPath)' is missing." 66 | } else { 67 | Write-Host "Maven javadoc file '$(mavenJavadocPath)' found." 68 | } 69 | -------------------------------------------------------------------------------- /eng/cd/templates/predeployment/validate-nuget.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: nugetPackageBaseName # (e.g. Microsoft.Azure.WebJobs.Extensions.RabbitMQ.2.0.0) 3 | type: string 4 | - name: nugetArtifactPath # path to which pipeline artifact is copied 5 | type: string 6 | - name: packageVersion 7 | type: string 8 | 9 | jobs: 10 | - job: ValidateCopyNuget 11 | displayName: Validate NuGet package files 12 | variables: 13 | nupkgPath: "${{ parameters.nugetArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.nugetPackageBaseName }}.nupkg" 14 | nupkgSymbolsPath: "${{ parameters.nugetArtifactPath }}\\${{ parameters.packageVersion}}\\${{ parameters.nugetPackageBaseName }}.symbols.nupkg" 15 | templateContext: 16 | type: releaseJob 17 | isProduction: true 18 | inputs: 19 | - input: pipelineArtifact 20 | pipeline: "_rabbitmq-extensionofficial" 21 | artifactName: "drop-extension" 22 | targetPath: "${{ parameters.nugetArtifactPath }}" 23 | steps: 24 | - task: PowerShell@2 25 | displayName: Display artifacts 26 | inputs: 27 | targetType: inline 28 | script: |- 29 | Write-Host "##[debug]Listing files in the workspace folder:" 30 | Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)" -Recurse 31 | Write-Host "##[debug]Listing files in the drop-extension folder:" 32 | Get-ChildItem -Path "${{ parameters.nugetArtifactPath }}" 33 | - task: PowerShell@2 34 | displayName: "Check NuGet Package Files Exist" 35 | inputs: 36 | targetType: inline 37 | script: | 38 | Write-Host "##[debug]Checking for $(nupkgPath)..." 39 | if (-not (Test-Path -Path $(nupkgPath))) { 40 | Write-Host "[error]NuGet package file '$(nupkgPath)' is missing." 41 | exit 1 42 | } 43 | 44 | Write-Host "##[debug]Checking for $(nupkgSymbolsPath)..." 45 | if (-not (Test-Path -Path $(nupkgSymbolsPath))) { 46 | Write-Host "[error]NuGet symbols package file '$(nupkgSymbolsPath)' is missing." 47 | exit 1 48 | } 49 | -------------------------------------------------------------------------------- /eng/cd/templates/publish.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | stageName: "" 3 | displayName: "" 4 | artifactPath: "" # path from which package files are copied to the storage account 5 | blobPath: "" 6 | 7 | stages: 8 | - stage: ${{ parameters.stageName }} 9 | displayName: ${{ parameters.displayName }} 10 | dependsOn: ValidateAndApprove 11 | condition: succeeded() 12 | jobs: 13 | - job: PublishJob 14 | displayName: ${{ parameters.displayName }} to Storage Account 15 | templateContext: 16 | type: releaseJob 17 | isProduction: true 18 | inputs: 19 | - input: pipelineArtifact 20 | pipeline: "_rabbitmq-extensionofficial" 21 | artifactName: "drop-extension" 22 | targetPath: "$(Build.ArtifactStagingDirectory)\\drop-extension" # default download path for nuget artifact folder 23 | - input: pipelineArtifact 24 | pipeline: "_rabbitmq-extensionofficial" 25 | artifactName: "drop-java-library" 26 | targetPath: "$(Build.ArtifactStagingDirectory)\\drop-java-library" # default download path for maven artifact folder 27 | steps: 28 | - task: AzureFileCopy@6 29 | displayName: Upload .NET artifacts 30 | inputs: 31 | SourcePath: "${{ parameters.artifactPath }}/*" 32 | ConnectedServiceNameARM: azure-sdk-partner-drops 33 | Destination: AzureBlob 34 | StorageAccountRM: azuresdkpartnerdrops 35 | ContainerName: drops 36 | BlobPrefix: "${{ parameters.blobPath }}" 37 | -------------------------------------------------------------------------------- /eng/ci/code-mirror.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - dev 5 | 6 | resources: 7 | repositories: 8 | - repository: eng 9 | type: git 10 | name: engineering 11 | ref: refs/tags/release 12 | 13 | variables: 14 | - template: ci/variables/cfs.yml@eng 15 | 16 | extends: 17 | template: ci/code-mirror.yml@eng -------------------------------------------------------------------------------- /eng/ci/official-build.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | # It is a tagged build if it is triggered by pushing a tag. 3 | isTaggedBuild: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }} 4 | prefix: $[format('0.{0:yyyy}.{0:MMdd}', pipeline.startTime)] 5 | version: $[format('{0}.{1}', variables.prefix, counter(variables.prefix, 1))] # e.g. 0.2001.0203.4 6 | fileVersion: $[variables.version] 7 | Codeql.Enabled: true 8 | 9 | schedules: 10 | - cron: "0 12 * * 0" 11 | displayName: Weekly Sunday build 12 | branches: 13 | include: 14 | - dev 15 | always: true 16 | 17 | trigger: 18 | branches: 19 | include: 20 | - dev 21 | - v*.x 22 | tags: 23 | include: 24 | - v*.*.* 25 | 26 | pr: 27 | branches: 28 | include: 29 | - dev 30 | paths: 31 | include: 32 | - /eng/ci/official-build.yml 33 | - /eng/ci/templates/official/** 34 | 35 | resources: 36 | repositories: 37 | - repository: 1ESPipelineTemplates 38 | type: git 39 | name: 1ESPipelineTemplates/1ESPipelineTemplates 40 | ref: refs/tags/release 41 | - repository: eng 42 | type: git 43 | name: engineering 44 | ref: refs/tags/release 45 | 46 | extends: 47 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 48 | parameters: 49 | pool: 50 | name: 1es-pool-azfunc 51 | image: 1es-windows-2022 52 | os: windows 53 | sdl: 54 | sourceAnalysisPool: 55 | name: 1es-pool-azfunc 56 | image: 1es-windows-2022 57 | os: windows 58 | stages: 59 | - stage: stage1 60 | displayName: Build Extension 61 | jobs: 62 | - template: /eng/ci/templates/official/build-extension.yml@self 63 | parameters: 64 | isTaggedBuild: ${{ variables.isTaggedBuild }} 65 | version: ${{ variables.version }} 66 | fileVersion: ${{ variables.fileVersion }} 67 | - template: /eng/ci/templates/official/build-java-library.yml@self 68 | parameters: 69 | isTaggedBuild: ${{ variables.isTaggedBuild }} 70 | version: ${{ variables.version }} 71 | -------------------------------------------------------------------------------- /eng/ci/public-linux-build.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | branches: 3 | include: 4 | - dev 5 | - v*.x 6 | 7 | schedules: 8 | - cron: "0 12 * * 0" 9 | displayName: Weekly Sunday build 10 | branches: 11 | include: 12 | - dev 13 | always: true 14 | 15 | trigger: 16 | branches: 17 | include: 18 | - dev 19 | - v*.x 20 | 21 | resources: 22 | repositories: 23 | - repository: 1es 24 | type: git 25 | name: 1ESPipelineTemplates/1ESPipelineTemplates 26 | ref: refs/tags/release 27 | 28 | variables: 29 | prefix: $[format('0.{0:yyyy}.{0:MMdd}', pipeline.startTime)] 30 | version: $[format('{0}.{1}', variables.prefix, counter(variables.prefix, 1))] # e.g. 0.2001.0203.4 31 | fileVersion: $[variables.version] 32 | Codeql.Enabled: true 33 | 34 | extends: 35 | template: v1/1ES.Unofficial.PipelineTemplate.yml@1es 36 | parameters: 37 | pool: 38 | name: 1es-pool-azfunc-public 39 | image: 1es-ubuntu-22.04 40 | os: linux 41 | sdl: 42 | sourceAnalysisPool: 43 | name: 1es-pool-azfunc-public 44 | image: 1es-windows-2022 45 | os: windows 46 | codeql: 47 | compiled: 48 | enabled: true # still only runs for default branch 49 | runSourceLanguagesInSourceAnalysis: true 50 | 51 | stages: 52 | - stage: buildAndTestExtensionLinux 53 | displayName: Build and Test Extension in Linux 54 | jobs: 55 | - template: /eng/ci/templates/public/build-test-extension.yml@self 56 | parameters: 57 | fileVersion: ${{ variables.fileVersion }} 58 | 59 | - stage: buildJavaLibraryLinux 60 | displayName: Build Java Library in Linux 61 | jobs: 62 | - template: /eng/ci/templates/public/build-java-library.yml@self 63 | parameters: 64 | version: ${{ variables.version }} 65 | -------------------------------------------------------------------------------- /eng/ci/public-windows-build.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | branches: 3 | include: 4 | - dev 5 | - v*.x 6 | 7 | schedules: 8 | - cron: "0 12 * * 0" 9 | displayName: Weekly Sunday build 10 | branches: 11 | include: 12 | - dev 13 | always: true 14 | 15 | trigger: 16 | branches: 17 | include: 18 | - dev 19 | - v*.x 20 | 21 | resources: 22 | repositories: 23 | - repository: 1es 24 | type: git 25 | name: 1ESPipelineTemplates/1ESPipelineTemplates 26 | ref: refs/tags/release 27 | 28 | variables: 29 | Codeql.Enabled: true 30 | prefix: $[format('0.{0:yyyy}.{0:MMdd}', pipeline.startTime)] 31 | version: $[format('{0}.{1}', variables.prefix, counter(variables.prefix, 1))] # e.g. 0.2001.0203.4 32 | fileVersion: $[variables.version] 33 | 34 | extends: 35 | template: v1/1ES.Unofficial.PipelineTemplate.yml@1es 36 | parameters: 37 | pool: 38 | name: 1es-pool-azfunc-public 39 | image: 1es-windows-2022 40 | os: windows 41 | sdl: 42 | codeql: 43 | compiled: 44 | enabled: true # still only runs for default branch 45 | runSourceLanguagesInSourceAnalysis: true 46 | 47 | stages: 48 | - stage: buildAndTestExtensionWindows 49 | displayName: Build and Test Extension in Windows 50 | jobs: 51 | - template: /eng/ci/templates/public/build-test-extension.yml@self 52 | parameters: 53 | fileVersion: ${{ variables.fileVersion }} 54 | 55 | - stage: buildJavaLibraryWindows 56 | displayName: Build Java Library in Windows 57 | jobs: 58 | - template: /eng/ci/templates/public/build-java-library.yml@self 59 | parameters: 60 | version: ${{ variables.version }} 61 | -------------------------------------------------------------------------------- /eng/ci/templates/official/build-extension.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: isTaggedBuild 3 | type: boolean 4 | default: false # Set to true if this is a tagged build 5 | - name: version 6 | type: string 7 | default: "" # The version of the library to be built. This is set by the build pipeline. 8 | - name: fileVersion 9 | type: string 10 | default: "" # The file version of the library to be built. This is set by the build pipeline. 11 | 12 | jobs: 13 | - job: buildRabbitMQExtension 14 | displayName: Build RabbitMQ Extension 15 | variables: 16 | isTaggedBuild: ${{ parameters.isTaggedBuild }} 17 | version: ${{ parameters.version }} 18 | fileVersion: ${{ parameters.fileVersion }} 19 | templateContext: 20 | outputs: 21 | - output: pipelineArtifact 22 | targetPath: $(Build.ArtifactStagingDirectory) 23 | artifactName: drop-extension 24 | steps: 25 | - ${{ if eq(variables.isTaggedBuild, 'True') }}: 26 | - powershell: | # Allow tags matching v1.2.3 and v1.2.3-xyz1 27 | $found = '$(Build.SourceBranchName)' | Select-String -Pattern '^v(((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))(?:-\w+)?)$' 28 | if (-not $found) { 29 | Write-Error "Found unexpected tag name: $(Build.SourceBranchName)." 30 | exit 1 31 | } 32 | Write-Host "##vso[task.setvariable variable=version]$($found.Matches.Groups[1].Value)" 33 | Write-Host "##vso[task.setvariable variable=fileVersion]$($found.Matches.Groups[2].Value)" 34 | 35 | displayName: Extract version # e.g. 1.2.3 36 | 37 | # .NET Core 2.1 SDK is required by ESRP Code Signing tasks. 38 | - task: UseDotNet@2 39 | displayName: Acquire .NET Core 2.1 SDK 40 | inputs: 41 | packageType: sdk 42 | version: 2.1.x 43 | performMultiLevelLookup: true 44 | 45 | - task: UseDotNet@2 46 | displayName: Acquire .NET 8.0 SDK 47 | inputs: 48 | packageType: sdk 49 | version: 8.0.x 50 | performMultiLevelLookup: true 51 | 52 | - task: DotNetCoreCLI@2 53 | displayName: Build solution 54 | inputs: 55 | command: build 56 | workingDirectory: extension 57 | arguments: --configuration Release -property:Version=$(fileVersion) -property:CommitHash=$(Build.SourceVersion) 58 | 59 | - task: DotNetCoreCLI@2 60 | displayName: Test extension 61 | inputs: 62 | command: test 63 | workingDirectory: extension 64 | arguments: --configuration Debug 65 | 66 | - template: ci/sign-files.yml@eng 67 | parameters: 68 | displayName: Sign extension assembly 69 | folderPath: extension/WebJobs.Extensions.RabbitMQ/bin/Release/netstandard2.0 70 | pattern: Microsoft.Azure.WebJobs.Extensions.RabbitMQ.dll 71 | signType: dll 72 | 73 | - task: DotNetCoreCLI@2 74 | displayName: Pack extension 75 | inputs: 76 | command: pack 77 | searchPatternPack: extension/WebJobs.Extensions.RabbitMQ/WebJobs.Extensions.RabbitMQ.csproj 78 | configurationToPack: Release 79 | buildProperties: Version=$(version);CommitHash=$(Build.SourceVersion) 80 | outputDir: $(Build.ArtifactStagingDirectory)/$(version) 81 | nobuild: true 82 | includesymbols: true 83 | verbosityPack: minimal 84 | 85 | - template: ci/sign-files.yml@eng 86 | parameters: 87 | displayName: Sign extension package 88 | folderPath: $(Build.ArtifactStagingDirectory)/$(version) 89 | pattern: Microsoft.Azure.WebJobs.Extensions.RabbitMQ.*.nupkg 90 | signType: nuget 91 | 92 | - task: DeleteFiles@1 93 | displayName: Cleanup staging directory 94 | inputs: 95 | sourceFolder: $(Build.ArtifactStagingDirectory)/$(version) 96 | contents: CodeSignSummary-*.md 97 | 98 | - task: ManifestGeneratorTask@0 99 | displayName: Generate SBOM manifest 100 | inputs: 101 | buildDropPath: $(Build.ArtifactStagingDirectory)/$(version) 102 | packageName: Azure Functions RabbitMQ Extension 103 | packageVersion: $(version) 104 | -------------------------------------------------------------------------------- /eng/ci/templates/official/build-java-library.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: isTaggedBuild 3 | type: boolean 4 | default: false # Set to true if this is a tagged build 5 | - name: version 6 | type: string 7 | default: "" # The version of the library to be built. This is set by the build pipeline. 8 | 9 | jobs: 10 | - job: buildJavaLibrary 11 | displayName: Build Java Library 12 | variables: 13 | isTaggedBuild: ${{ parameters.isTaggedBuild }} 14 | version: ${{ parameters.version }} 15 | templateContext: 16 | outputs: 17 | - output: pipelineArtifact 18 | targetPath: $(Build.ArtifactStagingDirectory) 19 | artifactName: drop-java-library 20 | steps: 21 | - ${{ if eq(variables.isTaggedBuild, 'True') }}: 22 | - powershell: | # Allow tags matching v1.2.3 and v1.2.3-xyz1 23 | $found = '$(Build.SourceBranchName)' | Select-String -Pattern '^v((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-\w+)?)$' 24 | if (-not $found) { 25 | Write-Error "Found unexpected tag name: $(Build.SourceBranchName)." 26 | exit 1 27 | } 28 | Write-Host "##vso[task.setvariable variable=version]$($found.Matches.Groups[1].Value)" 29 | 30 | displayName: Extract version # e.g. 1.2.3 31 | 32 | # A combination of --batch-mode and --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 33 | # options prevents spurious logging about downloading of Maven packages. 34 | - task: Maven@3 35 | displayName: Set library version 36 | inputs: 37 | mavenPomFile: java-library/pom.xml 38 | goals: versions:set 39 | options: --batch-mode --define=newVersion=$(version) --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 40 | 41 | - task: Maven@3 42 | displayName: Build library 43 | inputs: 44 | mavenPomFile: java-library/pom.xml 45 | options: --batch-mode --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 46 | 47 | - powershell: | 48 | $prefix = 'azure-functions-java-library-rabbitmq-$(version)' 49 | $source = 'java-library' 50 | $destination = '$(Build.ArtifactStagingDirectory)/$(version)' 51 | 52 | New-Item $destination -ItemType Directory 53 | Copy-Item "$source/pom.xml" "$destination/$prefix.pom" 54 | Copy-Item "$source/target/$prefix.jar" "$destination/$prefix.jar" 55 | Copy-Item "$source/target/$prefix-javadoc.jar" "$destination/$prefix-javadoc.jar" 56 | Copy-Item "$source/target/$prefix-sources.jar" "$destination/$prefix-sources.jar" 57 | 58 | displayName: Copy output files 59 | 60 | - task: ManifestGeneratorTask@0 61 | displayName: Generate SBOM manifest 62 | inputs: 63 | buildDropPath: $(Build.ArtifactStagingDirectory)/$(version) 64 | packageName: Azure Functions RabbitMQ Java Bindings 65 | packageVersion: $(version) 66 | -------------------------------------------------------------------------------- /eng/ci/templates/public/build-java-library.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: version 3 | type: string 4 | default: "" # The version of the library to be built. This is set by the build pipeline. 5 | 6 | jobs: 7 | - job: buildJavaLibrarylinux 8 | displayName: Build Linux Java Library 9 | variables: 10 | version: ${{ parameters.version }} 11 | steps: 12 | # A combination of --batch-mode and --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 13 | # options prevents spurious logging about downloading of Maven packages. 14 | - task: Maven@3 15 | displayName: Set library version 16 | inputs: 17 | mavenPomFile: java-library/pom.xml 18 | goals: versions:set 19 | options: --batch-mode --define=newVersion=$(version) --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 20 | 21 | - task: Maven@3 22 | displayName: Build library 23 | inputs: 24 | mavenPomFile: java-library/pom.xml 25 | options: --batch-mode --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --update-snapshots 26 | -------------------------------------------------------------------------------- /eng/ci/templates/public/build-test-extension.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: fileVersion 3 | type: string 4 | default: "" # The file version of the extension to be built. This is set by the build pipeline. 5 | 6 | jobs: 7 | - job: BuildExtension 8 | variables: 9 | fileVersion: ${{ parameters.fileVersion }} 10 | steps: 11 | - task: UseDotNet@2 12 | displayName: Acquire .NET 8.0 SDK 13 | inputs: 14 | packageType: sdk 15 | version: 8.0.x 16 | performMultiLevelLookup: true 17 | 18 | - task: DotNetCoreCLI@2 19 | displayName: Build solution 20 | inputs: 21 | command: build 22 | workingDirectory: extension 23 | arguments: --configuration Release -property:Version=$(fileVersion) -property:CommitHash=$(Build.SourceVersion) 24 | 25 | - task: DotNetCoreCLI@2 26 | displayName: Test extension 27 | inputs: 28 | command: test 29 | workingDirectory: extension 30 | arguments: --configuration Debug 31 | -------------------------------------------------------------------------------- /extension/.editorconfig: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Core EditorConfig Options # 3 | ############################### 4 | root = true 5 | 6 | # All files 7 | [*] 8 | indent_style = space 9 | insert_final_newline = true 10 | 11 | # XML project files 12 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 13 | indent_size = 2 14 | 15 | # XML config files 16 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 17 | indent_size = 2 18 | 19 | # Code files 20 | [*.{cs,csx,vb,vbx}] 21 | indent_size = 4 22 | charset = utf-8-bom 23 | ############################### 24 | # .NET Coding Conventions # 25 | ############################### 26 | [*.{cs,vb}] 27 | # Organize usings 28 | dotnet_sort_system_directives_first = true 29 | # this. preferences 30 | dotnet_style_qualification_for_field = true:silent 31 | dotnet_style_qualification_for_property = true:silent 32 | dotnet_style_qualification_for_method = true:silent 33 | dotnet_style_qualification_for_event = true:silent 34 | # Language keywords vs BCL types preferences 35 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 36 | dotnet_style_predefined_type_for_member_access = true:silent 37 | # Parentheses preferences 38 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 39 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 40 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 41 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 42 | # Modifier preferences 43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 44 | dotnet_style_readonly_field = true:suggestion 45 | # Expression-level preferences 46 | dotnet_style_object_initializer = true:suggestion 47 | dotnet_style_collection_initializer = true:suggestion 48 | dotnet_style_explicit_tuple_names = true:suggestion 49 | dotnet_style_null_propagation = true:suggestion 50 | dotnet_style_coalesce_expression = true:suggestion 51 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 52 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 54 | dotnet_style_prefer_auto_properties = true:silent 55 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 56 | dotnet_style_prefer_conditional_expression_over_return = true:silent 57 | ############################### 58 | # Naming Conventions # 59 | ############################### 60 | # Style Definitions 61 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 62 | # Use PascalCase for constant fields 63 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 64 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 65 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 66 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 67 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 68 | dotnet_naming_symbols.constant_fields.required_modifiers = const 69 | ############################### 70 | # Code Analysis Suppressions # 71 | ############################### 72 | dotnet_analyzer_diagnostic.severity = error 73 | dotnet_diagnostic.CA1014.severity = none # Mark assemblies with CLSCompliant 74 | dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase 75 | dotnet_diagnostic.CA1716.severity = none # Identifiers should not match keywords 76 | dotnet_diagnostic.IDE0058.severity = none # Remove unnecessary expression value 77 | dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure 78 | dotnet_diagnostic.SA0001.severity = none # XML comment analysis is disabled (to be removed) 79 | dotnet_diagnostic.IDE0005.severity = none # Remove unnecessary usings/imports 80 | 81 | ############################### 82 | # C# Coding Conventions # 83 | ############################### 84 | [*.cs] 85 | # var preferences 86 | csharp_style_var_for_built_in_types = false:silent 87 | csharp_style_var_when_type_is_apparent = true:silent 88 | csharp_style_var_elsewhere = false:silent 89 | # Expression-bodied members 90 | csharp_style_expression_bodied_methods = false:silent 91 | csharp_style_expression_bodied_constructors = false:silent 92 | csharp_style_expression_bodied_operators = false:silent 93 | csharp_style_expression_bodied_properties = true:silent 94 | csharp_style_expression_bodied_indexers = true:silent 95 | csharp_style_expression_bodied_accessors = true:silent 96 | # Pattern matching preferences 97 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 98 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 99 | # Null-checking preferences 100 | csharp_style_throw_expression = true:suggestion 101 | csharp_style_conditional_delegate_call = true:suggestion 102 | # Modifier preferences 103 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 104 | # Expression-level preferences 105 | csharp_prefer_braces = true:silent 106 | csharp_style_deconstructed_variable_declaration = true:suggestion 107 | csharp_prefer_simple_default_expression = true:suggestion 108 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 109 | csharp_style_inlined_variable_declaration = true:suggestion 110 | # Namespace declaration preferences 111 | csharp_style_namespace_declarations = file_scoped:suggestion 112 | ############################### 113 | # C# Formatting Rules # 114 | ############################### 115 | # New line preferences 116 | csharp_new_line_before_open_brace = all 117 | csharp_new_line_before_else = true 118 | csharp_new_line_before_catch = true 119 | csharp_new_line_before_finally = true 120 | csharp_new_line_before_members_in_object_initializers = true 121 | csharp_new_line_before_members_in_anonymous_types = true 122 | csharp_new_line_between_query_expression_clauses = true 123 | # Indentation preferences 124 | csharp_indent_case_contents = true 125 | csharp_indent_switch_labels = true 126 | csharp_indent_labels = flush_left 127 | # Space preferences 128 | csharp_space_after_cast = false 129 | csharp_space_after_keywords_in_control_flow_statements = true 130 | csharp_space_between_method_call_parameter_list_parentheses = false 131 | csharp_space_between_method_declaration_parameter_list_parentheses = false 132 | csharp_space_between_parentheses = false 133 | csharp_space_before_colon_in_inheritance_clause = true 134 | csharp_space_after_colon_in_inheritance_clause = true 135 | csharp_space_around_binary_operators = before_and_after 136 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 137 | csharp_space_between_method_call_name_and_opening_parenthesis = false 138 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 139 | # Wrapping preferences 140 | csharp_preserve_single_line_statements = true 141 | csharp_preserve_single_line_blocks = true 142 | ############################### 143 | # VB Coding Conventions # 144 | ############################### 145 | [*.vb] 146 | # Modifier preferences 147 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 148 | -------------------------------------------------------------------------------- /extension/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | 367 | ## 368 | ## Visual studio for Mac 369 | ## 370 | 371 | 372 | # globs 373 | Makefile.in 374 | *.userprefs 375 | *.usertasks 376 | config.make 377 | config.status 378 | aclocal.m4 379 | install-sh 380 | autom4te.cache/ 381 | *.tar.gz 382 | tarballs/ 383 | test-results/ 384 | 385 | # Mac bundle stuff 386 | *.dmg 387 | *.app 388 | 389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 390 | # General 391 | .DS_Store 392 | .AppleDouble 393 | .LSOverride 394 | 395 | # Icon must end with two \r 396 | Icon 397 | 398 | 399 | # Thumbnails 400 | ._* 401 | 402 | # Files that might appear in the root of a volume 403 | .DocumentRevisions-V100 404 | .fseventsd 405 | .Spotlight-V100 406 | .TemporaryItems 407 | .Trashes 408 | .VolumeIcon.icns 409 | .com.apple.timemachine.donotpresent 410 | 411 | # Directories potentially created on remote AFP share 412 | .AppleDB 413 | .AppleDesktop 414 | Network Trash Folder 415 | Temporary Items 416 | .apdisk 417 | 418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 419 | # Windows thumbnail cache files 420 | Thumbs.db 421 | ehthumbs.db 422 | ehthumbs_vista.db 423 | 424 | # Dump file 425 | *.stackdump 426 | 427 | # Folder config file 428 | [Dd]esktop.ini 429 | 430 | # Recycle Bin used on file shares 431 | $RECYCLE.BIN/ 432 | 433 | # Windows Installer files 434 | *.cab 435 | *.msi 436 | *.msix 437 | *.msm 438 | *.msp 439 | 440 | # Windows shortcuts 441 | *.lnk 442 | 443 | # JetBrains Rider 444 | .idea/ 445 | *.sln.iml 446 | 447 | ## 448 | ## Visual Studio Code 449 | ## 450 | .vscode/* 451 | !.vscode/settings.json 452 | !.vscode/tasks.json 453 | !.vscode/launch.json 454 | !.vscode/extensions.json 455 | -------------------------------------------------------------------------------- /extension/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-dotnettools.csharp", 4 | "EditorConfig.EditorConfig" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /extension/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "omnisharp.enableEditorConfigSupport": true, 6 | "omnisharp.enableRoslynAnalyzers": true 7 | } 8 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Samples/.editorconfig: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Core EditorConfig Options # 3 | ############################### 4 | 5 | [*.{cs,vb}] 6 | ############################### 7 | # Code Analysis Suppressions # 8 | ############################### 9 | dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter 10 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Samples/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using Microsoft.Azure.WebJobs.Host.Storage; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Samples; 11 | 12 | public static class Program 13 | { 14 | public static async Task Main() 15 | { 16 | // Add or remove types from this list to choose which functions will 17 | // be indexed by the JobHost. 18 | // To run some of the other samples included, add their types to this list 19 | var typeLocator = new SamplesTypeLocator( 20 | typeof(RabbitMQSamples)); 21 | 22 | IHostBuilder builder = new HostBuilder() 23 | .UseEnvironment("Development") 24 | .ConfigureWebJobs(webJobsBuilder => 25 | { 26 | webJobsBuilder 27 | .AddAzureStorageCoreServices() 28 | .AddAzureStorageQueues() 29 | .AddRabbitMQ() 30 | .AddTimers(); 31 | }) 32 | .ConfigureLogging(b => 33 | { 34 | b.SetMinimumLevel(LogLevel.Debug); 35 | b.AddConsole(); 36 | }) 37 | .ConfigureServices(s => 38 | { 39 | s.AddSingleton(typeLocator); 40 | }) 41 | .UseConsoleLifetime(); 42 | 43 | IHost host = builder.Build(); 44 | using (host) 45 | { 46 | var jobHost = (JobHost)host.Services.GetService(); 47 | 48 | await host.RunAsync(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Samples/RabbitMQSamples.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Logging; 7 | using Newtonsoft.Json; 8 | using RabbitMQ.Client; 9 | using RabbitMQ.Client.Events; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Samples; 12 | 13 | internal static class RabbitMQSamples 14 | { 15 | // Output samples 16 | // To run this sample with a specified amqp connection string, create a file called "appsettings.json" in the same directory. 17 | // In the file, add: 18 | // { 19 | // "connectionStrings": { 20 | // "rabbitMQ": "your connection string here" 21 | // } 22 | // } 23 | // Or, if you already have an appsettings.json, add rabbitMQ and your connection string to the connection strings property. 24 | public static void TimerTrigger_ConnectionString_StringOutput( 25 | [TimerTrigger("00:01")] TimerInfo timer, 26 | [RabbitMQ(QueueName = "queue")] out string outputMessage, 27 | ILogger logger) 28 | { 29 | outputMessage = "new"; 30 | logger.LogInformation($"RabbitMQ output binding message: {outputMessage}"); 31 | } 32 | 33 | public static void TimerTrigger_PocoOutput( 34 | [TimerTrigger("00:01")] TimerInfo timer, 35 | [RabbitMQ(QueueName = "queue")] out TestClass outputMessage, 36 | ILogger logger) 37 | { 38 | outputMessage = new TestClass { X = 1, Y = 1 }; 39 | logger.LogInformation($"RabbitMQ output binding message: {JsonConvert.SerializeObject(outputMessage)}"); 40 | } 41 | 42 | // To run: 43 | // 1. Create Azure Storage Account and go to the homepage for that account 44 | // 2. Look for Queue service and click Queues on the sidebar 45 | // 3. Create a queue named "samples-rabbitmq-messages" 46 | // 4. Add a message to the queue 47 | // 5. Run this sample and you will see the queue trigger fired. 48 | // *Note that any time the queue isn't empty, the trigger will continue to fire. 49 | // So you can add items to the queue while the sample is running, and the trigger will be called until the queue is empty. 50 | public static async Task ProcessMessage_RabbitMQAsyncCollector( 51 | [QueueTrigger(@"samples-rabbitmq-messages")] string message, 52 | [RabbitMQ(QueueName = "queue")] IAsyncCollector messages, 53 | ILogger logger) 54 | { 55 | logger.LogInformation("Received queue trigger"); 56 | byte[] messageInBytes = Encoding.UTF8.GetBytes(message); 57 | await messages.AddAsync(messageInBytes).ConfigureAwait(false); 58 | } 59 | 60 | // To run: 61 | // 1. Create Azure Storage Account and go to the homepage for that account 62 | // 2. Look for Queue service and click Queues on the sidebar 63 | // 3. Create a queue named "samples-rabbitmq-messages" 64 | // 4. Add a message to the queue in POCO format (i.e.: "{ "name": Katie }") 65 | // 5. Run this sample and you will see the queue trigger fired. 66 | // *Note that any time the queue isn't empty, the trigger will continue to fire. 67 | // So you can add items to the queue while the sample is running, and the trigger will be called until the queue is empty. 68 | public static void QueueTrigger_RabbitMQOutput( 69 | [QueueTrigger(@"samples-rabbitmq-messages")] TestClass message, 70 | [RabbitMQ(QueueName = "queue")] out TestClass outputMessage, 71 | ILogger logger) 72 | { 73 | outputMessage = message; 74 | logger.LogInformation($"RabbitMQ output binding message: {JsonConvert.SerializeObject(outputMessage)}"); 75 | } 76 | 77 | // Example that binds to client 78 | public static void BindToClient( 79 | [TimerTrigger("01:00", RunOnStartup = true)] TimerInfo timer, 80 | [RabbitMQ(ConnectionStringSetting = "rabbitMQ")] IModel client, 81 | ILogger logger) 82 | { 83 | _ = client.QueueDeclare("hello", false, false, false, null); 84 | logger.LogInformation("Opening connection and creating queue!"); 85 | } 86 | 87 | // Trigger samples 88 | public static void RabbitMQTrigger_String( 89 | [RabbitMQTrigger("new_test_queue", ConnectionStringSetting = "rabbitMQ")] string message, 90 | string consumerTag, 91 | ILogger logger) 92 | { 93 | logger.LogInformation($"RabbitMQ queue trigger function processed message: {message} and consumer tag: {consumerTag}"); 94 | } 95 | 96 | public static void RabbitMQTrigger_BasicDeliverEventArgs( 97 | [RabbitMQTrigger("queue")] BasicDeliverEventArgs args, 98 | ILogger logger) 99 | { 100 | logger.LogInformation($"RabbitMQ queue trigger function processed message: {Encoding.UTF8.GetString(args.Body.ToArray())}"); 101 | } 102 | 103 | // This sample should fail when running a console app that sends out a message incorrectly formatted. 104 | public static void RabbitMQTrigger_JsonToPOCO( 105 | [RabbitMQTrigger("new_test_queue", ConnectionStringSetting = "rabbitMQ")] TestClass pocObj, 106 | ILogger logger) 107 | { 108 | logger.LogInformation($"RabbitMQ queue trigger function processed message: {pocObj}"); 109 | } 110 | 111 | public static void RabbitMQTrigger_RabbitMQOutput( 112 | [RabbitMQTrigger("queue")] string inputMessage, 113 | [RabbitMQ(QueueName = "hello")] out string outputMessage, 114 | ILogger logger) 115 | { 116 | outputMessage = inputMessage; 117 | logger.LogInformation($"RabbitMQ output binding function sent message: {outputMessage}"); 118 | logger.LogInformation($"RabbitMQ output binding function sent message: {outputMessage}"); 119 | } 120 | 121 | public sealed class TestClass 122 | { 123 | public int X { get; set; } 124 | 125 | public int Y { get; set; } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Samples/SamplesTypeLocator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Samples; 8 | 9 | public class SamplesTypeLocator(params Type[] types) : ITypeLocator 10 | { 11 | private readonly Type[] types = types; 12 | 13 | public IReadOnlyList GetTypes() 14 | { 15 | return this.types; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Samples/WebJobs.Extensions.RabbitMQ.Samples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Samples 7 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Samples 8 | latest 9 | true 10 | Recommended 11 | true 12 | false 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | all 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Core EditorConfig Options # 3 | ############################### 4 | 5 | [*.{cs,vb}] 6 | ############################### 7 | # Code Analysis Suppressions # 8 | ############################### 9 | dotnet_diagnostic.CA1001.severity = none # Types that own disposable fields should be disposable 10 | dotnet_diagnostic.CA1707.severity = none # Identifiers should not contain underscores 11 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/BasicDeliverEventArgsValueProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using RabbitMQ.Client.Events; 9 | using Xunit; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 12 | 13 | public class BasicDeliverEventArgsValueProviderTests 14 | { 15 | [Fact] 16 | public async Task Poco_Conversion_Succeeds() 17 | { 18 | var expectedObject = new TestClass { X = 1, Y = 1 }; 19 | string expectedStringifiedJson = JsonConvert.SerializeObject(expectedObject); 20 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedStringifiedJson); 21 | var args = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 22 | var testValueProvider = new BasicDeliverEventArgsValueProvider(args, typeof(TestClass)); 23 | 24 | var actualObject = (TestClass)await testValueProvider.GetValueAsync(); 25 | 26 | Assert.Equal(expectedStringifiedJson, JsonConvert.SerializeObject(actualObject)); 27 | Assert.Equal(typeof(TestClass), testValueProvider.Type); 28 | Assert.Equal(expectedObject.X, actualObject.X); 29 | Assert.Equal(expectedObject.Y, actualObject.Y); 30 | } 31 | 32 | [Fact] 33 | public async Task StringifiedJson_Conversion_Succeeds() 34 | { 35 | var expectedObject = new TestClass { X = 1, Y = 1 }; 36 | string expectedStringifiedJson = JsonConvert.SerializeObject(expectedObject); 37 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedStringifiedJson); 38 | var args = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 39 | var testValueProvider = new BasicDeliverEventArgsValueProvider(args, typeof(string)); 40 | 41 | string actualStringifiedJson = (string)await testValueProvider.GetValueAsync(); 42 | var actualObject = (TestClass)JsonConvert.DeserializeObject(actualStringifiedJson, typeof(TestClass)); 43 | 44 | Assert.Equal(expectedStringifiedJson, actualStringifiedJson); 45 | Assert.Equal(typeof(string), testValueProvider.Type); 46 | Assert.Equal(expectedObject.X, actualObject.X); 47 | Assert.Equal(expectedObject.Y, actualObject.Y); 48 | } 49 | 50 | [Fact] 51 | public async Task BasicDeliverEventArgs_NoConversion_Succeeds() 52 | { 53 | string expectedString = "someString"; 54 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedString); 55 | var exceptedObject = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 56 | var testValueProvider = new BasicDeliverEventArgsValueProvider(exceptedObject, typeof(BasicDeliverEventArgs)); 57 | 58 | var actualObject = (BasicDeliverEventArgs)await testValueProvider.GetValueAsync(); 59 | 60 | Assert.Equal(actualObject, exceptedObject); 61 | Assert.True(ReferenceEquals(actualObject, exceptedObject)); 62 | Assert.Equal(typeof(BasicDeliverEventArgs), testValueProvider.Type); 63 | } 64 | 65 | [Fact] 66 | public async Task ByteArray_Conversion_Succeeds() 67 | { 68 | string expectedString = "someString"; 69 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedString); 70 | var exceptedObject = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 71 | var testValueProvider = new BasicDeliverEventArgsValueProvider(exceptedObject, typeof(byte[])); 72 | 73 | byte[] actualResult = (byte[])await testValueProvider.GetValueAsync(); 74 | 75 | Assert.Equal(expectedString, Encoding.UTF8.GetString(actualResult)); 76 | Assert.Equal(typeof(byte[]), testValueProvider.Type); 77 | } 78 | 79 | [Fact] 80 | public async Task ReadOnlyMemory_Byte_Conversion_Succeeds() 81 | { 82 | string expectedString = "someString"; 83 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedString); 84 | var exceptedObject = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 85 | var testValueProvider = new BasicDeliverEventArgsValueProvider(exceptedObject, typeof(ReadOnlyMemory)); 86 | 87 | var actualResult = (ReadOnlyMemory)await testValueProvider.GetValueAsync(); 88 | 89 | Assert.Equal(expectedString, Encoding.UTF8.GetString(actualResult.ToArray())); 90 | Assert.Equal(typeof(ReadOnlyMemory), testValueProvider.Type); 91 | } 92 | 93 | [Fact] 94 | public async Task NormalString_Conversion_Succeeds() 95 | { 96 | string expectedString = "someString"; 97 | byte[] stringInBytes = Encoding.UTF8.GetBytes(expectedString); 98 | var args = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, stringInBytes); 99 | var testValueProvider = new BasicDeliverEventArgsValueProvider(args, typeof(string)); 100 | 101 | string actualString = (string)await testValueProvider.GetValueAsync(); 102 | 103 | Assert.Equal(expectedString, actualString); 104 | Assert.Equal(typeof(string), testValueProvider.Type); 105 | } 106 | 107 | [Fact] 108 | public async Task InvalidFormat_Throws_JsonException() 109 | { 110 | string expectedString = "someString"; 111 | byte[] objJsonBytes = Encoding.UTF8.GetBytes(expectedString); 112 | var args = new BasicDeliverEventArgs("tag", 1, false, string.Empty, "queue", null, objJsonBytes); 113 | var testValueProvider = new BasicDeliverEventArgsValueProvider(args, typeof(TestClass)); 114 | 115 | await Assert.ThrowsAsync(testValueProvider.GetValueAsync); 116 | } 117 | 118 | private sealed class TestClass 119 | { 120 | public int X { get; set; } 121 | 122 | public int Y { get; set; } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/PocoToBytesConverterTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using Newtonsoft.Json; 7 | using Xunit; 8 | 9 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 10 | 11 | public class PocoToBytesConverterTests 12 | { 13 | [Fact] 14 | public void Converts_String_Correctly() 15 | { 16 | var sampleObj = new TestClass { X = 1, Y = 1 }; 17 | string res = JsonConvert.SerializeObject(sampleObj); 18 | byte[] expectedRes = Encoding.UTF8.GetBytes(res); 19 | 20 | var converter = new PocoToBytesConverter(); 21 | byte[] actualRes = converter.Convert(sampleObj).ToArray(); 22 | 23 | Assert.Equal(expectedRes, actualRes); 24 | } 25 | 26 | [Fact] 27 | public void NullString_Throws_Exception() 28 | { 29 | var converter = new PocoToBytesConverter(); 30 | Assert.Throws(() => converter.Convert(null)); 31 | } 32 | 33 | private sealed class TestClass 34 | { 35 | public int X { get; set; } 36 | 37 | public int Y { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQAsyncCollectorTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Azure.WebJobs.Logging; 7 | using Microsoft.Extensions.Logging; 8 | using Moq; 9 | using RabbitMQ.Client; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 13 | 14 | public class RabbitMQAsyncCollectorTests 15 | { 16 | [Fact] 17 | public async Task AddAsync_AddsMessagesToQueue() 18 | { 19 | object batchLock = new(); 20 | var mockRabbitMQService = new Mock(MockBehavior.Strict); 21 | var mockBatch = new Mock(); 22 | mockRabbitMQService.Setup(m => m.BasicPublishBatch).Returns(mockBatch.Object); 23 | mockRabbitMQService.Setup(m => m.PublishBatchLock).Returns(batchLock); 24 | 25 | var attribute = new RabbitMQAttribute 26 | { 27 | QueueName = "queue", 28 | }; 29 | 30 | var context = new RabbitMQContext 31 | { 32 | ResolvedAttribute = attribute, 33 | Service = mockRabbitMQService.Object, 34 | }; 35 | 36 | var loggerFactory = new LoggerFactory(); 37 | ILogger logger = loggerFactory.CreateLogger(LogCategories.CreateTriggerCategory(Constants.RabbitMQ)); 38 | var collector = new RabbitMQAsyncCollector(context, logger); 39 | 40 | byte[] body = Encoding.UTF8.GetBytes("hi"); 41 | await collector.AddAsync(body); 42 | 43 | #pragma warning disable 618 44 | mockBatch.Verify(m => m.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), body), Times.Exactly(1)); 45 | #pragma warning restore 618 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQClientBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using Moq; 9 | using RabbitMQ.Client; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 13 | 14 | public class RabbitMQClientBuilderTests 15 | { 16 | private static readonly IConfiguration EmptyConfig = new ConfigurationBuilder().Build(); 17 | private static readonly IDrainModeManager DrainModeManager = new Mock().Object; 18 | 19 | [Fact] 20 | public void Opens_Connection() 21 | { 22 | var options = new OptionsWrapper(new RabbitMQOptions()); 23 | var mockServiceFactory = new Mock(); 24 | var config = new RabbitMQExtensionConfigProvider(options, new Mock().Object, mockServiceFactory.Object, new LoggerFactory(), EmptyConfig, DrainModeManager); 25 | mockServiceFactory.Setup(m => m.CreateService(It.IsAny(), false, It.IsAny())).Returns(new Mock().Object); 26 | RabbitMQAttribute attr = GetTestAttribute(); 27 | 28 | var clientBuilder = new RabbitMQClientBuilder(config, options); 29 | IRabbitMQService service = clientBuilder.Convert(attr); 30 | 31 | mockServiceFactory.Verify(m => m.CreateService(It.IsAny(), false, It.IsAny()), Times.Exactly(1)); 32 | } 33 | 34 | [Fact] 35 | public void TestWhetherConnectionIsPooled() 36 | { 37 | var options = new OptionsWrapper(new RabbitMQOptions()); 38 | var mockServiceFactory = new Mock(); 39 | mockServiceFactory.SetupSequence(m => m.CreateService(It.IsAny(), false, It.IsAny())) 40 | .Returns(GetRabbitMQService()); 41 | var config = new RabbitMQExtensionConfigProvider(options, new Mock().Object, mockServiceFactory.Object, new LoggerFactory(), EmptyConfig, DrainModeManager); 42 | RabbitMQAttribute attr = GetTestAttribute(); 43 | 44 | var clientBuilder = new RabbitMQClientBuilder(config, options); 45 | 46 | IRabbitMQService service = clientBuilder.Convert(attr); 47 | IRabbitMQService service2 = clientBuilder.Convert(attr); 48 | 49 | Assert.Equal(service, service2); 50 | } 51 | 52 | private static RabbitMQAttribute GetTestAttribute() 53 | { 54 | return new RabbitMQAttribute 55 | { 56 | ConnectionStringSetting = "amqp://guest:guest@localhost:5672", 57 | }; 58 | } 59 | 60 | private static IRabbitMQService GetRabbitMQService() 61 | { 62 | var mockService = new Mock(); 63 | return mockService.Object; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQConfigProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using Moq; 9 | using Xunit; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 12 | 13 | public class RabbitMQConfigProviderTests 14 | { 15 | private static readonly IConfiguration EmptyConfig = new ConfigurationBuilder().Build(); 16 | private static readonly IDrainModeManager DrainModeManager = new Mock().Object; 17 | 18 | [Fact] 19 | public void Creates_Context_Correctly() 20 | { 21 | var options = new RabbitMQOptions { ConnectionString = "connectionStringFromOptions", QueueName = "queueNameFromOptions" }; 22 | var loggerFactory = new LoggerFactory(); 23 | var mockServiceFactory = new Mock(); 24 | var mockNameResolver = new Mock(); 25 | var config = new RabbitMQExtensionConfigProvider(new OptionsWrapper(options), mockNameResolver.Object, mockServiceFactory.Object, loggerFactory, EmptyConfig, DrainModeManager); 26 | var attribute = new RabbitMQAttribute { ConnectionStringSetting = "connectionStringSettingFromAttribute", QueueName = "queueNameFromAttributes" }; 27 | 28 | RabbitMQContext actualContext = config.CreateContext(attribute); 29 | 30 | Assert.Equal("connectionStringSettingFromAttribute", actualContext.ResolvedAttribute.ConnectionStringSetting); 31 | Assert.Equal("queueNameFromAttributes", actualContext.ResolvedAttribute.QueueName); 32 | } 33 | 34 | [Theory] 35 | [InlineData("connectionStringSettingFromAttribute", "queueNameFromAttribute", null, null)] 36 | [InlineData(null, "queueNameFromAttribute", "connectionStringFromOptions", null)] 37 | [InlineData(null, null, "connectionStringFromOptions", "queueNameFromOptions")] 38 | public void Handles_Null_Attributes_And_Options(string attrConnectionStringSetting, string attrQueueName, string optConnectionString, string optQueueName) 39 | { 40 | var attr = new RabbitMQAttribute 41 | { 42 | ConnectionStringSetting = attrConnectionStringSetting, 43 | QueueName = attrQueueName, 44 | }; 45 | 46 | var opt = new RabbitMQOptions 47 | { 48 | ConnectionString = optConnectionString, 49 | QueueName = optQueueName, 50 | }; 51 | 52 | var loggerFactory = new LoggerFactory(); 53 | var mockServiceFactory = new Mock(); 54 | var mockNameResolver = new Mock(); 55 | var config = new RabbitMQExtensionConfigProvider(new OptionsWrapper(opt), mockNameResolver.Object, mockServiceFactory.Object, loggerFactory, EmptyConfig, DrainModeManager); 56 | RabbitMQContext actualContext = config.CreateContext(attr); 57 | 58 | if (optConnectionString == null && optQueueName == null) 59 | { 60 | Assert.Equal(attrConnectionStringSetting, actualContext.ResolvedAttribute.ConnectionStringSetting); 61 | Assert.Equal(attrQueueName, actualContext.ResolvedAttribute.QueueName); 62 | } 63 | else if (attrConnectionStringSetting == null && optQueueName == null) 64 | { 65 | Assert.Equal(optConnectionString, actualContext.ResolvedAttribute.ConnectionStringSetting); 66 | Assert.Equal(attrQueueName, actualContext.ResolvedAttribute.QueueName); 67 | } 68 | else 69 | { 70 | Assert.Equal(optConnectionString, actualContext.ResolvedAttribute.ConnectionStringSetting); 71 | Assert.Equal(optQueueName, actualContext.ResolvedAttribute.QueueName); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQExtensionConfigProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Logging.Abstractions; 8 | using Microsoft.Extensions.Options; 9 | using Moq; 10 | using Xunit; 11 | 12 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 13 | 14 | public class RabbitMQExtensionConfigProviderTests 15 | { 16 | [Fact] 17 | public void TestConnectionPooling() 18 | { 19 | var rabbitmqServiceFactory = new Mock(); 20 | 21 | rabbitmqServiceFactory 22 | .SetupSequence(a => a.CreateService(It.IsAny(), It.IsAny(), false, It.IsAny())) 23 | .Returns(new Mock().Object); 24 | 25 | rabbitmqServiceFactory 26 | .SetupSequence(a => a.CreateService(It.IsAny(), false, It.IsAny())) 27 | .Returns(new Mock().Object); 28 | 29 | var extensionConfigProvider = new RabbitMQExtensionConfigProvider( 30 | new Mock>().Object, 31 | new Mock().Object, 32 | rabbitmqServiceFactory.Object, 33 | NullLoggerFactory.Instance, 34 | new Mock().Object, 35 | new Mock().Object); 36 | 37 | IRabbitMQService rabbitmqService1 = extensionConfigProvider.GetService("something", false); 38 | IRabbitMQService rabbitmqService2 = extensionConfigProvider.GetService("something", false); 39 | IRabbitMQService rabbitmqService3 = extensionConfigProvider.GetService("somethingElse", false); 40 | 41 | // 1 and 2 should be equal 42 | Assert.Equal(rabbitmqService1, rabbitmqService2); 43 | 44 | // 3 shouldn't be equal to 1 nor 2 45 | Assert.NotEqual(rabbitmqService1, rabbitmqService3); 46 | Assert.NotEqual(rabbitmqService2, rabbitmqService3); 47 | 48 | IRabbitMQService rabbitmqService4 = extensionConfigProvider.GetService("asomething", "asomething", false); 49 | IRabbitMQService rabbitmqService5 = extensionConfigProvider.GetService("asomething", "asomething", false); 50 | IRabbitMQService rabbitmqService6 = extensionConfigProvider.GetService("asomethingElse", "asomething", false); 51 | 52 | // 4 and 5 should be equal 53 | Assert.Equal(rabbitmqService4, rabbitmqService5); 54 | 55 | // 6 shouldn't be equal to 4 or 5 56 | Assert.NotEqual(rabbitmqService6, rabbitmqService4); 57 | Assert.NotEqual(rabbitmqService6, rabbitmqService5); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQOptionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Microsoft.Extensions.Options; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using Xunit; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 12 | 13 | public class RabbitMQOptionsTests 14 | { 15 | [Fact] 16 | public void TestDefaultOptions() 17 | { 18 | var options = new RabbitMQOptions(); 19 | 20 | Assert.Null(options.ConnectionString); 21 | Assert.Null(options.QueueName); 22 | Assert.Equal(30, options.PrefetchCount); 23 | Assert.False(options.DisableCertificateValidation); 24 | 25 | // Test formatted 26 | Assert.Equal(GetFormattedOption(options), options.Format()); 27 | } 28 | 29 | [Fact] 30 | public void TestConfiguredRabbitMQOptions() 31 | { 32 | string expectedConnectionString = "someConnectionString"; 33 | string expectedQueueName = "someQueueName"; 34 | ushort expectedPrefetchCount = 100; 35 | bool expectedDisableCertificateValidation = true; 36 | 37 | var options = new RabbitMQOptions() 38 | { 39 | ConnectionString = expectedConnectionString, 40 | QueueName = expectedQueueName, 41 | PrefetchCount = expectedPrefetchCount, 42 | DisableCertificateValidation = expectedDisableCertificateValidation, 43 | }; 44 | 45 | Assert.Equal(expectedConnectionString, options.ConnectionString); 46 | Assert.Equal(expectedQueueName, options.QueueName); 47 | Assert.Equal(expectedPrefetchCount, options.PrefetchCount); 48 | Assert.Equal(expectedDisableCertificateValidation, options.DisableCertificateValidation); 49 | 50 | // Test formatted 51 | Assert.Equal(GetFormattedOption(options), options.Format()); 52 | } 53 | 54 | [Fact] 55 | public void TestJobHostHasTheRightConfiguration() 56 | { 57 | ushort expectedPrefetchCount = 10; 58 | 59 | IHostBuilder builder = new HostBuilder() 60 | .UseEnvironment("Development") 61 | .ConfigureWebJobs(webJobsBuilder => 62 | { 63 | webJobsBuilder.AddRabbitMQ(a => a.PrefetchCount = expectedPrefetchCount); // set to non-default prefetch count 64 | }) 65 | .UseConsoleLifetime(); 66 | 67 | IHost host = builder.Build(); 68 | using (host) 69 | { 70 | IOptions config = host.Services.GetService>(); 71 | Assert.Equal(config.Value.PrefetchCount, expectedPrefetchCount); 72 | } 73 | } 74 | 75 | // TODO: Do not immitate source code. 76 | private static string GetFormattedOption(RabbitMQOptions option) 77 | { 78 | var options = new JObject 79 | { 80 | { nameof(option.QueueName), option.QueueName }, 81 | { nameof(option.PrefetchCount), option.PrefetchCount }, 82 | { nameof(option.DisableCertificateValidation), option.DisableCertificateValidation }, 83 | }; 84 | 85 | return options.ToString(Formatting.Indented); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQTriggerBindingProviderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Host; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.Logging.Abstractions; 9 | using Microsoft.Extensions.Options; 10 | using Moq; 11 | using Xunit; 12 | 13 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 14 | 15 | public class RabbitMQTriggerBindingProviderTests 16 | { 17 | [Fact] 18 | public async System.Threading.Tasks.Task Null_Context_Throws_Error() 19 | { 20 | IConfiguration emptyConfig = new ConfigurationBuilder().Build(); 21 | var configProvider = new RabbitMQExtensionConfigProvider( 22 | Options.Create(new RabbitMQOptions()), 23 | new DefaultNameResolver(emptyConfig), 24 | new Mock().Object, 25 | NullLoggerFactory.Instance, 26 | emptyConfig, 27 | new Mock().Object); 28 | var bindingProvider = new RabbitMQTriggerAttributeBindingProvider( 29 | new Mock().Object, 30 | configProvider, 31 | NullLogger.Instance, 32 | Options.Create(new RabbitMQOptions()), 33 | emptyConfig, 34 | new Mock().Object); 35 | await Assert.ThrowsAsync(() => bindingProvider.TryCreateAsync(null)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/RabbitMQTriggerBindingTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Azure.WebJobs.Host; 10 | using Microsoft.Azure.WebJobs.Host.Executors; 11 | using Microsoft.Extensions.Logging; 12 | using Moq; 13 | using RabbitMQ.Client; 14 | using RabbitMQ.Client.Events; 15 | using Xunit; 16 | 17 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 18 | 19 | public class RabbitMQTriggerBindingTests 20 | { 21 | [Fact] 22 | public void Verify_BindingDataContract_Types() 23 | { 24 | var expectedContract = new Dictionary(StringComparer.OrdinalIgnoreCase) 25 | { 26 | ["ConsumerTag"] = typeof(string), 27 | ["DeliveryTag"] = typeof(ulong), 28 | ["Redelivered"] = typeof(bool), 29 | ["Exchange"] = typeof(string), 30 | ["RoutingKey"] = typeof(string), 31 | ["BasicProperties"] = typeof(IBasicProperties), 32 | ["Body"] = typeof(ReadOnlyMemory), 33 | ["MessageActions"] = typeof(RabbitMQMessageActions), 34 | }; 35 | 36 | IReadOnlyDictionary actualContract = RabbitMQTriggerBinding.CreateBindingDataContract(); 37 | 38 | foreach (KeyValuePair item in actualContract) 39 | { 40 | Assert.Equal(expectedContract[item.Key], item.Value); 41 | } 42 | } 43 | 44 | [Fact] 45 | public void Verify_BindingDataContract_Values() 46 | { 47 | ulong deliveryTag = 1; 48 | 49 | var rand = new Random(); 50 | byte[] buffer = new byte[10]; 51 | rand.NextBytes(buffer); 52 | 53 | ReadOnlyMemory body = buffer; 54 | var eventArgs = new BasicDeliverEventArgs("ConsumerName", deliveryTag, false, "n/a", "QueueName", null, body); 55 | var messageActions = new RabbitMQMessageActions(Mock.Of(), eventArgs); 56 | 57 | var data = new Dictionary(StringComparer.OrdinalIgnoreCase) 58 | { 59 | ["ConsumerTag"] = "ConsumerName", 60 | ["DeliveryTag"] = deliveryTag, 61 | ["Redelivered"] = false, 62 | ["RoutingKey"] = "QueueName", 63 | ["Body"] = body, 64 | ["Exchange"] = eventArgs.Exchange, 65 | ["BasicProperties"] = eventArgs.BasicProperties, 66 | ["MessageActions"] = messageActions, 67 | }; 68 | 69 | IReadOnlyDictionary actualContract = RabbitMQTriggerBinding.CreateBindingData(eventArgs, messageActions); 70 | 71 | foreach (KeyValuePair item in actualContract) 72 | { 73 | Assert.Equal(data[item.Key], item.Value); 74 | } 75 | } 76 | 77 | [Theory] 78 | [InlineData(true)] 79 | [InlineData(false)] 80 | public async Task RabbitMQTrigger_ManualAck_BasicAckBehavior(bool disableAck) 81 | { 82 | // Arrange 83 | var mockservice = new Mock(); 84 | var mockModel = new Mock(); 85 | mockservice.Setup(a => a.CreateConsumer()).Returns(new AsyncEventingBasicConsumer(mockModel.Object)); 86 | 87 | var mockExecutor = new Mock(); 88 | var mockLogger = new Mock(); 89 | var mockDrainModeManager = new Mock(); 90 | var mockBasicProperties = new Mock(); 91 | 92 | // Simulate successful function execution 93 | mockExecutor 94 | .Setup(executor => executor.TryExecuteAsync(It.IsAny(), It.IsAny())) 95 | .ReturnsAsync(new FunctionResult(true)); 96 | 97 | var listener = new RabbitMQListener( 98 | mockservice.Object, 99 | mockExecutor.Object, 100 | mockLogger.Object, 101 | functionId: "test-function", 102 | queueName: "test-queue", 103 | disableAck: disableAck, 104 | prefetchCount: 10, 105 | drainModeManager: mockDrainModeManager.Object); 106 | 107 | var eventArgs = new BasicDeliverEventArgs 108 | { 109 | DeliveryTag = 1, 110 | Body = new ReadOnlyMemory([0x01, 0x02, 0x03]), 111 | BasicProperties = mockBasicProperties.Object, 112 | }; 113 | 114 | // Act 115 | await listener.StartAsync(CancellationToken.None); 116 | 117 | // Find the Consumer instance passed to RabbitMQService.Consume method 118 | IInvocation consumeInvocation = mockservice.Invocations 119 | .FirstOrDefault(invocation => invocation.Method.Name == "Consume"); 120 | 121 | Assert.NotNull(consumeInvocation); 122 | 123 | var consumer = consumeInvocation.Arguments[2] as AsyncEventingBasicConsumer; 124 | Assert.NotNull(consumer); 125 | 126 | // Simulate message delivery 127 | await consumer.HandleBasicDeliver( 128 | consumerTag: "ctag", 129 | deliveryTag: eventArgs.DeliveryTag, 130 | redelivered: false, 131 | exchange: string.Empty, 132 | routingKey: string.Empty, 133 | properties: eventArgs.BasicProperties, 134 | body: eventArgs.Body.ToArray()); 135 | 136 | // Assert 137 | if (disableAck) 138 | { 139 | mockservice.Verify(channel => channel.Acknowledge(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never, "BasicAck should not be called when DisableAck is true."); 140 | } 141 | else 142 | { 143 | mockservice.Verify(channel => channel.Acknowledge(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once, "BasicAck should be called when DisableAck is false."); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/Trigger/RabbitMQListenerTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Azure.WebJobs.Host.Executors; 9 | using Microsoft.Azure.WebJobs.Host.Scale; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Logging.Internal; 12 | using Moq; 13 | using RabbitMQ.Client; 14 | using Xunit; 15 | 16 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 17 | 18 | public class RabbitMQListenerTests 19 | { 20 | private static readonly IDrainModeManager DrainModeManager = new Mock().Object; 21 | 22 | /// 23 | /// Verifies that the scale monitor descriptor ID is set to expected value. 24 | /// 25 | [Theory] 26 | [InlineData("testUserFunctionId", "testQueueName", "testUserFunctionId-RabbitMQTrigger-testQueueName")] 27 | [InlineData("тестПользовательФункцияИд", "тестОчередьИмя", "тестПользовательФункцияИд-RabbitMQTrigger-тестОчередьИмя")] 28 | public void ScaleMonitorDescriptor_ReturnsExpectedValue(string functionId, string queueName, string expectedDescriptorId) 29 | { 30 | IScaleMonitor monitor = GetScaleMonitor(functionId, queueName); 31 | Assert.Equal(expectedDescriptorId, monitor.Descriptor.Id); 32 | } 33 | 34 | /// 35 | /// Verifies that no-scaling is requested if there are insufficient metrics available for making the scale decision. 36 | /// 37 | [Theory] 38 | [InlineData(null)] // metrics == null 39 | [InlineData(new uint[0])] // metrics.Length == 0 40 | [InlineData(new uint[] { 1000, 1000, 1000, 1000 })] // metrics.Length == 4. 41 | public void ScaleMonitorGetScaleStatus_InsufficentMetrics_ReturnsNone(uint[] messageCounts) 42 | { 43 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 44 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, 0); 45 | 46 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 47 | 48 | Assert.Equal(ScaleVote.None, scaleStatus.Vote); 49 | Assert.Contains("Requesting no-scaling: Insufficient metrics for making scale decision for function: 'testFunctionId', queue: 'testQueueName'.", logMessages); 50 | } 51 | 52 | /// 53 | /// Verifies that only the most recent samples are considered for making the scale decision. 54 | /// 55 | [Theory] 56 | [InlineData(new uint[] { 0, 0, 4, 3, 2, 0 }, 2, ScaleVote.None)] 57 | [InlineData(new uint[] { 0, 0, 4, 3, 2, 1, 0 }, 2, ScaleVote.ScaleIn)] 58 | [InlineData(new uint[] { 1000, 1000, 0, 1, 2, 1000 }, 1, ScaleVote.None)] 59 | [InlineData(new uint[] { 1000, 1000, 0, 1, 2, 3, 1000 }, 1, ScaleVote.ScaleOut)] 60 | public void ScaleMonitorGetScaleStatus_ExcessMetrics_IgnoresExcessMetrics(uint[] messageCounts, int workerCount, ScaleVote scaleVote) 61 | { 62 | (IScaleMonitor monitor, _) = GetScaleMonitor(); 63 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 64 | 65 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 66 | 67 | Assert.Equal(scaleVote, scaleStatus.Vote); 68 | } 69 | 70 | /// 71 | /// Verifies that scale-out is requested if the latest count of messages is above the combined limit of all workers. 72 | /// 73 | [Theory] 74 | [InlineData(new uint[] { 0, 0, 0, 0, 1 }, 0)] 75 | [InlineData(new uint[] { 0, 0, 0, 0, 1001 }, 1)] 76 | [InlineData(new uint[] { 0, 0, 0, 0, 10001 }, 10)] 77 | public void ScaleMonitorGetScaleStatus_LastCountAboveLimit_ReturnsScaleOut(uint[] messageCounts, int workerCount) 78 | { 79 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 80 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 81 | 82 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 83 | 84 | Assert.Equal(ScaleVote.ScaleOut, scaleStatus.Vote); 85 | Assert.Contains("Requesting scale-out: Found too many messages for function: 'testFunctionId', queue: 'testQueueName' relative to the number of workers.", logMessages); 86 | } 87 | 88 | /// 89 | /// Verifies that no-scaling is requested if the latest count of messages is not above the combined limit of all 90 | /// workers. 91 | /// 92 | [Theory] 93 | [InlineData(new uint[] { 0, 0, 0, 0, 0 }, 0)] 94 | [InlineData(new uint[] { 0, 0, 0, 0, 1000 }, 1)] 95 | [InlineData(new uint[] { 0, 0, 0, 0, 10000 }, 10)] 96 | public void ScaleMonitorGetScaleStatus_LastCountBelowLimit_ReturnsNone(uint[] messageCounts, int workerCount) 97 | { 98 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 99 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 100 | 101 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 102 | 103 | Assert.Equal(ScaleVote.None, scaleStatus.Vote); 104 | Assert.Contains("Requesting no-scaling: Found function: 'testFunctionId', queue: 'testQueueName' to not require scaling.", logMessages); 105 | } 106 | 107 | /// 108 | /// Verifies that scale-out is requested if the count of messages is strictly increasing and may exceed the combined 109 | /// limit of all workers. Since the metric samples are separated by 10 seconds, the existing implementation should 110 | /// only consider the last three samples in its calculation. 111 | /// 112 | [Theory] 113 | [InlineData(new uint[] { 0, 1, 500, 501, 751 }, 1)] 114 | [InlineData(new uint[] { 0, 1, 4999, 5001, 7500 }, 10)] 115 | public void ScaleMonitorGetScaleStatus_CountIncreasingAboveLimit_ReturnsScaleOut(uint[] messageCounts, int workerCount) 116 | { 117 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 118 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 119 | 120 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 121 | 122 | Assert.Equal(ScaleVote.ScaleOut, scaleStatus.Vote); 123 | Assert.Contains("Requesting scale-out: Found the messages for function: 'testFunctionId', queue: 'testQueueName' to be continuously increasing and may exceed the maximum limit set for the workers.", logMessages); 124 | } 125 | 126 | /// 127 | /// Verifies that no-scaling is requested if the count of messages is strictly increasing but it may still stay 128 | /// below the combined limit of all workers. Since the metric samples are separated by 10 seconds, the existing 129 | /// implementation should only consider the last three samples in its calculation. 130 | /// 131 | [Theory] 132 | [InlineData(new uint[] { 0, 1, 500, 501, 750 }, 1)] 133 | [InlineData(new uint[] { 0, 1, 5000, 5001, 7500 }, 10)] 134 | public void ScaleMonitorGetScaleStatus_CountIncreasingBelowLimit_ReturnsNone(uint[] messageCounts, int workerCount) 135 | { 136 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 137 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 138 | 139 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 140 | 141 | Assert.Equal(ScaleVote.None, scaleStatus.Vote); 142 | Assert.Contains("Avoiding scale-out: Found the messages for function: 'testFunctionId', queue: 'testQueueName' to be increasing but they may not exceed the maximum limit set for the workers.", logMessages); 143 | } 144 | 145 | /// 146 | /// Verifies that scale-in is requested if the count of messages is strictly decreasing (or zero) and is also below 147 | /// the combined limit of workers after being reduced by one. 148 | /// 149 | [Theory] 150 | [InlineData(new uint[] { 0, 0, 0, 0, 0 }, 1)] 151 | [InlineData(new uint[] { 1, 0, 0, 0, 0 }, 1)] 152 | [InlineData(new uint[] { 5, 4, 3, 2, 0 }, 1)] 153 | [InlineData(new uint[] { 9005, 9004, 9003, 9002, 9000 }, 10)] 154 | public void ScaleMonitorGetScaleStatus_CountDecreasingBelowLimit_ReturnsScaleIn(uint[] messageCounts, int workerCount) 155 | { 156 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 157 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 158 | 159 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 160 | 161 | Assert.Equal(ScaleVote.ScaleIn, scaleStatus.Vote); 162 | Assert.Contains("Requesting scale-in: Found function: 'testFunctionId', queue: 'testQueueName' to be either idle or the messages to be continuously decreasing.", logMessages); 163 | } 164 | 165 | /// 166 | /// Verifies that scale-in is requested if the count of messages is strictly decreasing (or zero) but it is still 167 | /// above the combined limit of workers after being reduced by one. 168 | /// 169 | [Theory] 170 | [InlineData(new uint[] { 5, 4, 3, 2, 1 }, 1)] 171 | [InlineData(new uint[] { 9005, 9004, 9003, 9002, 9001 }, 10)] 172 | public void ScaleMonitorGetScaleStatus_CountDecreasingAboveLimit_ReturnsNone(uint[] messageCounts, int workerCount) 173 | { 174 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 175 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 176 | 177 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 178 | 179 | Assert.Equal(ScaleVote.None, scaleStatus.Vote); 180 | Assert.Contains("Avoiding scale-in: Found the messages for function: 'testFunctionId', queue: 'testQueueName' to be decreasing but they are high enough to require all existing workers for processing.", logMessages); 181 | } 182 | 183 | /// 184 | /// Verifies that no-scaling is requested if the count of messages is neither strictly increasing and nor strictly 185 | /// decreasing. 186 | /// 187 | [Theory] 188 | [InlineData(new uint[] { 0, 0, 1, 2, 3 }, 1)] 189 | [InlineData(new uint[] { 1, 1, 0, 0, 0 }, 10)] 190 | public void ScaleMonitorGetScaleStatus_CountNotIncreasingOrDecreasing_ReturnsNone(uint[] messageCounts, int workerCount) 191 | { 192 | (IScaleMonitor monitor, List logMessages) = GetScaleMonitor(); 193 | ScaleStatusContext context = GetScaleStatusContext(messageCounts, workerCount); 194 | 195 | ScaleStatus scaleStatus = monitor.GetScaleStatus(context); 196 | 197 | Assert.Equal(ScaleVote.None, scaleStatus.Vote); 198 | 199 | // Ensure that no-scaling was not requested because of other conditions. 200 | Assert.DoesNotContain("Avoiding scale-out: Found the messages for function: 'testFunctionId', queue: 'testQueueName' to be increasing but they may not exceed the maximum limit set for the workers.", logMessages); 201 | Assert.DoesNotContain("Avoiding scale-in: Found the messages for function: 'testFunctionId', queue: 'testQueueName' to be decreasing but they are high enough to require all existing workers for processing.", logMessages); 202 | Assert.Contains("Requesting no-scaling: Found function: 'testFunctionId', queue: 'testQueueName' to not require scaling.", logMessages); 203 | } 204 | 205 | private static RabbitMQListener GetScaleMonitor(string functionId, string queueName) 206 | { 207 | return new RabbitMQListener( 208 | Mock.Of(), 209 | Mock.Of(), 210 | Mock.Of(), 211 | functionId, 212 | queueName, 213 | false, 214 | 7357, 215 | DrainModeManager); 216 | } 217 | 218 | private static (IScaleMonitor Monitor, List LogMessages) GetScaleMonitor() 219 | { 220 | (Mock mockLogger, List logMessages) = CreateMockLogger(); 221 | 222 | IScaleMonitor monitor = new RabbitMQListener( 223 | Mock.Of(), 224 | Mock.Of(), 225 | mockLogger.Object, 226 | "testFunctionId", 227 | "testQueueName", 228 | false, 229 | 7357, 230 | DrainModeManager); 231 | 232 | return (monitor, logMessages); 233 | } 234 | 235 | private static ScaleStatusContext GetScaleStatusContext(uint[] messageCounts, int workerCount) 236 | { 237 | DateTime now = DateTime.UtcNow; 238 | 239 | // Returns metric samples separated by 10 seconds. The time-difference is essential for testing the scale-out logic. 240 | return new ScaleStatusContext 241 | { 242 | Metrics = messageCounts?.Select((count, index) => new RabbitMQTriggerMetrics 243 | { 244 | MessageCount = count, 245 | Timestamp = now + TimeSpan.FromSeconds(10 * index), 246 | }), 247 | WorkerCount = workerCount, 248 | }; 249 | } 250 | 251 | private static (Mock Logger, List LogMessages) CreateMockLogger() 252 | { 253 | // Since multiple threads are not involved when computing the scale-status, it should be okay to not use 254 | // a thread-safe collection for storing the log messages. 255 | var logMessages = new List(); 256 | var mockLogger = new Mock(); 257 | 258 | // Both LogInformation() and LogDebug() are extension (static) methods and cannot be mocked. Hence, we need to 259 | // setup callback on an inner class method that gets eventually called by these methods in order to extract 260 | // the log message. 261 | mockLogger 262 | .Setup(logger => logger.Log(It.IsAny(), 0, It.IsAny(), null, It.IsAny>())) 263 | .Callback((LogLevel logLevel, EventId eventId, object state, Exception exception, Func formatter) => 264 | { 265 | logMessages.Add(state.ToString()); 266 | }); 267 | 268 | return (mockLogger, logMessages); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/UtilityTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Configuration; 5 | using Xunit; 6 | 7 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests; 8 | 9 | public class UtilityTests 10 | { 11 | private readonly IConfiguration emptyConfig = new ConfigurationBuilder().AddJsonFile("testappsettings.json").Build(); 12 | 13 | [Theory] 14 | [InlineData("", "hello", "hello")] 15 | [InlineData("rabbitMQTest", "hello", "amqp://guest:guest@tada:5672")] 16 | public void ResolveConnectionString(string attributeConnectionString, string optionsConnectionString, string expectedResolvedString) 17 | { 18 | string resolvedString = Utility.ResolveConnectionString(attributeConnectionString, optionsConnectionString, this.emptyConfig); 19 | Assert.Equal(expectedResolvedString, resolvedString); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/WebJobs.Extensions.RabbitMQ.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net8.0 6 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests 7 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests 8 | latest 9 | true 10 | Recommended 11 | true 12 | false 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | all 36 | 37 | 38 | 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | all 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.Tests/testappsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "connectionStrings": { 3 | "rabbitMQ": "amqp://guest:guest@localhost:5672", 4 | "rabbitMQTest": "amqp://guest:guest@tada:5672" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31912.275 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.RabbitMQ", "WebJobs.Extensions.RabbitMQ\WebJobs.Extensions.RabbitMQ.csproj", "{9DB4F409-0611-42FA-A64E-B17C23A406D3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.RabbitMQ.Tests", "WebJobs.Extensions.RabbitMQ.Tests\WebJobs.Extensions.RabbitMQ.Tests.csproj", "{275A08CC-6C95-4304-92DC-558996A422BE}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebJobs.Extensions.RabbitMQ.Samples", "WebJobs.Extensions.RabbitMQ.Samples\WebJobs.Extensions.RabbitMQ.Samples.csproj", "{DE817270-C43B-4DE8-947C-A45CDF3B963C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Debug|x86.Build.0 = Debug|Any CPU 24 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Release|x86.ActiveCfg = Release|Any CPU 27 | {9DB4F409-0611-42FA-A64E-B17C23A406D3}.Release|x86.Build.0 = Release|Any CPU 28 | {275A08CC-6C95-4304-92DC-558996A422BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {275A08CC-6C95-4304-92DC-558996A422BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {275A08CC-6C95-4304-92DC-558996A422BE}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {275A08CC-6C95-4304-92DC-558996A422BE}.Debug|x86.Build.0 = Debug|Any CPU 32 | {275A08CC-6C95-4304-92DC-558996A422BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {275A08CC-6C95-4304-92DC-558996A422BE}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {275A08CC-6C95-4304-92DC-558996A422BE}.Release|x86.ActiveCfg = Release|Any CPU 35 | {275A08CC-6C95-4304-92DC-558996A422BE}.Release|x86.Build.0 = Release|Any CPU 36 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Debug|x86.Build.0 = Debug|Any CPU 40 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Release|x86.ActiveCfg = Release|Any CPU 43 | {DE817270-C43B-4DE8-947C-A45CDF3B963C}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {89DC6C1F-5714-4338-AB85-1CB1864D06F1} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Bindings/RabbitMQAsyncCollector.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Logging; 8 | using RabbitMQ.Client; 9 | 10 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 11 | 12 | internal class RabbitMQAsyncCollector : IAsyncCollector> 13 | { 14 | private readonly RabbitMQContext context; 15 | private readonly ILogger logger; 16 | 17 | public RabbitMQAsyncCollector(RabbitMQContext context, ILogger logger) 18 | { 19 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); 20 | _ = context ?? throw new ArgumentNullException(nameof(context)); 21 | _ = context.Service ?? throw new ArgumentException("Value cannot be null. Parameter name: context.Service"); 22 | this.context = context; 23 | } 24 | 25 | public Task AddAsync(ReadOnlyMemory message, CancellationToken cancellationToken = default) 26 | { 27 | this.logger.LogDebug("Adding message to batch for publishing..."); 28 | 29 | lock (this.context.Service.PublishBatchLock) 30 | { 31 | this.context.Service.BasicPublishBatch.Add(exchange: string.Empty, routingKey: this.context.ResolvedAttribute.QueueName, mandatory: false, properties: null, body: message); 32 | } 33 | 34 | return Task.CompletedTask; 35 | } 36 | 37 | public Task FlushAsync(CancellationToken cancellationToken = default) 38 | { 39 | return this.PublishAsync(); 40 | } 41 | 42 | internal Task PublishAsync() 43 | { 44 | this.logger.LogDebug("Publishing messages to queue."); 45 | 46 | lock (this.context.Service.PublishBatchLock) 47 | { 48 | this.context.Service.BasicPublishBatch.Publish(); 49 | this.context.Service.ResetPublishBatch(); 50 | } 51 | 52 | return Task.CompletedTask; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Bindings/RabbitMQClientBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Options; 6 | using RabbitMQ.Client; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | internal class RabbitMQClientBuilder(RabbitMQExtensionConfigProvider configProvider, IOptions options) : IConverter 11 | { 12 | private readonly RabbitMQExtensionConfigProvider configProvider = configProvider; 13 | private readonly IOptions options = options; 14 | 15 | public IRabbitMQService Convert(RabbitMQAttribute attribute) 16 | { 17 | return this.CreateModelFromAttribute(attribute); 18 | } 19 | 20 | private IRabbitMQService CreateModelFromAttribute(RabbitMQAttribute attribute) 21 | { 22 | if (attribute == null) 23 | { 24 | throw new ArgumentNullException(nameof(attribute)); 25 | } 26 | 27 | string resolvedConnectionString = Utility.FirstOrDefault(attribute.ConnectionStringSetting, this.options.Value.ConnectionString); 28 | bool resolvedDisableCertificateValidation = Utility.FirstOrDefault(attribute.DisableCertificateValidation, this.options.Value.DisableCertificateValidation); 29 | 30 | return this.configProvider.GetService(resolvedConnectionString, resolvedDisableCertificateValidation); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/DefaultRabbitMQServiceFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 7 | 8 | internal class DefaultRabbitMQServiceFactory : IRabbitMQServiceFactory 9 | { 10 | public IRabbitMQService CreateService(string connectionString, string queueName, bool disableCertificateValidation, ILogger logger) 11 | { 12 | return new RabbitMQService(connectionString, queueName, disableCertificateValidation, logger); 13 | } 14 | 15 | public IRabbitMQService CreateService(string connectionString, bool disableCertificateValidation, ILogger logger) 16 | { 17 | return new RabbitMQService(connectionString, disableCertificateValidation, logger); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/IRabbitMQServiceFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 7 | 8 | public interface IRabbitMQServiceFactory 9 | { 10 | IRabbitMQService CreateService(string connectionString, string queueName, bool disableCertificateValidation, ILogger logger); 11 | 12 | IRabbitMQService CreateService(string connectionString, bool disableCertificateValidation, ILogger logger); 13 | } 14 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/PocoToBytesConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using Newtonsoft.Json; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | internal class PocoToBytesConverter : IConverter> 11 | { 12 | public ReadOnlyMemory Convert(T input) 13 | { 14 | _ = input ?? throw new ArgumentNullException(nameof(input)); 15 | 16 | string res = JsonConvert.SerializeObject(input); 17 | return Encoding.UTF8.GetBytes(res); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/RabbitMQExtensionConfigProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Text; 7 | using Microsoft.Azure.WebJobs.Description; 8 | using Microsoft.Azure.WebJobs.Host; 9 | using Microsoft.Azure.WebJobs.Host.Bindings; 10 | using Microsoft.Azure.WebJobs.Host.Config; 11 | using Microsoft.Azure.WebJobs.Logging; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.Logging; 14 | using Microsoft.Extensions.Options; 15 | 16 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 17 | 18 | [Extension("RabbitMQ")] 19 | internal class RabbitMQExtensionConfigProvider(IOptions options, INameResolver nameResolver, IRabbitMQServiceFactory rabbitMQServiceFactory, ILoggerFactory loggerFactory, IConfiguration configuration, IDrainModeManager drainModeManager) : IExtensionConfigProvider 20 | { 21 | private readonly IOptions options = options; 22 | private readonly INameResolver nameResolver = nameResolver; 23 | private readonly IRabbitMQServiceFactory rabbitMQServiceFactory = rabbitMQServiceFactory; 24 | private readonly ILogger logger = loggerFactory?.CreateLogger(LogCategories.CreateTriggerCategory("RabbitMQ")); 25 | private readonly IConfiguration configuration = configuration; 26 | private readonly ConcurrentDictionary connectionParametersToService = new(); 27 | private readonly IDrainModeManager drainModeManager = drainModeManager; 28 | 29 | public void Initialize(ExtensionConfigContext context) 30 | { 31 | _ = context ?? throw new ArgumentNullException(nameof(context)); 32 | 33 | #pragma warning disable 0618 34 | FluentBindingRule rule = context.AddBindingRule(); 35 | #pragma warning restore 0618 36 | 37 | rule.BindToCollector((attr) => 38 | { 39 | return new RabbitMQAsyncCollector(this.CreateContext(attr), this.logger); 40 | }); 41 | rule.BindToInput(new RabbitMQClientBuilder(this, this.options)); 42 | rule.AddConverter>(arg => Encoding.UTF8.GetBytes(arg)); 43 | rule.AddConverter>(arg => arg); 44 | rule.AddOpenConverter>(typeof(PocoToBytesConverter<>)); 45 | 46 | #pragma warning disable 0618 47 | FluentBindingRule triggerRule = context.AddBindingRule(); 48 | #pragma warning restore 0618 49 | 50 | // More details about why the BindToTrigger was chosen instead of BindToTrigger detailed here https://github.com/Azure/azure-functions-rabbitmq-extension/issues/110 51 | triggerRule.BindToTrigger(new RabbitMQTriggerAttributeBindingProvider( 52 | this.nameResolver, 53 | this, 54 | this.logger, 55 | this.options, 56 | this.configuration, 57 | this.drainModeManager)); 58 | } 59 | 60 | internal RabbitMQContext CreateContext(RabbitMQAttribute attribute) 61 | { 62 | string connectionString = Utility.FirstOrDefault(attribute.ConnectionStringSetting, this.options.Value.ConnectionString); 63 | string queueName = Utility.FirstOrDefault(attribute.QueueName, this.options.Value.QueueName); 64 | bool disableCertificateValidation = Utility.FirstOrDefault(attribute.DisableCertificateValidation, this.options.Value.DisableCertificateValidation); 65 | 66 | var resolvedAttribute = new RabbitMQAttribute 67 | { 68 | ConnectionStringSetting = connectionString, 69 | QueueName = queueName, 70 | DisableCertificateValidation = disableCertificateValidation, 71 | }; 72 | 73 | IRabbitMQService service = this.GetService(connectionString, queueName, disableCertificateValidation); 74 | 75 | return new RabbitMQContext 76 | { 77 | ResolvedAttribute = resolvedAttribute, 78 | Service = service, 79 | }; 80 | } 81 | 82 | internal IRabbitMQService GetService(string connectionString, string queueName, bool disableCertificateValidation) 83 | { 84 | string[] keyArray = 85 | [connectionString, queueName, disableCertificateValidation.ToString()]; 86 | string key = string.Join(",", keyArray); 87 | return this.connectionParametersToService.GetOrAdd(key, _ => this.rabbitMQServiceFactory.CreateService(connectionString, queueName, disableCertificateValidation, this.logger)); 88 | } 89 | 90 | // Overloaded method used only for getting the RabbitMQ client. 91 | internal IRabbitMQService GetService(string connectionString, bool disableCertificateValidation) 92 | { 93 | string[] keyArray = 94 | [connectionString, disableCertificateValidation.ToString()]; 95 | string key = string.Join(",", keyArray); 96 | return this.connectionParametersToService.GetOrAdd(key, _ => this.rabbitMQServiceFactory.CreateService(connectionString, disableCertificateValidation, this.logger)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/RabbitMQOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Hosting; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | /// 11 | /// Configuration options for the RabbitMQ extension. 12 | /// 13 | public class RabbitMQOptions : IOptionsFormatter 14 | { 15 | /// 16 | /// Gets or sets the RabbitMQ connection URI. 17 | /// 18 | public string ConnectionString { get; set; } 19 | 20 | /// 21 | /// Gets or sets the RabbitMQ queue name. 22 | /// 23 | public string QueueName { get; set; } 24 | 25 | /// 26 | /// Gets or sets the RabbitMQ QoS prefetch-count setting. It controls the number of RabbitMQ messages cached. 27 | /// 28 | public ushort PrefetchCount { get; set; } = 30; 29 | 30 | /// 31 | /// Gets or sets a value indicating whether certificate validation should be disabled. Not recommended for 32 | /// production. Does not apply when SSL is disabled. 33 | /// 34 | public bool DisableCertificateValidation { get; set; } 35 | 36 | public string Format() 37 | { 38 | var options = new JObject 39 | { 40 | [nameof(this.QueueName)] = this.QueueName, 41 | [nameof(this.PrefetchCount)] = this.PrefetchCount, 42 | [nameof(this.DisableCertificateValidation)] = this.DisableCertificateValidation, 43 | }; 44 | 45 | return options.ToString(Formatting.Indented); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Config/RabbitMQWebJobsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace Microsoft.Extensions.Hosting; 11 | 12 | /// 13 | /// Extension methods for RabbitMQ integration. 14 | /// 15 | public static class RabbitMQWebJobsBuilderExtensions 16 | { 17 | /// 18 | /// Adds the RabbitMQ extension to the provided . 19 | /// 20 | /// The to configure. 21 | public static IWebJobsBuilder AddRabbitMQ(this IWebJobsBuilder builder) 22 | { 23 | if (builder == null) 24 | { 25 | throw new ArgumentNullException(nameof(builder)); 26 | } 27 | 28 | builder.AddExtension() 29 | .ConfigureOptions((config, path, options) => 30 | { 31 | options.ConnectionString = config.GetWebJobsConnectionString(Constants.RabbitMQ); 32 | IConfigurationSection section = config.GetSection(path); 33 | section.Bind(options); 34 | }); 35 | 36 | builder.Services.AddSingleton(); 37 | return builder; 38 | } 39 | 40 | /// 41 | /// Adds the RabbitMQ extension to the provided . 42 | /// 43 | /// The to configure. 44 | /// An to configure the provided . 45 | public static IWebJobsBuilder AddRabbitMQ(this IWebJobsBuilder builder, Action configure) 46 | { 47 | if (builder == null) 48 | { 49 | throw new ArgumentNullException(nameof(builder)); 50 | } 51 | 52 | if (configure == null) 53 | { 54 | throw new ArgumentNullException(nameof(configure)); 55 | } 56 | 57 | builder.AddRabbitMQ(); 58 | builder.Services.Configure(configure); 59 | 60 | return builder; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 5 | 6 | internal static class Constants 7 | { 8 | public const string LocalHost = "localhost"; 9 | public const string RabbitMQ = "RabbitMQ"; 10 | } 11 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] 7 | [assembly: InternalsVisibleTo("Microsoft.Azure.WebJobs.Extensions.RabbitMQ.Tests")] 8 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/PublicKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/azure-functions-rabbitmq-extension/627b413feb1f4ef0be3e7f42501270c2f869de54/extension/WebJobs.Extensions.RabbitMQ/PublicKey.snk -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/RabbitMQAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.WebJobs.Description; 6 | 7 | namespace Microsoft.Azure.WebJobs; 8 | 9 | /// 10 | /// Attribute used to bind a parameter to RabbitMQ output message. 11 | /// 12 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] 13 | [Binding] 14 | public sealed class RabbitMQAttribute : Attribute 15 | { 16 | /// 17 | /// Gets or sets the setting name for RabbitMQ connection URI. 18 | /// 19 | [ConnectionString] 20 | public string ConnectionStringSetting { get; set; } 21 | 22 | /// 23 | /// Gets or sets the RabbitMQ queue name. 24 | /// 25 | [AutoResolve] 26 | public string QueueName { get; set; } 27 | 28 | /// 29 | /// Gets or sets a value indicating whether certificate validation should be disabled. Not recommended for 30 | /// production. Does not apply when SSL is disabled. 31 | /// 32 | public bool DisableCertificateValidation { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/RabbitMQContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 5 | 6 | internal class RabbitMQContext 7 | { 8 | public RabbitMQAttribute ResolvedAttribute { get; set; } 9 | 10 | public IRabbitMQService Service { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/RabbitMQWebJobsStartup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 5 | using Microsoft.Azure.WebJobs.Hosting; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | [assembly: WebJobsStartup(typeof(RabbitMQWebJobsStartup))] 9 | 10 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 11 | 12 | public class RabbitMQWebJobsStartup : IWebJobsStartup 13 | { 14 | public void Configure(IWebJobsBuilder builder) 15 | { 16 | builder.AddRabbitMQ(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Services/IRabbitMQService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using RabbitMQ.Client; 6 | using RabbitMQ.Client.Events; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | public interface IRabbitMQService 11 | { 12 | IBasicPublishBatch BasicPublishBatch { get; } 13 | 14 | object PublishBatchLock { get; } 15 | 16 | void ResetPublishBatch(); 17 | 18 | void ConfigureQos(uint prefetchSize, ushort prefetchCount, bool global); 19 | 20 | QueueDeclareOk GetQueueInfo(string queueName); 21 | 22 | AsyncEventingBasicConsumer CreateConsumer(); 23 | 24 | string Consume(string queue, bool autoAck, AsyncEventingBasicConsumer consumer); 25 | 26 | void OnMessageConsumed(string consumerTag, ulong deliveryTag); 27 | 28 | void Acknowledge(ulong deliveryTag, bool multiple, string logDetails); 29 | 30 | void Reject(ulong deliveryTag, bool requeue, string logDetails); 31 | 32 | void Publish(string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body); 33 | 34 | void Cancel(string consumerTag); 35 | 36 | void Close(); 37 | } 38 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Services/RabbitMQService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Net.Security; 7 | using Microsoft.Extensions.Logging; 8 | using RabbitMQ.Client; 9 | using RabbitMQ.Client.Events; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 12 | 13 | internal sealed class RabbitMQService : IRabbitMQService 14 | { 15 | private readonly ILogger logger; 16 | private readonly IModel model; 17 | private readonly ConcurrentDictionary deliveredTags = new(); 18 | 19 | public RabbitMQService(string connectionString, bool disableCertificateValidation, ILogger logger) 20 | { 21 | var connectionFactory = new ConnectionFactory 22 | { 23 | Uri = new Uri(connectionString), 24 | 25 | // Required to use async consumer. See: https://www.rabbitmq.com/dotnet-api-guide.html#consuming-async. 26 | DispatchConsumersAsync = true, 27 | }; 28 | 29 | if (disableCertificateValidation && connectionFactory.Ssl.Enabled) 30 | { 31 | connectionFactory.Ssl.AcceptablePolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors; 32 | } 33 | 34 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); 35 | this.model = this.AddShutdownHandler(connectionFactory.CreateConnection().CreateModel()); 36 | this.PublishBatchLock = new object(); 37 | } 38 | 39 | public RabbitMQService(string connectionString, string queueName, bool disableCertificateValidation, ILogger logger) 40 | : this(connectionString, disableCertificateValidation, logger) 41 | { 42 | _ = queueName ?? throw new ArgumentNullException(nameof(queueName)); 43 | 44 | this.model.QueueDeclarePassive(queueName); // Throws exception if queue doesn't exist 45 | this.BasicPublishBatch = this.model.CreateBasicPublishBatch(); 46 | } 47 | 48 | public IBasicPublishBatch BasicPublishBatch { get; private set; } 49 | 50 | public object PublishBatchLock { get; } 51 | 52 | // Typically called after a flush 53 | public void ResetPublishBatch() 54 | { 55 | this.BasicPublishBatch = this.model.CreateBasicPublishBatch(); 56 | } 57 | 58 | public void ConfigureQos(uint prefetchSize, ushort prefetchCount, bool global) 59 | { 60 | this.model.BasicQos(prefetchSize, prefetchCount, global); 61 | } 62 | 63 | public QueueDeclareOk GetQueueInfo(string queueName) 64 | { 65 | return this.model.QueueDeclarePassive(queueName); 66 | } 67 | 68 | public AsyncEventingBasicConsumer CreateConsumer() 69 | { 70 | return new AsyncEventingBasicConsumer(this.model); 71 | } 72 | 73 | public string Consume(string queue, bool autoAck, AsyncEventingBasicConsumer consumer) 74 | { 75 | return this.model.BasicConsume(queue, autoAck, consumer); 76 | } 77 | 78 | public void OnMessageConsumed(string consumerTag, ulong deliveryTag) 79 | { 80 | this.deliveredTags.TryAdd(deliveryTag, 0); 81 | } 82 | 83 | public void Acknowledge(ulong deliveryTag, bool multiple, string logDetails) 84 | { 85 | if (this.deliveredTags.TryRemove(deliveryTag, out _)) 86 | { 87 | this.model.BasicAck(deliveryTag, multiple); 88 | } 89 | else 90 | { 91 | this.logger.LogError($"Failed to acknowledge the message. DeliveryTag ({deliveryTag}) not found for {logDetails}"); 92 | } 93 | } 94 | 95 | public void Reject(ulong deliveryTag, bool requeue, string logDetails) 96 | { 97 | if (this.deliveredTags.TryRemove(deliveryTag, out _)) 98 | { 99 | this.model.BasicReject(deliveryTag, requeue); 100 | } 101 | else 102 | { 103 | this.logger.LogError($"Failed to reject the message. DeliveryTag ({deliveryTag}) not found for {logDetails}"); 104 | } 105 | } 106 | 107 | public void Publish(string exchange, string routingKey, IBasicProperties basicProperties, ReadOnlyMemory body) 108 | { 109 | this.model.BasicPublish(exchange, routingKey, mandatory: false, basicProperties, body); 110 | } 111 | 112 | public void Cancel(string consumerTag) 113 | { 114 | this.model.BasicCancel(consumerTag); 115 | } 116 | 117 | public void Close() 118 | { 119 | this.model.Close(); 120 | } 121 | 122 | private IModel AddShutdownHandler(IModel model) 123 | { 124 | model.ModelShutdown += (sender, args) => 125 | { 126 | this.logger.LogError($"[!] Channel closed due to error: {args.Exception?.Message}"); 127 | }; 128 | return model; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/BasicDeliverEventArgsValueProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host.Bindings; 8 | using Newtonsoft.Json; 9 | using RabbitMQ.Client.Events; 10 | 11 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 12 | 13 | public class BasicDeliverEventArgsValueProvider(BasicDeliverEventArgs input, Type destinationType) : IValueProvider 14 | { 15 | private readonly BasicDeliverEventArgs input = input; 16 | 17 | public Type Type { get; } = destinationType; 18 | 19 | public Task GetValueAsync() 20 | { 21 | if (this.Type.Equals(typeof(BasicDeliverEventArgs))) 22 | { 23 | return Task.FromResult(this.input); 24 | } 25 | else if (this.Type.Equals(typeof(ReadOnlyMemory))) 26 | { 27 | return Task.FromResult(this.input.Body); 28 | } 29 | else if (this.Type.Equals(typeof(byte[]))) 30 | { 31 | return Task.FromResult(this.input.Body.ToArray()); 32 | } 33 | 34 | string inputValue = this.ToInvokeString(); 35 | if (this.Type.Equals(typeof(string))) 36 | { 37 | return Task.FromResult(inputValue); 38 | } 39 | else 40 | { 41 | try 42 | { 43 | return Task.FromResult(JsonConvert.DeserializeObject(inputValue, this.Type)); 44 | } 45 | catch (JsonException e) 46 | { 47 | // Give useful error if object in queue is not deserialized properly. 48 | string msg = $@"Binding parameters to complex objects (such as '{this.Type.Name}') uses Json.NET serialization. The JSON parser failed: {e.Message}"; 49 | throw new InvalidOperationException(msg, e); 50 | } 51 | } 52 | } 53 | 54 | public string ToInvokeString() 55 | { 56 | return Encoding.UTF8.GetString(this.input.Body.ToArray()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQActivitySource.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Text; 7 | using RabbitMQ.Client; 8 | 9 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 10 | 11 | internal sealed class RabbitMQActivitySource 12 | { 13 | private static readonly ActivitySource ActivitySource = new("Microsoft.Azure.WebJobs.Extensions.RabbitMQ"); 14 | 15 | /// 16 | /// Creates a new activity. 17 | /// 18 | /// Represents the RabbitMQ message content header. 19 | /// The created activity object. 20 | public static Activity StartActivity(IBasicProperties basicProperties) 21 | { 22 | // Ideally, we would have used string values for headers, but RabbitMQ client has an old quirk where it does 23 | // not differentiate between string headers and byte-array headers when decoding them. See: 24 | // https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/415. Hence, it is decided to also set a byte[] 25 | // as the value for 'traceparent' header to keep its types consistent for all cases. 26 | if (basicProperties.Headers?.ContainsKey("traceparent") == true) 27 | { 28 | byte[] traceParentIdInBytes = basicProperties.Headers["traceparent"] as byte[]; 29 | string traceparentId = Encoding.UTF8.GetString(traceParentIdInBytes); 30 | return ActivitySource.StartActivity("Trigger", ActivityKind.Consumer, traceparentId); 31 | } 32 | else 33 | { 34 | Activity activity = ActivitySource.StartActivity("Trigger", ActivityKind.Consumer); 35 | 36 | // Method 'StartActivity' will return null if it has no event listeners. 37 | if (activity != null) 38 | { 39 | basicProperties.Headers ??= new Dictionary(); 40 | byte[] traceParentIdInBytes = Encoding.UTF8.GetBytes(activity.Id); 41 | basicProperties.Headers["traceparent"] = traceParentIdInBytes; 42 | } 43 | 44 | return activity; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQListener.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Globalization; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.Azure.WebJobs.Host; 12 | using Microsoft.Azure.WebJobs.Host.Executors; 13 | using Microsoft.Azure.WebJobs.Host.Listeners; 14 | using Microsoft.Azure.WebJobs.Host.Scale; 15 | using Microsoft.Extensions.Logging; 16 | using RabbitMQ.Client; 17 | using RabbitMQ.Client.Events; 18 | 19 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 20 | 21 | internal sealed class RabbitMQListener : IListener, IScaleMonitor 22 | { 23 | private const int ListenerNotStarted = 0; 24 | private const int ListenerStarting = 1; 25 | private const int ListenerStarted = 2; 26 | private const int ListenerStopping = 3; 27 | private const int ListenerStopped = 4; 28 | 29 | private const string RequeueCountHeaderName = "x-ms-rabbitmq-requeuecount"; 30 | 31 | private readonly IRabbitMQService service; 32 | private readonly ITriggeredFunctionExecutor executor; 33 | private readonly ILogger logger; 34 | private readonly string queueName; 35 | private readonly ushort prefetchCount; 36 | private readonly bool disableAck; 37 | private readonly string logDetails; 38 | private readonly IDrainModeManager drainModeManager; 39 | 40 | private readonly CancellationTokenSource listenerCancellationTokenSource; 41 | private int listenerState = ListenerNotStarted; 42 | private string consumerTag; 43 | 44 | public RabbitMQListener( 45 | IRabbitMQService service, 46 | ITriggeredFunctionExecutor executor, 47 | ILogger logger, 48 | string functionId, 49 | string queueName, 50 | bool disableAck, 51 | ushort prefetchCount, 52 | IDrainModeManager drainModeManager) 53 | { 54 | this.service = service ?? throw new ArgumentNullException(nameof(service)); 55 | this.executor = executor ?? throw new ArgumentNullException(nameof(executor)); 56 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); 57 | this.queueName = !string.IsNullOrWhiteSpace(queueName) ? queueName : throw new ArgumentNullException(nameof(queueName)); 58 | this.disableAck = disableAck; 59 | this.prefetchCount = prefetchCount; 60 | this.drainModeManager = drainModeManager; 61 | this.listenerCancellationTokenSource = new CancellationTokenSource(); 62 | 63 | _ = !string.IsNullOrWhiteSpace(functionId) ? true : throw new ArgumentNullException(nameof(functionId)); 64 | 65 | // Do not convert the scale-monitor ID to lower-case string since RabbitMQ queue names are case-sensitive. 66 | this.Descriptor = new ScaleMonitorDescriptor($"{functionId}-RabbitMQTrigger-{queueName}", functionId); 67 | this.logDetails = $"function: '{functionId}', queue: '{queueName}'"; 68 | } 69 | 70 | public ScaleMonitorDescriptor Descriptor { get; } 71 | 72 | public void Cancel() 73 | { 74 | this.StopAsync(CancellationToken.None).Wait(); 75 | } 76 | 77 | public void Dispose() 78 | { 79 | // Nothing to dispose. 80 | } 81 | 82 | public Task StartAsync(CancellationToken cancellationToken) 83 | { 84 | int previousState = Interlocked.CompareExchange(ref this.listenerState, ListenerStarting, ListenerNotStarted); 85 | 86 | // It is possible that the WebJobs SDKS invokes StartAsync() method more than once, if there are other trigger 87 | // listeners registered and some of them have failed to start. 88 | if (previousState != ListenerNotStarted) 89 | { 90 | throw new InvalidOperationException("The listener is either starting or has already started."); 91 | } 92 | 93 | // The RabbitMQ server (v3.11.2 as of latest) only has support for prefetch size of zero (no specific limit). 94 | // See: https://github.com/rabbitmq/rabbitmq-server/blob/v3.11.2/deps/rabbit/src/rabbit_channel.erl#L1543. 95 | // See: https://www.rabbitmq.com/amqp-0-9-1-reference.html#basic.qos.prefetch-size for protocol specification. 96 | this.service.ConfigureQos(prefetchSize: 0, this.prefetchCount, global: false); 97 | 98 | // We should use AsyncEventingBasicConsumer to create the consumer since our handler method is async. Using 99 | // EventingBasicConsumer led to issue: https://github.com/Azure/azure-functions-rabbitmq-extension/issues/211). 100 | AsyncEventingBasicConsumer consumer = this.service.CreateConsumer(); 101 | 102 | consumer.Received += ReceivedHandler; 103 | 104 | this.consumerTag = this.service.Consume(this.queueName, autoAck: false, consumer); 105 | this.logger.LogDebug($"Started consuming with consumerTag: {this.consumerTag} for {this.logDetails}."); 106 | 107 | this.listenerState = ListenerStarted; 108 | this.logger.LogDebug($"Started RabbitMQ trigger listener for {this.logDetails}."); 109 | 110 | return Task.CompletedTask; 111 | 112 | async Task ReceivedHandler(object model, BasicDeliverEventArgs args) 113 | { 114 | this.service.OnMessageConsumed(args.ConsumerTag, args.DeliveryTag); 115 | 116 | using Activity activity = RabbitMQActivitySource.StartActivity(args.BasicProperties); 117 | 118 | var input = new TriggeredFunctionData() { TriggerValue = args }; 119 | 120 | FunctionResult result = await this.executor.TryExecuteAsync(input, this.listenerCancellationTokenSource.Token).ConfigureAwait(false); 121 | 122 | if (!result.Succeeded) 123 | { 124 | // Retry by republishing a copy of message to the queue if the triggered function failed to run. 125 | args.BasicProperties.Headers ??= new Dictionary(); 126 | args.BasicProperties.Headers.TryGetValue(RequeueCountHeaderName, out object headerValue); 127 | int requeueCount = Convert.ToInt32(headerValue, CultureInfo.InvariantCulture) + 1; 128 | 129 | if (requeueCount >= 5) 130 | { 131 | // Discard or 'dead-letter' the message. See: https://www.rabbitmq.com/dlx.html. 132 | this.logger.LogDebug($"Rejecting message since the requeue count exceeded for {this.logDetails}."); 133 | this.service.Reject(args.DeliveryTag, requeue: false, logDetails: this.logDetails); 134 | return; 135 | } 136 | 137 | this.logger.LogDebug($"Republishing message for {this.logDetails}."); 138 | args.BasicProperties.Headers[RequeueCountHeaderName] = requeueCount; 139 | 140 | // We cannot call BasicReject() on the message with requeue = true since that would not enable a fixed 141 | // number of retry attempts. See: https://stackoverflow.com/q/23158310. 142 | this.service.Publish(exchange: string.Empty, routingKey: this.queueName, args.BasicProperties, args.Body); 143 | 144 | // Acknowledge the existing message after the message is re-published. 145 | this.service.Acknowledge(args.DeliveryTag, multiple: false, logDetails: this.logDetails); 146 | } 147 | else if (!this.disableAck) 148 | { 149 | // Acknowledge the existing message if manualAck is not set and function execution was successful. 150 | this.service.Acknowledge(args.DeliveryTag, multiple: false, logDetails: this.logDetails); 151 | } 152 | else 153 | { 154 | // Do not acknowledge the message if manualAck is set and function execution was successful. 155 | this.logger.LogDebug($"Not acknowledging message for {this.logDetails} since manualAck is set."); 156 | } 157 | } 158 | } 159 | 160 | public Task StopAsync(CancellationToken cancellationToken) 161 | { 162 | int previousState = Interlocked.CompareExchange(ref this.listenerState, ListenerStopping, ListenerStarted); 163 | 164 | if (previousState == ListenerStarted) 165 | { 166 | // TODO: Close RabbitMQ connection along with the channel. 167 | this.service.Cancel(this.consumerTag); 168 | this.service.Close(); 169 | 170 | if (!this.drainModeManager.IsDrainModeEnabled) 171 | { 172 | this.listenerCancellationTokenSource.Cancel(); 173 | } 174 | 175 | this.listenerState = ListenerStopped; 176 | this.logger.LogDebug($"Stopped RabbitMQ trigger listener for {this.logDetails}."); 177 | } 178 | 179 | return Task.CompletedTask; 180 | } 181 | 182 | async Task IScaleMonitor.GetMetricsAsync() 183 | { 184 | return await this.GetMetricsAsync().ConfigureAwait(false); 185 | } 186 | 187 | public Task GetMetricsAsync() 188 | { 189 | QueueDeclareOk queueInfo = this.service.GetQueueInfo(this.queueName); 190 | 191 | var metrics = new RabbitMQTriggerMetrics 192 | { 193 | MessageCount = queueInfo.MessageCount, 194 | Timestamp = DateTime.UtcNow, 195 | }; 196 | 197 | return Task.FromResult(metrics); 198 | } 199 | 200 | ScaleStatus IScaleMonitor.GetScaleStatus(ScaleStatusContext context) 201 | { 202 | return this.GetScaleStatusCore(context.WorkerCount, context.Metrics?.Cast().ToArray()); 203 | } 204 | 205 | public ScaleStatus GetScaleStatus(ScaleStatusContext context) 206 | { 207 | return this.GetScaleStatusCore(context.WorkerCount, context.Metrics?.ToArray()); 208 | } 209 | 210 | /// 211 | /// Returns scale recommendation i.e. whether to scale in or out the host application. The recommendation is 212 | /// made based on both the latest metrics and the trend of increase or decrease in the the number of messages in 213 | /// Ready state in the queue. In all of the calculations, it is attempted to keep the number of workers minimum 214 | /// while also ensuring that the message count per worker stays under the maximum limit. 215 | /// 216 | /// The current worker count for the host application. 217 | /// The collection of metrics samples to make the scale decision. 218 | private ScaleStatus GetScaleStatusCore(int workerCount, RabbitMQTriggerMetrics[] metrics) 219 | { 220 | // We require minimum 5 samples to estimate the trend of variations in message count with certain reliability. 221 | // These samples roughly cover the timespan of past 40 seconds. 222 | int minSamplesForScaling = 5; 223 | 224 | // TODO: Make this value configurable. 225 | // Upper limit on the count of messages that needs to be maintained per worker. 226 | int maxMessagesPerWorker = 1000; 227 | 228 | var status = new ScaleStatus 229 | { 230 | Vote = ScaleVote.None, 231 | }; 232 | 233 | // Do not make a scale decision unless we have enough samples. 234 | if (metrics == null || metrics.Length < minSamplesForScaling) 235 | { 236 | this.logger.LogInformation($"Requesting no-scaling: Insufficient metrics for making scale decision for {this.logDetails}."); 237 | return status; 238 | } 239 | 240 | // Consider only the most recent batch of samples in the rest of the method. 241 | var latestMetrics = new RabbitMQTriggerMetrics[minSamplesForScaling]; 242 | Array.Copy(metrics, metrics.Length - minSamplesForScaling, latestMetrics, 0, minSamplesForScaling); 243 | metrics = latestMetrics; 244 | 245 | string counts = string.Join(", ", metrics.Select(metric => metric.MessageCount)); 246 | this.logger.LogInformation($"Message counts: [{counts}], worker count: {workerCount}, maximum messages per worker: {maxMessagesPerWorker}."); 247 | 248 | // Add worker if the count of messages per worker exceeds the maximum limit. 249 | long lastMessageCount = metrics.Last().MessageCount; 250 | if (lastMessageCount > workerCount * maxMessagesPerWorker) 251 | { 252 | status.Vote = ScaleVote.ScaleOut; 253 | this.logger.LogInformation($"Requesting scale-out: Found too many messages for {this.logDetails} relative to the number of workers."); 254 | return status; 255 | } 256 | 257 | // Check if there is a continuous increase or decrease in the count of messages. 258 | bool isIncreasing = true; 259 | bool isDecreasing = true; 260 | for (int index = 0; index < metrics.Length - 1; index++) 261 | { 262 | isIncreasing = isIncreasing && metrics[index].MessageCount < metrics[index + 1].MessageCount; 263 | isDecreasing = isDecreasing && (metrics[index + 1].MessageCount == 0 || metrics[index].MessageCount > metrics[index + 1].MessageCount); 264 | } 265 | 266 | if (isIncreasing) 267 | { 268 | // Scale out only if the expected count of messages would exceed the combined limit after 30 seconds. 269 | DateTime referenceTime = metrics[metrics.Length - 1].Timestamp - TimeSpan.FromSeconds(30); 270 | RabbitMQTriggerMetrics referenceMetric = metrics.First(metric => metric.Timestamp > referenceTime); 271 | long expectedMessageCount = (2 * metrics[metrics.Length - 1].MessageCount) - referenceMetric.MessageCount; 272 | 273 | if (expectedMessageCount > workerCount * maxMessagesPerWorker) 274 | { 275 | status.Vote = ScaleVote.ScaleOut; 276 | this.logger.LogInformation($"Requesting scale-out: Found the messages for {this.logDetails} to be continuously increasing" + 277 | " and may exceed the maximum limit set for the workers."); 278 | return status; 279 | } 280 | else 281 | { 282 | this.logger.LogDebug($"Avoiding scale-out: Found the messages for {this.logDetails} to be increasing" + 283 | " but they may not exceed the maximum limit set for the workers."); 284 | } 285 | } 286 | 287 | if (isDecreasing) 288 | { 289 | // Scale in only if the count of messages will not exceed the combined limit post the scale-in operation. 290 | if (lastMessageCount <= (workerCount - 1) * maxMessagesPerWorker) 291 | { 292 | status.Vote = ScaleVote.ScaleIn; 293 | this.logger.LogInformation($"Requesting scale-in: Found {this.logDetails} to be either idle or the messages to be continuously decreasing."); 294 | return status; 295 | } 296 | else 297 | { 298 | this.logger.LogDebug($"Avoiding scale-in: Found the messages for {this.logDetails} to be decreasing" + 299 | " but they are high enough to require all existing workers for processing."); 300 | } 301 | } 302 | 303 | this.logger.LogInformation($"Requesting no-scaling: Found {this.logDetails} to not require scaling."); 304 | return status; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQMessageActions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using RabbitMQ.Client; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 10 | 11 | public class RabbitMQMessageActions 12 | { 13 | private readonly IRabbitMQService service; 14 | private readonly BasicDeliverEventArgs message; 15 | 16 | internal RabbitMQMessageActions(IRabbitMQService service, BasicDeliverEventArgs message) 17 | { 18 | this.service = service; 19 | this.message = message; 20 | } 21 | 22 | public async Task Reject(bool requeue = false) 23 | { 24 | await Task.Run(() => 25 | { 26 | this.service.Reject(deliveryTag: this.message.DeliveryTag, requeue, logDetails: $"ConsumerTag: {this.message.ConsumerTag}"); 27 | }); 28 | } 29 | 30 | public async Task Acknowledge() 31 | { 32 | await Task.Run(() => 33 | { 34 | this.service.Acknowledge(deliveryTag: this.message.DeliveryTag, multiple: false, logDetails: $"ConsumerTag: {this.message.ConsumerTag}"); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQTriggerAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Azure.WebJobs.Description; 6 | 7 | namespace Microsoft.Azure.WebJobs; 8 | 9 | /// 10 | /// Attribute used to bind a parameter to RabbitMQ trigger message. 11 | /// 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// RabbitMQ queue name. 16 | [AttributeUsage(AttributeTargets.Parameter)] 17 | [Binding] 18 | public sealed class RabbitMQTriggerAttribute(string queueName) : Attribute 19 | { 20 | /// 21 | /// Gets or sets the setting name for RabbitMQ connection URI. 22 | /// 23 | [ConnectionString] 24 | public string ConnectionStringSetting { get; set; } 25 | 26 | /// 27 | /// Gets the RabbitMQ queue name. 28 | /// 29 | public string QueueName { get; private set; } = queueName; 30 | 31 | /// 32 | /// Gets or sets a value indicating whether certificate validation should be disabled. Not recommended for 33 | /// production. Does not apply when SSL is disabled. 34 | /// 35 | public bool DisableCertificateValidation { get; set; } 36 | 37 | /// 38 | /// Gets or sets a value indicating whether message acknowledgements from service would be disabled and needs to be done manually. 39 | /// 40 | public bool DisableAck { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQTriggerAttributeBindingProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Azure.WebJobs.Host.Triggers; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 14 | 15 | internal class RabbitMQTriggerAttributeBindingProvider( 16 | INameResolver nameResolver, 17 | RabbitMQExtensionConfigProvider provider, 18 | ILogger logger, 19 | IOptions options, 20 | IConfiguration configuration, 21 | IDrainModeManager drainModeManager) : ITriggerBindingProvider 22 | { 23 | private readonly INameResolver nameResolver = nameResolver ?? throw new ArgumentNullException(nameof(nameResolver)); 24 | private readonly RabbitMQExtensionConfigProvider provider = provider ?? throw new ArgumentNullException(nameof(provider)); 25 | private readonly ILogger logger = logger ?? throw new ArgumentNullException(nameof(logger)); 26 | private readonly IOptions options = options; 27 | private readonly IConfiguration configuration = configuration; 28 | private readonly IDrainModeManager drainModeManager = drainModeManager; 29 | 30 | public Task TryCreateAsync(TriggerBindingProviderContext context) 31 | { 32 | _ = context ?? throw new ArgumentNullException(nameof(context)); 33 | 34 | ParameterInfo parameter = context.Parameter; 35 | RabbitMQTriggerAttribute attribute = parameter.GetCustomAttribute(inherit: false); 36 | 37 | if (attribute == null) 38 | { 39 | return Task.FromResult(null); 40 | } 41 | 42 | string connectionString = Utility.ResolveConnectionString(attribute.ConnectionStringSetting, this.options.Value.ConnectionString, this.configuration); 43 | string queueName = this.Resolve(attribute.QueueName) ?? throw new InvalidOperationException("RabbitMQ queue name is missing"); 44 | bool disableCertificateValidation = attribute.DisableCertificateValidation || this.options.Value.DisableCertificateValidation; 45 | bool disableAck = attribute.DisableAck; 46 | 47 | IRabbitMQService service = this.provider.GetService(connectionString, queueName, disableCertificateValidation); 48 | 49 | return Task.FromResult(new RabbitMQTriggerBinding(service, queueName, disableAck, this.logger, parameter.ParameterType, this.options.Value.PrefetchCount, this.drainModeManager)); 50 | } 51 | 52 | private string Resolve(string name) 53 | { 54 | return this.nameResolver.ResolveWholeString(name) ?? name; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQTriggerBinding.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Azure.WebJobs.Host.Bindings; 9 | using Microsoft.Azure.WebJobs.Host.Listeners; 10 | using Microsoft.Azure.WebJobs.Host.Protocols; 11 | using Microsoft.Azure.WebJobs.Host.Triggers; 12 | using Microsoft.Extensions.Logging; 13 | using RabbitMQ.Client; 14 | using RabbitMQ.Client.Events; 15 | 16 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 17 | 18 | internal class RabbitMQTriggerBinding(IRabbitMQService service, string queueName, bool disableAck, ILogger logger, Type parameterType, ushort prefetchCount, IDrainModeManager drainModeManager) : ITriggerBinding 19 | { 20 | private readonly IRabbitMQService service = service; 21 | private readonly ILogger logger = logger; 22 | private readonly Type parameterType = parameterType; 23 | private readonly string queueName = queueName; 24 | private readonly bool disableAck = disableAck; 25 | private readonly ushort prefetchCount = prefetchCount; 26 | private readonly IDrainModeManager drainModeManager = drainModeManager; 27 | 28 | public Type TriggerValueType => typeof(BasicDeliverEventArgs); 29 | 30 | public IReadOnlyDictionary BindingDataContract { get; } = CreateBindingDataContract(); 31 | 32 | public Task BindAsync(object value, ValueBindingContext context) 33 | { 34 | var message = (BasicDeliverEventArgs)value; 35 | IReadOnlyDictionary bindingData = CreateBindingData(message, this.disableAck ? new RabbitMQMessageActions(this.service, message) : null); 36 | 37 | return Task.FromResult(new TriggerData(new BasicDeliverEventArgsValueProvider(message, this.parameterType), bindingData)); 38 | } 39 | 40 | public Task CreateListenerAsync(ListenerFactoryContext context) 41 | { 42 | _ = context ?? throw new ArgumentNullException(nameof(context), "Missing listener context"); 43 | 44 | return Task.FromResult(new RabbitMQListener( 45 | this.service, 46 | context.Executor, 47 | this.logger, 48 | context.Descriptor.Id, 49 | this.queueName, 50 | this.disableAck, 51 | this.prefetchCount, 52 | this.drainModeManager)); 53 | } 54 | 55 | public ParameterDescriptor ToParameterDescriptor() 56 | { 57 | return new RabbitMQTriggerParameterDescriptor 58 | { 59 | QueueName = this.queueName, 60 | }; 61 | } 62 | 63 | internal static IReadOnlyDictionary CreateBindingDataContract() 64 | { 65 | var contract = new Dictionary(StringComparer.OrdinalIgnoreCase) 66 | { 67 | ["ConsumerTag"] = typeof(string), 68 | ["DeliveryTag"] = typeof(ulong), 69 | ["Redelivered"] = typeof(bool), 70 | ["Exchange"] = typeof(string), 71 | ["RoutingKey"] = typeof(string), 72 | ["BasicProperties"] = typeof(IBasicProperties), 73 | ["Body"] = typeof(ReadOnlyMemory), 74 | ["MessageActions"] = typeof(RabbitMQMessageActions), 75 | }; 76 | 77 | return contract; 78 | } 79 | 80 | internal static IReadOnlyDictionary CreateBindingData(BasicDeliverEventArgs value, RabbitMQMessageActions messageActions) 81 | { 82 | var bindingData = new Dictionary(StringComparer.OrdinalIgnoreCase); 83 | 84 | SafeAddValue(() => bindingData.Add(nameof(value.ConsumerTag), value.ConsumerTag)); 85 | SafeAddValue(() => bindingData.Add(nameof(value.DeliveryTag), value.DeliveryTag)); 86 | SafeAddValue(() => bindingData.Add(nameof(value.Redelivered), value.Redelivered)); 87 | SafeAddValue(() => bindingData.Add(nameof(value.Exchange), value.Exchange)); 88 | SafeAddValue(() => bindingData.Add(nameof(value.RoutingKey), value.RoutingKey)); 89 | SafeAddValue(() => bindingData.Add(nameof(value.BasicProperties), value.BasicProperties)); 90 | SafeAddValue(() => bindingData.Add(nameof(value.Body), value.Body)); 91 | SafeAddValue(() => bindingData.Add(nameof(messageActions), messageActions)); 92 | 93 | return bindingData; 94 | } 95 | 96 | private static void SafeAddValue(Action addValue) 97 | { 98 | try 99 | { 100 | addValue(); 101 | } 102 | catch (ArgumentException) 103 | { 104 | // some message property getters can throw, based on the 105 | // state of the message 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQTriggerMetrics.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Azure.WebJobs.Host.Scale; 5 | 6 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 7 | 8 | internal class RabbitMQTriggerMetrics : ScaleMetrics 9 | { 10 | public uint MessageCount { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Trigger/RabbitMQTriggerParameterDescriptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Microsoft.Azure.WebJobs.Host.Protocols; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | internal class RabbitMQTriggerParameterDescriptor : TriggerParameterDescriptor 11 | { 12 | public string QueueName { get; set; } 13 | 14 | public override string GetTriggerReason(IDictionary arguments) 15 | { 16 | return $"RabbitMQ message detected from queue: {this.QueueName} at {DateTime.Now}"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/Utility.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using Microsoft.Extensions.Configuration; 7 | 8 | namespace Microsoft.Azure.WebJobs.Extensions.RabbitMQ; 9 | 10 | internal static class Utility 11 | { 12 | internal static string FirstOrDefault(params string[] values) 13 | { 14 | return values.FirstOrDefault(v => !string.IsNullOrEmpty(v)); 15 | } 16 | 17 | internal static TValue FirstOrDefault(params TValue[] values) 18 | where TValue : IEquatable 19 | { 20 | return values.FirstOrDefault(v => !v.Equals(default)); 21 | } 22 | 23 | internal static string ResolveConnectionString(string attributeConnectionStringKey, string optionsConnectionString, IConfiguration configuration) 24 | { 25 | // Is the connection-string key present in the binding attribute? 26 | if (attributeConnectionStringKey != null) 27 | { 28 | string resolvedString = configuration.GetConnectionStringOrSetting(attributeConnectionStringKey); 29 | 30 | // Is there a value associated with the key? 31 | if (!string.IsNullOrEmpty(resolvedString)) 32 | { 33 | return resolvedString; 34 | } 35 | } 36 | 37 | return optionsConnectionString; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/WebJobs.Extensions.RabbitMQ.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library 7 | netstandard2.0 8 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ 9 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ 10 | false 11 | PublicKey.snk 12 | true 13 | latest 14 | true 15 | true 16 | All 17 | true 18 | true 19 | $(NoWarn);CS1591;CS1573 20 | 21 | 22 | Microsoft.Azure.WebJobs.Extensions.RabbitMQ 23 | $(Version) 24 | Microsoft 25 | This package contains binding extensions for RabbitMQ. 26 | © Microsoft Corporation. All rights reserved. 27 | true 28 | MIT 29 | https://github.com/Azure/azure-functions-rabbitmq-extension/wiki 30 | webjobs.png 31 | Microsoft Azure WebJobs AzureFunctions 32 | 33 | 34 | true 35 | true 36 | embedded 37 | 38 | 39 | 40 | 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | runtime; build; native; contentfiles; analyzers; buildtransitive 59 | all 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /extension/WebJobs.Extensions.RabbitMQ/webjobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/azure-functions-rabbitmq-extension/627b413feb1f4ef0be3e7f42501270c2f869de54/extension/WebJobs.Extensions.RabbitMQ/webjobs.png -------------------------------------------------------------------------------- /extension/stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "documentationRules": { 5 | "companyName": ".NET Foundation", 6 | "copyrightText": "Copyright (c) .NET Foundation. All rights reserved.\r\nLicensed under the MIT License. See License.txt in the project root for license information.", 7 | "xmlHeader": false, 8 | "documentInterfaces": false, 9 | "documentInternalElements": false, 10 | "documentExposedElements": false, 11 | "documentPrivateElements": false, 12 | "documentPrivateFields": false 13 | }, 14 | "orderingRules": { 15 | "usingDirectivesPlacement": "outsideNamespace" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "minor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /java-library/.gitignore: -------------------------------------------------------------------------------- 1 | # Java: https://raw.githubusercontent.com/github/gitignore/main/Java.gitignore 2 | 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | replay_pid* 27 | 28 | # Maven: https://raw.githubusercontent.com/github/gitignore/main/Maven.gitignore 29 | 30 | target/ 31 | pom.xml.tag 32 | pom.xml.releaseBackup 33 | pom.xml.versionsBackup 34 | pom.xml.next 35 | release.properties 36 | dependency-reduced-pom.xml 37 | buildNumber.properties 38 | .mvn/timing.properties 39 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 40 | .mvn/wrapper/maven-wrapper.jar 41 | 42 | # Eclipse m2e generated files 43 | # Eclipse Core 44 | .project 45 | # JDT-specific (Eclipse Java Development Tools) 46 | .classpath 47 | 48 | # Visual Studio Code: https://raw.githubusercontent.com/github/gitignore/main/Global/VisualStudioCode.gitignore 49 | 50 | .vscode/* 51 | !.vscode/settings.json 52 | !.vscode/tasks.json 53 | !.vscode/launch.json 54 | !.vscode/extensions.json 55 | !.vscode/*.code-snippets 56 | 57 | # Local History for Visual Studio Code 58 | .history/ 59 | 60 | # Built Visual Studio Code Extensions 61 | *.vsix 62 | -------------------------------------------------------------------------------- /java-library/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vscjava.vscode-java-pack" 4 | ] 5 | } -------------------------------------------------------------------------------- /java-library/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "java.configuration.updateBuildConfiguration": "interactive" 6 | } 7 | -------------------------------------------------------------------------------- /java-library/mvnBuild.bat: -------------------------------------------------------------------------------- 1 | mvn clean package "--define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" --update-snapshots 2 | -------------------------------------------------------------------------------- /java-library/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.microsoft.azure.functions 6 | azure-functions-java-library-rabbitmq 7 | 0.0.0 8 | jar 9 | 10 | 11 | com.microsoft.maven 12 | java-8-parent 13 | 8.0.1 14 | 15 | 16 | Microsoft Azure Functions Java RabbitMQ Types 17 | This package contains all Java interfaces and annotations to interact with Microsoft Azure Functions runtime for RabbitMQ service. 18 | 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | MIT License 26 | https://opensource.org/licenses/MIT 27 | repo 28 | 29 | 30 | 31 | 32 | scm:git:https://github.com/Azure/azure-functions-rabbitmq-extension 33 | scm:git:git@github.com:Azure/azure-functions-rabbitmq-extension 34 | https://github.com/Azure/azure-functions-rabbitmq-extension 35 | HEAD 36 | 37 | 38 | 39 | 40 | vivekjilla 41 | Vivek Jilla 42 | vijilla@microsoft.com 43 | 44 | 45 | aloiva 46 | Pranava Vedagnya Gaddam 47 | prgaddam@microsoft.com 48 | 49 | 50 | manikantanallagatla 51 | Manikanta Nallagatla 52 | mnallagatla@microsoft.com 53 | 54 | 55 | 56 | 57 | 58 | ossrh 59 | Sonatype Snapshots 60 | https://oss.sonatype.org/content/repositories/snapshots/ 61 | true 62 | default 63 | 64 | 65 | 66 | 67 | 68 | maven.snapshots 69 | Maven Central Snapshot Repository 70 | https://oss.sonatype.org/content/repositories/snapshots/ 71 | 72 | false 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 81 | 82 | com.microsoft.azure.functions 83 | azure-functions-java-library 84 | 1.4.2 85 | 86 | 87 | junit 88 | junit 89 | 4.13.2 90 | test 91 | 92 | 93 | org.easymock 94 | easymock 95 | 4.3 96 | test 97 | 98 | 99 | 100 | 101 | 102 | 103 | maven-compiler-plugin 104 | ${maven-compiler.version} 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | ${maven-source.version} 110 | 111 | 112 | attach-sources 113 | 114 | jar 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-javadoc-plugin 122 | ${maven-javadoc.version} 123 | 124 | none 125 | 126 | 127 | 128 | attach-javadocs 129 | 130 | jar 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /java-library/src/main/java/com/microsoft/azure/functions/rabbitmq/annotation/RabbitMQOutput.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for 4 | * license information. 5 | */ 6 | 7 | package com.microsoft.azure.functions.rabbitmq.annotation; 8 | 9 | import com.microsoft.azure.functions.annotation.CustomBinding; 10 | 11 | import java.lang.annotation.ElementType; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.RetentionPolicy; 14 | import java.lang.annotation.Target; 15 | 16 | /** 17 | *

18 | * Java annotation used to bind a parameter to RabbitMQ output message. The type 19 | * of parameter can be a native Java type such as int, 20 | * String or byte[] or a POJO. 21 | *

22 | * 23 | *

24 | * Example function that uses a RabbitMQ trigger and output binding: 25 | *

26 | * 27 | *
28 |  * @FunctionName("RabbitMQExample")
29 |  * public void run(
30 |  *         @RabbitMQTrigger(connectionStringSetting = "ConnectionString", queueName = "input-queue") String message,
31 |  *         @RabbitMQOutput(connectionStringSetting = "ConnectionString", queueName = "output-queue") OutputBinding<String> output,
32 |  *         final ExecutionContext context) {
33 |  *     context.getLogger().info("Java RabbitMQ trigger function processed a message: " + message);
34 |  *     output.setValue(message);
35 |  * }
36 |  * 
37 | */ 38 | 39 | @Retention(RetentionPolicy.RUNTIME) 40 | @Target({ ElementType.PARAMETER, ElementType.METHOD }) 41 | @CustomBinding(direction = "out", name = "outputMessage", type = "rabbitMq") 42 | public @interface RabbitMQOutput { 43 | /** 44 | * Setting name for RabbitMQ connection URI. 45 | * @return Setting name for RabbitMQ connection URI. 46 | */ 47 | String connectionStringSetting() default ""; 48 | 49 | /** 50 | *

Defines how Functions runtime should treat the parameter value. Possible values are:

51 | *
    52 | *
  • "" or string: treat it as a string whose value is serialized from the parameter
  • 53 | *
  • binary: treat it as a binary data whose value comes from for example OutputBinding<byte[]>
  • 54 | *
55 | * 56 | * @return The dataType which will be used by the Functions runtime. 57 | */ 58 | String dataType() default ""; 59 | 60 | /** 61 | * Whether certificate validation should be disabled. Not recommended for production. Does not apply when SSL is 62 | * disabled. 63 | * 64 | * @return Whether certificate validation should be disabled. 65 | */ 66 | boolean disableCertificateValidation() default false; 67 | 68 | /** 69 | * RabbitMQ queue name. 70 | * @return RabbitMQ queue name. 71 | */ 72 | String queueName() default ""; 73 | } 74 | -------------------------------------------------------------------------------- /java-library/src/main/java/com/microsoft/azure/functions/rabbitmq/annotation/RabbitMQTrigger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for 4 | * license information. 5 | */ 6 | 7 | package com.microsoft.azure.functions.rabbitmq.annotation; 8 | 9 | import com.microsoft.azure.functions.annotation.CustomBinding; 10 | 11 | import java.lang.annotation.ElementType; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.RetentionPolicy; 14 | import java.lang.annotation.Target; 15 | 16 | /** 17 | *

18 | * Java annotation used to bind a parameter to RabbitMQ trigger message. The 19 | * type of parameter can be a native Java type such as int, 20 | * String or byte[] or a POJO. 21 | *

22 | * 23 | *

24 | * Example function that uses a RabbitMQ trigger binding: 25 | *

26 | * 27 | *
28 |  * @FunctionName("RabbitMQExample")
29 |  * public void run(
30 |  *         @RabbitMQTrigger(connectionStringSetting = "ConnectionString", queueName = "input-queue") String message,
31 |  *         final ExecutionContext context) {
32 |  *     context.getLogger().info("Java RabbitMQ trigger function processed a message: " + message);
33 |  * }
34 |  * 
35 | */ 36 | 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @Target(ElementType.PARAMETER) 39 | @CustomBinding(direction = "in", name = "inputMessage", type = "rabbitMqTrigger") 40 | public @interface RabbitMQTrigger { 41 | 42 | /** 43 | * Setting name for RabbitMQ connection URI. 44 | * @return Setting name for RabbitMQ connection URI. 45 | */ 46 | String connectionStringSetting() default ""; 47 | 48 | /** 49 | *

Defines how Functions runtime should treat the parameter value. Possible values are:

50 | *
    51 | *
  • "": get the value as a string, and try to deserialize to actual parameter type like POJO
  • 52 | *
  • string: always get the value as a string
  • 53 | *
  • binary: get the value as a binary data, and try to deserialize to actual parameter type byte[]
  • 54 | *
55 | * 56 | * @return The dataType which will be used by the Functions runtime. 57 | */ 58 | String dataType() default ""; 59 | 60 | /** 61 | * Whether certificate validation should be disabled. Not recommended for production. Does not apply when SSL is 62 | * disabled. 63 | * 64 | * @return Whether certificate validation should be disabled. 65 | */ 66 | boolean disableCertificateValidation() default false; 67 | 68 | /** 69 | * RabbitMQ queue name. 70 | * @return RabbitMQ queue name. 71 | */ 72 | String queueName() default ""; 73 | } 74 | -------------------------------------------------------------------------------- /java-library/src/test/java/com/microsoft/azure/functions/rabbitmq/annotation/RabbitMQOutputTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for 4 | * license information. 5 | */ 6 | 7 | package com.microsoft.azure.functions.rabbitmq.annotation; 8 | 9 | import org.easymock.*; 10 | import org.junit.*; 11 | 12 | public class RabbitMQOutputTests { 13 | 14 | @Test 15 | public void TestRabbitMQOutput() { 16 | RabbitMQOutput outputMock = EasyMock.mock(RabbitMQOutput.class); 17 | 18 | EasyMock.expect(outputMock.connectionStringSetting()).andReturn("testConnectionStringSetting"); 19 | EasyMock.expect(outputMock.dataType()).andReturn("testDataType"); 20 | EasyMock.expect(outputMock.disableCertificateValidation()).andReturn(true); 21 | EasyMock.expect(outputMock.queueName()).andReturn("testQueueName"); 22 | EasyMock.replay(outputMock); 23 | 24 | Assert.assertEquals("testConnectionStringSetting", outputMock.connectionStringSetting()); 25 | Assert.assertEquals("testDataType", outputMock.dataType()); 26 | Assert.assertEquals(true, outputMock.disableCertificateValidation()); 27 | Assert.assertEquals("testQueueName", outputMock.queueName()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java-library/src/test/java/com/microsoft/azure/functions/rabbitmq/annotation/RabbitMQTriggerTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for 4 | * license information. 5 | */ 6 | 7 | package com.microsoft.azure.functions.rabbitmq.annotation; 8 | 9 | import org.easymock.*; 10 | import org.junit.*; 11 | 12 | public class RabbitMQTriggerTests { 13 | 14 | @Test 15 | public void TestRabbitMQTrigger() { 16 | RabbitMQTrigger triggerMock = EasyMock.mock(RabbitMQTrigger.class); 17 | 18 | EasyMock.expect(triggerMock.connectionStringSetting()).andReturn("testConnectionStringSetting"); 19 | EasyMock.expect(triggerMock.dataType()).andReturn("testDataType"); 20 | EasyMock.expect(triggerMock.disableCertificateValidation()).andReturn(true); 21 | EasyMock.expect(triggerMock.queueName()).andReturn("testQueueName"); 22 | EasyMock.replay(triggerMock); 23 | 24 | Assert.assertEquals("testConnectionStringSetting", triggerMock.connectionStringSetting()); 25 | Assert.assertEquals("testDataType", triggerMock.dataType()); 26 | Assert.assertEquals(true, triggerMock.disableCertificateValidation()); 27 | Assert.assertEquals("testQueueName", triggerMock.queueName()); 28 | } 29 | } 30 | --------------------------------------------------------------------------------