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:
Larry Osterman 2023-11-30 11:40:30 -08:00 committed by GitHub
parent 25a96f1322
commit faeb832f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 466 additions and 66 deletions

143
doc/SnippetGeneration.md Normal file
View 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.

View File

@ -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 }}

View File

@ -91,3 +91,4 @@ jobs:
CmakeGeneratePath: sdk/${{ parameters.ServiceDirectory }}/${{ artifact.Path }}
GenerateArgs: ${{ cmakeOption.Value }}
Env: "$(CmakeEnvArg)"
PackageName: ${{ artifact.Name }}

View File

@ -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 }}

View 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
}

View File

@ -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
![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fcore%2Fcore-opentelemetry%2FREADME.png)
![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fcore%2Fcore-opentelemetry%2FREADME.png)

View File

@ -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.

View File

@ -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();

View File

@ -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)};

View File

@ -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;

View File

@ -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/

View File

@ -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)
{

View File

@ -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);