-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
64ba5b8
commit c678077
Showing
24 changed files
with
1,101 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
namespace LaunchDarkly.Client.Files | ||
{ | ||
/// <summary> | ||
/// The entry point for the file data source, which allows you to use local files as a source of | ||
/// feature flag state. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// This would typically be used in a test environment, to operate using a predetermined feature flag | ||
/// state without an actual LaunchDarkly connection. | ||
/// </para> | ||
/// <para> | ||
/// To use this component, call <see cref="FileDataSource()"/> to obtain a factory object, call one or | ||
/// methods to configure it, and then add it to your LaunchDarkly client configuration. At a | ||
/// minimum, you will want to call <see cref="FileDataSourceFactory.WithFilePaths(string[])"/> to specify | ||
/// your data file(s); you can also use <see cref="FileDataSourceFactory.WithAutoUpdate(bool)"/> to | ||
/// specify that flags should be reloaded when a file is modified. See <see cref="FileDataSourceFactory"/> | ||
/// for all configuration options. | ||
/// </para> | ||
/// <code> | ||
/// var fileSource = FileComponents.FileDataSource() | ||
/// .WithFilePaths("./testData/flags.json") | ||
/// .WithAutoUpdate(true); | ||
/// var config = Configuration.Default("sdkKey") | ||
/// .WithUpdateProcessorFactory(fileSource) | ||
/// .Build(); | ||
/// </code> | ||
/// <para> | ||
/// This will cause the client <i>not</i> to connect to LaunchDarkly to get feature flags. The | ||
/// client may still make network connections to send analytics events, unless you have disabled | ||
/// this with <c>configuration.WithEventProcessor(Components.NullEventProcessor)</c>. | ||
/// </para> | ||
/// <para> | ||
/// Flag data files are JSON by default (although it is possible to specify a parser for another format, | ||
/// such as YAML; see <see cref="FileDataSourceFactory.WithParser(System.Func{string, object})"/>). They | ||
/// contain an object with three possible properties: | ||
/// </para> | ||
/// <list type="bullet"> | ||
/// <item><c>flags</c>: Feature flag definitions.</item> | ||
/// <item><c>flagVersions</c>: Simplified feature flags that contain only a value.</item> | ||
/// <item><c>segments</c>: User segment definitions.</item> | ||
/// </list> | ||
/// <para> | ||
/// The format of the data in <c>flags</c> and <c>segments</c> is defined by the LaunchDarkly application | ||
/// and is subject to change. Rather than trying to construct these objects yourself, it is simpler | ||
/// to request existing flags directly from the LaunchDarkly server in JSON format, and use this | ||
/// output as the starting point for your file. In Linux you would do this: | ||
/// </para> | ||
/// <code> | ||
/// curl -H "Authorization: {your sdk key}" https://app.launchdarkly.com/sdk/latest-all | ||
/// </code> | ||
/// <para> | ||
/// The output will look something like this (but with many more properties): | ||
/// </para> | ||
/// <code> | ||
/// { | ||
/// "flags": { | ||
/// "flag-key-1": { | ||
/// "key": "flag-key-1", | ||
/// "on": true, | ||
/// "variations": [ "a", "b" ] | ||
/// } | ||
/// }, | ||
/// "segments": { | ||
/// "segment-key-1": { | ||
/// "key": "segment-key-1", | ||
/// "includes": [ "user-key-1" ] | ||
/// } | ||
/// } | ||
/// } | ||
/// </code> | ||
/// <para> | ||
/// Data in this format allows the SDK to exactly duplicate all the kinds of flag behavior supported | ||
/// by LaunchDarkly. However, in many cases you will not need this complexity, but will just want to | ||
/// set specific flag keys to specific values. For that, you can use a much simpler format: | ||
/// </para> | ||
/// <code> | ||
/// { | ||
/// "flagValues": { | ||
/// "my-string-flag-key": "value-1", | ||
/// "my-boolean-flag-key": true, | ||
/// "my-integer-flag-key": 3 | ||
/// } | ||
/// } | ||
/// </code> | ||
/// <para> | ||
/// It is also possible to specify both <c>flags</c> and <c>flagValues</c>, if you want some flags | ||
/// to have simple values and others to have complex behavior. However, it is an error to use the | ||
/// same flag key or segment key more than once, either in a single file or across multiple files. | ||
/// </para> | ||
/// <para> | ||
/// If the data source encounters any error in any file-- malformed content, a missing file, or a | ||
/// duplicate key-- it will not load flags from any of the files. | ||
/// </para> | ||
/// </remarks> | ||
public static class FileComponents | ||
{ | ||
/// <summary> | ||
/// Creates a <see cref="FileDataSourceFactory"/> which you can use to configure the file data source. | ||
/// </summary> | ||
/// <returns>a <see cref="FileDataSourceFactory"/></returns> | ||
public static FileDataSourceFactory FileDataSource() | ||
{ | ||
return new FileDataSourceFactory(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Common.Logging; | ||
|
||
namespace LaunchDarkly.Client.Files | ||
{ | ||
internal class FileDataSource : IUpdateProcessor | ||
{ | ||
private static readonly ILog Log = LogManager.GetLogger(typeof(FileDataSource)); | ||
private readonly IFeatureStore _featureStore; | ||
private readonly List<string> _paths; | ||
private readonly IDisposable _reloader; | ||
private readonly FlagFileParser _parser; | ||
private volatile bool _started; | ||
private volatile bool _loadedValidData; | ||
|
||
public FileDataSource(IFeatureStore featureStore, List<string> paths, bool autoUpdate, TimeSpan pollInterval, | ||
Func<string, object> alternateParser) | ||
{ | ||
_featureStore = featureStore; | ||
_paths = new List<string>(paths); | ||
_parser = new FlagFileParser(alternateParser); | ||
if (autoUpdate) | ||
{ | ||
try | ||
{ | ||
#if NETSTANDARD1_4 || NETSTANDARD1_6 | ||
_reloader = new FilePollingReloader(_paths, TriggerReload, pollInterval); | ||
#else | ||
_reloader = new FileWatchingReloader(_paths, TriggerReload); | ||
#endif | ||
} | ||
catch (Exception e) | ||
{ | ||
Log.ErrorFormat("Unable to watch files for auto-updating: {0}", e); | ||
_reloader = null; | ||
} | ||
} | ||
else | ||
{ | ||
_reloader = null; | ||
} | ||
} | ||
|
||
public Task<bool> Start() | ||
{ | ||
_started = true; | ||
LoadAll(); | ||
|
||
// We always complete the start task regardless of whether we successfully loaded data or not; | ||
// if the data files were bad, they're unlikely to become good within the short interval that | ||
// LdClient waits on this task, even if auto-updating is on. | ||
TaskCompletionSource<bool> initTask = new TaskCompletionSource<bool>(); | ||
initTask.SetResult(_loadedValidData); | ||
return initTask.Task; | ||
} | ||
|
||
public bool Initialized() | ||
{ | ||
return _loadedValidData; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
} | ||
|
||
private void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
{ | ||
_reloader?.Dispose(); | ||
} | ||
} | ||
|
||
private void LoadAll() | ||
{ | ||
Dictionary<IVersionedDataKind, IDictionary<string, IVersionedData>> allData = | ||
new Dictionary<IVersionedDataKind, IDictionary<string, IVersionedData>>(); | ||
foreach (var path in _paths) | ||
{ | ||
try | ||
{ | ||
var content = File.ReadAllText(path); | ||
var data = _parser.Parse(content); | ||
data.AddToData(allData); | ||
} | ||
catch (Exception e) | ||
{ | ||
Log.ErrorFormat("{0}: {1}", path, e); | ||
return; | ||
} | ||
} | ||
_featureStore.Init(allData); | ||
_loadedValidData = true; | ||
} | ||
|
||
private void TriggerReload() | ||
{ | ||
if (_started) | ||
{ | ||
LoadAll(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.