Skip to content

Commit

Permalink
Table converter (#1506)
Browse files Browse the repository at this point in the history
* Add E2E tests for blob SDK type bindings (#1360)

* Add analyzer for SupportsDeferredBindingAttribute (#1367)

* Update SupportsDeferredBinding diagnostic code & update docs (#1377)

* Revert "Remove types, tests & logic related to the SDK-binding feature (#1374)"

This reverts commit eac5b19.

* Added unit tests for BlobStorageConverter (#1370)

* Adding test project
* Added tests

* Initial changes

* Updating metadata

* Adding a test

* Code refactoring

* Cosmos DB converter for SDK-type support and samples (#1406)

* Execution flow added

* Update pipeline variables to include tags (#1427)

* Update extension variables to not use build number with tags (#1429)

* Execution flow

* cleanup

* Cleanup

* Cleanup

* Cleanup execution flow

* Added test

* Adding tests

* code cleanup

* Code cleanup

* code refactor

* Added logic to check type before sending to converter

* Updated tests

* Removing advertised converters from options

* Adding options back

* Changes to check type

* updating type check logic

* correction in metadata generation

* code cleanup

* Test cleanup

* Added changes to support poco

* Metadata generation changes

* Changes on Invocation side

* correcting type check and tests

* Test for poco invocation support

* Added tests for poco invocation flows

* Metadata generator minor update

* Grpc definition update

* Added poco and poco collection check

* cleanup metadata generation

* Adding support for type collection

* code cleanup

* Fixing metadata gen

* Test fix

* Grpc definition fix

* Removing BlobContainerClient advertisement

* Readding containerclient

* Updated metadata generator

* Changes as per latest design

* code cleanup

* updatin tests input

* code cleanup

* Cleanup Metadata generation

* correcting converters fallback logic

* Removing converter advertisement on cosmos trigger

* Fixing failing test

* test check in

* Undo testing changes in sample app

* spacing fix

* updated tests

* non secret changes

* Addressing PR feedback

* e2e test work

* adding comments

* Removed json deserialization attribute

* Rename to AllowConverterFallback

* Addressing feedback

* Metada generation cleanup

* Test cleanup

* Addressing feedback

* Updated grpcFunctionDefinition for multiple inputConverterAttribute

* Removing cosmos converter registration

* Address PR feedback

* Address PR feedback

* Test fix

* Minor

* addressing comments

* getting e2e working

* Implement bypass deferred binding (#1462)

Implement bypass deferred binding

* Build issue fix

* comments

* Update test/E2ETests/E2EApps/E2EApp/Table/TableInputBindingFunctions.cs

Co-authored-by: Lilian Kasem <likasem@microsoft.com>

* Adding check for PR in yml file (#1546)

* addressing some more comments

* dependency

* one more dependency

* trying to compile lol

* seeing if tables project compiles with dependency

* removing local instance

* see if this compiles

* trying to remove test

* nvm adding it back

* adding keys to nuget.config

* trying to get feed

* trying again

* more yml

* fixing spacing

* ci.yml

* Add ability to bind to SBReceivedMessage (#1313)

* another change

* trying again

* formatting changes

* changes

* updating worker.sdk package

* fixing unit test

* getting unit tests to pass

* fixing test

---------

Co-authored-by: Lilian Kasem <likasem@microsoft.com>
Co-authored-by: Surgupta <surgupta@microsoft.com>
Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
  • Loading branch information
4 people committed May 25, 2023
1 parent cd25c65 commit db4ad52
Show file tree
Hide file tree
Showing 35 changed files with 2,232 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Globalization;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Azure.Functions.Worker.Extensions
Expand Down Expand Up @@ -30,6 +32,45 @@ internal static IConfigurationSection GetWebJobsConnectionStringSection(this ICo
return section;
}

/// <summary>
/// Either constructs the serviceUri from the provided accountName
/// or retrieves the serviceUri for the specific resource (i.e. blobServiceUri or queueServiceUri)
/// </summary>
/// <param name="configuration">configuration section for a given connection name </param>
/// <param name="subDomain">The subdomain of the serviceUri (i.e. blob, queue, table)</param>
/// <param name="serviceUri">The serviceUri for the specific resource (i.e. blobServiceUri or queueServiceUri)</param>
internal static bool TryGetServiceUriForStorageAccounts(this IConfiguration configuration, string subDomain, out Uri serviceUri)
{
if (subDomain is null)
{
throw new ArgumentNullException(nameof(subDomain));
}
var serviceUriConfig = string.Format(CultureInfo.InvariantCulture, "{0}ServiceUri", subDomain);

if (configuration.GetValue<string>("accountName") is { } accountName)
{
serviceUri = FormatServiceUri(accountName, subDomain);
return true;
}
else if (configuration.GetValue<string>($"{subDomain}ServiceUri") is { } uriStr)
{
serviceUri = new Uri(uriStr);
return true;
}

serviceUri = default(Uri)!;
return false;
}

/// <summary>
/// Generates the serviceUri for a particular storage resource
/// </summary>
private static Uri FormatServiceUri(string accountName, string subDomain, string defaultProtocol = "https", string endpointSuffix = "core.windows.net")
{
var uri = string.Format(CultureInfo.InvariantCulture, "{0}://{1}.{2}.{3}", defaultProtocol, accountName, subDomain, endpointSuffix);
return new Uri(uri);
}

/// <summary>
/// Creates a WebJobs specific prefixed string using a given connection name.
/// </summary>
Expand Down Expand Up @@ -58,4 +99,4 @@ private static IConfigurationSection GetConnectionStringOrSetting(this IConfigur
return configuration.GetSection(connectionName);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
Expand Down Expand Up @@ -52,7 +52,7 @@ public void Configure(string name, BlobStorageBindingOptions options)
}
else
{
if (TryGetServiceUri(connectionSection, out Uri serviceUri))
if (connectionSection.TryGetServiceUriForStorageAccounts(BlobServiceUriSubDomain, out Uri serviceUri))
{
options.ServiceUri = serviceUri;
}
Expand All @@ -61,39 +61,5 @@ public void Configure(string name, BlobStorageBindingOptions options)
options.BlobClientOptions = (BlobClientOptions)_componentFactory.CreateClientOptions(typeof(BlobClientOptions), null, connectionSection);
options.Credential = _componentFactory.CreateTokenCredential(connectionSection);
}

/// <summary>
/// Either constructs the serviceUri from the provided accountName
/// or retrieves the serviceUri for the specific resource (i.e. blobServiceUri or queueServiceUri)
/// </summary>
private bool TryGetServiceUri(IConfiguration configuration, out Uri serviceUri)
{
var serviceUriConfig = string.Format(CultureInfo.InvariantCulture, "{0}ServiceUri", BlobServiceUriSubDomain);

string accountName;
string uriStr;
if ((accountName = configuration.GetValue<string>("accountName")) is not null)
{
serviceUri = FormatServiceUri(accountName);
return true;
}
else if ((uriStr = configuration.GetValue<string>(serviceUriConfig)) is not null)
{
serviceUri = new Uri(uriStr);
return true;
}

serviceUri = default(Uri)!;
return false;
}

/// <summary>
/// Generates the serviceUri for a particular storage resource
/// </summary>
private Uri FormatServiceUri(string accountName, string defaultProtocol = "https", string endpointSuffix = "core.windows.net")
{
var uri = string.Format(CultureInfo.InvariantCulture, "{0}://{1}.{2}.{3}", defaultProtocol, accountName, BlobServiceUriSubDomain, endpointSuffix);
return new Uri(uri);
}
}
}
}
2 changes: 1 addition & 1 deletion extensions/Worker.Extensions.Tables/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

### Microsoft.Azure.Functions.Worker.Extensions.Tables <version>

- <entry>
- Add abiility to bind table input to TableClient, TableEntity, and IEnumerable<TableEntity> (#1470)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Azure.Core;
using Azure.Data.Tables;

namespace Microsoft.Azure.Functions.Worker.Extensions.Tables.Config
{
internal class TablesBindingOptions
{
public string? ConnectionString { get; set; }

public Uri? ServiceUri { get; set; }

public TokenCredential? Credential { get; set; }

public TableClientOptions? TableClientOptions { get; set; }

private TableServiceClient? TableServiceClient;

internal virtual TableServiceClient CreateClient()
{
if (ConnectionString is null && ServiceUri is null)
{
throw new ArgumentNullException(nameof(ConnectionString) + " " + nameof(ServiceUri));
}

if (TableServiceClient is not null)
{
return TableServiceClient;
}

TableServiceClient = !string.IsNullOrEmpty(ConnectionString)
? new TableServiceClient(ConnectionString, TableClientOptions) // Connection string based auth;
: new TableServiceClient(ServiceUri, Credential, TableClientOptions); // AAD auth

return TableServiceClient;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using Azure.Data.Tables;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.Functions.Worker.Extensions.Tables.Config
{
internal class TablesBindingOptionsSetup : IConfigureNamedOptions<TablesBindingOptions>
{
private readonly IConfiguration _configuration;
private readonly AzureComponentFactory _componentFactory;

private const string TablesServiceUriSubDomain = "table";

public TablesBindingOptionsSetup(IConfiguration configuration, AzureComponentFactory componentFactory)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_componentFactory = componentFactory ?? throw new ArgumentNullException(nameof(componentFactory));
}

public void Configure(TablesBindingOptions options)
{
Configure(Options.DefaultName, options);
}

public void Configure(string name, TablesBindingOptions options)
{
if (string.IsNullOrWhiteSpace(name))
{
name = Constants.Storage; // default
}

IConfigurationSection connectionSection = _configuration.GetWebJobsConnectionStringSection(name);

if (!connectionSection.Exists())
{
// Not found
throw new InvalidOperationException($"Tables connection configuration '{name}' does not exist. " +
"Make sure that it is a defined App Setting.");
}

if (!string.IsNullOrWhiteSpace(connectionSection.Value))
{
options.ConnectionString = connectionSection.Value;
}
else
{
if (connectionSection.TryGetServiceUriForStorageAccounts(TablesServiceUriSubDomain, out Uri serviceUri))
{
options.ServiceUri = serviceUri;
}
}

options.TableClientOptions = (TableClientOptions)_componentFactory.CreateClientOptions(typeof(TableClientOptions), null, connectionSection);
options.Credential = _componentFactory.CreateTokenCredential(connectionSection);
}
}
}
20 changes: 20 additions & 0 deletions extensions/Worker.Extensions.Tables/src/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Azure.Functions.Worker.Extensions.Tables
{
internal class Constants
{
internal const string Storage = "Storage";
internal const string TablesExtensionName = "AzureStorageTables";
internal const string TableName = "TableName";
internal const string PartitionKey = "PartitionKey";
internal const string RowKey = "RowKey";
internal const string Connection = "Connection";
internal const string Take = "Take";
internal const string Filter = "Filter";
// Media content types
internal const string JsonContentType = "application/json";
}
}
7 changes: 7 additions & 0 deletions extensions/Worker.Extensions.Tables/src/Nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
using System.Runtime.CompilerServices;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;

[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Tables", "1.0.0")]
[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.Tables", "1.2.0-beta.1")]
[assembly: InternalsVisibleTo("Microsoft.Azure.Functions.WorkerExtension.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001005148be37ac1d9f58bd40a2e472c9d380d635b6048278f7d47480b08c928858f0f7fe17a6e4ce98da0e7a7f0b8c308aecd9e9b02d7e9680a5b5b75ac7773cec096fbbc64aebd429e77cb5f89a569a79b28e9c76426783f624b6b70327eb37341eb498a2c3918af97c4860db6cdca4732787150841e395a29cfacb959c1fd971c1")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
36 changes: 36 additions & 0 deletions extensions/Worker.Extensions.Tables/src/TableExtensionStartup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Core;
using Microsoft.Azure.Functions.Worker.Extensions.Tables.Config;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

[assembly: WorkerExtensionStartup(typeof(TableExtensionStartup))]

namespace Microsoft.Azure.Functions.Worker
{
/// <summary>
/// Table extension startup.
/// </summary>
public class TableExtensionStartup : WorkerExtensionStartup
{
/// <summary>
/// Configure table extension startup.
/// </summary>
public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder)
{
if (applicationBuilder == null)
{
throw new ArgumentNullException(nameof(applicationBuilder));
}

applicationBuilder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory
applicationBuilder.Services.AddOptions<TablesBindingOptions>();
applicationBuilder.Services.AddSingleton<IConfigureOptions<TablesBindingOptions>, TablesBindingOptionsSetup>();
}
}
}
17 changes: 17 additions & 0 deletions extensions/Worker.Extensions.Tables/src/TableInputAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker.Converters;
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
using Microsoft.Azure.Functions.Worker.Extensions.Tables.TypeConverters;

namespace Microsoft.Azure.Functions.Worker
{
/// <summary>
/// Attribute used to configure a parameter as the input target for the Azure Storage Tables binding.
/// </summary>
[AllowConverterFallback(true)]
[InputConverter(typeof(TableClientConverter))]
[InputConverter(typeof(TableEntityConverter))]
[InputConverter(typeof(TableEntityEnumerableConverter))]
public class TableInputAttribute : InputBindingAttribute
{
/// <summary>Initializes a new instance of the <see cref="TableInputAttribute"/> class.</summary>
Expand Down Expand Up @@ -37,6 +43,17 @@ public TableInputAttribute(string tableName, string partitionKey, string rowKey)
RowKey = rowKey;
}

/// <summary>Initializes a new instance of the <see cref="TableInputAttribute"/> class.</summary>
/// <param name="tableName">The name of the table containing the entity.</param>
/// <param name="partitionKey">The partition key of the entity.</param>
/// <param name="take">The number of entities to return </param>
public TableInputAttribute(string tableName, string partitionKey, int take)
{
TableName = tableName;
PartitionKey = partitionKey;
Take = take;
}

/// <summary>Gets the name of the table to which to bind.</summary>
/// <remarks>When binding to a table entity, gets the name of the table containing the entity.</remarks>
public string TableName { get; }
Expand Down

0 comments on commit db4ad52

Please sign in to comment.