Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SecretReferenceConfigurationSetting, FeatureFlagConfigurationSetting and AddSyncToken #18487

Merged
merged 1 commit into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sdk/appconfiguration/Azure.Data.AppConfiguration/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## 1.1.0-beta.1 (Unreleased)

### Changes

#### New Features

- Added `SecretReferenceConfigurationSetting` type to represent a configuration setting that references a KeyVault Secret.
- Added `FeatureFlagConfigurationSetting` type to represent a configuration setting that controls a feature flag.
maririos marked this conversation as resolved.
Show resolved Hide resolved
- Added `AddSyncToken` to `ConfigurationClient` to be able to provide external synchronization tokens.

## 1.0.2 (2020-09-10)

Expand Down
49 changes: 0 additions & 49 deletions sdk/appconfiguration/Azure.Data.AppConfiguration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,55 +83,6 @@ The [Label][label_concept] property of a Configuration Setting provides a way to

For example, MaxRequests may be 100 in "NorthAmerica" and 200 in "WestEurope". By creating a Configuration Setting named MaxRequests with a label of "NorthAmerica" and another, only with a different value, with a "WestEurope" label, an application can seamlessly retrieve Configuration Settings as it runs in these two dimensions.
maririos marked this conversation as resolved.
Show resolved Hide resolved

Properties of a Configuration Setting:

```C# Snippet:SettingProperties
/// <summary>
/// The primary identifier of the configuration setting.
/// A <see cref="Key"/> is used together with a <see cref="Label"/> to uniquely identify a configuration setting.
/// </summary>
public string Key { get; set; }

/// <summary>
/// A value used to group configuration settings.
/// A <see cref="Label"/> is used together with a <see cref="Key"/> to uniquely identify a configuration setting.
/// </summary>
public string Label { get; set; }

/// <summary>
/// The configuration setting's value.
/// </summary>
public string Value { get; set; }

/// <summary>
/// The content type of the configuration setting's value.
/// Providing a proper content-type can enable transformations of values when they are retrieved by applications.
/// </summary>
public string ContentType { get; set; }

/// <summary>
/// An ETag indicating the state of a configuration setting within a configuration store.
/// </summary>
public ETag ETag { get; internal set; }

/// <summary>
/// The last time a modifying operation was performed on the given configuration setting.
/// </summary>
public DateTimeOffset? LastModified { get; internal set; }

/// <summary>
/// A value indicating whether the configuration setting is read only.
/// A read only configuration setting may not be modified until it is made writable.
/// </summary>
public bool? IsReadOnly { get; internal set; }

/// <summary>
/// A dictionary of tags used to assign additional properties to a configuration setting.
/// These can be used to indicate how a configuration setting may be applied.
/// </summary>
public IDictionary<string, string> Tags
```

### Thread safety
We guarantee that all client instance methods are thread-safe and independent of each other ([guideline](https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-service-methods-thread-safety)). This ensures that the recommendation of reusing client instances is always safe, even across threads.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public ConfigurationClient(System.Uri endpoint, Azure.Core.TokenCredential crede
public virtual Azure.Response<Azure.Data.AppConfiguration.ConfigurationSetting> AddConfigurationSetting(string key, string value, string label = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Data.AppConfiguration.ConfigurationSetting>> AddConfigurationSettingAsync(Azure.Data.AppConfiguration.ConfigurationSetting setting, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Data.AppConfiguration.ConfigurationSetting>> AddConfigurationSettingAsync(string key, string value, string label = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual void AddSyncToken(string token) { }
public virtual Azure.Response DeleteConfigurationSetting(Azure.Data.AppConfiguration.ConfigurationSetting setting, bool onlyIfUnchanged = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response DeleteConfigurationSetting(string key, string label = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response> DeleteConfigurationSettingAsync(Azure.Data.AppConfiguration.ConfigurationSetting setting, bool onlyIfUnchanged = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -56,7 +57,7 @@ public static partial class ConfigurationModelFactory
{
public static Azure.Data.AppConfiguration.ConfigurationSetting ConfigurationSetting(string key, string value, string label = null, string contentType = null, Azure.ETag eTag = default(Azure.ETag), System.DateTimeOffset? lastModified = default(System.DateTimeOffset?), bool? isReadOnly = default(bool?)) { throw null; }
}
public sealed partial class ConfigurationSetting
public partial class ConfigurationSetting
{
public ConfigurationSetting(string key, string value, string label = null) { }
public string ContentType { get { throw null; } set { } }
Expand All @@ -74,6 +75,28 @@ public ConfigurationSetting(string key, string value, string label = null) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override string ToString() { throw null; }
}
public partial class FeatureFlagConfigurationSetting : Azure.Data.AppConfiguration.ConfigurationSetting
{
public FeatureFlagConfigurationSetting(string featureId, bool isEnabled, string label = null) : base (default(string), default(string), default(string)) { }
public System.Collections.Generic.IList<Azure.Data.AppConfiguration.FeatureFlagFilter> ClientFilters { get { throw null; } }
public string Description { get { throw null; } set { } }
public string DisplayName { get { throw null; } set { } }
public string FeatureId { get { throw null; } set { } }
public bool IsEnabled { get { throw null; } set { } }
public static string KeyPrefix { get { throw null; } }
}
public partial class FeatureFlagFilter
{
public FeatureFlagFilter(string name) { }
public FeatureFlagFilter(string name, System.Collections.Generic.IReadOnlyDictionary<string, object> parameters) { }
public string Name { get { throw null; } }
public System.Collections.Generic.IReadOnlyDictionary<string, object> Parameters { get { throw null; } }
}
public partial class SecretReferenceConfigurationSetting : Azure.Data.AppConfiguration.ConfigurationSetting
{
public SecretReferenceConfigurationSetting(string key, System.Uri secretId, string label = null) : base (default(string), default(string), default(string)) { }
public System.Uri SecretId { get { throw null; } set { } }
}
[System.FlagsAttribute]
public enum SettingFields : uint
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
<PackageTags>Microsoft Azure Application Configuration;Data;AppConfig;$(PackageCommonTags)</PackageTags>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
<NoWarn>$(NoWarn);3021</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand All @@ -20,20 +19,20 @@
<Import Project="$(MSBuildThisFileDirectory)..\..\..\core\Azure.Core\src\Azure.Core.props" />

<ItemGroup>
<Compile Include="$(AzureCoreSharedSources)Argument.cs" />
<Compile Include="$(AzureCoreSharedSources)AzureResourceProviderNamespaceAttribute.cs" />
<Compile Include="$(AzureCoreSharedSources)ConnectionString.cs" />
<Compile Include="$(AzureCoreSharedSources)ArrayBufferWriter.cs" />
<Compile Include="$(AzureCoreSharedSources)ClientDiagnostics.cs" />
<Compile Include="$(AzureCoreSharedSources)HttpMessageSanitizer.cs" />
<Compile Include="$(AzureCoreSharedSources)ContentTypeUtilities.cs" />
<Compile Include="$(AzureCoreSharedSources)ConditionalRequestOptionsExtensions.cs" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScope.cs" />
<Compile Include="$(AzureCoreSharedSources)HashCodeBuilder.cs" />
<Compile Include="$(AzureCoreSharedSources)NoBodyResponseOfT.cs" />
<Compile Include="$(AzureCoreSharedSources)PageResponseEnumerator.cs" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" />
<Compile Include="$(AzureCoreSharedSources)TaskExtensions.cs" />
<Compile Include="$(AzureCoreSharedSources)Argument.cs" LinkBase="Shared"/>
pakrym marked this conversation as resolved.
Show resolved Hide resolved
<Compile Include="$(AzureCoreSharedSources)AzureResourceProviderNamespaceAttribute.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)ConnectionString.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)ArrayBufferWriter.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)ClientDiagnostics.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)HttpMessageSanitizer.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)ContentTypeUtilities.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)ConditionalRequestOptionsExtensions.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)DiagnosticScope.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)HashCodeBuilder.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)NoBodyResponseOfT.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)PageResponseEnumerator.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" LinkBase="Shared"/>
<Compile Include="$(AzureCoreSharedSources)TaskExtensions.cs" LinkBase="Shared"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Azure.Data.AppConfiguration
public partial class ConfigurationClient
{
private readonly Uri _endpoint;
private readonly SyncTokenPolicy _syncTokenPolicy;
private readonly HttpPipeline _pipeline;
private readonly ClientDiagnostics _clientDiagnostics;

Expand Down Expand Up @@ -50,7 +51,8 @@ public ConfigurationClient(string connectionString, ConfigurationClientOptions o

ParseConnectionString(connectionString, out _endpoint, out var credential, out var secret);

_pipeline = CreatePipeline(options, new AuthenticationPolicy(credential, secret));
_syncTokenPolicy = new SyncTokenPolicy();
_pipeline = CreatePipeline(options, new AuthenticationPolicy(credential, secret), _syncTokenPolicy);

_clientDiagnostics = new ClientDiagnostics(options);
}
Expand All @@ -77,16 +79,19 @@ public ConfigurationClient(Uri endpoint, TokenCredential credential, Configurati
Argument.AssertNotNull(credential, nameof(credential));

_endpoint = endpoint;
_pipeline = CreatePipeline(options, new BearerTokenAuthenticationPolicy(credential, GetDefaultScope(endpoint)));
_syncTokenPolicy = new SyncTokenPolicy();
_pipeline = CreatePipeline(options, new BearerTokenAuthenticationPolicy(credential, GetDefaultScope(endpoint)), _syncTokenPolicy);

_clientDiagnostics = new ClientDiagnostics(options);
}

private static HttpPipeline CreatePipeline(ConfigurationClientOptions options, HttpPipelinePolicy authenticationPolicy)
=> HttpPipelineBuilder.Build(options,
new HttpPipelinePolicy[] { new CustomHeadersPolicy(), new ApiVersionPolicy(options.GetVersionString()) },
new HttpPipelinePolicy[] { authenticationPolicy, new SyncTokenPolicy() },
private static HttpPipeline CreatePipeline(ConfigurationClientOptions options, HttpPipelinePolicy authenticationPolicy, HttpPipelinePolicy syncTokenPolicy)
{
return HttpPipelineBuilder.Build(options,
new HttpPipelinePolicy[] {new CustomHeadersPolicy(), new ApiVersionPolicy(options.GetVersionString())},
new HttpPipelinePolicy[] {authenticationPolicy, syncTokenPolicy},
new ResponseClassifier());
}

private static string GetDefaultScope(Uri uri)
=> $"{uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped)}/.default";
Expand Down Expand Up @@ -986,5 +991,15 @@ private Request CreateSetReadOnlyRequest(string key, string label, MatchConditi

return request;
}

/// <summary>
/// Adds an external synchronization token to ensure service requests receive up-to-date values.
/// </summary>
/// <param name="token">The synchronization token value.</param>
public virtual void AddSyncToken(string token)
{
Argument.AssertNotNull(token, nameof(token));
_syncTokenPolicy.AddToken(token);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

using System;
using System.Buffers;
using System.Globalization;
using System.IO;
using System.Text.Json;
Expand Down Expand Up @@ -74,15 +73,46 @@ public static ConfigurationSetting ReadSetting(ref Utf8JsonReader reader)

private static ConfigurationSetting ReadSetting(JsonElement root)
{
// TODO (pri 2): make the deserializer version resilient
var setting = new ConfigurationSetting();
ConfigurationSetting setting;

if (IsFeatureFlag(root))
{
setting = new FeatureFlagConfigurationSetting();
}
else if (IsSecretReference(root))
{
setting = new SecretReferenceConfigurationSetting();
}
else
{
setting = new ConfigurationSetting();
}

foreach (JsonProperty property in root.EnumerateObject())
{
ReadPropertyValue(setting, property);
}

return setting;
}

private static bool IsSecretReference(JsonElement settingElement)
{
return settingElement.TryGetProperty("content_type", out var contentTypeProperty) &&
contentTypeProperty.ValueKind == JsonValueKind.String &&
contentTypeProperty.GetString() == SecretReferenceConfigurationSetting.SecretReferenceContentType;
}

private static bool IsFeatureFlag(JsonElement settingElement)
{
return settingElement.TryGetProperty("content_type", out var contentTypeProperty) &&
contentTypeProperty.ValueKind == JsonValueKind.String &&
contentTypeProperty.GetString() == FeatureFlagConfigurationSetting.FeatureFlagContentType &&
settingElement.TryGetProperty("key", out var keyProperty) &&
keyProperty.ValueKind == JsonValueKind.String &&
keyProperty.GetString().StartsWith(FeatureFlagConfigurationSetting.KeyPrefix, StringComparison.Ordinal);
}

private static void ReadPropertyValue(ConfigurationSetting setting, JsonProperty property)
{
if (property.NameEquals("content_type"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ namespace Azure.Data.AppConfiguration
/// A setting, defined by a unique combination of a key and label.
/// </summary>
[JsonConverter(typeof(ConfigurationSettingJsonConverter))]
public sealed class ConfigurationSetting
public class ConfigurationSetting
{
private IDictionary<string, string> _tags;
private string _value;

internal ConfigurationSetting()
{
Expand All @@ -33,7 +34,6 @@ public ConfigurationSetting(string key, string value, string label = null)
Label = label;
}

#region Snippet:SettingProperties
/// <summary>
/// The primary identifier of the configuration setting.
/// A <see cref="Key"/> is used together with a <see cref="Label"/> to uniquely identify a configuration setting.
Expand All @@ -49,7 +49,21 @@ public ConfigurationSetting(string key, string value, string label = null)
/// <summary>
/// The configuration setting's value.
/// </summary>
public string Value { get; set; }
public string Value
{
get => GetValue();
set => SetValue(value);
}

internal virtual string GetValue()
{
return _value;
}

internal virtual void SetValue(string value)
{
_value = value;
}

/// <summary>
/// The content type of the configuration setting's value.
Expand Down Expand Up @@ -78,7 +92,6 @@ public ConfigurationSetting(string key, string value, string label = null)
/// These can be used to indicate how a configuration setting may be applied.
/// </summary>
public IDictionary<string, string> Tags
#endregion Setting:Properties
{
get => _tags ?? (_tags = new Dictionary<string, string>());
internal set => _tags = value;
Expand Down
Loading