From faeb832f8a5a236533db2b2ad4a9c54347bba561 Mon Sep 17 00:00:00 2001 From: Larry Osterman Date: Thu, 30 Nov 2023 11:40:30 -0800 Subject: [PATCH] Initial cut at snippet generation tool. Also updated KeyVault Secrets readme to use snippets as a working proof of concept. (#5213) * First cut at snippet generation - markdown files only for now. * Removed trailing blank lines in updated file * Use snippet generator for keyvault snippets --- doc/SnippetGeneration.md | 143 +++++++++++++++++ .../templates/jobs/archetype-sdk-client.yml | 20 ++- .../templates/jobs/cmake-generate.tests.yml | 1 + .../templates/steps/cmake-generate.yml | 11 +- eng/scripts/Generate-Snippets.ps1 | 148 ++++++++++++++++++ sdk/core/azure-core-amqp/README.md | 35 ++++- sdk/core/azure-core-amqp/samples/README.md | 23 +++ .../eventhub_async_writer_sample.cpp | 5 +- .../eventhub_token_reader_sample.cpp | 4 +- .../eventhub_writer_sample.cpp | 12 +- .../azure-security-keyvault-secrets/README.md | 108 ++++++++----- .../sample1_basic_operations.cpp | 18 ++- .../sample4_get_secrets_deleted.cpp | 4 +- 13 files changed, 466 insertions(+), 66 deletions(-) create mode 100644 doc/SnippetGeneration.md create mode 100644 eng/scripts/Generate-Snippets.ps1 diff --git a/doc/SnippetGeneration.md b/doc/SnippetGeneration.md new file mode 100644 index 000000000..d9c1a5828 --- /dev/null +++ b/doc/SnippetGeneration.md @@ -0,0 +1,143 @@ +# Azure SDK for C++ Snippet Generation + +One of the most important tools for ensuring a consistent and high-quality developer experience is the code snippets that are provided to developers. These snippets are used in documentation, quickstarts, and tutorials. They are also used in the interactive documentation (Try It) and in the VS Code extension. + +It is critical that code snippets remain up-to-date and accurate. A snippet which represents out-of-date code is in many ways worse than no snippet at all. + +To ensure that all snippets are up-to-date, we have created a tool that generates snippets from the source code of a package. A developer can run this tool before checking in changes to the snippets to update the snippets in documentation. +This tool is also run as part of the CI process to verify that the checked in documentation matches the generated snippets. This ensures that the snippets are always up-to-date. + +## About Snippets + +There are two concepts that need to be considered when generating snippets: the snippet itself and the snippet source. + +The snippet is the code that is inserted into the documentation. It is the code that the developer will copy and paste into their application. The snippet source is the code that is used to generate the snippet. The snippet source is typically a function call or a class method call. + +For example, consider the following snippet source: + +```cpp +auto client = Azure::Service::CreateFromConnectionString(connectionString); +``` + +This snippet source is a function call. + +When a developer wants to include this snippet in a documentation page, they insert the snippet into existing markdown: + +~~~markdown + +This is an example of how to use the CreateFromConnectionString API to create a client: + +```cpp +auto client = Azure::Service::CreateFromConnectionString(connectionString); +``` +~~~ + +The problem with this of course is that if the signature of the `CreateFromConnectionString` function changes, the snippet will no +longer be accurate. To solve this problem, we use the snippet source to generate the snippet. + +## Creating Snippets for the Azure SDK for C++ + +There are two steps to generating snippets for the Azure SDK for C++. The first is annotating the source code, +the second is running the snippet generation tool to update the snippets in the documentation. + +### Snippet Source + +Snippet Sources are marked using two special markers: + +```cpp +// @begin_snippet: snippet_name +``` + +and + +```cpp +// @end_snippet +``` + +These markers are used to identify the snippet source. The snippet source is then used to generate the snippet. The snippet is generated by removing the markers and the leading indentation. + +For the example above, the snippet source would be: + +```cpp +// @begin_snippet: CreateServiceFromConnectionString +auto client = Azure::Service::CreateFromConnectionString(connectionString); +// @end_snippet +``` + +The "name" of a snippet can be any string of "word" characters as defined by [.Net Regular +Expressions](https://learn.microsoft.com/dotnet/standard/base-types/character-classes-in-regular-expressions#WordCharacter). + +Note that it is critically important that the snippet source be valid code. +The snippet generation tool does not compile the snippet source, instead the snippet source should be included in the source code built with the project. +That ensures that all snippets are valid code reflecting the current state of the service implementation. + +Snippet sources can be located in any source file, including header files. + +### Snippet use in documentation + +The snippet source is then used in the documentation: + +~~~markdown +This is an example of how to use the CreateFromConnectionString API to create a client: + + +```cpp +auto client = Azure::Service::CreateFromConnectionString(connectionString); +``` +~~~ + +For C++ code, snippets similarly inserted into doxygen comments: + +```cpp +/** + * @brief This is an example of how to use the CreateFromConnectionString API to create a client: + * + * + * \code{cpp} + * auto client = Azure::Service::CreateFromConnectionString(connectionString); + * ``` + */ +``` + +*** NOTE: AS OF 11/30/2023, DOXYGEN SNIPPET INSERTION IS NOT YET SUPPORTED. *** + + +The snippet source is identified by the word following the `@insert_snippet` marker. + +### Snippet Generation + +The snippet generation tool, located in `eng\scripts\Generate-Snippets.ps1`, is a powershell script used to generate snippets from the source code. The tool is run from the root of the repository: + +```bash +pwsh -f eng\scripts\Generate-Snippets.ps1 -source_dir -output_dir + +``` + +The tool will recursively search the source directory for snippet sources and parse the snippet sources in each +source file. + +It then recursively walks the output directory and for each file that contains a snippet, it will replace the +snippet with the corresponding parsed snippet. + + +## Snippet Generation in CI + +The snippet generation tool is also run as part of the CI process. The CI process will run the snippet generation tool +and then compare the generated snippets with the checked in snippets. If there are any differences, the CI process will +fail. + +This ensures that the checked in snippets are always up-to-date. + +## Snippet tool documentation. + +The Generate-Snippets.ps1 script has 3 parameters: + +* -source_dir: The directory containing the source code to be parsed for snippets. +* -output_dir: The directory containing the files to be updated with the generated snippets. +* -verify: If specified, the script will verify that the generated snippets match the checked in snippets. + +The script runs in two passes: The first pass parses the source code and identifies the snippets. The second pass +updates the output files with the generated snippets. + +If two snippets have the same name, the snippet generation tool will fail. Similarly, if an output file +references a snippet which cannot be found, the tool will fail. diff --git a/eng/pipelines/templates/jobs/archetype-sdk-client.yml b/eng/pipelines/templates/jobs/archetype-sdk-client.yml index 5ab2e49c9..848bab457 100644 --- a/eng/pipelines/templates/jobs/archetype-sdk-client.yml +++ b/eng/pipelines/templates/jobs/archetype-sdk-client.yml @@ -1,5 +1,5 @@ # cSpell:ignore vsts -# cSpell:ignore parseazuresdkcpp +# cSpell:ignore parseazuresdkcpp sbom parameters: - name: Artifacts @@ -232,7 +232,7 @@ jobs: # Set AZURE_SDK_VERSION for use in Doxygen generation Write-Host "##vso[task.setvariable variable=AZURE_SDK_VERSION]$version" - displayName: Create package info JSON file + displayName: Create package info JSON file for ${{ artifact.Name }} - task: Powershell@2 inputs: @@ -252,7 +252,7 @@ jobs: build/vcpkg/ports/${{ artifact.VcpkgPortName }}/. ` $(Build.ArtifactStagingDirectory)/packages/${{ artifact.Name }}/vcpkg/port pwsh: true - displayName: Copy vcpkg port files from build + displayName: Copy vcpkg port files from build for ${{ artifact.Name }} - pwsh: | $outputPath = Join-Path -Path $(Build.ArtifactStagingDirectory) packages/${{ artifact.Name }} @@ -263,7 +263,7 @@ jobs: } else { Write-Warning "$changeLogPath does not exist" } - displayName: Copy CHANGELOG.md to package artifact + displayName: Copy CHANGELOG.md to package artifact for ${{ artifact.Name }} - script: cmake --build . --target ${{ artifact.Path }}-docs workingDirectory: build @@ -287,7 +287,17 @@ jobs: -TargetFolder $(Build.ArtifactStagingDirectory)/packages/${{ artifact.Name }}/docs/docs.ms ignoreLASTEXITCODE: true pwsh: true - displayName: Generate artifacts for docs.ms + displayName: Generate ${{ artifact.Name }} artifacts for docs.ms + + - task: Powershell@2 + inputs: + filePath: $(System.DefaultWorkingDirectory)/eng/scripts/Generate-Snippets.ps1 + arguments: > + -source_dir $(Build.SourcesDirectory)/sdk/${{parameters.ServiceDirectory}}/${{artifact.Name}} + -output_dir $(Build.SourcesDirectory)/sdk/${{parameters.ServiceDirectory}}/${{artifact.Name}} + -verify + pwsh: true + displayName: Verify Snippets are correct for ${{artifact.Name}} - pwsh: | New-Item -ItemType directory -Path $(Build.ArtifactStagingDirectory) -Name docs/${{ artifact.Name }} diff --git a/eng/pipelines/templates/jobs/cmake-generate.tests.yml b/eng/pipelines/templates/jobs/cmake-generate.tests.yml index 66456a111..46d414934 100644 --- a/eng/pipelines/templates/jobs/cmake-generate.tests.yml +++ b/eng/pipelines/templates/jobs/cmake-generate.tests.yml @@ -91,3 +91,4 @@ jobs: CmakeGeneratePath: sdk/${{ parameters.ServiceDirectory }}/${{ artifact.Path }} GenerateArgs: ${{ cmakeOption.Value }} Env: "$(CmakeEnvArg)" + PackageName: ${{ artifact.Name }} diff --git a/eng/pipelines/templates/steps/cmake-generate.yml b/eng/pipelines/templates/steps/cmake-generate.yml index f39df2633..124b869fe 100644 --- a/eng/pipelines/templates/steps/cmake-generate.yml +++ b/eng/pipelines/templates/steps/cmake-generate.yml @@ -2,27 +2,28 @@ parameters: CmakeGeneratePath: '' Env: '' GenerateArgs: '' + PackageName: '' steps: - script: mkdir build workingDirectory: ${{ parameters.CmakeGeneratePath }} - displayName: create working directory + displayName: create working directory for ${{ parameters.PackageName }} - pwsh: Write-Host "ENVs - ${{ parameters.Env }} " workingDirectory: ${{ parameters.CmakeGeneratePath }}/build - displayName: ENVs + displayName: ENVs for ${{ parameters.PackageName }} - script: pwd workingDirectory: ${{ parameters.CmakeGeneratePath }}/build - displayName: Show current path + displayName: Show current path for ${{ parameters.PackageName }} - script: | ${{ parameters.Env }} cmake ${{ parameters.GenerateArgs }} .. workingDirectory: ${{ parameters.CmakeGeneratePath }}/build - displayName: cmake generate + displayName: cmake generate for ${{ parameters.PackageName }} env: VCPKG_BINARY_SOURCES: $(VCPKG_BINARY_SOURCES_SECRET) - script: rm -rf build workingDirectory: ${{ parameters.CmakeGeneratePath }} - displayName: clean build folder + displayName: clean build folder for ${{ parameters.PackageName }} diff --git a/eng/scripts/Generate-Snippets.ps1 b/eng/scripts/Generate-Snippets.ps1 new file mode 100644 index 000000000..1469d87f1 --- /dev/null +++ b/eng/scripts/Generate-Snippets.ps1 @@ -0,0 +1,148 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Powershell script to generate snippets from C++ source files. +# Usage: generate_snippets.ps1 + +param( + [Parameter(Mandatory = $true)] + [string]$source_dir, + + [Parameter(Mandatory = $true)] + [string]$output_dir, + + [Parameter(Mandatory = $false)][switch] + [bool]$verify = $false +) + +function ParseSnippets { + param ( + [Parameter(Mandatory = $true)] + [string[]]$input_files + ) + + # for each file in $input_files, read the contents of the file into a string, then search the string for patterns delimited by @begin_snippet and @end_snippet. + # for each pattern found, extract the snippet name and contents, and save the snippet in a map of snippet name to snippet contents. + # then, for each snippet in the map, write the snippet contents to a file in $output_dir with the snippet name as the file name. + #$snippet_pattern = '@begin_snippet:\s+(?\w+)\s+(?.*?)@end_snippet' + + $snippet_map = @{} + $snippet_pattern = '@begin_snippet:\s+(?\w+)\s+\n(?.*?)\s+(//|`*/)\s+@end_snippet' + foreach ($cpp_file in $input_files) { + Write-Host "Scanning source: $cpp_file" + $cpp_file_contents = Get-Content $cpp_file -Raw + $snippet_matches = [regex]::Matches($cpp_file_contents, $snippet_pattern, 'Singleline') + + foreach ($snippet_match in $snippet_matches) { + Write-Host "Found snippet: $($snippet_match.Groups['snippet_name'].Value)" + $snippet_name = $snippet_match.Groups['snippet_name'].Value + $snippet_contents = $snippet_match.Groups['snippet_contents'].Value + if ($snippet_map[$snippet_name]) { + Write-Host "ERROR: Duplicate snippet name: $snippet_name" + exit 1 + } + else { + $snippet_map[$snippet_name] = $snippet_contents + } + } + } + return (, $snippet_map) +} + +function ProcessSnippetsInFile { + param ( + [Parameter(Mandatory = $true)] + [hashtable]$snippet_map, + [Parameter(Mandatory = $true)] + [object]$output_file + ) + $output_file_contents = Get-Content $output_file -Raw + $snippet_matches = [regex]::Matches($output_file_contents, '@insert_snippet:\s+(?\w+)', 'Singleline') + + # if there is no match, we don't need to do anything else. + if ($snippet_matches.Count -eq 0) { + return $true; + } + + $original_file_contents = $output_file_contents + + foreach ($snippet_match in $snippet_matches) { + $snippet_name = $snippet_match.Groups['snippet_name'].Value + Write-Host "Replacing snippet $snippet_name in file $output_file." + if (!$snippet_map[$snippet_name]) { + Write-Host "ERROR: Unknown snippet name: $snippet_name in file $output_file" + return $false + } + + + if ($output_file.Extension -eq '.md') { + + # Remove the existing snippet text, if any. + $output_file_contents = [Regex]::Replace($output_file_contents, "\s+``````cpp.+?``````\s+", "`r`n`r`n", 'Singleline') + + # Insert the snippet text. + $snippet_text = $snippet_map[$snippet_name] + $output_file_contents = $output_file_contents -replace "\s+", "`r`n``````cpp`r`n$snippet_text`r`n```````r`n`r`n" + + } + elseif ($output_file.Extension -eq '.hpp') { + $output_file_contents = $output_file_contents -replace '@insert_snippet:\s+(?\w+)', '$snippet_map[$snippet_name]' + } + elseif ($output_file.Extension -eq '.cpp') { + $output_file_contents = $output_file_contents -replace '@insert_snippet:\s+(?\w+)', '$snippet_map[$snippet_name]' + } + else { + Write-Host "ERROR: Unknown file extension: $output_file" + return $false + } + + } + # The Regex::Replace above inserts an extra newline at the end of the file. Remove it. + $output_file_contents = $output_file_contents -replace "`r`n\s*\Z", "`r`n" + $original_contents = $original_file_contents -replace "`r`n\s*\Z", "`r`n" + + if ($verify) { + if ($output_file_contents -ne $original_contents) { + Write-Host "ERROR: Snippet contents does not match for file: $output_file." + return $false + } + } + elseif (!$verify) { + Write-Host "Writing file: $output_file" + Set-Content -Path $output_file.FullName -Value $output_file_contents + } + return $true + +} + +$source_dir = Resolve-Path $source_dir +$output_dir = Resolve-Path $output_dir + +$input_files = Get-ChildItem -Path $source_dir -Include *.cpp, *.hpp -Recurse + +# The snippet generator only processes markdown files currently. +$output_files = Get-ChildItem -Path $output_dir -Include *.md -Recurse + +$snippet_map = @{} +$snippet_map = ParseSnippets($input_files) + +# for each file in $output_files, read the contents of the file, searching for a string @insert_snippet: . Insert the corresponding snippet from the $snippet_map +# into the file at that location and write it out. +$failed = $false +foreach ($output_file in $output_files) { + $result = ProcessSnippetsInFile -snippet_map $snippet_map -output_file $output_file + if (!$result) { + $failed = $true + } +} +if ($failed) { + Write-Host "ERROR: Snippet generation failed." + + Write-Host "`r`nTo fix this error, run the following command locally:" + Write-Host "`r`n`r`n`t powershell -ExecutionPolicy Bypass -File eng/scripts/Generate-Snippets.ps1 -source_dir $source_dir -output_dir $output_dir`r`n" + Write-Host "`r`nThen, run the following command to verify the changes." + Write-Host "`r`n`r`n`t powershell -ExecutionPolicy Bypass -File eng/scripts/Generate-Snippets.ps1 -source_dir $source_dir -output_dir $output_dir -verify`r`n" + Write-Host "`r`nFinally, commit the changes and push to the remote branch." + + exit 1 +} diff --git a/sdk/core/azure-core-amqp/README.md b/sdk/core/azure-core-amqp/README.md index 6e1bf986c..15ef22d57 100644 --- a/sdk/core/azure-core-amqp/README.md +++ b/sdk/core/azure-core-amqp/README.md @@ -39,6 +39,38 @@ The AMQP library provides the following classes: ## Examples +### Create an AMQP Message Sender + +An AMQP Message Sender is responsible for sending messages to an AMQP server over an AMQP Session. + + +```cpp + Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; + senderOptions.Name = "sender-link"; + senderOptions.MessageSource = "source"; + senderOptions.SettleMode = Azure::Core::Amqp::_internal::SenderSettleMode::Unsettled; + senderOptions.MaxMessageSize = std::numeric_limits::max(); + + Azure::Core::Amqp::_internal::MessageSender sender( + session, credentials->GetEntityPath(), senderOptions, nullptr); +``` + +Once the message sender has been created, it can be used to send messages to the remote server. + + +```cpp + Azure::Core::Amqp::Models::AmqpMessage message; + message.SetBody(Azure::Core::Amqp::Models::AmqpBinaryData{'H', 'e', 'l', 'l', 'o'}); + + constexpr int maxMessageSendCount = 5; + + int messageSendCount = 0; + while (messageSendCount < maxMessageSendCount) + { + auto result = sender.Send(message); + messageSendCount += 1; + } +``` ## Next steps @@ -73,4 +105,5 @@ Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk [cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview [cloud_shell_bash]: https://shell.azure.com/bash -![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fcore%2Fcore-opentelemetry%2FREADME.png) \ No newline at end of file +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fcore%2Fcore-opentelemetry%2FREADME.png) + diff --git a/sdk/core/azure-core-amqp/samples/README.md b/sdk/core/azure-core-amqp/samples/README.md index c2d079523..466fee032 100644 --- a/sdk/core/azure-core-amqp/samples/README.md +++ b/sdk/core/azure-core-amqp/samples/README.md @@ -73,6 +73,28 @@ Demonstrates reading messages from the Azure Event Hubs service using the AMQP p ### eventhub_sas_writer_sample Demonstrates writing messages to the Azure Event Hubs service using the AMQP protocol with SAS authentication. + +```cpp + Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; + senderOptions.Name = "sender-link"; + senderOptions.MessageSource = "source"; + senderOptions.SettleMode = Azure::Core::Amqp::_internal::SenderSettleMode::Unsettled; + senderOptions.MaxMessageSize = std::numeric_limits::max(); + + Azure::Core::Amqp::_internal::MessageSender sender( + session, credentials->GetEntityPath(), senderOptions, nullptr); +``` + + +```cpp + Azure::Core::Amqp::_internal::ConnectionOptions connectionOptions; + connectionOptions.ContainerId = "whatever"; + connectionOptions.EnableTrace = true; + connectionOptions.Port = credential->GetPort(); + Azure::Core::Amqp::_internal::Connection connection( + credential->GetHostName(), credential, connectionOptions); +``` + ### eventhub_token_reader_sample Demonstrates reading messages from the Azure Event Hubs service using the AMQP protocol with an Azure bearer token authentication. @@ -90,3 +112,4 @@ Demonstrates receiving messages from a local AMQP server using the AMQP protocol ### eventhub_get_eventhub_properties_sample Demonstrates receiving messages from the Azure Event Hubs service using an AMQP Management API. + diff --git a/sdk/core/azure-core-amqp/samples/internal/eventhub_async_writer_sample/eventhub_async_writer_sample.cpp b/sdk/core/azure-core-amqp/samples/internal/eventhub_async_writer_sample/eventhub_async_writer_sample.cpp index a9d9cdad2..0b1c180ad 100644 --- a/sdk/core/azure-core-amqp/samples/internal/eventhub_async_writer_sample/eventhub_async_writer_sample.cpp +++ b/sdk/core/azure-core-amqp/samples/internal/eventhub_async_writer_sample/eventhub_async_writer_sample.cpp @@ -31,17 +31,18 @@ int main() Azure::Core::Amqp::_internal::SessionOptions sessionOptions; sessionOptions.InitialIncomingWindowSize = std::numeric_limits::max(); sessionOptions.InitialOutgoingWindowSize = std::numeric_limits::max(); - Azure::Core::Amqp::_internal::Session session(connection); + // @begin_snippet: CreateSender Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; senderOptions.Name = "sender-link"; - senderOptions.MessageSource = "ingress"; + senderOptions.MessageSource = "source"; senderOptions.SettleMode = Azure::Core::Amqp::_internal::SenderSettleMode::Unsettled; senderOptions.MaxMessageSize = std::numeric_limits::max(); Azure::Core::Amqp::_internal::MessageSender sender( session, credentials->GetEntityPath(), senderOptions, nullptr); + // @end_snippet // Open the connection to the remote. sender.Open(); diff --git a/sdk/core/azure-core-amqp/samples/internal/eventhub_token_reader_sample/eventhub_token_reader_sample.cpp b/sdk/core/azure-core-amqp/samples/internal/eventhub_token_reader_sample/eventhub_token_reader_sample.cpp index 8c103c40f..d12e74810 100644 --- a/sdk/core/azure-core-amqp/samples/internal/eventhub_token_reader_sample/eventhub_token_reader_sample.cpp +++ b/sdk/core/azure-core-amqp/samples/internal/eventhub_token_reader_sample/eventhub_token_reader_sample.cpp @@ -22,7 +22,7 @@ int main() { entityPath = std::getenv("EVENTHUB_NAME"); } - + // @begin_snippet: create_connection Azure::Core::Amqp::_internal::ConnectionOptions connectionOptions; connectionOptions.ContainerId = "whatever"; connectionOptions.EnableTrace = true; @@ -30,6 +30,8 @@ int main() Azure::Core::Amqp::_internal::Connection connection( credential->GetHostName(), credential, connectionOptions); + // @end_snippet + Azure::Core::Amqp::_internal::SessionOptions sessionOptions; sessionOptions.InitialIncomingWindowSize = 100; Azure::Core::Amqp::_internal::Session session{connection.CreateSession(sessionOptions)}; diff --git a/sdk/core/azure-core-amqp/samples/internal/eventhub_writer_sample/eventhub_writer_sample.cpp b/sdk/core/azure-core-amqp/samples/internal/eventhub_writer_sample/eventhub_writer_sample.cpp index 7fd7a267d..df3b20c96 100644 --- a/sdk/core/azure-core-amqp/samples/internal/eventhub_writer_sample/eventhub_writer_sample.cpp +++ b/sdk/core/azure-core-amqp/samples/internal/eventhub_writer_sample/eventhub_writer_sample.cpp @@ -32,11 +32,6 @@ int main() auto timeStart = std::chrono::high_resolution_clock::now(); - constexpr int maxMessageSendCount = 5; - - Azure::Core::Amqp::Models::AmqpMessage message; - message.SetBody(Azure::Core::Amqp::Models::AmqpBinaryData{'H', 'e', 'l', 'l', 'o'}); - Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; senderOptions.Name = "sender-link"; senderOptions.MessageSource = "ingress"; @@ -48,12 +43,19 @@ int main() // Open the connection to the remote. sender.Open(); + // @begin_snippet: SendMessages + Azure::Core::Amqp::Models::AmqpMessage message; + message.SetBody(Azure::Core::Amqp::Models::AmqpBinaryData{'H', 'e', 'l', 'l', 'o'}); + + constexpr int maxMessageSendCount = 5; + int messageSendCount = 0; while (messageSendCount < maxMessageSendCount) { auto result = sender.Send(message); messageSendCount += 1; } + // @end_snippet auto timeEnd = std::chrono::high_resolution_clock::now(); std::chrono::nanoseconds timeDiff = timeEnd - timeStart; diff --git a/sdk/keyvault/azure-security-keyvault-secrets/README.md b/sdk/keyvault/azure-security-keyvault-secrets/README.md index 9bf9b3523..a826a6056 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/README.md +++ b/sdk/keyvault/azure-security-keyvault-secrets/README.md @@ -51,87 +51,110 @@ For detailed samples please review the samples provided. First step is to create a SecretClient. -```cpp Snippet:SecretSample1CreateCredential -auto credential = std::make_shared(); + +```cpp + auto credential = std::make_shared(); -// create client -SecretClient secretClient(std::getenv("AZURE_KEYVAULT_URL"), credential); + // create client + SecretClient secretClient(std::getenv("AZURE_KEYVAULT_URL"), credential); ``` ### Create a secret We call the secret client to create a secret. -```cpp Snippet:SecretSample1SetSecret -std::string secretName("MySampleSecret"); -std::string secretValue("my secret value"); + +```cpp + std::string secretName("MySampleSecret"); + std::string secretValue("my secret value"); -secretClient.SetSecret(secretName, secretValue); + secretClient.SetSecret(secretName, secretValue); ``` ### Get a secret We retrieve a secret by name. -```cpp Snippet:SecretSample1GetSecret -// get secret -Secret secret = secretClient.GetSecret(secretName).Value; -std::cout << "Secret is returned with name " << secret.Name << " and value " << secret.Value - << std::endl; + +```cpp + // get secret + KeyVaultSecret secret = secretClient.GetSecret(secretName).Value; + + std::cout << "Secret is returned with name " << secret.Name << " and value " + << secret.Value.Value() << std::endl; ``` ### Update a secret Updating an existing secret -```cpp Snippet:SecretSample1UpdateSecretProperties -// change one of the properties -secret.Properties.ContentType = "my content"; -// update the secret -Secret updatedSecret = secretClient.UpdateSecretProperties(secret.Name, secret.Properties.Version, secret.Properties) - .Value; -std::cout << "Secret's content type is now " << updatedSecret.Properties.ContentType.Value() - << std::endl; + +```cpp + // change one of the properties + secret.Properties.ContentType = "my content"; + // update the secret + KeyVaultSecret updatedSecret = secretClient.UpdateSecretProperties(secret.Properties).Value; + std::cout << "Secret's content type is now " << updatedSecret.Properties.ContentType.Value() + << std::endl; ``` ### Delete a secret Delete an existing secret. -```cpp Snippet:SecretSample1DeleteSecret -// start deleting the secret -DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret.Name); + +```cpp + // start deleting the secret + DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret.Name); + + // You only need to wait for completion if you want to purge or recover the secret. + // The duration of the delete operation might vary + // in case returns too fast increase the timeout value + operation.PollUntilDone(20s); + + // purge the deleted secret + secretClient.PurgeDeletedSecret(secret.Name); ``` ### Delete and purge a secret Delete and Purge a secret. -```cpp Snippet:SecretSample1DeleteSecret -// start deleting the secret -DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret.Name); -// You only need to wait for completion if you want to purge or recover the secret. -operation.PollUntilDone(std::chrono::milliseconds(2000)); -// purge the deleted secret -secretClient.PurgeDeletedSecret(secret.Name); + +```cpp + // start deleting the secret + DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret.Name); + + // You only need to wait for completion if you want to purge or recover the secret. + // The duration of the delete operation might vary + // in case returns too fast increase the timeout value + operation.PollUntilDone(20s); + + // purge the deleted secret + secretClient.PurgeDeletedSecret(secret.Name); ``` ### List Secrets List all the secrets in keyvault. -```cpp Snippet:SecretSample4ListAllSecrets - // get properties of secrets -for (auto secrets = secretClient.GetPropertiesOfSecrets(); secrets.HasPage(); secrets.MoveToNextPage()) -{ // go through every secret of each page returned - for (auto const& secret : secrets.Items) - { - std::cout << "Found Secret with name: " << secret.Name << std::endl; - } -} + +```cpp + // get all the versions of a secret + for (auto secretsVersion = secretClient.GetPropertiesOfSecretsVersions(secret1.Name); + secretsVersion.HasPage(); + secretsVersion.MoveToNextPage()) + { // go through each version of the secret + // the number of results returned for in a page is not guaranteed + // it can be anywhere from 0 to 25 + for (auto const& secret : secretsVersion.Items) + { + std::cout << "Found Secret with name: " << secret.Name + << " and with version: " << secret.Version << std::endl; + } + } ``` - ## Troubleshooting When you interact with the Azure Key Vault Secrets client library using the C++ SDK, errors returned by the service correspond to the same HTTP status codes returned for requests. @@ -216,4 +239,5 @@ Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk [azure_sub]: https://azure.microsoft.com/free/ [api_reference]: https://azure.github.io/azure-sdk-for-cpp/keyvault.html [secrets_client_src]: https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/keyvault/azure-security-keyvault-secrets -[keyvault_docs]: https://docs.microsoft.com/azure/key-vault/ \ No newline at end of file +[keyvault_docs]: https://docs.microsoft.com/azure/key-vault/ + diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample1-basic-operations/sample1_basic_operations.cpp b/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample1-basic-operations/sample1_basic_operations.cpp index e789a65f6..98e76f4fe 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample1-basic-operations/sample1_basic_operations.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample1-basic-operations/sample1_basic_operations.cpp @@ -21,32 +21,41 @@ using namespace std::chrono_literals; int main() { + // @begin_snippet: SecretSample1CreateCredential auto credential = std::make_shared(); // create client SecretClient secretClient(std::getenv("AZURE_KEYVAULT_URL"), credential); - - std::string secretName("MySampleSecret"); - std::string secretValue("my secret value"); + // @end_snippet try { // create secret - secretClient.SetSecret(secretName, secretValue); + // @begin_snippet: SecretSample1CreateSecret + std::string secretName("MySampleSecret"); + std::string secretValue("my secret value"); + secretClient.SetSecret(secretName, secretValue); + // @end_snippet + + // @begin_snippet: SecretSample1GetSecret // get secret KeyVaultSecret secret = secretClient.GetSecret(secretName).Value; std::cout << "Secret is returned with name " << secret.Name << " and value " << secret.Value.Value() << std::endl; + // @end_snippet + // @begin_snippet: SecretSample1UpdateSecretProperties // change one of the properties secret.Properties.ContentType = "my content"; // update the secret KeyVaultSecret updatedSecret = secretClient.UpdateSecretProperties(secret.Properties).Value; std::cout << "Secret's content type is now " << updatedSecret.Properties.ContentType.Value() << std::endl; + // @end_snippet + // @begin_snippet: SecretSample1DeleteSecret // start deleting the secret DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret.Name); @@ -57,6 +66,7 @@ int main() // purge the deleted secret secretClient.PurgeDeletedSecret(secret.Name); + // @end_snippet } catch (Azure::Core::Credentials::AuthenticationException const& e) { diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample4-get-secrets-deleted/sample4_get_secrets_deleted.cpp b/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample4-get-secrets-deleted/sample4_get_secrets_deleted.cpp index 7f1622132..f7be828f7 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample4-get-secrets-deleted/sample4_get_secrets_deleted.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/samples/sample4-get-secrets-deleted/sample4_get_secrets_deleted.cpp @@ -50,12 +50,13 @@ int main() } } + // @begin_snippet: SecretSample4ListAllSecrets // get all the versions of a secret for (auto secretsVersion = secretClient.GetPropertiesOfSecretsVersions(secret1.Name); secretsVersion.HasPage(); secretsVersion.MoveToNextPage()) { // go through each version of the secret - // the number of results returned for in a page is not guaranteed + // the number of results returned for in a page is not guaranteed // it can be anywhere from 0 to 25 for (auto const& secret : secretsVersion.Items) { @@ -63,6 +64,7 @@ int main() << " and with version: " << secret.Version << std::endl; } } + // @end_snippet // start deleting the secret DeleteSecretOperation operation = secretClient.StartDeleteSecret(secret1.Name);