internal async Task <bool> AddAsync(InstanceCreateNames instance, string location, string requiredVersion, string sourceUrl, InstanceFineTuning tuning, CancellationToken cancellationToken)
        {
            string rgName = instance.ResourceGroupName;
            bool   ok     = await MakeSureResourceGroupExistsAsync(instance.IsCustom, location, rgName, cancellationToken);

            if (!ok)
            {
                return(false);
            }

            ok = await DeployArmTemplateAsync(instance, location, rgName, tuning, cancellationToken);

            if (!ok)
            {
                return(false);
            }

            // check runtime package
            var package = new FunctionRuntimePackage(logger);

            ok = await package.UpdateVersionAsync(requiredVersion, sourceUrl, instance, azure, cancellationToken);

            if (ok)
            {
                var devopsLogonData = DevOpsLogon.Load().connection;
                if (devopsLogonData.Mode == DevOpsTokenType.PAT)
                {
                    logger.WriteVerbose($"Saving Azure DevOps token");
                    ok = await ChangeAppSettingsAsync(instance, devopsLogonData, SaveMode.Default, cancellationToken);

                    if (ok)
                    {
                        logger.WriteInfo($"Azure DevOps token saved");
                    }
                    else
                    {
                        logger.WriteError($"Failed to save Azure DevOps token");
                    }
                }
                else
                {
                    logger.WriteWarning($"Azure DevOps token type {devopsLogonData.Mode} is unsupported");
                    ok = false;
                }
            }
            return(ok);
        }
        private async Task <bool> DeployArmTemplateAsync(InstanceCreateNames instance, string location, string rgName, InstanceFineTuning tuning, CancellationToken cancellationToken)
        {
            // IDEA the template should create a Storage account and/or a Key Vault for Rules' use
            // TODO https://github.com/gjlumsden/AzureFunctionsSlots suggest that slots must be created in template
            var    resourceName = "aggregator.cli.Instances.instance-template.json";
            string armTemplateString;
            var    assembly = Assembly.GetExecutingAssembly();

            using (Stream stream = assembly.GetManifestResourceStream(resourceName))
                using (StreamReader reader = new StreamReader(stream))
                {
                    armTemplateString = await reader.ReadToEndAsync();
                }

            var parsedTemplate = JObject.Parse(armTemplateString);

            // sanity checks
            if (parsedTemplate.SelectToken("parameters.aggregatorVersion") == null)
            {
                // not good, blah
                logger.WriteWarning($"Something is wrong with the ARM template");
                return(false);
            }

            var infoVersion    = GetCustomAttribute <AssemblyInformationalVersionAttribute>();
            var templateParams = new Dictionary <string, Dictionary <string, object> > {
                // TODO give use more control by setting more parameters
                { "webLocation", new Dictionary <string, object> {
                      { "value", location }
                  } },
                { "aiLocation", new Dictionary <string, object> {
                      { "value", tuning.AppInsightLocation }
                  } },
                { "storageAccountType", new Dictionary <string, object> {
                      { "value", "Standard_LRS" }
                  } },

                { "functionAppName", new Dictionary <string, object> {
                      { "value", instance.FunctionAppName }
                  } },
                { "storageAccountName", new Dictionary <string, object> {
                      { "value", instance.StorageAccountName }
                  } },
                { "hostingPlanName", new Dictionary <string, object> {
                      { "value", instance.HostingPlanName }
                  } },
                { "appInsightName", new Dictionary <string, object> {
                      { "value", instance.AppInsightName }
                  } },

                { "aggregatorVersion", new Dictionary <string, object> {
                      { "value", infoVersion.InformationalVersion }
                  } },
                { "hostingPlanSkuName", new Dictionary <string, object> {
                      { "value", tuning.HostingPlanSku }
                  } },
                { "hostingPlanSkuTier", new Dictionary <string, object> {
                      { "value", tuning.HostingPlanTier }
                  } },
            };

            string deploymentName = SdkContext.RandomResourceName("aggregator", 24);

            logger.WriteInfo($"Started deployment (id: {deploymentName})");
            var deployment = await azure.Deployments.Define(deploymentName)
                             .WithExistingResourceGroup(rgName)
                             .WithTemplate(armTemplateString)
                             .WithParameters(templateParams)
                             .WithMode(DeploymentMode.Incremental)
                             .CreateAsync(cancellationToken);

            // poll
            const int pollIntervalInSeconds = 3;
            int       totalDelay            = 0;

            while (!(StringComparer.OrdinalIgnoreCase.Equals(deployment.ProvisioningState, "Succeeded") ||
                     StringComparer.OrdinalIgnoreCase.Equals(deployment.ProvisioningState, "Failed") ||
                     StringComparer.OrdinalIgnoreCase.Equals(deployment.ProvisioningState, "Cancelled")))
            {
                SdkContext.DelayProvider.Delay(pollIntervalInSeconds * 1000);
                totalDelay += pollIntervalInSeconds;
                logger.WriteVerbose($"Deployment running ({totalDelay}s)");
                await deployment.RefreshAsync(cancellationToken);
            }
            logger.WriteInfo($"Deployment {deployment.ProvisioningState}");

            return(deployment.ProvisioningState == "Succeeded");
        }