Skip to content

Commit

Permalink
AppConfig design
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym committed Feb 10, 2021
1 parent 9a94c34 commit 4684ca7
Show file tree
Hide file tree
Showing 147 changed files with 11,930 additions and 27,332 deletions.
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.
- 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.

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

0 comments on commit 4684ca7

Please sign in to comment.