Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Fix tests

Add more configs

Fix test

Clean up

Integrate feature flag service

Remove unneeded code

Cleanup

Add missing configs

Try #2

WIP
  • Loading branch information
loic-sharma committed Jan 29, 2019
1 parent 91a4d51 commit 4178449
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 31 deletions.
3 changes: 3 additions & 0 deletions src/NuGetGallery.Core/CoreConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ public static class Folders
public const string SymbolPackagesFolderName = "symbol-packages";
public const string SymbolPackageBackupsFolderName = "symbol-package-backups";
public const string FlatContainerFolderName = "v3-flatcontainer";
public const string FeatureFlagsContainerFolderName ="feature-flags";
}

public const string NuGetSymbolPackageFileExtension = ".snupkg";

public const string UploadTracingKeyHeaderName = "upload-id";

public const string LicenseFileName = "license";

public const string FeatureFlagsFileName = "flags.json";
}
}
32 changes: 32 additions & 0 deletions src/NuGetGallery.Core/Features/FeatureFlagFileStorageService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NuGet.Services.FeatureFlags;

namespace NuGetGallery.Features
{
public class FeatureFlagFileStorageService : IFeatureFlagStorageService
{
private readonly ICoreFileStorageService _storage;
private readonly FeatureFlagOptions _options;
private readonly JsonSerializer _serializer;

public FeatureFlagFileStorageService(ICoreFileStorageService storage, FeatureFlagOptions options)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
_options = options ?? throw new ArgumentNullException(nameof(options));
_serializer = new JsonSerializer();
}

public async Task<FeatureFlagsState> GetAsync()
{
using (var stream = await _storage.GetFileAsync(CoreConstants.Folders.FeatureFlagsContainerFolderName, CoreConstants.FeatureFlagsFileName))
using (var streamReader = new StreamReader(stream))
using (var reader = new JsonTextReader(streamReader))
{
return _serializer.Deserialize<FeatureFlagsState>(reader);
}
}
}
}
13 changes: 10 additions & 3 deletions src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
<Compile Include="Extensions\StorageExceptionExtensions.cs" />
<Compile Include="Extensions\UserExtensionsCore.cs" />
<Compile Include="Extensions\ValidationIssueExtensions.cs" />
<Compile Include="Features\FeatureFlagFileStorageService.cs" />
<Compile Include="ICloudStorageStatusDependency.cs" />
<Compile Include="Infrastructure\AzureEntityList.cs" />
<Compile Include="Infrastructure\ElmahException.cs" />
Expand Down Expand Up @@ -214,14 +215,20 @@
<EmbeddedResource Include="Infrastructure\MigrateUserToOrganization.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NuGet.Services.Entities">
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.FeatureFlags">
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Messaging.Email">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Validation">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Validation.Issues">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.StrongName.AnglicanGeek.MarkdownMailer">
<Version>1.2.0</Version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class CloudBlobCoreFileStorageService : ICoreFileStorageService
CoreConstants.Folders.RevalidationFolderName,
CoreConstants.Folders.StatusFolderName,
CoreConstants.Folders.PackagesContentFolderName,
CoreConstants.Folders.FeatureFlagsContainerFolderName,
};

protected readonly ICloudBlobClient _client;
Expand Down Expand Up @@ -633,6 +634,7 @@ private static string GetContentType(string folderName)
case CoreConstants.Folders.ContentFolderName:
case CoreConstants.Folders.RevalidationFolderName:
case CoreConstants.Folders.StatusFolderName:
case CoreConstants.Folders.FeatureFlagsContainerFolderName:
return CoreConstants.JsonContentType;

case CoreConstants.Folders.UserCertificatesFolderName:
Expand Down Expand Up @@ -667,6 +669,7 @@ private static string GetCacheControl(string folderName)
case CoreConstants.Folders.UserCertificatesFolderName:
case CoreConstants.Folders.PackagesContentFolderName:
case CoreConstants.Folders.FlatContainerFolderName:
case CoreConstants.Folders.FeatureFlagsContainerFolderName:
return null;

default:
Expand Down
7 changes: 7 additions & 0 deletions src/NuGetGallery/App_Data/Files/Feature-Flags/Flags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Features": {
},

"Flights": {
}
}
8 changes: 8 additions & 0 deletions src/NuGetGallery/App_Start/AppActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
using Elmah;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using NuGet.Services.FeatureFlags;
using NuGet.Services.Search.Client.Correlation;
using NuGetGallery;
using NuGetGallery.Configuration;
using NuGetGallery.Diagnostics;
using NuGetGallery.Features;
using NuGetGallery.Infrastructure;
using NuGetGallery.Infrastructure.Jobs;
using WebActivatorEx;
Expand Down Expand Up @@ -271,6 +273,12 @@ private static void BackgroundJobsPostStart(IAppConfiguration configuration)
}
}

var featureFlags = DependencyResolver.Current.GetService<IFeatureFlagRefreshService>();
if (featureFlags != null)
{
HostingEnvironment.QueueBackgroundWorkItem(featureFlags.RunAsync);
}

if (jobs.AnySafe())
{
var jobCoordinator = new NuGetJobCoordinator();
Expand Down
35 changes: 35 additions & 0 deletions src/NuGetGallery/App_Start/DefaultDependenciesModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.ServiceRuntime;
using NuGet.Services.Entities;
using NuGet.Services.FeatureFlags;
using NuGet.Services.KeyVault;
using NuGet.Services.Licenses;
using NuGet.Services.Logging;
Expand All @@ -38,6 +39,7 @@
using NuGetGallery.Configuration;
using NuGetGallery.Cookies;
using NuGetGallery.Diagnostics;
using NuGetGallery.Features;
using NuGetGallery.Infrastructure;
using NuGetGallery.Infrastructure.Authentication;
using NuGetGallery.Infrastructure.Lucene;
Expand Down Expand Up @@ -358,6 +360,7 @@ protected override void Load(ContainerBuilder builder)
.As<ILicenseExpressionSegmentator>()
.InstancePerLifetimeScope();

RegisterFeatureFlagsService(builder, configuration);
RegisterMessagingService(builder, configuration);

builder.Register(c => HttpContext.Current.User)
Expand Down Expand Up @@ -406,6 +409,38 @@ protected override void Load(ContainerBuilder builder)
ConfigureAutocomplete(builder, configuration);
}

private static void RegisterFeatureFlagsService(ContainerBuilder builder, ConfigurationService configuration)
{
builder
.Register(context => new FeatureFlagOptions
{
RefreshInterval = configuration.Current.FeatureFlagsRefreshInterval,
MaximumStaleness = configuration.Current.FeatureFlagsMaximumStaleness,
})
.AsSelf()
.SingleInstance();

builder
.RegisterType<FeatureFlagRefreshService>()
.As<IFeatureFlagRefreshService>()
.SingleInstance();

builder
.RegisterType<FeatureFlagClient>()
.As<IFeatureFlagClient>()
.InstancePerDependency();

builder
.RegisterType<FlightClient>()
.As<IFlightClient>()
.InstancePerDependency();

builder
.RegisterType<FeatureFlagService>()
.As<IFeatureFlagService>()
.InstancePerDependency();
}

private static void RegisterMessagingService(ContainerBuilder builder, ConfigurationService configuration)
{
if (configuration.Current.AsynchronousEmailServiceEnabled)
Expand Down
3 changes: 3 additions & 0 deletions src/NuGetGallery/App_Start/StorageDependent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NuGet.Services.FeatureFlags;
using NuGetGallery.Configuration;
using NuGetGallery.Features;

namespace NuGetGallery
{
Expand Down Expand Up @@ -91,6 +93,7 @@ public static IEnumerable<StorageDependent> GetAll(IAppConfiguration configurati
Create<UploadFileService, IUploadFileService>(configuration.AzureStorage_Uploads_ConnectionString, isSingleInstance: false),
Create<CoreLicenseFileService, ICoreLicenseFileService>(configuration.AzureStorage_Packages_ConnectionString, isSingleInstance: false),
Create<RevalidationStateService, IRevalidationStateService>(configuration.AzureStorage_Revalidation_ConnectionString, isSingleInstance: false),
Create<FeatureFlagFileStorageService, IFeatureFlagStorageService>(configuration.AzureStorage_FeatureFlags_ConnectionString, isSingleInstance: true)
};

var connectionStringToBindingKey = dependents
Expand Down
7 changes: 7 additions & 0 deletions src/NuGetGallery/Configuration/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,18 @@ public class AppConfiguration : IAppConfiguration
[DisplayName("AzureStorage.Revalidation.ConnectionString")]
public string AzureStorage_Revalidation_ConnectionString { get; set; }

[DisplayName("AzureStorage.FeatureFlags.ConnectionString")]
public string AzureStorage_FeatureFlags_ConnectionString { get; set; }

/// <summary>
/// Gets a setting if Read Access Geo Redundant is enabled in azure storage
/// </summary>
public bool AzureStorageReadAccessGeoRedundant { get; set; }

public TimeSpan FeatureFlagsRefreshInterval { get; set; }

public TimeSpan FeatureFlagsMaximumStaleness { get; set; }

public bool AsynchronousPackageValidationEnabled { get; set; }

public bool BlockingAsynchronousPackageValidationEnabled { get; set; }
Expand Down
16 changes: 16 additions & 0 deletions src/NuGetGallery/Configuration/IAppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,27 @@ public interface IAppConfiguration : IMessageServiceConfiguration
/// </summary>
string AzureStorage_Revalidation_ConnectionString { get; set; }

/// <summary>
/// The Azure storage connection string used for feature flags.
/// </summary>
string AzureStorage_FeatureFlags_ConnectionString { get; set; }

/// <summary>
/// Gets a setting if Read Access Geo Redundant is enabled in azure storage
/// </summary>
bool AzureStorageReadAccessGeoRedundant { get; set; }

/// <summary>
/// How frequently the feature flags should be refreshed.
/// </summary>
TimeSpan FeatureFlagsRefreshInterval { get; set; }

/// <summary>
/// The maximum refresh staleness allowed for feature flags.
/// If the threshold is reached, the returned feature flags will be labeled as stale.
/// </summary>
TimeSpan FeatureFlagsMaximumStaleness { get; set; }

/// <summary>
/// Gets a boolean indicating whether asynchronous package validation is enabled.
/// </summary>
Expand Down
11 changes: 7 additions & 4 deletions src/NuGetGallery/NuGetGallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,9 @@
<Compile Include="Services\ActionsRequiringPermissions.cs" />
<Compile Include="Services\AsynchronousPackageValidationInitiator.cs" />
<Compile Include="Infrastructure\Mail\BackgroundMarkdownMessageService.cs" />
<Compile Include="Services\FeatureFlagService.cs" />
<Compile Include="Services\GalleryContentFileMetadataService.cs" />
<Compile Include="Services\IFeatureFlagService.cs" />
<Compile Include="Services\InvalidLicenseUrlValidationMessage.cs" />
<Compile Include="Services\InvalidUrlEncodingForLicenseUrlValidationMessage.cs" />
<Compile Include="Services\ISymbolPackageUploadService.cs" />
Expand Down Expand Up @@ -1547,6 +1549,7 @@
<Content Include="App_Data\Files\Content\ReadOnly.md" />
<Content Include="App_Data\Files\Content\Team.json" />
<Content Include="App_Data\Files\Content\Terms-Of-Use.md" />
<Content Include="App_Data\Files\Feature-Flags\Flags.json" />
<Content Include="Branding\Views\web.config" />
<Compile Include="Views\NuGetViewBase.cs" />
<Content Include="Scripts\d3\d3.js" />
Expand Down Expand Up @@ -2282,16 +2285,16 @@
<Version>5.0.0-preview1.5665</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.KeyVault">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Logging">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Owin">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Sql">
<Version>2.40.0</Version>
<Version>2.41.0-loshar-feature-flags-2310813</Version>
</PackageReference>
<PackageReference Include="Owin">
<Version>1.0.0</Version>
Expand Down
37 changes: 37 additions & 0 deletions src/NuGetGallery/Services/FeatureFlagService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using NuGet.Services.Entities;
using NuGet.Services.FeatureFlags;

namespace NuGetGallery
{
public class FeatureFlagService : IFeatureFlagService
{
private const string GalleryPrefix = "NuGetGallery.";

// Typosquatting detection
private const string TyposquattingFeatureName = GalleryPrefix + "Typosquatting";
private const string TyposquattingFlightName = GalleryPrefix + "TyposquattingFlight";

private readonly IFeatureFlagClient _featureFlagClient;
private readonly IFlightClient _flightClient;

public FeatureFlagService(IFeatureFlagClient featureFlagClient, IFlightClient flightClient)
{
_featureFlagClient = featureFlagClient ?? throw new ArgumentNullException(nameof(featureFlagClient));
_flightClient = flightClient ?? throw new ArgumentNullException(nameof(flightClient));
}

public bool IsTyposquattingEnabled()
{
return _featureFlagClient.IsEnabled(TyposquattingFeatureName, @default: false);
}

public bool IsTyposquattingEnabled(User user)
{
return _flightClient.IsEnabled(TyposquattingFlightName, user, @default: false);
}
}
}
25 changes: 25 additions & 0 deletions src/NuGetGallery/Services/IFeatureFlagService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using NuGet.Services.Entities;

namespace NuGetGallery
{
public interface IFeatureFlagService
{
/// <summary>
/// Whether typosquatting detection is enabled on package uploads. If true, new packages
/// cannot have an id that is similar to existing packages' ids.
/// </summary>
/// <returns></returns>
bool IsTyposquattingEnabled();

/// <summary>
/// Whether typosquatting detection is enabled for a specific user's package uploads. If true,
/// new packages from this user cannot have an id that is similar to existing packages' ids.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool IsTyposquattingEnabled(User user);
}
}
Loading

0 comments on commit 4178449

Please sign in to comment.