private async Task <IReadOnlyList <DomainModel.Package> > FindPackagesAsync(IEnumerable <ITemplateItem> items, ProvisioningAppDBContext context) { var packages = new List <DomainModel.Package>(); foreach (ITemplateFolder folder in items.OfType <ITemplateFolder>()) { WriteLog($"Processing folder {folder.Path}..."); try { DomainModel.Package package = await GetPackageAsync(folder, context); if (package == null) { continue; } if (!String.IsNullOrEmpty(package.DisplayName)) { packages.Add(package); } } catch (Exception ex) { WriteLog($"Error processing folder {folder.Path}: {ex.Message} - {ex.StackTrace}"); } } return(packages); }
private async Task FillAuthorAsync(DomainModel.Package package, string path) { if (!(_sourceProvider is IAuthorProvider ap)) { return; } ITemplateAuthor author = await ap.GetAuthorAsync(path); if (author == null) { return; } package.Author = author.Name; package.AuthorLink = author.Link; }
private async Task FillPackageAsync(DomainModel.Package package, ITemplateFile packageFile) { using (Stream stream = await packageFile.DownloadAsync()) { // Crate a copy of the source stream MemoryStream mem = new MemoryStream(); await stream.CopyToAsync(mem); mem.Position = 0; // Prepare the output hierarchy ProvisioningHierarchy hierarchy = null; if (packageFile.Path.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { // That's an XML Provisioning Template file XDocument xml = XDocument.Load(mem); mem.Position = 0; // Deserialize the stream into a provisioning hierarchy reading any // dependecy with the Azure Blob Storage connector var formatter = XMLPnPSchemaFormatter.GetSpecificFormatter(xml.Root.Name.NamespaceName); var templateLocalFolder = $"{ConfigurationManager.AppSettings["BlobTemplatesProvider:ContainerName"]}/{packageFile.Path.Substring(0, packageFile.Path.LastIndexOf('/'))}"; var provider = new XMLAzureStorageTemplateProvider( ConfigurationManager.AppSettings["BlobTemplatesProvider:ConnectionString"], templateLocalFolder); formatter.Initialize(provider); // Get the full hierarchy hierarchy = ((IProvisioningHierarchyFormatter)formatter).ToProvisioningHierarchy(mem); } else if (packageFile.Path.EndsWith(".pnp", StringComparison.InvariantCultureIgnoreCase)) { // That's a PnP Package file // Get a provider based on the in-memory .PNP Open XML file OpenXMLConnector openXmlConnector = new OpenXMLConnector(mem); XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( openXmlConnector); // Get the .xml provisioning template file name var xmlTemplateFileName = openXmlConnector.Info?.Properties?.TemplateFileName ?? packageFile.Path.Substring(packageFile.Path.LastIndexOf('/') + 1) .ToLower().Replace(".pnp", ".xml"); // Get the full hierarchy hierarchy = provider.GetHierarchy(xmlTemplateFileName); } if (hierarchy != null) { // We se the DisplayName to the DisplayName of the hierarchy if we don't have a siteTitle in the settings.json file if (string.IsNullOrEmpty(package.DisplayName)) { package.DisplayName = hierarchy.DisplayName; } package.ImagePreviewUrl = ChangeUri(packageFile.DownloadUri, hierarchy?.ImagePreviewUrl ?? String.Empty); package.Description = await GetDescriptionAsync(packageFile.GetDirectoryPath()) ?? hierarchy?.Description ?? ""; package.Version = hierarchy?.Version.ToString(); package.PackageProperties = JsonConvert.SerializeObject(hierarchy.Parameters); } } }
private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context) { var items = await _cloneProvider.GetAsync(folder.Path, WriteLog); // Read settings file ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME); if (settingsFile == null) { WriteLog($"Cannot find file {SETTINGS_NAME}"); return(null); } #region Prepare the settings file and its outline var settings = await settingsFile.DownloadAsJsonAsync(new TemplateSettings()); #endregion // Read the package file ITemplateFile packageFile = items.FindFile(settings.packageFile); if (packageFile == null) { WriteLog($"Cannot find file {settings.packageFile}"); return(null); } #region Fix the Preview Image URLs // Fix the URLs in the metadata settings if (settings?.metadata?.displayInfo?.previewImages != null) { int previewImagesCount = settings.metadata.displayInfo.previewImages.Length; for (var n = 0; n < previewImagesCount; n++) { var previewImageUrl = settings.metadata.displayInfo.previewImages[n].url; settings.metadata.displayInfo.previewImages[n].url = ChangeUri(packageFile.DownloadUri, previewImageUrl); } } if (settings?.metadata?.displayInfo?.detailItemCategories != null) { int detailItemCategoriesCount = settings.metadata.displayInfo.detailItemCategories.Length; for (var n = 0; n < detailItemCategoriesCount; n++) { if (settings.metadata.displayInfo.detailItemCategories[n].items != null) { var detailItemCategoryItemsCount = settings.metadata.displayInfo.detailItemCategories[n].items.Length; for (var m = 0; m < detailItemCategoryItemsCount; m++) { var detailItemCategoryItemPreviewImageUrl = settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage; if (!String.IsNullOrEmpty(detailItemCategoryItemPreviewImageUrl)) { settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage = ChangeUri(packageFile.DownloadUri, detailItemCategoryItemPreviewImageUrl); } } } } } #endregion var package = new DomainModel.Package { Id = !string.IsNullOrEmpty(settings.templateId) ? new Guid(settings.templateId) : Guid.NewGuid(), PackageUrl = packageFile.DownloadUri.ToString(), // New properties for Wave2 Promoted = settings.promoted, Preview = settings.preview, TimesApplied = 0, PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata), // New properties for Wave 5 Abstract = settings.@abstract, SortOrder = settings.sortOrder, SortOrderPromoted = settings.sortOrderPromoted, RepositoryRelativeUrl = folder.Path, MatchingSiteBaseTemplateId = settings.matchingSiteBaseTemplateId, ForceNewSite = settings.forceNewSite, Visible = settings.visible, PageTemplateId = settings.metadata?.displayInfo?.pageTemplateId, // New properties for Wave 12 DisplayName = settings.metadata?.displayInfo?.siteTitle, ForceExistingSite = settings.forceExistingSite, }; // Read the instructions.md and the provisioning.md files String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME); String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME); package.Instructions = instructionsContent; package.ProvisionRecap = provisioningContent; // Read any pre-requirement content files var preRequirementContents = items.FindFiles(i => i.Path.ToLower().Contains("prerequirement-") && i.Path.EndsWith(".md", StringComparison.InvariantCultureIgnoreCase)); // Process content for pre-requirements, if any if (preRequirementContents != null) { // Get the existing content pages for the current folder var existingDbContentPages = context.ContentPages .ToList() .Where(cp => cp.Id.StartsWith(folder.Path, StringComparison.InvariantCultureIgnoreCase)) .ToDictionary(cp => cp.Id, StringComparer.OrdinalIgnoreCase); foreach (var prc in preRequirementContents) { // Get the file content String fileContent = await GetHtmlContentAsync(folder.Path, prc.Path.Substring(prc.Path.LastIndexOf('/') + 1)); // Update Content Page, if already exists if (existingDbContentPages.TryGetValue(prc.Path, out ContentPage dbContentPage)) { dbContentPage.Content = fileContent; context.Entry(dbContentPage).State = EntityState.Modified; } else { // Add new Content Page dbContentPage = new ContentPage { Id = prc.Path, Content = fileContent, }; context.Entry(dbContentPage).State = EntityState.Added; } existingDbContentPages.Remove(prc.Path); } // Remove leftover content pages, if any var objectStateManager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager; foreach (var dbContentPage in existingDbContentPages) { context.Entry(dbContentPage.Value).State = EntityState.Deleted; } } // Find the categories to apply if (settings.categories != null && settings.categories.Length > 0) { var dbCategories = settings.categories.Select(c => { Category dbCategory = context.Categories.Find(c); if (dbCategory == null) { WriteLog($"Cannot find category with id {c}"); } return(dbCategory); }).ToArray(); package.Categories.AddRange(dbCategories); } // Find the platforms to apply if (settings.platforms != null && settings.platforms.Length > 0) { var dbPlatforms = settings.platforms.Select(p => { Platform dbPlatform = context.Platforms.Find(p); if (dbPlatform == null) { WriteLog($"Cannot find platform with id {p}"); } return(dbPlatform); }).ToArray(); package.TargetPlatforms.AddRange(dbPlatforms); } // Find then author and fill her/his information await FillAuthorAsync(package, packageFile.Path); // Open the package and set info await FillPackageAsync(package, packageFile); return(package); }
private async Task <CanProvisionResult> CanProvisionInternal(CanProvisionModel model) { var canProvisionResult = new CanProvisionResult(); String provisioningScope = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"]; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; var tokenId = $"{model.TenantId}-{model.UserPrincipalName.ToLower().GetHashCode()}-{provisioningScope}-{provisioningEnvironment}"; var graphAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, "https://graph.microsoft.com/"); // Retrieve the provisioning package from the database and from the Blob Storage var context = dbContext; DomainModel.Package package = null; // Get the package if (ProvisioningAppManager.IsTestingEnvironment) { // Process all packages in the test environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId)); } else { // Process not-preview packages in the production environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(model.PackageId) && p.Preview == false); } if (package != null) { // Retrieve parameters from the package/template definition var packageFileUrl = new Uri(package.PackageUrl); var packageLocalFolder = packageFileUrl.AbsolutePath.Substring(1, packageFileUrl.AbsolutePath.LastIndexOf('/') - 1); var packageFileName = packageFileUrl.AbsolutePath.Substring(packageLocalFolder.Length + 2); ProvisioningHierarchy hierarchy = GetHierarchyFromStorage(packageLocalFolder, packageFileName); // If we have the hierarchy if (hierarchy != null) { var accessTokens = new Dictionary <String, String>(); AuthenticationManager authManager = new AuthenticationManager(); var ptai = new ProvisioningTemplateApplyingInformation(); // Retrieve the SPO URL for the Admin Site var rootSiteUrl = model.SPORootSiteUrl; // Retrieve the SPO Access Token for SPO var spoAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, rootSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Access Token for any further context cloning accessTokens.Add(new Uri(rootSiteUrl).Authority, spoAccessToken); // Define a PnPProvisioningContext scope to share the security context across calls using (var pnpProvisioningContext = new PnPProvisioningContext(async(r, s) => { if (accessTokens.ContainsKey(r)) { // In this scenario we just use the dictionary of access tokens // in fact the overall operation for sure will take less than 1 hour // (in fact, it's a matter of few seconds) return(await Task.FromResult(accessTokens[r])); } else { // Try to get a fresh new Access Token var token = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, $"https://{r}", ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); accessTokens.Add(r, token); return(token); } })) { // If the user is an admin (SPO or Tenant) we run the Tenant level CanProvision rules if (model.UserIsSPOAdmin || model.UserIsTenantAdmin) { // Retrieve the SPO URL for the Admin Site var adminSiteUrl = model.SPORootSiteUrl.Replace(".sharepoint.com", "-admin.sharepoint.com"); // Retrieve the SPO Access Token for the Admin Site var spoAdminAccessToken = await ProvisioningAppManager.AccessTokenProvider.GetAccessTokenAsync( tokenId, adminSiteUrl, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], ConfigurationManager.AppSettings["ida:AppUrl"]); // Store the SPO Admin Access Token for any further context cloning accessTokens.Add(new Uri(adminSiteUrl).Authority, spoAdminAccessToken); // Connect to SPO Admin Site and evaluate the CanProvision rules for the hierarchy using (var tenantContext = authManager.GetAzureADAccessTokenAuthenticatedContext(adminSiteUrl, spoAdminAccessToken)) { using (var pnpTenantContext = PnPClientContext.ConvertFrom(tenantContext)) { // Creat the Tenant object for the current SPO Admin Site context TenantAdmin.Tenant tenant = new TenantAdmin.Tenant(pnpTenantContext); // Run the CanProvision rules against the current tenant canProvisionResult = CanProvisionRulesManager.CanProvision(tenant, hierarchy, null, ptai); } } } else { // Otherwise we run the Site level CanProvision rules // Connect to SPO Root Site and evaluate the CanProvision rules for the hierarchy using (var clientContext = authManager.GetAzureADAccessTokenAuthenticatedContext(rootSiteUrl, spoAccessToken)) { using (var pnpContext = PnPClientContext.ConvertFrom(clientContext)) { // Run the CanProvision rules against the root site canProvisionResult = CanProvisionRulesManager.CanProvision(pnpContext.Web, hierarchy.Templates[0], ptai); } } } } } } else { throw new ApplicationException("Invalid request, the requested package/template is not available!"); } return(canProvisionResult); }
public async Task <ProvisionContentPackResponse> ProvisionContentPack(ProvisionContentPackRequest provisionRequest) { var provisionResponse = new ProvisionContentPackResponse(); // If the input paramenters are missing, raise a BadRequest response if (provisionRequest == null) { ThrowEmptyRequest(); } // If the TenantId input argument is missing, raise a BadRequest response if (String.IsNullOrEmpty(provisionRequest.TenantId)) { ThrowMissingArgument("TenantId"); } // If the UserPrincipalName input argument is missing, raise a BadRequest response if (String.IsNullOrEmpty(provisionRequest.UserPrincipalName)) { ThrowMissingArgument("UserPrincipalName"); } // If the PackageIds input argument is missing, raise a BadRequest response if (provisionRequest.Packages == null || provisionRequest.Packages.Count == 0) { ThrowMissingArgument("Packages"); } if (provisionRequest != null && !String.IsNullOrEmpty(provisionRequest.TenantId) && !String.IsNullOrEmpty(provisionRequest.UserPrincipalName) && provisionRequest.Packages != null && provisionRequest.Packages.Count > 0) { try { // Process the AuthorizationCode request String provisioningScope = ConfigurationManager.AppSettings["SPPA:ProvisioningScope"]; String provisioningEnvironment = ConfigurationManager.AppSettings["SPPA:ProvisioningEnvironment"]; var tokenId = $"{provisionRequest.TenantId}-{provisionRequest.UserPrincipalName.ToLower().GetHashCode()}-{provisioningScope}-{provisioningEnvironment}"; try { // Retrieve the refresh token and store it in the KeyVault await ProvisioningAppManager.AccessTokenProvider.SetupSecurityFromAuthorizationCodeAsync( tokenId, provisionRequest.AuthorizationCode, provisionRequest.TenantId, ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"], "https://graph.microsoft.com/", provisionRequest.RedirectUri); } catch (Exception ex) { // In case of any authorization exception, raise an Unauthorized exception ThrowUnauthorized(ex); } // Validate the Package IDs var context = dbContext; DomainModel.Package package = null; // Get the first item to provision var item = provisionRequest.Packages.First(); // And remove it from the whole list of items provisionRequest.Packages.RemoveAt(0); var childrenItems = provisionRequest.Packages; // Get the package if (ProvisioningAppManager.IsTestingEnvironment) { // Process all packages in the test environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(item.PackageId)); } else { // Process not-preview packages in the production environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(item.PackageId) && p.Preview == false); } // If the package is not valid if (package == null) { // Throw an exception accordingly throw new ArgumentException("Invalid Package Id!"); } // First of all, validate the provisioning request for pre-requirements provisionResponse.CanProvisionResult = await CanProvisionInternal( new CanProvisionModel { PackageId = item.PackageId, TenantId = provisionRequest.TenantId, UserPrincipalName = provisionRequest.UserPrincipalName, SPORootSiteUrl = provisionRequest.SPORootSiteUrl, UserIsSPOAdmin = true, // We assume that the request comes from an Admin UserIsTenantAdmin = true, // We assume that the request comes from an Admin }); // If the package can be provisioned onto the target if (provisionResponse.CanProvisionResult.CanProvision) { // Prepare the provisioning request var request = new ProvisioningActionModel(); request.ActionType = ActionType.Tenant; // Do we want to support site/tenant or just one? request.ApplyCustomTheme = false; request.ApplyTheme = false; // Do we need to apply any special theme? request.CorrelationId = Guid.NewGuid(); request.CustomLogo = null; request.DisplayName = $"Provision Content Pack {item.PackageId}"; request.PackageId = item.PackageId; request.TargetSiteAlreadyExists = false; // Do we want to check this? request.TargetSiteBaseTemplateId = null; request.TenantId = provisionRequest.TenantId; request.UserIsSPOAdmin = true; // We don't use this in the job request.UserIsTenantAdmin = true; // We don't use this in the job request.UserPrincipalName = provisionRequest.UserPrincipalName.ToLower(); request.NotificationEmail = provisionRequest.NotificationEmail; request.PackageProperties = item.Parameters; request.ChildrenItems = childrenItems; request.Webhooks = provisionRequest.Webhooks; // Enqueue the provisioning request await ProvisioningAppManager.EnqueueProvisioningRequest(request); } // Set the status of the provisioning request provisionResponse.ProvisioningStarted = true; } catch (HttpResponseException) { throw; } catch (Exception ex) { // In case of any other exception, raise an InternalServerError exception ThrowInternalServerError(ex); } } // Return to the requested URL return(provisionResponse); }
private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context) { var items = await _cloneProvider.GetAsync(folder.Path, WriteLog); // Read settings file ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME); if (settingsFile == null) { WriteLog($"Cannot find file {SETTINGS_NAME}"); return(null); } var settings = await settingsFile.DownloadAsJsonAsync(new { @abstract = "", sortOrder = 0, categories = new string[0], packageFile = "", promoted = false, sortOrderPromoted = 0, preview = false, metadata = new { properties = new[] { new { name = "", caption = "", description = "", editor = "", editorSettings = "", } } } }); // Read the package file ITemplateFile packageFile = items.FindFile(settings.packageFile); if (packageFile == null) { WriteLog($"Cannot find file {settings.packageFile}"); return(null); } var package = new DomainModel.Package { Id = Guid.NewGuid(), PackageUrl = packageFile.DownloadUri.ToString(), // New properties for Wave2 Promoted = settings.promoted, Preview = settings.preview, TimesApplied = 0, PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata), // New properties for Wave 5 Abstract = settings.@abstract, SortOrder = settings.sortOrder, SortOrderPromoted = settings.sortOrderPromoted, RepositoryRelativeUrl = folder.Path, }; // Read the instructions.md and the provisioning.md files String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME); String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME); package.Instructions = instructionsContent; package.ProvisionRecap = provisioningContent; // Find the categories to apply var dbCategories = settings.categories.Select(c => { Category dbCategory = context.Categories.Find(c); if (dbCategory == null) { WriteLog($"Cannot find category with id {c}"); } return(dbCategory); }).ToArray(); package.Categories.AddRange(dbCategories); // Find then author and fill his informations await FillAuthorAsync(package, packageFile.Path); // Open the package and set info await FillPackageAsync(package, packageFile); return(package); }
private void LoadPackageDataIntoModel(string packageId, ProvisioningActionModel model) { var context = GetDataContext(); DomainModel.Package package = null; // Get the package if (Boolean.Parse(ConfigurationManager.AppSettings["TestEnvironment"])) { // Process all packages in the test environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(packageId)); } else { // Process not-preview packages in the production environment package = context.Packages.FirstOrDefault(p => p.Id == new Guid(packageId) && p.Preview == false); } if (package != null) { if ((package.PackageType == PackageType.Tenant && !this.Request.Url.AbsolutePath.Contains("/tenant/")) || (package.PackageType == PackageType.SiteCollection && !this.Request.Url.AbsolutePath.Contains("/site/"))) { throw new ApplicationException("Invalid request, the requested package/template is not valid for the current request!"); } model.DisplayName = package.DisplayName; model.ActionType = package.PackageType == PackageType.SiteCollection ? ActionType.Site : ActionType.Tenant; // Configure content for instructions model.Instructions = package.Instructions; // If we don't have specific instructions if (model.Instructions == null) { // Get the default instructions var instructionsPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/GenericInstructions.md"); if (instructionsPage != null) { model.Instructions = instructionsPage.Content; } } // Configure content for provisioning recap model.ProvisionRecap = package.ProvisionRecap; // If we don't have specific provisioning recap if (String.IsNullOrEmpty(model.ProvisionRecap)) { // Get the default provisioning recap var provisionRecapPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/GenericProvisioning.md"); if (provisionRecapPage != null) { model.ProvisionRecap = provisionRecapPage.Content; } } // Retrieve parameters from the package/template definition var packageFileUrl = new Uri(package.PackageUrl); var packageLocalFolder = packageFileUrl.AbsolutePath.Substring(1, packageFileUrl.AbsolutePath.LastIndexOf('/') - 1); var packageFileName = packageFileUrl.AbsolutePath.Substring(packageLocalFolder.Length + 2); ProvisioningHierarchy hierarchy = GetHierarchyFromStorage(packageLocalFolder, packageFileName); // If we have the hierarchy and its parameters if (hierarchy != null && hierarchy.Parameters != null) { // Use them model.PackageProperties = hierarchy.Parameters; } else { // Otherwise, use an empty list of parameters model.PackageProperties = new Dictionary <string, string>(); } // Configure the metadata properties var metadata = new { properties = new[] { new { name = "", caption = "", description = "", editor = "", editorSettings = "", } } }; var metadataProperties = JsonConvert.DeserializeAnonymousType(package.PropertiesMetadata, metadata); model.MetadataProperties = metadataProperties.properties.ToDictionary( i => i.name, i => new MetadataProperty { Name = i.name, Caption = i.caption, Description = i.description, Editor = i.editor, EditorSettings = i.editorSettings }); model.MetadataPropertiesJson = JsonConvert.SerializeObject(model.MetadataProperties); // Get the service description content var contentPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/ProvisioningIntro.md"); if (contentPage != null) { model.ProvisionDescription = contentPage.Content; } // Get the pre-reqs Header content var preReqsHeaderPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/CanProvisionPreReqsHeader.md"); if (preReqsHeaderPage != null) { model.MissingPreReqsHeader = preReqsHeaderPage.Content; } // Get the pre-reqs Footer content var preReqsFooterPage = context.ContentPages.FirstOrDefault(cp => cp.Id == "system/pages/CanProvisionPreReqsFooter.md"); if (preReqsFooterPage != null) { model.MissingPreReqsFooter = preReqsFooterPage.Content; } } else { throw new ApplicationException("Invalid request, the requested package/template is not available!"); } }
private static void LogReporting(ProvisioningActionModel action, string provisioningEnvironment, DateTime startProvisioning, DomainModel.Package package, Int32 outcome, String details = null) { // Prepare the reporting event var provisioningEvent = new { EventId = action.CorrelationId, EventStartDateTime = startProvisioning, EventEndDateTime = DateTime.Now, EventOutcome = outcome, EventDetails = details, EventFromProduction = provisioningEnvironment.ToUpper() == "PROD" ? 1 : 0, TemplateId = action.PackageId, TemplateDisplayName = package?.DisplayName, }; try { // Make the Azure Function call for reporting HttpHelper.MakePostRequest(ConfigurationManager.AppSettings["SPPA:ReportingFunctionUrl"], provisioningEvent, "application/json", null); } catch { // Intentionally ignore any reporting issue } }
private async Task <DomainModel.Package> GetPackageAsync(ITemplateFolder folder, ProvisioningAppDBContext context) { var items = await _cloneProvider.GetAsync(folder.Path, WriteLog); // Read settings file ITemplateFile settingsFile = items.OfType <ITemplateFile>().FindFile(SETTINGS_NAME); if (settingsFile == null) { WriteLog($"Cannot find file {SETTINGS_NAME}"); return(null); } #region Prepare the settings file and its outline var settings = await settingsFile.DownloadAsJsonAsync(new TemplateSettings()); #endregion // Read the package file ITemplateFile packageFile = items.FindFile(settings.packageFile); if (packageFile == null) { WriteLog($"Cannot find file {settings.packageFile}"); return(null); } #region Fix the Preview Image URLs // Fix the URLs in the metadata settings if (settings?.metadata?.displayInfo?.previewImages != null) { int previewImagesCount = settings.metadata.displayInfo.previewImages.Length; for (var n = 0; n < previewImagesCount; n++) { var previewImageUrl = settings.metadata.displayInfo.previewImages[n].url; settings.metadata.displayInfo.previewImages[n].url = ChangeUri(packageFile.DownloadUri, previewImageUrl); } } if (settings?.metadata?.displayInfo?.detailItemCategories != null) { int detailItemCategoriesCount = settings.metadata.displayInfo.detailItemCategories.Length; for (var n = 0; n < detailItemCategoriesCount; n++) { if (settings.metadata.displayInfo.detailItemCategories[n].items != null) { var detailItemCategoryItemsCount = settings.metadata.displayInfo.detailItemCategories[n].items.Length; for (var m = 0; m < detailItemCategoryItemsCount; m++) { var detailItemCategoryItemPreviewImageUrl = settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage; if (!String.IsNullOrEmpty(detailItemCategoryItemPreviewImageUrl)) { settings.metadata.displayInfo.detailItemCategories[n].items[m].previewImage = ChangeUri(packageFile.DownloadUri, detailItemCategoryItemPreviewImageUrl); } } } } } #endregion var package = new DomainModel.Package { Id = Guid.NewGuid(), PackageUrl = packageFile.DownloadUri.ToString(), // New properties for Wave2 Promoted = settings.promoted, Preview = settings.preview, TimesApplied = 0, PropertiesMetadata = JsonConvert.SerializeObject(settings.metadata), // New properties for Wave 5 Abstract = settings.@abstract, SortOrder = settings.sortOrder, SortOrderPromoted = settings.sortOrderPromoted, RepositoryRelativeUrl = folder.Path, MatchingSiteBaseTemplateId = settings.matchingSiteBaseTemplateId, ForceNewSite = settings.forceNewSite, Visible = settings.visible, PageTemplateId = settings.metadata?.displayInfo?.pageTemplateId, }; // Read the instructions.md and the provisioning.md files String instructionsContent = await GetHtmlContentAsync(folder.Path, INSTRUCTIONS_NAME); String provisioningContent = await GetHtmlContentAsync(folder.Path, PROVISIONING_NAME); package.Instructions = instructionsContent; package.ProvisionRecap = provisioningContent; // Find the categories to apply if (settings.categories != null && settings.categories.Length > 0) { var dbCategories = settings.categories.Select(c => { Category dbCategory = context.Categories.Find(c); if (dbCategory == null) { WriteLog($"Cannot find category with id {c}"); } return(dbCategory); }).ToArray(); package.Categories.AddRange(dbCategories); } // Find the platforms to apply if (settings.platforms != null && settings.platforms.Length > 0) { var dbPlatforms = settings.platforms.Select(p => { Platform dbPlatform = context.Platforms.Find(p); if (dbPlatform == null) { WriteLog($"Cannot find platform with id {p}"); } return(dbPlatform); }).ToArray(); package.TargetPlatforms.AddRange(dbPlatforms); } // Find then author and fill his informations await FillAuthorAsync(package, packageFile.Path); // Open the package and set info await FillPackageAsync(package, packageFile); return(package); }