/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if (onlineStatusLog == null) { return; //No logger? nothing to do } try { if (string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } var response = webException.Response; var responseText = GetWebResponseAsText(response); response.Close(); if (responseText == null) { responseText = ""; } onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Query for each of the specified groups and pull down their members list from Azure /// </summary> /// <param name="azureSessionToken"></param> /// <param name="groupsToRolesSyncList"></param> private async Task GeneratGroupsMembersListFromAzureGroups(GraphServiceClient azureGraph, IEnumerable <ProvisionConfigExternalDirectorySync.SynchronizeGroupToGroup> groupsToGroupsSyncList) { foreach (var groupToRetrieve in groupsToGroupsSyncList) { _statusLogs.AddStatus("Azure getting group: " + groupToRetrieve.SourceGroupName); var thisGroupAsSet = await azureGraph.Groups.Request().Select(x => new { x.Id, x.DisplayName }).Filter("displayName eq '" + groupToRetrieve.SourceGroupName + "'").GetAsync(); //If the group does not exist in Azure, not the error condition if (thisGroupAsSet.Count < 1) { _statusLogs.AddError("Azure AD group does not exist" + groupToRetrieve.SourceGroupName); throw new Exception("814-722: Azure AD group does not exist" + groupToRetrieve.SourceGroupName); } var thiGroupId = thisGroupAsSet.CurrentPage[0].Id; //https://docs.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http //UNDONE: Filter down to just USERS and SUB-GROUPS var thisGroupsMembers = await azureGraph.Groups[thiGroupId].Members.Request().GetAsync(); //TEST: Test paging by forcing small page size //var thisGroupsMembers = await azureGraph.Groups[thiGroupId].Members.Request().Top(2).GetAsync(); //Get all the users in the group and sub-groups await AzureRecurseGroupsGenerateGroupMembersList(azureGraph, thisGroupsMembers, groupToRetrieve); } }
/// <summary> /// Adds a keyed item to the dictionary /// </summary> /// <param name="key"></param> /// <param name="item"></param> /// <param name="statusLogger">If non-NULL; then trap and record errors, If NULL the error will get thrown upward</param> public void AddItem(string key, T item, TaskStatusLogs statusLogger = null) { //There are cases where building the dictionary may fail, such as if the incoming data has //duplicate ID entries. If we have a status logger, we want to log the error and then //continue onward try { _dictionary.Add(key, item); } catch (Exception exAddDictionaryItem) { //If we have an error logger, then log the error if (statusLogger != null) { string itemDescription = "null item"; if (item != null) { itemDescription = item.ToString(); } statusLogger.AddError("Error building lookup dictionary. Item: " + itemDescription + ", " + exAddDictionaryItem.ToString()); } else //Otherwise thrown the error upward { throw; } } }
/// <summary> /// If we have Project Mapping information, generate a project based path for the download /// </summary> /// <param name="basePath"></param> /// <param name="projectList"></param> /// <param name="projectId"></param> /// <returns></returns> public static string EnsureProjectBasedPath(string basePath, IProjectsList projectList, IHasProjectId project, TaskStatusLogs statusLog) { //If we have no project list to do lookups in then just return the base path if (projectList == null) { return(basePath); } //Look up the project name var projWithId = projectList.FindProjectWithId(project.ProjectId); if (projWithId == null) { statusLog.AddError("Project not found with id " + project.ProjectId); return(basePath); } //Turn the project name into a directory name var safeDirectoryName = GenerateWindowsSafeFilename(projWithId.Name); var pathWithProject = Path.Combine(basePath, safeDirectoryName); //If needed, create the directory if (!Directory.Exists(pathWithProject)) { Directory.CreateDirectory(pathWithProject); } return(pathWithProject); }
/// <summary> /// If we have Project Mapping information, generate a project based path for the download /// </summary> /// <param name="basePath"></param> /// <param name="projectList"></param> /// <param name="projectId"></param> /// <returns></returns> public static string EnsureProjectBasedPath(string basePath, IProjectsList projectList, IHasProjectId project, TaskStatusLogs statusLog) { //If we have no project list to do lookups in then just return the base path if (projectList == null) return basePath; //Look up the project name var projWithId = projectList.FindProjectWithId(project.ProjectId); if(projWithId == null) { statusLog.AddError("Project not found with id " + project.ProjectId); return basePath; } //Turn the project name into a directory name var safeDirectoryName = GenerateWindowsSafeFilename(projWithId.Name); var pathWithProject = Path.Combine(basePath, safeDirectoryName); //If needed, create the directory if(!Directory.Exists(pathWithProject)) { Directory.CreateDirectory(pathWithProject); } return pathWithProject; }
/// <summary> /// Provision a site based on a file based manifest /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnProvisionSiteFromManifestFile_Click(object sender, EventArgs e) { var statusLogs = new TaskStatusLogs(); statusLogs.AddStatus("Starting..."); UpdateStatusText(statusLogs, true); string pathSecrets = txtPathToSecrets.Text; if (!File.Exists(pathSecrets)) { MessageBox.Show("Secrets file does not exist at specified path (" + pathSecrets + ")"); return; } string pathProvisionPlan = txtPathToFileProvisioningConfig.Text; if (!File.Exists(pathProvisionPlan)) { MessageBox.Show("Config file does not exist at specified path (" + pathProvisionPlan + ")"); return; } string pathOutput = Path.Combine( Path.GetDirectoryName(pathProvisionPlan), "out"); //Show the user a command line that they can use to run this same work GenerateProvisioningCommandLine( CommandLineParser.Command_ProvisionFromFileManifest, pathSecrets, pathProvisionPlan, pathOutput); //Run the work try { ProvisionFromFileManifest(statusLogs, pathSecrets, pathProvisionPlan, pathOutput); } catch (Exception ex) { statusLogs.AddError("Untrapped error: " + ex.Message); } //Update the status text to it's final state UpdateStatusText(statusLogs, true); //Open the file explorer to the output directory if (Directory.Exists(pathOutput)) { System.Diagnostics.Process.Start(pathOutput); } }
/// <summary> /// Remaps the 'repository-location' node of the Data Source XML /// </summary> /// <param name="xnodeRepository"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleWorkbooksRepositoryNode(XmlDocument xmlDoc, XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, bool ignoreIfMissing, TaskStatusLogs statusLog) { var siteId = serverMapInfo.SiteId; //Get the XML sub mode we need var xnodeRepository = xNodeDatasource.SelectSingleNode("repository-location"); if (xnodeRepository == null) { if (ignoreIfMissing) { return; } statusLog.AddError("Workbook remapper, no workbook 'repository-location' node found"); return; } /////////////////////////////////////////////////////////////////////////////////////// helper_SetRespositorySite(xmlDoc, xnodeRepository, serverMapInfo); /////////////////////////////////////////////////////////////////////////////////////// var attrPath = xnodeRepository.Attributes["path"]; if (attrPath != null) { //Is there a site specified if (!string.IsNullOrWhiteSpace(siteId)) { attrPath.Value = "/t/" + siteId + "/workbooks"; } else //Default site { attrPath.Value = "/workbooks"; } } else { statusLog.AddError("Workbook remapper 'path' attribute not found"); } }
/// <summary> /// There should only be one *.twb file in the unzipped set of files /// </summary> /// <returns></returns> private string GetPathToUnzippedTwb() { var twbFiles = Directory.EnumerateFiles(this.UnzipDirectory, "*.twb"); foreach (var twb in twbFiles) { return(twb); } _statusLog.AddError("Twb editor; no twb file found"); return(null); }
/// <summary> /// Sign us out /// </summary> /// <param name="serverUrls"></param> public void SignOut(TableauServerUrls serverUrls) { if (!_isSignedIn) { StatusLog.AddError("Session not signed in. Sign out aborted"); } //Perform the sign out var signOut = new TableauServerSignOut(serverUrls, this); signOut.ExecuteRequest(); _isSignedIn = false; }
/// <summary> /// Creates a TWB file that points to the CSV file /// </summary> /// <param name="pathReportCsv"></param> private void Execute_GenerateSiteInventoryFile_Twb(string pathReportCsv) { _statusLog.AddStatusHeader("Generate site inventory TWB"); try { //Calculate the name/path for the output TWB. It will match the name/path of the CSV file string pathTwbOut = PathHelper.GetInventoryTwbPathMatchingCsvPath(pathReportCsv); this.StatusLog.AddStatusHeader("Generating Tableau Workbook " + pathTwbOut); var twbGenerateFromTemplate = new TwbReplaceCSVReference( PathHelper.GetInventoryTwbTemplatePath(), //*.twb we are using as our template pathTwbOut, //Output *.twb we are generating "siteInventory", //Datasource name in tempalte workbook pathReportCsv, //CSV file we want to associate with the datasource above _statusLog); //Transform the template into the output file bool successRemapping = twbGenerateFromTemplate.Execute(); if (!successRemapping) { this.StatusLog.AddError("Error generating site inventory TWB. No data source could be found to remap"); } //Store it as our output if (File.Exists(pathTwbOut)) { _pathGeneratedSiteInventoryReportTwb = pathTwbOut; } } catch (Exception ex) { StatusLog.AddError("Error generating Twb file: " + ex.ToString()); } }
/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if (onlineStatusLog == null) { return; //No logger? nothing to do } try { if (string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } string responseText = ""; //NOTE: In some cases (e.g. time-out) the response may be NULL var response = webException.Response; if (response != null) { responseText = GetWebResponseAsText(response); response.Close(); } //Cannonicalize a blank result... if (string.IsNullOrEmpty(responseText)) { responseText = ""; } onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("811-830: Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Finds and changes a datasource reference inside a Workbook. Changes the CSV file the data source points to /// </summary> /// <param name="xmlDoc"></param> /// <param name="datasourceName"></param> /// <param name="pathToTargetCsv"></param> /// <param name="statusLog"></param> private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string datasourceName, string pathToTargetCsv, TaskStatusLogs statusLog) { int replaceItemCount = 0; string newCsvDirectory = Path.GetDirectoryName(_datasourceNewCsvPath); string newCsvFileName = Path.GetFileName(_datasourceNewCsvPath); string newDatasourceRelationName = Path.GetFileNameWithoutExtension(newCsvFileName) + "#csv"; string newDatasourceRelationTable = "[" + newDatasourceRelationName + "]"; string seekDatasourceCaption = _datasourceName; var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); if (xDataSources != null) { //Look through the data sources foreach (XmlNode xnodeDatasource in xDataSources) { //If the data source is matching the caption we are looking for if (XmlHelper.SafeParseXmlAttribute(xnodeDatasource, "caption", "") == seekDatasourceCaption) { var xnodeConnection = xnodeDatasource.SelectSingleNode("connection"); //It should be 'textscan', it would be unexpected if it were not if (XmlHelper.SafeParseXmlAttribute(xnodeConnection, "class", "") == "textscan") { //Point to the new directory/path xnodeConnection.Attributes["directory"].Value = newCsvDirectory; xnodeConnection.Attributes["filename"].Value = newCsvFileName; //And it's got a Relation we need to update var xNodeRelation = xnodeConnection.SelectSingleNode("relation"); xNodeRelation.Attributes["name"].Value = newDatasourceRelationName; xNodeRelation.Attributes["table"].Value = newDatasourceRelationTable; replaceItemCount++; } else { _statusLog.AddError("Data source remap error. Expected data source to be 'textscan'"); } } //end if } //end foreach } //end if return(replaceItemCount > 0); }
/// <summary> /// Called to run us in commandn line mode /// </summary> /// <param name="commandLine"></param> internal void RunStartupCommandLine_Inner(TaskStatusLogs statusLogs) { statusLogs.AddStatusHeader("Processing command line"); string pathProvisionPlan = AppSettings.CommandLine_PathProvisionPlan; string pathSecrets = AppSettings.CommandLine_PathSecrets; string pathOutput = AppSettings.CommandLine_PathOutput; //If an output directory was not specified, then output into an "out" subdirectory in the directory where the provision plan is if (string.IsNullOrWhiteSpace(pathOutput)) { pathOutput = Path.Combine( Path.GetDirectoryName(pathProvisionPlan), "out"); } //==================================================================================== //Based on the command specified, run the specified task //==================================================================================== switch (AppSettings.CommandLine_Command) { case CommandLineParser.Command_ProvisionFromAzure: //Update the paths in the UI so the user can see & re-run them if they want txtPathToSecrets.Text = pathSecrets; txtPathToAzureAdProvisioningConfig.Text = pathProvisionPlan; //Run the work... ProvisionFromAzureAd(statusLogs, pathSecrets, pathProvisionPlan, pathOutput); break; case CommandLineParser.Command_ProvisionFromFileManifest: //Update the paths in the UI so the user can see & re-run them if they want txtPathToSecrets.Text = pathSecrets; txtPathToFileProvisioningConfig.Text = pathProvisionPlan; //Run the work... ProvisionFromFileManifest(statusLogs, pathSecrets, pathProvisionPlan, pathOutput); break; default: statusLogs.AddError("1101-432: Unknown command: " + AppSettings.CommandLine_Command); break; } }
/// <summary> /// Remaps global references in the workbook that refer to the site/server /// </summary> /// <param name="xmlDoc"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapWorkbookGlobalReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xnodeWorkbook = xmlDoc.SelectSingleNode("//workbook"); if(xnodeWorkbook == null) { statusLog.AddError("Workbook remapper, 'workbook' node not found"); return; } //See if there is an an XML base node var attrXmlBase = xnodeWorkbook.Attributes["xml:base"]; if(attrXmlBase != null) { attrXmlBase.Value = serverMapInfo.ServerNameWithProtocol; } //We may also have a repository node RemapSingleWorkbooksRepositoryNode(xmlDoc, xnodeWorkbook, serverMapInfo, true, statusLog); }
/// <summary> /// Remaps global references in the workbook that refer to the site/server /// </summary> /// <param name="xmlDoc"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapWorkbookGlobalReferences(XmlDocument xmlDoc, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { var xnodeWorkbook = xmlDoc.SelectSingleNode("//workbook"); if (xnodeWorkbook == null) { statusLog.AddError("Workbook remapper, 'workbook' node not found"); return; } //See if there is an an XML base node var attrXmlBase = xnodeWorkbook.Attributes["xml:base"]; if (attrXmlBase != null) { attrXmlBase.Value = serverMapInfo.ServerNameWithProtocol; } //We may also have a repository node RemapSingleWorkbooksRepositoryNode(xmlDoc, xnodeWorkbook, serverMapInfo, true, statusLog); }
/// <summary> /// Remaps the 'repository-location' node of the Data Source XML /// </summary> /// <param name="xnodeRepository"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleWorkbooksRepositoryNode(XmlDocument xmlDoc, XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, bool ignoreIfMissing, TaskStatusLogs statusLog) { var siteId = serverMapInfo.SiteId; //Get the XML sub mode we need var xnodeRepository = xNodeDatasource.SelectSingleNode("repository-location"); if (xnodeRepository == null) { if (ignoreIfMissing) return; statusLog.AddError("Workbook remapper, no workbook 'repository-location' node found"); return; } /////////////////////////////////////////////////////////////////////////////////////// helper_SetRespositorySite(xmlDoc, xnodeRepository, serverMapInfo); /////////////////////////////////////////////////////////////////////////////////////// var attrPath = xnodeRepository.Attributes["path"]; if (attrPath != null) { //Is there a site specified if (!string.IsNullOrWhiteSpace(siteId)) { attrPath.Value = "/t/" + siteId + "/workbooks"; } else //Default site { attrPath.Value = "/workbooks"; } } else { statusLog.AddError("Workbook remapper 'path' attribute not found"); } }
/// <summary> /// Finds and changes a datasource reference inside a Workbook. Changes the CSV file the data source points to /// </summary> /// <param name="xmlDoc"></param> /// <param name="oldDatasourceFilename">Filenane (without path) of the datasource we want to replace. Case insensitive</param> /// <param name="pathToTargetCsv"></param> /// <param name="statusLog"></param> private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string oldDatasourceFilename, string pathToTargetCsv, TaskStatusLogs statusLog) { int replaceItemCount = 0; string newCsvDirectory = Path.GetDirectoryName(_datasourceNewCsvPath); string newCsvFileName = Path.GetFileName(_datasourceNewCsvPath); string newDatasourceRelationName = Path.GetFileNameWithoutExtension(newCsvFileName) + "#csv"; string newDatasourceRelationTable = "[" + newDatasourceRelationName + "]"; var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); if (xDataSources != null) { //Look through the data sources foreach (XmlNode xnodeDatasource in xDataSources) { var xConnections = xnodeDatasource.SelectNodes(".//connection"); if (xConnections != null) { foreach (XmlNode xThisConnection in xConnections) { //If its a 'textscan' (CSV) and the file name matches the expected type, then this is a datasource's connection we want to remap //to point to a new CSV file if ((XmlHelper.SafeParseXmlAttribute(xThisConnection, "class", "") == "textscan") && (string.Compare(XmlHelper.SafeParseXmlAttribute(xThisConnection, "filename", ""), oldDatasourceFilename, true) == 0)) { //Find any relation nodes beneath the datasource //Newer version of the document model put the textscan connection inside a federated data source //to deal with that, we need to look upward from the connection and adjacent in the DOM to find the correct //node to replace. This is done by looking at child nodes in the datasource var xNodeAllConnectionRelations = xnodeDatasource.SelectNodes(".//relation"); XmlNode xNodeRelation = null; if (xNodeAllConnectionRelations != null) { if (xNodeAllConnectionRelations.Count == 1) { xNodeRelation = xNodeAllConnectionRelations[0]; } else { statusLog.AddError("CSV replacement. Expected 1 Relation in data source definition, actual " + xNodeAllConnectionRelations.Count.ToString()); } } //Only if we have all the elements need to replace, should we go ahead with the replacement if ((xNodeRelation != null) && (xThisConnection != null)) { //Point to the new directory/path xThisConnection.Attributes["directory"].Value = newCsvDirectory; xThisConnection.Attributes["filename"].Value = newCsvFileName; xNodeRelation.Attributes["name"].Value = newDatasourceRelationName; xNodeRelation.Attributes["table"].Value = newDatasourceRelationTable; replaceItemCount++; } } } //end: foreach xThisConnection } } //end foreach } //end if return(replaceItemCount > 0); }
/// <summary> /// Executes the authentication request against the Tableau server /// </summary> public bool ExecuteRequest() { var webRequest = WebRequest.Create(_onlineUrls.UrlLogin); string bodyText = xmlLogIn; bodyText = bodyText.Replace("{{iwsUserName}}", _userName); bodyText = bodyText.Replace("{{iwsPassword}}", _password); bodyText = bodyText.Replace("{{iwsSiteUrl}}", _siteUrlSegment); AssertTemplateFullyReplaced(bodyText); //=============================================================================================== //Make the sign in request, trap and note, and rethrow any errors //=============================================================================================== try { SendRequestContents(webRequest, bodyText); } catch (Exception exSendRequest) { StatusLog.AddError("Error sending sign in request: " + exSendRequest); throw; } //=============================================================================================== //Get the web response, trap and note, and rethrow any errors //=============================================================================================== WebResponse response; try { response = webRequest.GetResponse(); } catch (Exception exResponse) { StatusLog.AddError("Error returned from sign in response: " + exResponse); throw; } var allHeaders = response.Headers; var cookies = allHeaders["Set-Cookie"]; _logInCookies = cookies; //Keep any cookies //=============================================================================================== //Get the web response's XML payload, trap and note, and rethrow any errors //=============================================================================================== XmlDocument xmlDoc; try { xmlDoc = GetWebResponseAsXml(response); } catch (Exception exSignInResponse) { StatusLog.AddError("Error returned from sign in xml response: " + exSignInResponse); throw; } var nsManager = XmlHelper.CreateTableauXmlNamespaceManager("iwsOnline"); var credentialNode = xmlDoc.SelectSingleNode("//iwsOnline:credentials", nsManager); var siteNode = xmlDoc.SelectSingleNode("//iwsOnline:site", nsManager); _logInSiteId = siteNode.Attributes["id"].Value; _logInToken = credentialNode.Attributes["token"].Value; //Adding the UserId to the log-in return was a feature that was added late in the product cycle. //For this reason this code is going to defensively look to see if hte attribute is there var userNode = xmlDoc.SelectSingleNode("//iwsOnline:user", nsManager); string userId = null; if (userNode != null) { var userIdAttribute = userNode.Attributes?["id"]; if (userIdAttribute != null) { userId = userIdAttribute.Value; } _logInUserId = userId; } //Output some status text... if (!string.IsNullOrWhiteSpace(userId)) { StatusLog.AddStatus("Log-in returned user id: '" + userId + "'", -10); } else { StatusLog.AddStatus("No User Id returned from log-in request"); return(false); //Failed sign in } return(true); //Success }
/// <summary> /// Called to attempt to execute a custom command (to be run after the user logs in) /// </summary> /// <param name="onlineLogin"></param> /// <param name="customCommand"></param> private void AttemptExecutionOfCustomHttpGet(TableauServerSignIn onlineLogin, string customCommand) { _statusLog.AddStatusHeader("GET request: " + customCommand); var customGetRequest = new SendPostLogInCommand(_onlineUrls, onlineLogin, customCommand); try { var customResult = customGetRequest.ExecuteRequest(); _statusLog.AddStatus("GET result: " + customResult); } catch (Exception exCustomCommand) { _statusLog.AddError("Error during custom GET, " + exCustomCommand.ToString()); } }
/// <summary> /// Attempt to log any detailed information we find about the failed web request /// </summary> /// <param name="webException"></param> /// <param name="onlineStatusLog"></param> private static void AttemptToLogWebException(WebException webException, string description, TaskStatusLogs onlineStatusLog) { if(onlineStatusLog == null) return; //No logger? nothing to do try { if(string.IsNullOrWhiteSpace(description)) { description = "web request failed"; } var response = webException.Response; var responseText = GetWebResponseAsText(response); response.Close(); if(responseText == null) responseText = ""; onlineStatusLog.AddError(description + ": " + webException.Message + "\r\n" + responseText + "\r\n"); } catch (Exception ex) { onlineStatusLog.AddError("Error in web request exception: " + ex.Message); return; } }
/// <summary> /// Remaps necesary attributes inside of the datasource->connection node to point to a new server /// </summary> /// <param name="xDSourceConnection"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleDataServerConnectionNode(XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { //Get the XML sub mode we need var xNodeConnection = xNodeDatasource.SelectSingleNode("connection"); if (xNodeConnection == null) { statusLog.AddError("Workbook remapper, no 'connection' node found"); return; } //==================================================================================== //PORT NUMBER //==================================================================================== var attrPort = xNodeConnection.Attributes["port"]; if (attrPort != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrPort.Value = "80"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrPort.Value = "443"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'port'"); } //==================================================================================== //Server name //==================================================================================== var attrServer = xNodeConnection.Attributes["server"]; if (attrServer != null) { attrServer.Value = serverMapInfo.ServerName; } else { statusLog.AddError("Workbook remapper, missing attribute 'server'"); } //==================================================================================== //Channel //==================================================================================== var attrChannel = xNodeConnection.Attributes["channel"]; if (attrChannel != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrChannel.Value = "http"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrChannel.Value = "https"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'channel'"); } }
/// <summary> /// Remaps necesary attributes inside of the datasource->connection node to point to a new server /// </summary> /// <param name="xDSourceConnection"></param> /// <param name="serverMapInfo"></param> /// <param name="statusLog"></param> private static void RemapSingleDataServerConnectionNode(XmlNode xNodeDatasource, ITableauServerSiteInfo serverMapInfo, TaskStatusLogs statusLog) { //Get the XML sub mode we need var xNodeConnection = xNodeDatasource.SelectSingleNode("connection"); if(xNodeConnection == null) { statusLog.AddError("Workbook remapper, no 'connection' node found"); return; } //==================================================================================== //PORT NUMBER //==================================================================================== var attrPort = xNodeConnection.Attributes["port"]; if(attrPort != null) { if(serverMapInfo.Protocol == ServerProtocol.http) { attrPort.Value = "80"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrPort.Value = "443"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'port'"); } //==================================================================================== //Server name //==================================================================================== var attrServer = xNodeConnection.Attributes["server"]; if (attrServer != null) { attrServer.Value = serverMapInfo.ServerName; } else { statusLog.AddError("Workbook remapper, missing attribute 'server'"); } //==================================================================================== //Channel //==================================================================================== var attrChannel = xNodeConnection.Attributes["channel"]; if (attrChannel != null) { if (serverMapInfo.Protocol == ServerProtocol.http) { attrChannel.Value = "http"; } else if (serverMapInfo.Protocol == ServerProtocol.https) { attrChannel.Value = "https"; } else { statusLog.AddError("Workbook remapper, unknown protocol"); } } else { statusLog.AddError("Workbook remapper, missing attribute 'channel'"); } }
/// <summary> /// Generate a maniefest file based on the current Online site /// </summary> /// <param name="statusLogs"></param> /// <param name="pathSecrets"></param> /// <param name="pathOutputFile"></param> /// <param name="ignoreAllUsersGroup">(recommend TRUE) If false, the manifest file will contain the "all users" group</param> private void GenerateManifestFromOnlineSite( TaskStatusLogs statusLogs, string pathSecrets, string pathOutputFile, bool ignoreAllUsersGroup) { var pathOutputs = Path.GetDirectoryName(pathOutputFile); ProvisionConfigSiteAccess secretsConfig; //=========================================================================================== //Get the sign in information //=========================================================================================== try { //Load the config from the files secretsConfig = new ProvisionConfigSiteAccess(pathSecrets); } catch (Exception exSignInConfig) { statusLogs.AddError("Error loading sign in config file"); throw new Exception("1012-327: Error parsing sign in config, " + exSignInConfig.Message); } //=========================================================================================== //Create a place for out output files //=========================================================================================== FileIOHelper.CreatePathIfNeeded(pathOutputs); var provisionSettings = ProvisionConfigExternalDirectorySync.FromDefaults(); //=========================================================================================== //Download all the data we need from the Tableau Online site //=========================================================================================== statusLogs.AddStatusHeader("Retrieving information from Tableau"); UpdateStatusText(statusLogs); var tableauDownload = new TableauProvisionDownload( secretsConfig, this, statusLogs, ignoreAllUsersGroup); try { tableauDownload.Execute(); } catch (Exception exTableauDownload) { statusLogs.AddError("Error retrieving data from Tableau"); throw new Exception("813-0148: Error in Tableau Download, " + exTableauDownload.Message); } //=========================================================================================== //Write the provisioning manifest out to a file //=========================================================================================== statusLogs.AddStatusHeader("Writing out manifest file for Tableau provisioning"); UpdateStatusText(statusLogs); var outputProvisioningRoles = tableauDownload.ProvisioningManifestResults; try { outputProvisioningRoles.GenerateProvisioningManifestFile(pathOutputFile, provisionSettings); } catch (Exception exWriteProvisioningManifest) { statusLogs.AddError("Error creating provisioning manifest"); throw new Exception("1012-252: Error writing provisioning manifest, " + exWriteProvisioningManifest.Message); } }
/// <summary> /// Run the provisioning pulling from AzureAD /// </summary> /// <param name="pathSecrets"></param> /// <param name="pathProvisionPlan"></param> private void ProvisionFromAzureAd( TaskStatusLogs statusLogs, string pathSecrets, string pathProvisionPlan, bool deployToTableauTarget, string pathOutputs) { //=========================================================================================== //Create a place for out output files //=========================================================================================== FileIOHelper.CreatePathIfNeeded(pathOutputs); AzureAdConfig configSignInAzure; ProvisionConfigExternalDirectorySync configGroupsMapping; //=========================================================================================== //Get the sign in information //=========================================================================================== try { //Load the config from the files configSignInAzure = new AzureAdConfig(pathSecrets); } catch (Exception exSignInConfig) { statusLogs.AddError("Error loading sign in config file. Error: " + exSignInConfig.Message); throw new Exception("813-1212: Error parsing sign in config, " + exSignInConfig.Message); } //=========================================================================================== //Get the Groups/Roles mapping information //=========================================================================================== try { configGroupsMapping = new ProvisionConfigExternalDirectorySync(pathProvisionPlan); } catch (Exception exGroupsMapping) { statusLogs.AddError("Error loading sync groups provisioning file. Error: " + exGroupsMapping.Message); throw new Exception("813-1214: Error parsing sync groups, " + exGroupsMapping.Message); } //=========================================================================================== //Download all the data we need from Azure //=========================================================================================== statusLogs.AddStatusHeader("Retrieving information from Azure AD"); UpdateStatusText(statusLogs); var azureDownload = new AzureDownload(configSignInAzure, configGroupsMapping, this, statusLogs, null); try { azureDownload.Execute(); //Sanity test IwsDiagnostics.Assert(azureDownload.IsExecuteComplete.Value, "813-834: Internal error. Async work still running"); } catch (Exception exAzureDownload) { statusLogs.AddError("Error retrieving data from Azure AD. Error: " + exAzureDownload.Message); throw new Exception("813-0148: Error in Azure Download, " + exAzureDownload.Message); } //=========================================================================================== //Write the provisioning manifest out to an intermediary file //=========================================================================================== statusLogs.AddStatusHeader("Writing out manifest file for Tableau provisioning"); UpdateStatusText(statusLogs); var outputProvisioningRoles = azureDownload.ProvisioningManifestResults; string provisioningManifest = Path.Combine(pathOutputs, "ProvisioningManifest.xml"); try { outputProvisioningRoles.GenerateProvisioningManifestFile(provisioningManifest, configGroupsMapping); } catch (Exception exWriteProvisioningManifest) { statusLogs.AddError("Error creating provisioning manifest. Error: " + exWriteProvisioningManifest.Message); throw new Exception("813-739: Error writing provisioning manifest, " + exWriteProvisioningManifest.Message); } //================================================================================================= //See if this is a test run, or whether we want to actually deploy the provisioning //================================================================================================= if (deployToTableauTarget) { //=========================================================================================== //Provision the Tableau site using the manifest file we just created //=========================================================================================== statusLogs.AddStatusHeader("Provision Tableau site using generated manifest file"); UpdateStatusText(statusLogs); try { ProvisionFromFileManifest(statusLogs, pathSecrets, provisioningManifest, pathOutputs); } catch (Exception exProvisionSite) { statusLogs.AddError("Error provisioning Tableau Online site. Error: " + exProvisionSite.Message); throw new Exception("814-353: Error provisioning Tableau Online site, " + exProvisionSite.Message); } } else { statusLogs.AddStatusHeader("Skipping Tableau site provisioning step (generate manifest only)"); } }