[InlineData(null, "sitereplicator", "filecounter", "filecountermvc")]    // default site extension endpoint (v2)
        // [InlineData("https://api.nuget.org/v3/index.json", "bootstrap", "knockoutjs", "angularjs")]    // v3 endpoint
        public async Task SiteExtensionParallelInstallationTest(string feedEndpoint, string testPackageId1, string testPackageId2, string testPackageId3)
        {
            const string appName = "SiteExtensionParallelInstallationTest";

            string[] packageIds = { testPackageId1, testPackageId2, testPackageId3 };
            await ApplicationManager.RunAsync(appName, async appManager =>
            {
                var manager = appManager.SiteExtensionManager;
                await CleanSiteExtensions(manager);

                List <Task <HttpResponseMessage> > tasks        = new List <Task <HttpResponseMessage> >();
                List <Task <HttpResponseMessage> > pollingTasks = new List <Task <HttpResponseMessage> >();

                UpdateHeaderIfGoingToBeArmRequest(manager.Client, true);
                TestTracer.Trace("Start parallel install multiple extensions ...");
                foreach (var packageId in packageIds)
                {
                    tasks.Add(Task.Run <HttpResponseMessage>(async() =>
                    {
                        TestTracer.Trace("Install package '{0}' fresh from '{1}' async", packageId, feedEndpoint);
                        HttpResponseMessage responseMessage    = await manager.InstallExtension(id: packageId, feedUrl: feedEndpoint);
                        ArmEntry <SiteExtensionInfo> armResult = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                        Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                        Assert.Equal(HttpStatusCode.Created, responseMessage.StatusCode);
                        return(responseMessage);
                    }));
                }

                await Task.WhenAll(tasks);

                foreach (var packageId in packageIds)
                {
                    pollingTasks.Add(Task.Run <HttpResponseMessage>(async() =>
                    {
                        TestTracer.Trace("Poll '{0}' for status for '{1}'. Expecting 200 response eventually with site operation header.", feedEndpoint, packageId);
                        HttpResponseMessage responseMessage    = await PollAndVerifyAfterArmInstallation(manager, packageId);
                        ArmEntry <SiteExtensionInfo> armResult = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                        Assert.Equal(feedEndpoint, armResult.Properties.FeedUrl);
                        Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                        Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                        return(responseMessage);
                    }));
                }

                await Task.WhenAll(pollingTasks);

                TestTracer.Trace("Installation are done, expecting only one site operation header return (trigger site restart)");
                // should only trigger restart once
                IEnumerable <Task <HttpResponseMessage> > responses = pollingTasks.Where((task) =>
                {
                    return(task.Result.Headers.Contains(Constants.SiteOperationHeaderKey));
                });

                Assert.Equal(1, responses.Count());
            });
        }
Example #2
0
        public async Task <HttpResponseMessage> InstallExtensionArm(string id, ArmEntry <SiteExtensionInfo> requestInfo)
        {
            if (requestInfo == null)
            {
                // Body should not be empty
                return(Request.CreateResponse(HttpStatusCode.BadRequest));
            }

            return(await InstallExtension(id, requestInfo.Properties));
        }
        public async Task SiteExtensionInstallPackageToWebRootAsyncTests()
        {
            const string appName                = "SiteExtensionInstallPackageToWebRootAsyncTests";
            const string externalPackageId      = "SimpleSvc";
            const string externalPackageVersion = "1.0.0";
            const string externalFeed           = "https://www.myget.org/F/simplesvc/";

            // site extension 'webrootxdttest' search for xdt files under site extension 'webrootxdttest' folder, and print out xdt content onto page
            const string externalPackageWithXdtId = "webrootxdttest";

            await ApplicationManager.RunAsync(appName, async appManager =>
            {
                var manager = appManager.SiteExtensionManager;
                await CleanSiteExtensions(manager);

                // install/update
                TestTracer.Trace("Perform InstallExtension with id '{0}', version '{1}' from '{2}'", externalPackageId, externalPackageVersion, externalFeed);
                UpdateHeaderIfGoingToBeArmRequest(manager.Client, true);
                HttpResponseMessage responseMessage    = await manager.InstallExtension(externalPackageId, externalPackageVersion, externalFeed, SiteExtensionInfo.SiteExtensionType.WebRoot);
                ArmEntry <SiteExtensionInfo> armResult = null;
                if (responseMessage.StatusCode == HttpStatusCode.OK)
                {
                    TestTracer.Trace("Installation done within 15 seconds, no polling needed.");
                    armResult = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                }
                else
                {
                    Assert.Equal(HttpStatusCode.Created, responseMessage.StatusCode);
                    responseMessage = await PollAndVerifyAfterArmInstallation(manager, externalPackageId);
                    armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                }

                // shouldn`t see restart header since package doesn`t come with XDT
                Assert.False(responseMessage.Headers.Contains(Constants.SiteOperationHeaderKey), "Must not contain restart header");
                Assert.Equal(externalFeed, armResult.Properties.FeedUrl);
                Assert.Equal(externalPackageVersion, armResult.Properties.Version);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);

                TestTracer.Trace("GET request to verify package content has been copied to wwwroot");
                HttpClient client      = new HttpClient();
                responseMessage        = await client.GetAsync(appManager.SiteUrl);
                string responseContent = await responseMessage.Content.ReadAsStringAsync();
                Assert.NotNull(responseContent);
                Assert.True(responseContent.Contains(@"<h3>Site for testing</h3>"));

                TestTracer.Trace("GetLocalExtension should return WebRoot type SiteExtensionInfo");
                responseMessage = await manager.GetLocalExtension(externalPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(SiteExtensionInfo.SiteExtensionType.WebRoot, armResult.Properties.Type);

                responseMessage = await manager.GetLocalExtensions(externalPackageId);
                var results     = await responseMessage.Content.ReadAsAsync <ArmListEntry <SiteExtensionInfo> >();
                foreach (var item in results.Value)
                {
                    if (string.Equals(externalPackageId, item.Properties.Id, StringComparison.OrdinalIgnoreCase))
                    {
                        Assert.Equal(SiteExtensionInfo.SiteExtensionType.WebRoot, item.Properties.Type);
                    }
                }

                // delete
                TestTracer.Trace("Perform UninstallExtension with id '{0}' only.", externalPackageId);
                responseMessage = await manager.UninstallExtension(externalPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Null(armResult);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);

                TestTracer.Trace("GET request to verify package content has been removed wwwroot");
                responseMessage = await client.GetAsync(appManager.SiteUrl);
                Assert.Equal(HttpStatusCode.Forbidden, responseMessage.StatusCode);

                // install package that with xdt file
                TestTracer.Trace("Perform InstallExtension with id '{0}' from '{1}'", externalPackageWithXdtId, externalFeed);
                responseMessage = await manager.InstallExtension(externalPackageWithXdtId, feedUrl: externalFeed, type: SiteExtensionInfo.SiteExtensionType.WebRoot);
                Assert.Equal(HttpStatusCode.Created, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                // package come with XDT, which would require site restart
                TestTracer.Trace("Poll for status. Expecting 200 response eventually with site operation header.");
                responseMessage = await PollAndVerifyAfterArmInstallation(manager, externalPackageWithXdtId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                // after successfully installed, should return SiteOperationHeader to notify GEO to restart website
                Assert.True(responseMessage.Headers.Contains(Constants.SiteOperationHeaderKey));
                Assert.Equal(externalFeed, armResult.Properties.FeedUrl);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);

                TestTracer.Trace("GET request to verify package content has been copied to wwwroot");
                responseMessage = await client.GetAsync(appManager.SiteUrl);
                responseContent = await responseMessage.Content.ReadAsStringAsync();
                Assert.NotNull(responseContent);
                Assert.True(responseContent.Contains(@"1 files"));
                Assert.True(responseContent.Contains(@"site\path\shall\not\be\found")); // xdt content
            });
        }
        public async Task SiteExtensionShouldNotSeeButAbleToInstallUnlistedPackage()
        {
            const string appName             = "SiteExtensionShouldNotSeeUnlistPackage";
            const string externalPackageId   = "SimpleSite";
            const string unlistedVersion     = "3.0.0";
            const string latestListedVersion = "2.0.0";
            const string externalFeed        = "https://www.myget.org/F/simplesvc/";

            await ApplicationManager.RunAsync(appName, async appManager =>
            {
                var manager = appManager.SiteExtensionManager;
                await CleanSiteExtensions(manager);

                HttpResponseMessage response = await manager.GetRemoteExtension(externalPackageId, feedUrl: externalFeed);
                SiteExtensionInfo info       = await response.Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.NotEqual(unlistedVersion, info.Version);

                response = await manager.GetRemoteExtension(externalPackageId, version: unlistedVersion, feedUrl: externalFeed);
                info     = await response.Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.Equal(unlistedVersion, info.Version);

                response = await manager.GetRemoteExtensions(externalPackageId, allowPrereleaseVersions: true, feedUrl: externalFeed);
                List <SiteExtensionInfo> infos = await response.Content.ReadAsAsync <List <SiteExtensionInfo> >();
                Assert.NotEmpty(infos);
                foreach (var item in infos)
                {
                    Assert.NotEqual(unlistedVersion, item.Version);
                }

                response = await manager.InstallExtension(externalPackageId, feedUrl: externalFeed);
                info     = await response.Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.Equal(externalPackageId, info.Id);
                Assert.Equal(latestListedVersion, info.Version);
                Assert.Equal(externalFeed, info.FeedUrl);

                TestTracer.Trace("Should able to installed unlisted package if specify version");
                response = await manager.InstallExtension(externalPackageId, version: unlistedVersion, feedUrl: externalFeed);
                info     = await response.Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.Equal(externalPackageId, info.Id);
                Assert.Equal(unlistedVersion, info.Version);
                Assert.Equal(externalFeed, info.FeedUrl);

                UpdateHeaderIfGoingToBeArmRequest(manager.Client, isArmRequest: true);
                response = await manager.GetRemoteExtension(externalPackageId, feedUrl: externalFeed);
                ArmEntry <SiteExtensionInfo> armInfo = await response.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.NotEqual(unlistedVersion, armInfo.Properties.Version);

                response = await manager.GetRemoteExtensions(externalPackageId, allowPrereleaseVersions: true, feedUrl: externalFeed);
                ArmListEntry <SiteExtensionInfo> armInfos = await response.Content.ReadAsAsync <ArmListEntry <SiteExtensionInfo> >();
                Assert.NotEmpty(armInfos.Value);
                foreach (var item in armInfos.Value)
                {
                    Assert.NotEqual(unlistedVersion, item.Properties.Version);
                }

                response = await manager.InstallExtension(externalPackageId, feedUrl: externalFeed);
                Assert.Equal(HttpStatusCode.Created, response.StatusCode);
                await PollAndVerifyAfterArmInstallation(manager, externalPackageId);
                response = await manager.GetLocalExtension(externalPackageId);
                armInfo  = await response.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(externalPackageId, armInfo.Properties.Id);
                Assert.Equal(latestListedVersion, armInfo.Properties.Version);
                Assert.Equal(externalFeed, armInfo.Properties.FeedUrl);

                response = await manager.InstallExtension(externalPackageId, version: unlistedVersion, feedUrl: externalFeed);
                Assert.Equal(HttpStatusCode.Created, response.StatusCode);
                await PollAndVerifyAfterArmInstallation(manager, externalPackageId);
                response = await manager.GetLocalExtension(externalPackageId);
                armInfo  = await response.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(externalPackageId, armInfo.Properties.Id);
                Assert.Equal(unlistedVersion, armInfo.Properties.Version);
                Assert.Equal(externalFeed, armInfo.Properties.FeedUrl);
            });
        }
        public async Task SiteExtensionGetArmTest()
        {
            const string appName              = "SiteExtensionGetAsyncTest";
            const string externalPackageId    = "filecounter";
            const string externalFeed         = "https://api.nuget.org/v3/index.json";
            const string installationArgument = "arg0";

            await ApplicationManager.RunAsync(appName, async appManager =>
            {
                var manager = appManager.SiteExtensionManager;
                await CleanSiteExtensions(manager);

                UpdateHeaderIfGoingToBeArmRequest(manager.Client, true);
                TestTracer.Trace("GetRemoteExtensions with Arm header, expecting site extension info will be wrap inside Arm envelop");
                HttpResponseMessage responseMessage            = await manager.GetRemoteExtensions(externalPackageId, true, externalFeed);
                ArmListEntry <SiteExtensionInfo> armResultList = await responseMessage.Content.ReadAsAsync <ArmListEntry <SiteExtensionInfo> >();
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.NotNull(armResultList);
                Assert.NotEmpty(armResultList.Value);
                Assert.NotNull(armResultList.Value.Where(item => string.Equals(externalPackageId, item.Properties.Id, StringComparison.OrdinalIgnoreCase)));
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                TestTracer.Trace("GetRemoteExtension with Arm header, expecting site extension info will be wrap inside Arm envelop");
                responseMessage = await manager.GetRemoteExtension(externalPackageId, feedUrl: externalFeed);
                ArmEntry <SiteExtensionInfo> armResult = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.NotNull(armResult);
                Assert.Equal(externalPackageId, armResult.Properties.Id);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                UpdateHeaderIfGoingToBeArmRequest(manager.Client, false);
                responseMessage = await manager.InstallExtension(externalPackageId, feedUrl: externalFeed, installationArgs: installationArgument);
                SiteExtensionInfo syncResult = await responseMessage.Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.Equal(externalPackageId, syncResult.Id);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                UpdateHeaderIfGoingToBeArmRequest(manager.Client, true);
                TestTracer.Trace("GetLocalExtensions (no filter) with Arm header, expecting site extension info will be wrap inside Arm envelop");
                responseMessage = await manager.GetLocalExtensions();
                armResultList   = await responseMessage.Content.ReadAsAsync <ArmListEntry <SiteExtensionInfo> >();
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.NotNull(armResultList);
                Assert.NotEmpty(armResultList.Value);
                Assert.NotNull(armResultList.Value.Where(item => string.Equals(externalPackageId, item.Properties.Id, StringComparison.OrdinalIgnoreCase)));
                Assert.Equal(Constants.SiteExtensionProvisioningStateSucceeded, armResultList.Value.First <ArmEntry <SiteExtensionInfo> >().Properties.ProvisioningState);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                foreach (var item in armResultList.Value)
                {
                    Assert.Equal(Constants.SiteExtensionProvisioningStateSucceeded, item.Properties.ProvisioningState);
                    Assert.Equal(installationArgument, item.Properties.InstallationArgs);
                }

                TestTracer.Trace("GetLocalExtensions (with filter) with Arm header, expecting site extension info will be wrap inside Arm envelop");
                responseMessage = await manager.GetLocalExtensions(externalPackageId);
                armResultList   = await responseMessage.Content.ReadAsAsync <ArmListEntry <SiteExtensionInfo> >();
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.NotNull(armResultList);
                Assert.NotEmpty(armResultList.Value);
                Assert.NotNull(armResultList.Value.Where(item => string.Equals(externalPackageId, item.Properties.Id, StringComparison.OrdinalIgnoreCase)));
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                foreach (var item in armResultList.Value)
                {
                    Assert.Equal(Constants.SiteExtensionProvisioningStateSucceeded, item.Properties.ProvisioningState);
                    Assert.Equal(installationArgument, item.Properties.InstallationArgs);
                }
            });
        }
        [InlineData("https://api.nuget.org/v3/index.json", "filecounter")] // v3 endpoint
        public async Task SiteExtensionInstallUninstallAsyncTest(string feedEndpoint, string testPackageId)
        {
            TestTracer.Trace("Testing against feed: '{0}'", feedEndpoint);

            const string appName = "SiteExtensionInstallUninstallAsyncTest";

            await ApplicationManager.RunAsync(appName, async appManager =>
            {
                var manager = appManager.SiteExtensionManager;
                await CleanSiteExtensions(manager);

                // Get the latest package, make sure that call will return right away when we try to update
                TestTracer.Trace("Get latest package '{0}' from '{1}'", testPackageId, feedEndpoint);
                SiteExtensionInfo latestPackage = await(await manager.GetRemoteExtension(testPackageId, feedUrl: feedEndpoint)).Content.ReadAsAsync <SiteExtensionInfo>();
                Assert.Equal(testPackageId, latestPackage.Id);

                // install from non-default endpoint
                UpdateHeaderIfGoingToBeArmRequest(manager.Client, true);
                TestTracer.Trace("Install package '{0}'-'{1}' fresh from '{2}' async", testPackageId, latestPackage.Version, feedEndpoint);
                HttpResponseMessage responseMessage    = await manager.InstallExtension(id: testPackageId, version: latestPackage.Version, feedUrl: feedEndpoint);
                ArmEntry <SiteExtensionInfo> armResult = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(string.Empty, armResult.Location);   // test "x-ms-geo-location" header is empty, same value should be assign to "Location"
                Assert.Equal(HttpStatusCode.Created, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                TestTracer.Trace("Poll for status. Expecting 200 response eventually with site operation header.");
                responseMessage = await PollAndVerifyAfterArmInstallation(manager, testPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                // after successfully installed, should return SiteOperationHeader to notify GEO to restart website
                Assert.True(responseMessage.Headers.Contains(Constants.SiteOperationHeaderKey));
                Assert.Equal(feedEndpoint, armResult.Properties.FeedUrl);
                Assert.Equal(latestPackage.Version, armResult.Properties.Version);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                TestTracer.Trace("Get '{0}' again. Expecting 200 response without site operation header", testPackageId);
                responseMessage = await manager.GetLocalExtension(testPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                // get again shouldn`t see SiteOperationHeader anymore
                Assert.False(responseMessage.Headers.Contains(Constants.SiteOperationHeaderKey));
                Assert.Equal(feedEndpoint, armResult.Properties.FeedUrl);
                Assert.Equal(latestPackage.Version, armResult.Properties.Version);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                TestTracer.Trace("Try to update package '{0}' without given a feed", testPackageId);
                // Not passing feed endpoint will default to feed endpoint from installed package
                // Update should return right away, expecting code to look up from feed that store in local package
                // since we had installed the latest package, there is nothing to update.
                // We shouldn`t see any site operation header value
                // And there is no polling, since it finished within 15 seconds
                responseMessage = await manager.InstallExtension(testPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Equal(string.Empty, armResult.Location);   // test "x-ms-geo-location" header is empty, same value should be assign to "Location"
                Assert.Equal(latestPackage.Id, armResult.Properties.Id);
                Assert.Equal(feedEndpoint, armResult.Properties.FeedUrl);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));
                // no installation operation happened, shouldn`t see SiteOperationHeader return
                Assert.False(responseMessage.Headers.Contains(Constants.SiteOperationHeaderKey));

                TestTracer.Trace("Uninstall '{0}' async", testPackageId);
                responseMessage = await manager.UninstallExtension(testPackageId);
                armResult       = await responseMessage.Content.ReadAsAsync <ArmEntry <SiteExtensionInfo> >();
                Assert.Null(armResult);
                Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
                Assert.True(responseMessage.Headers.Contains(Constants.RequestIdHeader));

                TestTracer.Trace("Get '{0}' again. Expecting 404.", testPackageId);
                var ex = await KuduAssert.ThrowsUnwrappedAsync <HttpUnsuccessfulRequestException>(async() =>
                {
                    await manager.GetLocalExtension(testPackageId);
                });
                Assert.Equal(HttpStatusCode.NotFound, ex.ResponseMessage.StatusCode);
            });
        }
 public Task <HttpResponseMessage> CreateOrUpdateArm(string name, ArmEntry <FunctionEnvelope> armFunctionEnvelope)
 {
     return(CreateOrUpdateHelper(name, Task.FromResult(armFunctionEnvelope.Properties)));
 }
Example #8
0
 public async Task <HttpResponseMessage> InstallExtensionArm(string id, ArmEntry <SiteExtensionInfo> requestInfo)
 {
     return(await InstallExtension(id, requestInfo.Properties));
 }
Example #9
0
 public Task<HttpResponseMessage> CreateOrUpdateArm(string name, ArmEntry<FunctionEnvelope> armFunctionEnvelope)
 {
     return CreateOrUpdateHelper(name, Task.FromResult(armFunctionEnvelope.Properties));
 }
Example #10
0
 public HttpResponseMessage CreateTriggeredJobArm(string jobName, ArmEntry <TriggeredJob> armTriggeredJob)
 {
     return(SetJobSettings(jobName, armTriggeredJob.Properties.Settings, _triggeredJobsManager));
 }
Example #11
0
 public HttpResponseMessage CreateContinuousJobArm(string jobName, ArmEntry <ContinuousJob> armContinuousJob)
 {
     return(SetJobSettings(jobName, armContinuousJob.Properties.Settings, _continuousJobsManager));
 }
Example #12
0
 public async Task<HttpResponseMessage> InstallExtensionArm(string id, ArmEntry<SiteExtensionInfo> requestInfo)
 {
     return await InstallExtension(id, requestInfo.Properties);
 }
Example #13
0
 public ArmBuilder <TObject> AddEntry(ArmEntry entry)
 {
     return(entry.IsArrayElement
         ? AddArrayElement(entry.Key, entry.Value)
         : AddSingleElement(entry.Key, entry.Value));
 }