Skip to content
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
40 changes: 40 additions & 0 deletions .github/rulesets/publish-branch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "Publish Branch Protection",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/publish"],
"exclude": []
}
},
"rules": [
{
"type": "deletion"
},
{
"type": "non_fast_forward"
},
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 1,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": false,
"require_last_push_approval": false,
"required_review_thread_resolution": true
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"required_status_checks": [
{
"context": "check-version"
}
]
}
}
]
}
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ jobs:
9.0.x
10.0.x

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore
run: for proj in src/*/*.csproj tests/*/*.csproj; do dotnet build "$proj" -c Release; done

- name: Test
run: dotnet test --test-modules "**/bin/Release/**/*.Tests.dll"
7 changes: 2 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,14 @@ jobs:
9.0.x
10.0.x

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore
run: for proj in src/*/*.csproj tests/*/*.csproj; do dotnet build "$proj" -c Release; done

- name: Test
run: dotnet test --test-modules "**/bin/Release/**/*.Tests.dll"

- name: Pack
run: dotnet pack --configuration Release --no-build -o ./nupkg
run: for proj in src/*/*.csproj; do dotnet pack "$proj" -c Release --no-build -o ./nupkg; done

- name: Push to NuGet
run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<!-- Centralized versioning -->
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionPrefix>1.0.1</VersionPrefix>
<VersionSuffix>prerelease</VersionSuffix>
</PropertyGroup>

Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
# AzureFunctions.DisabledWhen

Conditionally disable Azure Functions based on configuration values using attributes.

## Packages
![NuGet](https://img.shields.io/nuget/v/AzureFunctions.DisabledWhen?logo=nuget) ![License](https://img.shields.io/github/license/Anders-Toegersen/AzureFunctions.DisabledWhen)

| Package | Description |
|---------|-------------|
| `AzureFunctions.DisabledWhen` | Reflection-based implementation |
| `AzureFunctions.DisabledWhen.SourceGenerator` | Source-generated implementation |
Conditionally disable Azure Functions based on configuration values using attributes.

## Installation

### Reflection-based (simple setup)
### Simple setup

```bash
dotnet add package AzureFunctions.DisabledWhen
```

See [`sample/SampleFunctionApp`](sample/SampleFunctionApp) for a working example.

### Source-generated (AOT-compatible)
### Source generated setup

```bash
dotnet add package AzureFunctions.DisabledWhen
Expand All @@ -42,14 +37,28 @@ See [`sample/SampleFunctionApp.SourceGenerated`](sample/SampleFunctionApp.Source

### 1. Register the metadata provider

In your `Program.cs`, call `UseDisabledWhen()` on the host builder **after** `ConfigureFunctionsWebApplication()`:
In your `Program.cs`, call `UseDisabledWhen()` on the builder **after** `ConfigureFunctionsWebApplication()`:

```csharp
using AzureFunctions.DisabledWhen;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();
builder.UseDisabledWhen();

var host = builder.Build();
host.Run();
```

The legacy `IHostBuilder` pattern is also supported:

```csharp
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.UseDisabledWhen() // Must be called after ConfigureFunctionsWebApplication
.UseDisabledWhen()
.Build();

host.Run();
Expand Down
10 changes: 5 additions & 5 deletions sample/SampleFunctionApp.SourceGenerated/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using AzureFunctions.DisabledWhen;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.UseDisabledWhen()
.ConfigureLogging(logging => logging.AddConsole())
.Build();
var builder = FunctionsApplication.CreateBuilder(args);
builder.UseDisabledWhen();
builder.ConfigureLogging(logging => logging.AddConsole());

var host = builder.Build();
host.Run();
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -9,13 +9,11 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AzureFunctions.DisabledWhen\AzureFunctions.DisabledWhen.csproj" />
<ProjectReference Include="..\..\src\AzureFunctions.DisabledWhen.SourceGenerator\AzureFunctions.DisabledWhen.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AzureFunctions.DisabledWhen" Version="1.0.1-prerelease" />
<PackageReference Include="AzureFunctions.DisabledWhen.SourceGenerator" Version="1.0.1-prerelease">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
Expand Down
5 changes: 1 addition & 4 deletions sample/SampleFunctionApp/SampleFunctionApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AzureFunctions.DisabledWhen\AzureFunctions.DisabledWhen.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AzureFunctions.DisabledWhen" Version="1.0.1-prerelease" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
<PackageId>AzureFunctions.DisabledWhen.SourceGenerator</PackageId>
<Description>Conditionally disable Azure Functions based on configuration values using attributes and source generation.</Description>
<PackageTags>Azure;Functions;Disable</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<!-- Analyzer packaging -->
<PropertyGroup>
<IncludeBuildOutput>false</IncludeBuildOutput>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public sealed class DisabledWhenGenerator : IIncrementalGenerator
private const string DisabledWhenLocalAttributeName = "AzureFunctions.DisabledWhen.DisabledWhenLocalAttribute";
private const string DisabledWhenNullOrEmptyAttributeName = "AzureFunctions.DisabledWhen.DisabledWhenNullOrEmptyAttribute";
private const string UseDisabledWhenMethodName = "AzureFunctions.DisabledWhen.IHostBuilderExtensions.UseDisabledWhen";
private const string UseDisabledWhenAppBuilderMethodName = "AzureFunctions.DisabledWhen.IFunctionsWorkerApplicationBuilderExtensions.UseDisabledWhen";

public void Initialize(IncrementalGeneratorInitializationContext context)
{
Expand Down Expand Up @@ -99,7 +100,17 @@ invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
}

var fullName = $"{methodSymbol.ContainingType.ToDisplayString()}.{methodSymbol.Name}";
if (fullName != UseDisabledWhenMethodName)

BuilderType builderType;
if (fullName == UseDisabledWhenMethodName)
{
builderType = BuilderType.HostBuilder;
}
else if (fullName == UseDisabledWhenAppBuilderMethodName)
{
builderType = BuilderType.FunctionsWorkerApplicationBuilder;
}
else
{
return null;
}
Expand All @@ -112,7 +123,7 @@ invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
return null;
}

return new InterceptorInfo(interceptableLocation.Version, interceptableLocation.Data);
return new InterceptorInfo(interceptableLocation.Version, interceptableLocation.Data, builderType);
}

private static FunctionDisableInfo? ExtractDisabledWhenInfo(GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ public static SourceText GenerateRegistry(
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace System.Runtime.CompilerServices
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.1")]
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : global::System.Attribute
{
Expand All @@ -40,7 +41,7 @@ public InterceptsLocationAttribute(int version, string data) { }

namespace AzureFunctions.DisabledWhen
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.1")]
file readonly struct DisabledCondition
{
public DisabledCondition(string configKey, string? configValue, global::System.StringComparison comparer, bool matchNullOrEmpty = false)
Expand All @@ -57,7 +58,7 @@ public DisabledCondition(string configKey, string? configValue, global::System.S
public bool MatchNullOrEmpty { get; }
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.1")]
file static class DisabledWhenRegistry
{
private static readonly global::System.Collections.Generic.Dictionary<string, global::System.Collections.Generic.List<DisabledCondition>> Conditions =
Expand All @@ -78,7 +79,7 @@ public static bool TryGetConditions(string entryPoint, out global::System.Collec
}
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.1")]
file sealed class GeneratedDisabledWhenMetadataProvider : IFunctionMetadataProvider
{
private readonly IServiceProvider serviceProvider;
Expand All @@ -99,7 +100,8 @@ public async Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(st
{
var functionMetadataProvider = this.serviceProvider
.GetServices<IFunctionMetadataProvider>()
.Last(x => x.GetType() != typeof(GeneratedDisabledWhenMetadataProvider));
.LastOrDefault(x => x.GetType() != typeof(GeneratedDisabledWhenMetadataProvider))
?? throw new global::System.InvalidOperationException("No other IFunctionMetadataProvider is registered. Ensure the default Azure Functions metadata provider is available before calling UseDisabledWhen().");

var metaData = await functionMetadataProvider
.GetFunctionMetadataAsync(directory)
Expand Down Expand Up @@ -139,12 +141,21 @@ public async Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(st
}
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("AzureFunctions.DisabledWhen.SourceGenerator", "1.0.1")]
file static class UseDisabledWhenInterceptor
{
{{interceptorMethods}}
private static IHostBuilder UseDisabledWhenGenerated(this IHostBuilder builder)
=> builder.ConfigureServices(services => services.AddSingleton<IFunctionMetadataProvider, GeneratedDisabledWhenMetadataProvider>());
{
global::System.ArgumentNullException.ThrowIfNull(builder);
return builder.ConfigureServices(services => services.AddSingleton<IFunctionMetadataProvider, GeneratedDisabledWhenMetadataProvider>());
}

private static void UseDisabledWhenGenerated(this IFunctionsWorkerApplicationBuilder builder)
{
global::System.ArgumentNullException.ThrowIfNull(builder);
builder.Services.AddSingleton<IFunctionMetadataProvider, GeneratedDisabledWhenMetadataProvider>();
}
}
}
""";
Expand All @@ -164,12 +175,24 @@ private static string GenerateInterceptorMethods(ImmutableArray<InterceptorInfo>

foreach (var interceptor in interceptors.Distinct())
{
sb.AppendLine($$"""
if (interceptor.BuilderType == BuilderType.FunctionsWorkerApplicationBuilder)
{
sb.AppendLine($$"""
[global::System.Runtime.CompilerServices.InterceptsLocation({{interceptor.Version}}, "{{interceptor.Data}}")]
public static void UseDisabledWhen_{{index}}(this IFunctionsWorkerApplicationBuilder builder)
=> builder.UseDisabledWhenGenerated();

""");
}
else
{
sb.AppendLine($$"""
[global::System.Runtime.CompilerServices.InterceptsLocation({{interceptor.Version}}, "{{interceptor.Data}}")]
public static IHostBuilder UseDisabledWhen_{{index}}(this IHostBuilder builder)
=> builder.UseDisabledWhenGenerated();

""");
}
index++;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
namespace AzureFunctions.DisabledWhen.SourceGenerator.Internal;

internal enum BuilderType
{
HostBuilder,
FunctionsWorkerApplicationBuilder,
}

internal readonly record struct InterceptorInfo
{
public InterceptorInfo(int version, string data)
public InterceptorInfo(int version, string data, BuilderType builderType)
{
Version = version;
Data = data;
BuilderType = builderType;
}

public int Version { get; }

public string Data { get; }

public BuilderType BuilderType { get; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="AzureFunctions.DisabledWhen.Tests" />
</ItemGroup>
Expand Down
Loading