/// <summary> /// Starts a deployment at provided target scope and returns <see cref="BicepDeploymentStartResponse"/>. /// </summary> /// <param name="deploymentCollectionProvider">deployment collection provider</param> /// <param name="armClient">arm client</param> /// <param name="documentPath">path to bicep file used in deployment</param> /// <param name="template">template used in deployment</param> /// <param name="parametersFilePath">path to parameter file used in deployment</param> /// <param name="id">id string to create the ResourceIdentifier from</param> /// <param name="scope">target scope</param> /// <param name="location">location to store the deployment data</param> /// <param name="deploymentId">deployment id</param> /// <param name="parametersFileName">parameters file name</param> /// <param name="parametersFileUpdateOption"><see cref="ParametersFileUpdateOption"/>update, create or overwrite parameters file</param> /// <param name="updatedDeploymentParameters">parameters that were updated during deployment flow</param> /// <param name="portalUrl">azure management portal URL</param> /// <param name="deploymentName">deployment name</param> /// <param name="deploymentOperationsCache">deployment operations cache that needs to be updated</param> /// <returns><see cref="BicepDeploymentStartResponse"/></returns> public static async Task <BicepDeploymentStartResponse> StartDeploymentAsync( IDeploymentCollectionProvider deploymentCollectionProvider, ArmClient armClient, string documentPath, string template, string parametersFilePath, string id, string scope, string location, string deploymentId, string parametersFileName, ParametersFileUpdateOption parametersFileUpdateOption, List <BicepUpdatedDeploymentParameter> updatedDeploymentParameters, string portalUrl, string deploymentName, IDeploymentOperationsCache deploymentOperationsCache) { if ((scope == LanguageConstants.TargetScopeTypeSubscription || scope == LanguageConstants.TargetScopeTypeManagementGroup) && string.IsNullOrWhiteSpace(location)) { return(new BicepDeploymentStartResponse(false, string.Format(LangServerResources.MissingLocationDeploymentFailedMessage, documentPath), null)); } ArmDeploymentCollection?deploymentCollection; var resourceIdentifier = new ResourceIdentifier(id); try { deploymentCollection = deploymentCollectionProvider.GetDeploymentCollection(armClient, resourceIdentifier, scope); } catch (Exception e) { return(new BicepDeploymentStartResponse(false, string.Format(LangServerResources.DeploymentFailedWithExceptionMessage, documentPath, e.Message), null)); } if (deploymentCollection is not null) { JsonElement parameters; try { var updatedParametersFileContents = DeploymentParametersHelper.GetUpdatedParametersFileContents(documentPath, parametersFileName, parametersFilePath, parametersFileUpdateOption, updatedDeploymentParameters); parameters = JsonElementFactory.CreateElement(updatedParametersFileContents); } catch (Exception e) { return(new BicepDeploymentStartResponse(false, e.Message, null)); } var deploymentProperties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental) { Template = new BinaryData(JsonDocument.Parse(template).RootElement), Parameters = new BinaryData(parameters) }; var armDeploymentContent = new ArmDeploymentContent(deploymentProperties) { Location = location, }; try { var deploymentOperation = await deploymentCollection.CreateOrUpdateAsync(WaitUntil.Started, deploymentName, armDeploymentContent); if (deploymentOperation is null) { return(new BicepDeploymentStartResponse(false, string.Format(LangServerResources.DeploymentFailedMessage, documentPath), null)); } deploymentOperationsCache.CacheDeploymentOperation(deploymentId, deploymentOperation); var linkToDeploymentInAzurePortal = GetLinkToDeploymentInAzurePortal(portalUrl, id, deploymentName); return(new BicepDeploymentStartResponse( true, string.Format(LangServerResources.DeploymentStartedMessage, documentPath), string.Format(LangServerResources.ViewDeploymentInPortalMessage, linkToDeploymentInAzurePortal))); } catch (Exception e) { return(new BicepDeploymentStartResponse(false, string.Format(LangServerResources.DeploymentFailedWithExceptionMessage, documentPath, e.Message), null)); } } return(new BicepDeploymentStartResponse(false, string.Format(LangServerResources.DeploymentFailedMessage, documentPath), null)); }
// Per documentation here- https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/parameter-files // parameters file should be of below format: //{ // "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", // "contentVersion": "1.0.0.0", // "parameters": { // "<first-parameter-name>": { // "value": "<first-value>" // }, // "<second-parameter-name>": { // "value": "<second-value>" // } // } //} // However, azure-sdk-for-net expects parameters to be of name and value pairs: // https://github.com/Azure/azure-sdk-for-net/blob/1e25b1bfc9b54df35d907aa7b2c10ff07082e845/sdk/resources/Azure.ResourceManager.Resources/src/Generated/Models/ArmDeploymentProperties.cs#L27 // We'll work around the above issue by first detecting the format of the file. // If it's in the format descibed in the docs, we'll extract the parameters value and use that for actual deployment. // If the user chose to create a new parameters file during the deployment flow, we'll follow the format // mentioned in the docs as a best practise. public static string GetUpdatedParametersFileContents( string documentPath, string parametersFileName, string parametersFilePath, ParametersFileUpdateOption updateOrCreateParametersFile, IEnumerable <BicepUpdatedDeploymentParameter> updatedDeploymentParameters) { try { // Parameter file follows format mentioned here: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/parameter-files var armSchemaStyleParametersFile = @"{ ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"", ""contentVersion"": ""1.0.0.0"", ""parameters"": { } }"; // We will send across the secure param values to the azure sdk that handles deployment, // but will avoid writing it to the parameters file for security reasons var updatedParametersFile = !string.IsNullOrWhiteSpace(parametersFilePath) ? File.ReadAllText(parametersFilePath) : armSchemaStyleParametersFile; var updatedParametersFileWithoutSecureParams = updatedParametersFile; var jObject = GetParametersObjectValue(updatedParametersFile, out bool isArmStyleTemplate); foreach (var updatedDeploymentParameter in updatedDeploymentParameters.Reverse()) { var name = updatedDeploymentParameter.name; // Check to make sure parameters mentioned in parameters file are not overwritten if (jObject.ContainsKey(name)) { continue; } else { var jsonEditor = new JsonEditor(updatedParametersFile); var propertyPaths = new List <string>(); if (isArmStyleTemplate) { propertyPaths.Add("parameters"); propertyPaths.Add(name); } else { propertyPaths.Add(name); } var valueObject = UpdateJObjectBasedOnParameterType( updatedDeploymentParameter.parameterType, updatedDeploymentParameter.value, JObject.Parse("{}")); (int line, int column, string text)? insertion = jsonEditor.InsertIfNotExist(propertyPaths.ToArray(), valueObject); if (insertion.HasValue) { var(line, column, insertText) = insertion.Value; updatedParametersFile = JsonEditor.ApplyInsertion(updatedParametersFile, (line, column, insertText)); if (!updatedDeploymentParameter.isSecure) { updatedParametersFileWithoutSecureParams = JsonEditor.ApplyInsertion(updatedParametersFileWithoutSecureParams, (line, column, insertText)); } } } } if (updatedDeploymentParameters.Any()) { if (updateOrCreateParametersFile == ParametersFileUpdateOption.Update) { File.WriteAllText(parametersFilePath, updatedParametersFileWithoutSecureParams); } // ParametersFileCreateOrUpdate will have a value of "Overwrite" only if the parameters // file with name <bicep_file_name>.parameters.json already exists and user chose to // overwrite it with values from this deployment else if (updateOrCreateParametersFile == ParametersFileUpdateOption.Create || updateOrCreateParametersFile == ParametersFileUpdateOption.Overwrite) { var directoryContainingBicepFile = Path.GetDirectoryName(documentPath); if (directoryContainingBicepFile is not null) { File.WriteAllText(Path.Combine(directoryContainingBicepFile, parametersFileName), updatedParametersFileWithoutSecureParams); } } } var updatedJObject = GetParametersObjectValue(updatedParametersFile, out _); return(updatedJObject.ToString()); } catch (Exception e) { throw new Exception(string.Format(LangServerResources.InvalidParameterFileDeploymentFailedMessage, documentPath, parametersFilePath, e.Message)); } }