Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.17k stars 4.53k forks source link

[QUERY] Azure Function Slot 'Deployment slot setting' - How to Get / Create? #30463

Open countchivas opened 1 year ago

countchivas commented 1 year ago

Library name and version

azure-sdk-for-net

Query/Question

I've been unable to achieve this.

I've tried WebSiteResource.GetSlotConfigNamesResource() but the Data property never returns, always InvalidOperationException.

Thanks!

Environment

No response

azure-sdk commented 1 year ago

Label prediction was below confidence level 0.6 for Model:ServiceLabels: 'ARM:0.4071534,Functions:0.10776681,Tables:0.07895351'

m-redding commented 1 year ago

Thank you for your feedback. Tagging and routing to the team member best able to assist.

xboxeer commented 1 year ago

@countchivas before getting the Data, you should first call the Get method like this

var website = this.client.GetDefaultSubscription().GetResourceGroup(resourceGroupName).Value.GetWebSites().Get("test");
var slotConfigNamesResource = website.Value.GetSlotConfigNamesResource().Get().Value;

this is because xxxxxResource in Azure mgmt SDK is a like a client to operate a resource, there is createOrUpdate, delete in a xxxxResource class, so by default it does not make a call to Azure to reduce unnecessary http request(imaging you want to delete a resource, you don't need its Data property by that time). You should always make a .Get() call to retrieve the Data from Azure

countchivas commented 1 year ago

@countchivas before getting the Data, you should first call the Get method like this

var website = this.client.GetDefaultSubscription().GetResourceGroup(resourceGroupName).Value.GetWebSites().Get("test");
var slotConfigNamesResource = website.Value.GetSlotConfigNamesResource().Get().Value;

this is because xxxxxResource in Azure mgmt SDK is a like a client to operate a resource, there is createOrUpdate, delete in a xxxxResource class, so by default it does not make a call to Azure to reduce unnecessary http request(imaging you want to delete a resource, you don't need its Data property by that time). You should always make a .Get() call to retrieve the Data from Azure

Many thanks for the reply.

I tried your code and I'm receiving the following error:

System.ArgumentNullException : Value cannot be null. (Parameter 'resourceId')

Stack Trace:  Argument.AssertNotNullOrEmpty(String value, String name) ResourceIdentifier.ctor(String resourceId) SlotConfigNamesResourceData.DeserializeSlotConfigNamesResourceData(JsonElement element) WebAppsRestOperations.ListSlotConfigurationNames(String subscriptionId, String resourceGroupName, String name, CancellationToken cancellationToken) SlotConfigNamesResource.Get(CancellationToken cancellationToken)

I'm using Azure.ResourceManager.AppService 1.0.0-beta.3 Azure.Core 1.25.0 Azure.ResourceManager.Resources 1.3.0

countchivas commented 1 year ago

Just to clarify, I'd like to achieve the equivalent to this but using SDK

az webapp config appsettings set -g myResourceGroup-n myFuncApp -s staging --slot-settings myFirstSetting=myValue

xboxeer commented 1 year ago

Just to clarify, I'd like to achieve the equivalent to this but using SDK

az webapp config appsettings set -g myResourceGroup-n myFuncApp -s staging --slot-settings myFirstSetting=myValue

Based on the command line you shared, i believe you used wrong code, there is no SlotConfigNamesResource in your resource group that's why you are seeing this exception. The right way to do so is the using the following code

public void UpdateWebSiteConfig(string resourceGroupName)
        {
            var website = this.client.GetDefaultSubscription().GetResourceGroup(resourceGroupName).Value.GetWebSite("testWebsite");
            var existingSetting = website.Value.GetSiteSlot("Test").Value.GetApplicationSettingsSlot();
            existingSetting.Value.Properties.Add("testkk", "testvv");
            var returnValue = website.Value.GetSiteSlot("Test").Value.UpdateApplicationSettingsSlot(existingSetting);

        }

Result: image

You should first get the Slot resource by calling GetSiteSlot, then find out the way to update its AppSettings

countchivas commented 1 year ago

Many thanks for your reply.

I ran this code and it does create an appsetting item but it doesn't create it as a 'deployment slot setting'.

This az cli command returns this az webapp config appsettings set -g myResourceGroup -n myFuncApp -s staging --slot-settings myFirstSetting=myValue

image
xboxeer commented 1 year ago

Many thanks for your reply.

I ran this code and it does create an appsetting item but it doesn't create it as a 'deployment slot setting'.

This az cli command returns this az webapp config appsettings set -g myResourceGroup -n myFuncApp -s staging --slot-settings myFirstSetting=myValue

image

I looked at the trace of az cli and sdk and found the az cli called another rest operation slotconfigname to mark the deployment slot setting as true, and that is the method you called at the very begining, we noticed there is a wrong definition in rest api that's why there is a value can not null issue. We'll do some fix to the current version of app service SDK. The app service SDK it self is still in beta version so we are open to any suggestion, feel free to take a survey https://www.surveymonkey.com/r/YSYFMFF if you are interested to provide any feedback

xboxeer commented 1 year ago

Issue fix tracking here : https://github.com/Azure/azure-sdk-for-net/pull/30526

cmenzi commented 1 year ago

@xboxeer, @Yao725 Any updates on this? The issue still exists in 1.0.1.

The issue comes from the method DeserializeSlotConfigNamesResourceData, where the Id gets read and because the Azure API returns null, bam!

See: https://resources.azure.com/

image

Either the API is does not follow the contract, or the SDK must handle this case.

cmenzi commented 1 year ago

Workaround for all those who cannot wait :-)

private class Fix30463 : HttpPipelinePolicy
{
    public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
    {
        ProcessNext(message, pipeline);
        var lastPart = message.Request.Uri.Path.Split('/').LastOrDefault();
        if  (lastPart != null && lastPart.EqualsOrdinalIgnoreCase("slotConfigNames"))
        {
            var jObject = JsonNode.Parse(message.Response.Content.ToStream()).AsObject();
            if (jObject["id"] == null)
            {
                jObject["id"] = message.Request.Uri.Path.ToString();
                message.Response.ContentStream = BinaryData.FromString(jObject.ToJsonString()).ToStream();
            }
        }
    }

    public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
    {
        return ProcessNextAsync(message, pipeline);
    }
}

and where the ArmClient is created

var options = new ArmClientOptions()
options.AddPolicy(new Fix30463(), HttpPipelinePosition.PerCall);

new ArmClient(tokenCredential, SubscriptionId, options);
jbindert commented 1 week ago

Still experiencing this issue on Azure.ResourceManager.AppService 1.0.2.

Any timeline you can share of when the fix for this will be released?