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
This commit is contained in:
parent
25a96f1322
commit
faeb832f8a
143
doc/SnippetGeneration.md
Normal file
143
doc/SnippetGeneration.md
Normal file
@ -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:
|
||||
|
||||
<!-- @insert_snippet: CreateServiceFromConnectionString -->
|
||||
```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:
|
||||
*
|
||||
* <!-- @insert_snippet: CreateServiceFromConnectionString -->
|
||||
* \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 <path to source code containing snippets> -output_dir <path to directory containing output files>
|
||||
|
||||
```
|
||||
|
||||
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.
|
||||
@ -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 }}
|
||||
|
||||
@ -91,3 +91,4 @@ jobs:
|
||||
CmakeGeneratePath: sdk/${{ parameters.ServiceDirectory }}/${{ artifact.Path }}
|
||||
GenerateArgs: ${{ cmakeOption.Value }}
|
||||
Env: "$(CmakeEnvArg)"
|
||||
PackageName: ${{ artifact.Name }}
|
||||
|
||||
@ -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 }}
|
||||
|
||||
148
eng/scripts/Generate-Snippets.ps1
Normal file
148
eng/scripts/Generate-Snippets.ps1
Normal file
@ -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 <source_dir> <output_dir>
|
||||
|
||||
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+(?<snippet_name>\w+)\s+(?<snippet_contents>.*?)@end_snippet'
|
||||
|
||||
$snippet_map = @{}
|
||||
$snippet_pattern = '@begin_snippet:\s+(?<snippet_name>\w+)\s+\n(?<snippet_contents>.*?)\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+(?<snippet_name>\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+@insert_snippet:\s+$snippet_name\s*-->\s+``````cpp.+?``````\s+", "<!-- @insert_snippet: $snippet_name -->`r`n`r`n", 'Singleline')
|
||||
|
||||
# Insert the snippet text.
|
||||
$snippet_text = $snippet_map[$snippet_name]
|
||||
$output_file_contents = $output_file_contents -replace "<!--\s+@insert_snippet:\s+$snippet_name\s*-->\s+", "<!-- @insert_snippet: $snippet_name -->`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+(?<snippet_name>\w+)', '$snippet_map[$snippet_name]'
|
||||
}
|
||||
elseif ($output_file.Extension -eq '.cpp') {
|
||||
$output_file_contents = $output_file_contents -replace '@insert_snippet:\s+(?<snippet_name>\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: <snippet_name>. 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
|
||||
}
|
||||
@ -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.
|
||||
|
||||
<!-- @insert_snippet: CreateSender -->
|
||||
```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<uint16_t>::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.
|
||||
|
||||
<!-- @insert_snippet: SendMessages -->
|
||||
```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
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
<!-- @insert_snippet: CreateSender -->
|
||||
```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<uint16_t>::max();
|
||||
|
||||
Azure::Core::Amqp::_internal::MessageSender sender(
|
||||
session, credentials->GetEntityPath(), senderOptions, nullptr);
|
||||
```
|
||||
|
||||
<!-- @insert_snippet: create_connection -->
|
||||
```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.
|
||||
|
||||
|
||||
@ -31,17 +31,18 @@ int main()
|
||||
Azure::Core::Amqp::_internal::SessionOptions sessionOptions;
|
||||
sessionOptions.InitialIncomingWindowSize = std::numeric_limits<int32_t>::max();
|
||||
sessionOptions.InitialOutgoingWindowSize = std::numeric_limits<uint16_t>::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<uint16_t>::max();
|
||||
|
||||
Azure::Core::Amqp::_internal::MessageSender sender(
|
||||
session, credentials->GetEntityPath(), senderOptions, nullptr);
|
||||
// @end_snippet
|
||||
|
||||
// Open the connection to the remote.
|
||||
sender.Open();
|
||||
|
||||
@ -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)};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<Azure::Identity::DefaultAzureCredential>();
|
||||
<!-- @insert_snippet: SecretSample1CreateCredential -->
|
||||
```cpp
|
||||
auto credential = std::make_shared<Azure::Identity::DefaultAzureCredential>();
|
||||
|
||||
// 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");
|
||||
<!-- @insert_snippet: SecretSample1CreateSecret -->
|
||||
```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;
|
||||
<!-- @insert_snippet: SecretSample1GetSecret -->
|
||||
```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;
|
||||
<!-- @insert_snippet: SecretSample1UpdateSecretProperties -->
|
||||
```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);
|
||||
<!-- @insert_snippet: SecretSample1DeleteSecret -->
|
||||
```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);
|
||||
<!-- @insert_snippet: SecretSample1DeleteSecret -->
|
||||
```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;
|
||||
}
|
||||
}
|
||||
<!-- @insert_snippet: SecretSample4ListAllSecrets -->
|
||||
```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/
|
||||
[keyvault_docs]: https://docs.microsoft.com/azure/key-vault/
|
||||
|
||||
|
||||
@ -21,32 +21,41 @@ using namespace std::chrono_literals;
|
||||
|
||||
int main()
|
||||
{
|
||||
// @begin_snippet: SecretSample1CreateCredential
|
||||
auto credential = std::make_shared<Azure::Identity::DefaultAzureCredential>();
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user