public static DeploymentOrchestrationInput Validate(DeploymentOrchestrationInput input, ARMFunctions functions, IInfrastructure infrastructure) { if (input.Template != null) { return(input); } input.Template = Template.Parse(input.TemplateContent, input, functions, infrastructure); foreach (var res in input.Template.Resources) { #region Deployment if (res.FullType == infrastructure.BuiltinServiceTypes.Deployments) { var deploy = Parse(res, input, functions, infrastructure); input.Deployments.Add(deploy.DeploymentName, deploy); foreach (var item in deploy.Deployments.Values) { input.Deployments.Add(item.DeploymentName, item); } deploy.Deployments.Clear(); } #endregion #region dependsOn for (int i = res.DependsOn.Count - 1; i >= 0; i--) { string dependsOnName = res.DependsOn[i]; // https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/define-resource-dependency#dependson // When a conditional resource isn't deployed, Azure Resource Manager automatically removes it from the required dependencies. if (!input.Template.Resources.ContainsKey(dependsOnName)) { if (input.Template.ConditionFalseResources.Contains(dependsOnName)) { res.DependsOn.RemoveAt(i); } else { throw new Exception($"cannot find dependson resource named '{dependsOnName}'"); } } // check duplicated dependsOn if (HasSameName(res.DependsOn, i - 1, dependsOnName)) { res.DependsOn.RemoveAt(i); } } // TODO: check circular dependencies #endregion } return(input); }
public static DeploymentOrchestrationInput Parse(Resource resource, DeploymentContext deploymentContext, ARMFunctions functions, IInfrastructure infrastructure) { var armContext = new Dictionary <string, object>() { { ContextKeys.ARM_CONTEXT, deploymentContext }, { ContextKeys.IS_PREPARE, true } }; using var doc = JsonDocument.Parse(resource.Properties); var rootElement = doc.RootElement; var mode = DeploymentMode.Incremental; if (rootElement.TryGetProperty("mode", out JsonElement _mode)) { if (_mode.GetString().Equals(DeploymentMode.Complete.ToString(), StringComparison.OrdinalIgnoreCase)) { mode = DeploymentMode.Complete; } if (_mode.GetString().Equals(DeploymentMode.OnlyCreation.ToString(), StringComparison.OrdinalIgnoreCase)) { mode = DeploymentMode.OnlyCreation; } } string template = string.Empty; if (rootElement.TryGetProperty("template", out JsonElement _template)) { template = _template.GetRawText(); } TemplateLink templateLink = null; if (rootElement.TryGetProperty("templateLink", out JsonElement _templateLink)) { templateLink = new TemplateLink() { ContentVersion = _templateLink.GetProperty("contentVersion").GetString(), Uri = functions.Evaluate(_templateLink.GetProperty("uri").GetString(), armContext).ToString() }; } // https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#scope-for-expressions-in-nested-templates string parameters = string.Empty; ParametersLink parametersLink = null; if (rootElement.TryGetProperty("expressionEvaluationOptions", out JsonElement _expressionEvaluationOptions) && _expressionEvaluationOptions.GetProperty("scope").GetString().Equals("inner", StringComparison.OrdinalIgnoreCase)) { if (rootElement.TryGetProperty("parameters", out JsonElement _parameters)) { parameters = _parameters.GetRawText(); } if (rootElement.TryGetProperty("parametersLink", out JsonElement _parametersLink)) { parametersLink = new ParametersLink() { ContentVersion = _parametersLink.GetProperty("contentVersion").GetString(), Uri = functions.Evaluate(_parametersLink.GetProperty("uri").GetString(), armContext).ToString() }; } } else { parameters = deploymentContext.Parameters; using MemoryStream ms = new MemoryStream(); using Utf8JsonWriter writer = new Utf8JsonWriter(ms); writer.WriteStartObject(); using var doc1 = JsonDocument.Parse(template); var root1 = doc1.RootElement; foreach (var node in root1.EnumerateObject()) { if (node.Name.Equals("parameters", StringComparison.OrdinalIgnoreCase) || node.Name.Equals("variables", StringComparison.OrdinalIgnoreCase)) { continue; } writer.WritePropertyName(node.Name); node.Value.WriteTo(writer); } if (!string.IsNullOrWhiteSpace(deploymentContext.Template.Parameters)) { using var p = JsonDocument.Parse(deploymentContext.Template.Parameters); writer.WritePropertyName("parameters"); p.RootElement.WriteTo(writer); } if (!string.IsNullOrWhiteSpace(deploymentContext.Template.Variables)) { using var v = JsonDocument.Parse(deploymentContext.Template.Variables); writer.WritePropertyName("variables"); v.RootElement.WriteTo(writer); } writer.WriteEndObject(); writer.Flush(); template = Encoding.UTF8.GetString(ms.ToArray()); } var(groupId, groupType, hierarchyId) = infrastructure.GetGroupInfo(resource.ManagementGroupId, resource.SubscriptionId, resource.ResourceGroup); var deployInput = new DeploymentOrchestrationInput() { RootId = deploymentContext.RootId, DeploymentId = Guid.NewGuid().ToString("N"), ParentId = deploymentContext.GetResourceId(infrastructure), GroupId = groupId, GroupType = groupType, HierarchyId = hierarchyId, CorrelationId = deploymentContext.CorrelationId, SubscriptionId = resource.SubscriptionId, ManagementGroupId = resource.ManagementGroupId, ResourceGroup = resource.ResourceGroup, DeploymentName = resource.Name, Mode = mode, TemplateContent = template, TemplateLink = templateLink, Parameters = parameters, ParametersLink = parametersLink, ApiVersion = resource.ApiVersion, CreateByUserId = deploymentContext.CreateByUserId, LastRunUserId = deploymentContext.LastRunUserId, DependsOn = resource.DependsOn, Extensions = deploymentContext.Extensions, TenantId = deploymentContext.TenantId }; return(Validate(deployInput, functions, infrastructure)); }
public override async Task <TaskResult> RunTask(OrchestrationContext context, string arg) { DeploymentOrchestrationInput input = this.DataConverter.Deserialize <DeploymentOrchestrationInput>(arg); if (string.IsNullOrEmpty(input.RootId)) { input.RootId = input.DeploymentId; } #region validate template // when Template had value, this orchestration call by internal, the template string content already be parsed if (input.Template == null || input.Deployments.Count > 0) { var valid = await context.ScheduleTask <TaskResult>(ValidateTemplateActivity.Name, "1.0", input); if (valid.Code != 200) { return(valid); } input = DataConverter.Deserialize <DeploymentOrchestrationInput>(valid.Content); } else { helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Input = arg, Stage = ProvisioningStage.ValidateTemplate });; } #endregion validate template #region InjectBeforeDeployment if (infrastructure.InjectBeforeDeployment) { var injectBeforeDeploymenteResult = await context.CreateSubOrchestrationInstance <TaskResult>( RequestOrchestration.Name, "1.0", new AsyncRequestActivityInput() { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, ProvisioningStage = ProvisioningStage.InjectBeforeDeployment, DeploymentContext = input, Resource = null }); if (injectBeforeDeploymenteResult.Code != 200) { helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure, null) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Stage = ProvisioningStage.BeforeDeploymentFailed, Result = DataConverter.Serialize(injectBeforeDeploymenteResult) }); return(injectBeforeDeploymenteResult); } } #endregion InjectBeforeDeployment #region Before Deployment if (infrastructure.BeforeDeploymentOrchestration != null) { foreach (var t in infrastructure.BeforeDeploymentOrchestration) { var r = await context.CreateSubOrchestrationInstance <TaskResult>(t.Name, t.Version, input); if (r.Code != 200) { helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure, null) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Stage = ProvisioningStage.BeforeDeploymentFailed, Result = DataConverter.Serialize(r) }); return(r); } input = DataConverter.Deserialize <DeploymentOrchestrationInput>(r.Content); } } #endregion Before Deployment #region DependsOn if (input.DependsOn.Count > 0) { waitHandler = new TaskCompletionSource <string>(); await context.ScheduleTask <TaskResult>(WaitDependsOnActivity.Name, "1.0", new WaitDependsOnActivityInput() { ProvisioningStage = ProvisioningStage.DependsOnWaited, DeploymentContext = input, Resource = null, DependsOn = input.DependsOn }); await waitHandler.Task; var r = DataConverter.Deserialize <TaskResult>(waitHandler.Task.Result); if (r.Code != 200) { helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure, null) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Stage = ProvisioningStage.DependsOnWaitedFailed, Result = DataConverter.Serialize(r) }); return(r); } } #endregion DependsOn bool hasFailResource = false; #region Provisioning resources ConcurrentBag <Task <TaskResult> > tasks = new ConcurrentBag <Task <TaskResult> >(); Dictionary <string, List <Resource> > copyDic = new Dictionary <string, List <Resource> >(); foreach (var resource in input.Template.Resources) { if (resource.FullType == infrastructure.BuiltinServiceTypes.Deployments || resource.Type == Copy.ServiceType) { continue; } if (!string.IsNullOrEmpty(resource.CopyId)) { if (!copyDic.TryGetValue(resource.CopyName, out List <Resource> rList)) { rList = new List <Resource>(); copyDic.Add(resource.CopyName, rList); } rList.Add(resource); continue; } tasks.Add(context.CreateSubOrchestrationInstance <TaskResult>( ResourceOrchestration.Name, "1.0", new ResourceOrchestrationInput() { Resource = resource, Context = input, })); } foreach (var deploy in input.Deployments) { tasks.Add(context.CreateSubOrchestrationInstance <TaskResult>( DeploymentOrchestration.Name, "1.0", DataConverter.Serialize(deploy.Value))); } foreach (var key in copyDic.Keys) { var c = input.Template.Resources[key] as CopyResource; tasks.Add(context.CreateSubOrchestrationInstance <TaskResult>(CopyOrchestration.Name, "1.0", new CopyOrchestrationInput() { Resource = c, Context = input, Resources = copyDic[key] })); } await Task.WhenAll(tasks.ToArray()); foreach (var t in tasks) { if (t.Result.Code != 200) { hasFailResource = true; break; } } #endregion Provisioning resources if (input.Mode == DeploymentMode.Complete) { // TODO: complete mode, delete resource not exist in template } string rtv = null; #region After Deployment if (infrastructure.AfterDeploymentOrhcestration != null) { foreach (var t in infrastructure.AfterDeploymentOrhcestration) { var r = await context.CreateSubOrchestrationInstance <TaskResult>(t.Name, t.Version, input); if (r.Code != 200) { helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure, null) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Stage = ProvisioningStage.AfterDeploymentOrhcestrationFailed, Result = DataConverter.Serialize(r) }); return(r); } input = DataConverter.Deserialize <DeploymentOrchestrationInput>(r.Content); } } #endregion After Deployment if (infrastructure.InjectAfterDeployment) { var injectAfterDeploymenteResult = await context.CreateSubOrchestrationInstance <TaskResult>( RequestOrchestration.Name, "1.0", new AsyncRequestActivityInput() { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, ProvisioningStage = ProvisioningStage.InjectAfterDeployment, DeploymentContext = input, Resource = null }); if (injectAfterDeploymenteResult.Code != 200) { return(injectAfterDeploymenteResult); } } #region get template outputs if (!string.IsNullOrEmpty(input.Template.Outputs)) { try { rtv = this.GetOutputs(input); } catch (Exception ex) { hasFailResource = true; rtv = ex.Message; } } #endregion get template outputs helper.SaveDeploymentOperation(new DeploymentOperation(input, infrastructure, null) { InstanceId = context.OrchestrationInstance.InstanceId, ExecutionId = context.OrchestrationInstance.ExecutionId, Stage = hasFailResource ? ProvisioningStage.Failed : ProvisioningStage.Successed, Result = rtv }); return(new TaskResult() { Code = hasFailResource ? 500 : 200, Content = rtv }); }