azure-sdk-for-net icon indicating copy to clipboard operation
azure-sdk-for-net copied to clipboard

resourceGroup.GetAppCertificates() error "Value cannot be an empty string. (Parameter 'resourceId')"

Open absolutebandit opened this issue 2 years ago • 4 comments

Library name and version

Azure.ResourceManager.AppService 1.0.0

Describe the bug

I try to get the managed certificate of an app service using resourceGroup.GetAppCertificates but the result set had this error Value cannot be an empty string. (Parameter 'resourceId'). Any operations on the resulting AppCertificateCollection fail with this error. I tried getting the managed certificates from an app service where I had created the certificate using the SDK and then also from a manually created (using the portal) resource group/app service/managed certificate but I get the same results.

If I use the method resourceGroup.GetAppCertificates() on a resource group that only contains "bring your own" certificates then it works fine. The certificate data for these looks exactly the same as for my managed certificates (except the issuer/issue date).

One other strange thing I noticed is that upon creating the managed certificates using certificates.CreateOrUpdate() or the REST APIS it requires a "serverFarmId" but when I view the certificates in resources.azure.com the serverFarmId shows as null?

I also have the issue that the certificates.CreateOrUpdate() returns 202 which causes an exception as mentioned here. I can handle that but I would really like to be able to get the newly created managed certificate using the new azure resource SDK so I can use the thumbprint for a next step. For the time being I will resort to using the REST APIS but please let me know if there is any advice or a work around for this. Is it a bug or am I doing something wrong?

Expected behavior

AppCertificateCollection is returned containing managed certificates I can use.

Actual behavior

Value cannot be an empty string. (Parameter 'resourceId') error is thrown when I try to use the AppCertificateCollection for anything.

Stack Trace

at Azure.Core.Argument.AssertNotNullOrEmpty(String value, String name) at Azure.Core.ResourceIdentifier..ctor(String resourceId) at Azure.ResourceManager.AppService.AppCertificateData.DeserializeAppCertificateData(JsonElement element) at Azure.ResourceManager.AppService.Models.CertificateCollection.DeserializeCertificateCollection(JsonElement element) at Azure.ResourceManager.AppService.CertificatesRestOperations.ListByResourceGroup(String subscriptionId, String resourceGroupName, CancellationToken cancellationToken) at Azure.ResourceManager.AppService.AppCertificateCollection.<>c__DisplayClass10_0.<GetAll>g__FirstPageFunc|0(Nullable1 pageSizeHint) at Azure.Core.PageableHelpers.FuncPageable1.<AsPages>d__4.MoveNext() at Azure.Pageable1.<GetEnumerator>d__8.MoveNext() at System.Collections.Generic.LargeArrayBuilder1.AddRange(IEnumerable1 items) at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at System.Linq.SystemCore_EnumerableDebugView1.get_Items()

Reproduction Steps

  1. Create a managed certificate on an app service using either the azure portal or the Azure.ResourceManager.AppService 1.0.0 package.

  2. Retrieve the AppCertificateCollection using resourceGroup.GetCertificates()

  3. Inspecting the returned value results will show the above mentioned error. Also calling ExistsAsync, GetAsyc etc will invoke the error.

Environment

Microsoft Visual Studio Enterprise 2022 (64-bit) - Current Version 17.4.3

.NET SDK: Version: 7.0.101 Commit: bb24aafa11

Runtime Environment: OS Name: Windows OS Version: 10.0.22621 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\7.0.101\

Host: Version: 7.0.1 Architecture: x64 Commit: 97203d38ba

.NET SDKs installed: 6.0.404 [C:\Program Files\dotnet\sdk] 7.0.101 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables: Not set

global.json file: Not found

absolutebandit avatar Dec 29 '22 10:12 absolutebandit

I was able to achieve what I wanted using Generic Resources as follows but I'd still like to know why it doesn't work with the Typed Resources:

// Get/create managed certificate
GenericResource managedCertificateResource;
var appCertificateName = $"{siteHostName}-{website.Data.Name}";
var subscriptionId = "mysubid";
var certificateResourceId = AppCertificateResource.CreateResourceIdentifier(subscriptionId, resourceGroupName, appCertificateName);

var genericResources = client.GetGenericResources();
if (!await genericResources.ExistsAsync(certificateResourceId))
{
    var appCertificateData = new
    {
        CanonicalName = siteHostName, 
        HostNames = new List<string> { siteHostName }, 
        ServerFarmId = appServicePlan.Id.ToString()
    };

    var createCertificateData = new GenericResourceData(AzureLocation.WestEurope)
    {
         Properties = BinaryData.FromObjectAsJson(appCertificateData, new JsonSerializerOptions()),
    };

    var createAppCertificateOperationResult = await genericResources.CreateOrUpdateAsync(WaitUntil.Completed, certificateResourceId, createCertificateData, CancellationToken.None);
    managedCertificateResource = createAppCertificateOperationResult.Value;
}
else
{
     managedCertificateResource = await client.GetGenericResource(certificateResourceId).GetAsync();
}

var appCertificateProperties = managedCertificateResource.Data.Properties.ToObject<AppCertificateProperties>(new JsonObjectSerializer());

// Bind the Custom Domain with Certificate (SNI/SLL)
if (siteHostNameBindingResource.Data.Thumbprint == null)
{
    var bindingData = siteHostNameBindingResource.Data;
    bindingData.HostNameType = AppServiceHostNameType.Verified;
    bindingData.SslState = HostNameBindingSslState.SniEnabled;
    bindingData.CustomHostNameDnsRecordType = CustomHostNameDnsRecordType.CName;
    bindingData.Thumbprint = BinaryData.FromString("\"" + appCertificateProperties.thumbprint + "\"");
    var updateSiteHostNameBindingOperationResult = await siteHostNameBindings.CreateOrUpdateAsync(WaitUntil.Completed, siteHostName, bindingData, CancellationToken.None);
    siteHostNameBindingResource = updateSiteHostNameBindingOperationResult.Value;
}

absolutebandit avatar Dec 29 '22 14:12 absolutebandit

Thank you for your feedback. Tagging and routing to the team member best able to assist. Please expect delayed responses due to the US holidays.

jsquire avatar Dec 29 '22 15:12 jsquire

Hi @absolutebandit, for your questions here:

  • I will try to reproduce the exception about resourceGroup.GetCertificates(), it might take some time.
  • What do you mean by saying it requires a serverFarmId? Will the service request fail if you don't set this property? From te perspective of SDK, ServerFarmId is not required. In our sdk, if the property is required, we will put it into the ctor. And AppCertificateData only requires location when we try to construct it.
  • The exception will be thrown when the service return 202 is because the service didn't define 202 in the API service, so we don't have a good work around for this. I will try to see if we can fix it from API spec side.

Yao725 avatar Jan 10 '23 09:01 Yao725

Hi Yao,

Yes if you don't set the server farm id the request will fail with a validation error. I just noted that it's strange that it's requried when the property is still showing as null in the resource json once the certificate is created. However this is not the main issue and it's not a problem for me, I can just set the server farm id.

As mentioned my code is currently using the gerneric resource approach as shown above and is shipped like this for now but if you can fix the issue with the typed resources we would prefer to use that approach and patch our code later.

Thanks for looking into this.

absolutebandit avatar Jan 10 '23 10:01 absolutebandit

Thanks for you feedback, we have created this feature request for this kind of issue. Will discuss to finalize the solution.

Yao725 avatar Jan 13 '23 02:01 Yao725