diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-02-06 18:20:21 +0200 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-02-06 18:20:21 +0200 |
| commit | 0baf31baf01a291b6d2c6f5d45fb15fbbc146198 (patch) | |
| tree | 96d6fc1b4a5f1385ce2224233ea076d85972ee0d /Software/Visual_Studio/Azure | |
| parent | 258399bb206839cdb7cd276839675fa14910bc36 (diff) | |
| download | Tango-0baf31baf01a291b6d2c6f5d45fb15fbbc146198.tar.gz Tango-0baf31baf01a291b6d2c6f5d45fb15fbbc146198.zip | |
Working on Azure Utils.
Diffstat (limited to 'Software/Visual_Studio/Azure')
18 files changed, 1169 insertions, 652 deletions
diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModelLocator.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModelLocator.cs index a522d62cc..196848e95 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModelLocator.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Tango.AzureUtils.UI.Managers; using Tango.AzureUtils.UI.ViewModels; +using Tango.Core; using Tango.Core.DI; namespace Tango.AzureUtils.UI @@ -13,11 +14,16 @@ namespace Tango.AzureUtils.UI { static ViewModelLocator() { - TangoIOC.Default.Register<MainViewVM>(); - TangoIOC.Default.Register<EnvironmentUpgradeViewVM>(); - TangoIOC.Default.Register<EnvironmentCreationViewVM>(); + ExtendedObject obj = new ExtendedObject(); - TangoIOC.Default.Register<IStatusManager, DefaultStatusManager>(); + if (!obj.DesignMode) + { + TangoIOC.Default.Register<MainViewVM>(); + TangoIOC.Default.Register<EnvironmentUpgradeViewVM>(); + TangoIOC.Default.Register<EnvironmentCreationViewVM>(); + + TangoIOC.Default.Register<IStatusManager, DefaultStatusManager>(); + } } public static MainViewVM MainViewVM diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentCreationViewVM.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentCreationViewVM.cs index 74995c16d..fe585191d 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentCreationViewVM.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentCreationViewVM.cs @@ -68,6 +68,8 @@ namespace Tango.AzureUtils.UI.ViewModels public override void OnApplicationReady() { Email = "roy@twine-s.com"; + SlotName = "ROY"; + Password = "1Creativity"; } public override void OnAuthenticated(IAzure azure, List<IWebAppBase> apps) @@ -100,7 +102,11 @@ namespace Tango.AzureUtils.UI.ViewModels if (!Validate()) return; IsFree = false; - await _environmentManager.CreateDeploymentSlot(_machineServiceApp as IWebApp, SelectedDeploymentSlot, SlotName, Email, Password); + await _environmentManager.CreateEnvironment(_machineServiceApp as IWebApp, SelectedDeploymentSlot, SlotName, new CreateEnvironmentConfiguration() + { + Email = Email, + Password = Password + }); } catch (Exception ex) { diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentUpgradeViewVM.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentUpgradeViewVM.cs index 56228e1c5..695ef9d4f 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentUpgradeViewVM.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/ViewModels/EnvironmentUpgradeViewVM.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.AzureUtils.Deployment; +using Tango.AzureUtils.Environment; using Tango.Core.Commands; using Tango.SharedUI; @@ -34,11 +35,11 @@ namespace Tango.AzureUtils.UI.ViewModels set { _selectedTargetAPp = value; RaisePropertyChangedAuto(); } } - private DeploymentManager _deploymentManager; - public DeploymentManager DeploymentManager + private EnvironmentManager _environmentManager; + public EnvironmentManager EnvironmentManager { - get { return _deploymentManager; } - set { _deploymentManager = value; RaisePropertyChangedAuto(); } + get { return _environmentManager; } + set { _environmentManager = value; RaisePropertyChangedAuto(); } } private bool _canUpgrade; @@ -48,11 +49,23 @@ namespace Tango.AzureUtils.UI.ViewModels set { _canUpgrade = value; RaisePropertyChangedAuto(); } } + private UpgradeEnvironmentConfiguration _config; + public UpgradeEnvironmentConfiguration Config + { + get { return _config; } + set { _config = value; RaisePropertyChangedAuto(); } + } + + public RelayCommand ValidateUpgradeCommand { get; set; } + public RelayCommand UpgradeEnvironmentCommand { get; set; } + public EnvironmentUpgradeViewVM() { + Config = new UpgradeEnvironmentConfiguration(); ValidateUpgradeCommand = new RelayCommand(() => ValidateUpgrade()); + UpgradeEnvironmentCommand = new RelayCommand(() => UpgradeEnvironment()); } public override void OnAuthenticated(IAzure azure, List<IWebAppBase> apps) @@ -62,9 +75,8 @@ namespace Tango.AzureUtils.UI.ViewModels SelectedSourceApp = Apps.FirstOrDefault(); SelectedTargetApp = Apps.FirstOrDefault(); - DeploymentManager = new DeploymentManager(azure); - DeploymentManager.Progress += (x, e) => StatusManager.UpdateStatus(e); - DeploymentManager.UpgradeConfiguration.PropertyChanged += (x, e) => CanUpgrade = false; + EnvironmentManager = new EnvironmentManager(azure); + EnvironmentManager.Progress += (x, e) => StatusManager.UpdateStatus(e); } private async void ValidateUpgrade() @@ -78,7 +90,7 @@ namespace Tango.AzureUtils.UI.ViewModels { IsFree = false; StatusManager.UpdateStatus(AzureUtilsStage.Validating, "Validating configuration...", true); - await DeploymentManager.ValidateUpgrade(SelectedSourceApp, SelectedTargetApp); + await EnvironmentManager.ValidateEnvironmentUpgrade(SelectedSourceApp, SelectedTargetApp, Config); CanUpgrade = true; StatusManager.UpdateStatus(AzureUtilsStage.Ready, "Configuration validated successfully."); } @@ -92,5 +104,22 @@ namespace Tango.AzureUtils.UI.ViewModels IsFree = true; } } + + private async void UpgradeEnvironment() + { + try + { + IsFree = false; + await EnvironmentManager.UpgradeEnvironment(SelectedSourceApp, SelectedTargetApp, Config); + } + catch (Exception ex) + { + StatusManager.UpdateStatus(ex); + } + finally + { + IsFree = true; + } + } } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml index 67eaa52e2..6653653d0 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml @@ -38,21 +38,20 @@ <StackPanel> <GroupBox Header="Upgrade Configuration" Padding="10"> <StackPanel> - <CheckBox IsChecked="{Binding DeploymentManager.UpgradeConfiguration.UpgradeMachineStudio}" >Upgrade Machine Studio</CheckBox> - <CheckBox Margin="0 5 0 0" IsChecked="{Binding DeploymentManager.UpgradeConfiguration.UpgradePPC}" >Upgrade PPC</CheckBox> - <CheckBox Margin="0 5 0 0" IsChecked="{Binding DeploymentManager.UpgradeConfiguration.UpgradeMachineService}" >Upgrade Machine Service</CheckBox> + <CheckBox Click="OnConfigChanged" IsChecked="{Binding Config.SynchronizeDatabaseSchema}" >Upgrade Database Schema</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.SynchronizeDatabaseData}" >Upgrade Database Static Collections</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.CopyMachineStudioStorageBlobs}" >Upgrade Machine Studio Blob Storage</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.CopyPPCStorageBlobs}" >Upgrade PPC Blob Storage</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.UpgradeMachineStudioDatabaseVersion}" >Upgrade Machine Studio Database Version</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.UpgradePPCDatabaseVersion}" >Upgrade PPC Database Version</CheckBox> + <CheckBox Click="OnConfigChanged" Margin="0 5 0 0" IsChecked="{Binding Config.CopyMachineServiceFiles}" >Upgrade Machine Service</CheckBox> </StackPanel> </GroupBox> <StackPanel Margin="0 20 0 0"> <Button Padding="20" Command="{Binding ValidateUpgradeCommand}">Validate</Button> <StackPanel Margin="0 20 0 0" IsEnabled="{Binding CanUpgrade}"> - <Button Padding="20">Upgrade Database Schema</Button> - <Button Margin="0 10 0 0" Padding="20">Upgrade Database Static Collections</Button> - <Button Margin="0 10 0 0" Padding="20">Upgrade Machine Studio</Button> - <Button Margin="0 10 0 0" Padding="20">Upgrade PPC</Button> - <Button Margin="0 10 0 0" Padding="20">Upgrade Machine Service</Button> - <Button Margin="0 10 0 0" Padding="20">Full Upgrade</Button> + <Button Margin="0 10 0 0" Padding="20" Command="{Binding UpgradeEnvironmentCommand}">Full Upgrade</Button> </StackPanel> </StackPanel> </StackPanel> diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml.cs index f904a2aef..3c3ad9b28 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils.UI/Views/EnvironmentUpgradeView.xaml.cs @@ -24,5 +24,10 @@ namespace Tango.AzureUtils.UI.Views { InitializeComponent(); } + + private void OnConfigChanged(object sender, RoutedEventArgs e) + { + (DataContext as ViewModels.EnvironmentUpgradeViewVM).CanUpgrade = false; + } } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/ActiveDirectory/ActiveDirectoryManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/ActiveDirectory/ActiveDirectoryManager.cs index 4527a1f53..fe5934fa9 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/ActiveDirectory/ActiveDirectoryManager.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/ActiveDirectory/ActiveDirectoryManager.cs @@ -12,50 +12,82 @@ namespace Tango.AzureUtils.ActiveDirectory public class ActiveDirectoryManager : AzureUtilsComponentBase { private AuthenticationResult _authResult; - private AzureUtilsCredentials _credentials; private ActiveDirectoryClient _adClient; - public ActiveDirectoryManager(IAzure azure, AzureUtilsCredentials credentials) : base(azure) + #region Constructors + + /// <summary> + /// Initializes a new instance of the <see cref="ActiveDirectoryManager"/> class. + /// </summary> + /// <param name="azure">The azure instance.</param> + public ActiveDirectoryManager(IAzure azure) : base(azure) + { + + } + + #endregion + + #region Private Methods + + private ActiveDirectoryClient GetActiveDirectoryClient() { - _credentials = credentials; + if (_adClient == null) + { + var credentials = AzureUtilsAuthenticationFactory.GetCredentials(); + _adClient = new ActiveDirectoryClient(new Uri($"https://graph.windows.net/{credentials.TenantID}"), async () => await Task.FromResult(_authResult.AccessToken)); + } + return _adClient; } - public async Task Authenticate() + #endregion + + #region Public Methods + + /// <summary> + /// Authenticates using application credentials. + /// </summary> + /// <param name="credentials">The credentials.</param> + /// <returns></returns> + public async Task Authenticate(AzureUtilsCredentials credentials) { if (_authResult == null) { - var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{_credentials.TenantID}"); - ClientCredential clientCredentials = new ClientCredential(_credentials.ClientID, _credentials.ClientSecret); + var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{credentials.TenantID}"); + ClientCredential clientCredentials = new ClientCredential(credentials.ClientID, credentials.ClientSecret); _authResult = await authContext.AcquireTokenAsync("https://graph.windows.net/", clientCredentials); } } + /// <summary> + /// Authenticates using an AD account. + /// </summary> + /// <param name="email">The email.</param> + /// <param name="password">The password.</param> + /// <returns></returns> public async Task Authenticate(String email, String password) { + OnProgress(AzureUtilsStage.ActiveDirectory, $"Authenticating with active directory graph..."); if (_authResult == null) { - var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{_credentials.TenantID}"); + var credentials = AzureUtilsAuthenticationFactory.GetCredentials(); + var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{credentials.TenantID}"); authContext.TokenCache.Clear(); UserCredential userCredential = new UserPasswordCredential(email, password); _authResult = await authContext.AcquireTokenAsync("https://graph.windows.net/", "ec612854-7abc-457b-808a-5d0c5ba80c57", userCredential); } } - private ActiveDirectoryClient GetActiveDirectoryClient() - { - if (_adClient == null) - { - _adClient = new ActiveDirectoryClient(new Uri($"https://graph.windows.net/{_credentials.TenantID}"), async () => await Task.FromResult(_authResult.AccessToken)); - } - return _adClient; - } - - public async Task<bool> IsGroupExists(String name) + /// <summary> + /// Determines whether the specified group name exists. + /// </summary> + /// <param name="groupName">Name of the group.</param> + /// <returns></returns> + public async Task<bool> IsGroupExists(String groupName) { try { var client = GetActiveDirectoryClient(); - var g = await client.Groups.Where(x => x.DisplayName == name).Take(1).ExecuteSingleAsync(); + var g = await client.Groups.Where(x => x.DisplayName == groupName).Take(1).ExecuteSingleAsync(); return g != null; } catch @@ -64,33 +96,49 @@ namespace Tango.AzureUtils.ActiveDirectory } } - public async Task AddGroup(String name) + /// <summary> + /// Adds the specified group. + /// </summary> + /// <param name="groupName">Name of the group.</param> + /// <returns></returns> + public async Task AddGroup(String groupName) { + OnProgress(AzureUtilsStage.ActiveDirectory, $"Creating environment group '{groupName}'..."); var client = GetActiveDirectoryClient(); await client.Groups.AddGroupAsync(new Group() { - DisplayName = name, + DisplayName = groupName, MailEnabled = false, MailNickname = Guid.NewGuid().ToString().ToLower(), SecurityEnabled = true }); } - public async Task AddUserToGroup(String group, String userEmail) + /// <summary> + /// Adds the specified user to the specified group. + /// </summary> + /// <param name="groupName">Name of the group.</param> + /// <param name="userEmail">The user email.</param> + /// <returns></returns> + public async Task AddUserToGroup(String groupName, String userEmail) { + OnProgress(AzureUtilsStage.ActiveDirectory, $"Adding environment group user '{userEmail}'..."); + var client = GetActiveDirectoryClient(); List<Group> groups = new List<Group>(); var user = await client.Users.Where(x => x.UserPrincipalName == userEmail).ExecuteSingleAsync(); - var g = await client.Groups.Where(x => x.DisplayName == group).Take(1).ExecuteSingleAsync(); + var g = await client.Groups.Where(x => x.DisplayName == groupName).Take(1).ExecuteSingleAsync(); var gg = g as Group; gg.Members.Add(user as DirectoryObject); await gg.UpdateAsync(); } + + #endregion } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsComponentBase.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsComponentBase.cs index 5ad584cd7..840f7bb58 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsComponentBase.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsComponentBase.cs @@ -35,6 +35,11 @@ namespace Tango.AzureUtils }); } + protected virtual void OnCompleted(String message) + { + OnProgress(AzureUtilsStage.Ready, message, 0, 100, false); + } + protected virtual void OnProgress(AzureUtilsProgressEventArgs e) { Progress?.Invoke(this, e); @@ -69,5 +74,13 @@ namespace Tango.AzureUtils { ConfirmationRequired?.Invoke(this, e); } + + protected T CreateManager<T>() where T : AzureUtilsComponentBase + { + T manager = Activator.CreateInstance(typeof(T), new object[] { Azure }) as T; + manager.ConfirmationRequired += (x, e) => OnRequestConfirmation(e); + manager.Progress += (x, e) => OnProgress(e); + return manager; + } } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsStage.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsStage.cs index 994ada3aa..c6a9c76e3 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsStage.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/AzureUtilsStage.cs @@ -16,18 +16,16 @@ namespace Tango.AzureUtils [Description("Confirmation Required")] ConfirmationRequired, - //Deployment - Downloading, - Uploading, - - //Environment - Creating, - - [Description("Environment Group")] - EnvironmentGroup, - [Description("Deployment Slot")] - DeploymentSlot, + [Description("FTP Download")] + FtpDownload, + [Description("FTP Upload")] + FtpUpload, + [Description("Active Directory")] + ActiveDirectory, + Deployment, Database, Storage, + Environment, + Validation, } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Database/DatabaseManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Database/DatabaseManager.cs new file mode 100644 index 000000000..b54f243f8 --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Database/DatabaseManager.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data.Entity; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.Azure.Management.AppService.Fluent; +using Microsoft.Azure.Management.Fluent; +using Microsoft.Azure.Management.Sql.Fluent; +using Microsoft.Azure.Management.Sql.Fluent.Models; +using Tango.BL; +using Tango.BL.Entities; +using Tango.Core; +using Tango.Core.DB; +using Tango.Core.Helpers; + +namespace Tango.AzureUtils.Database +{ + public class DatabaseManager : AzureUtilsComponentBase + { + #region Constructors + + /// <summary> + /// Initializes a new instance of the <see cref="DatabaseManager"/> class. + /// </summary> + /// <param name="azure">The azure.</param> + public DatabaseManager(IAzure azure) : base(azure) + { + + } + + #endregion + + #region SQL Examiner + + /// <summary> + /// Opens the SQL examiner schema using the specified source app and target app configuration. + /// </summary> + /// <param name="sourceApp">The source application.</param> + /// <param name="targetApp">The target application.</param> + /// <returns></returns> + public async Task OpenSQLExaminerSchema(IWebAppBase sourceApp, IWebAppBase targetApp) + { + String projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Database", "GENERAL_ENV_UPGRADE.seproj"); + + using (Stream stream = GetProjectFileStream(projectFile)) + { + XElement projectXml = XElement.Load(stream); + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + ApplyDatabaseSettingsToProjectXml(projectXml, sourceSettings, targetSettings); + + var tempFile = TemporaryManager.CreateImaginaryFile(".seproj"); + tempFile.Persist = true; + + File.WriteAllText(tempFile, projectXml.ToString()); + + Process.Start(tempFile); + } + } + + /// <summary> + /// Opens the SQL examiner data using the specified source app and target app configuration. + /// </summary> + /// <param name="sourceApp">The source application.</param> + /// <param name="targetApp">The target application.</param> + /// <param name="forCreation">if set to <c>true</c> [for creation].</param> + /// <returns></returns> + public async Task OpenSQLExaminerData(IWebAppBase sourceApp, IWebAppBase targetApp, bool forCreation = false) + { + String projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Database", "GENERAL_ENV_UPGRADE.sdeproj"); + + if (forCreation) + { + projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Database", "GENERAL_ENV_CREATE.sdeproj"); + } + + using (Stream stream = GetProjectFileStream(projectFile)) + { + XElement projectXml = XElement.Load(stream); + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + ApplyDatabaseSettingsToProjectXml(projectXml, sourceSettings, targetSettings); + + var tempFile = TemporaryManager.CreateImaginaryFile(".sdeproj"); + tempFile.Persist = true; + + File.WriteAllText(tempFile, projectXml.ToString()); + + Process.Start(tempFile); + } + } + + private Stream GetProjectFileStream(String projectFile) + { + byte[] projectBytes = File.ReadAllBytes(projectFile); + MemoryStream stream = new MemoryStream(projectBytes); + return stream; + } + + private void ApplyDatabaseSettingsToProjectXml(XElement projectXml, MachineServiceSettings sourceSettings, MachineServiceSettings targetSettings) + { + var sourceElement = projectXml.Elements().SelectMany(x => x.Descendants()).SingleOrDefault(x => x.Name == "Source" && x.Attributes().SingleOrDefault(y => y.Name == "id").Value == "1"); + var targetElement = projectXml.Elements().SelectMany(x => x.Descendants()).SingleOrDefault(x => x.Name == "Source" && x.Attributes().SingleOrDefault(y => y.Name == "id").Value == "2"); + + ApplyDatabaseSettingsToSourceElement(sourceElement, sourceSettings); + ApplyDatabaseSettingsToSourceElement(targetElement, targetSettings); + } + + private void ApplyDatabaseSettingsToSourceElement(XElement sourceElement, MachineServiceSettings settings) + { + sourceElement.Element("ServerName").SetValue(settings.DB_ADDRESS); + sourceElement.Element("Database").SetValue(settings.DB_CATALOG); + sourceElement.Element("Login").SetValue(settings.DB_USER_NAME); + sourceElement.Element("Password").SetValue(settings.DB_PASSWORD); + } + + #endregion + + #region Azure SQL Database + + public async Task<bool> IsDatabaseExists(String serverName, String databaseName) + { + OnProgress(AzureUtilsStage.Database, $"Checking {serverName} database server..."); + var sqlServer = (await Azure.SqlServers.ListAsync()).SingleOrDefault(x => x.Name == serverName); + + var database = (await sqlServer.Databases.ListAsync()).SingleOrDefault(x => x.Name == databaseName); + + return database != null; + } + + public async Task<ISqlDatabase> AddDatabase(String serverName, String databaseName) + { + OnProgress(AzureUtilsStage.Database, $"Checking {serverName} database server..."); + var sqlServer = (await Azure.SqlServers.ListAsync()).SingleOrDefault(x => x.Name == serverName); + + OnProgress(AzureUtilsStage.Database, $"Creating new database '{databaseName}'..."); + var database = await sqlServer.Databases.Define(databaseName).WithEdition(DatabaseEdition.Standard).CreateAsync(); + + return database; + } + + public Task<ISqlDatabase> AddDatabase(String databaseName) + { + return AddDatabase("twine", databaseName); + } + + #endregion + + #region Users & Permissions + + public async Task AddEnvironmentGroupPermissions(String address, String databaseName, String groupName, String email, String password) + { + OnProgress(AzureUtilsStage.Database, $"Adding environment group permissions on '{databaseName}'..."); + + using (DbManager db = DbManager.FromDataSource(new DataSource() + { + Type = DataSourceType.Azure, + Address = address, + Catalog = databaseName, + UserName = email, + Password = password, + IntegratedSecurity = false + })) + { + await db.ExecuteCommandAsync($"CREATE USER [{groupName}] FROM EXTERNAL PROVIDER"); + await db.ExecuteCommandAsync($"ALTER ROLE db_datareader ADD MEMBER [{groupName}];"); + await db.ExecuteCommandAsync($"ALTER ROLE db_datawriter ADD MEMBER [{groupName}];"); + } + } + + public async Task AddBackupUser(String address, String databaseName, String userName, String password) + { + OnProgress(AzureUtilsStage.Database, $"Adding BackupUser permissions on '{databaseName}'..."); + + using (DbManager db = DbManager.FromCredentials(address, databaseName, userName, password)) + { + await db.ExecuteCommandAsync("CREATE USER [BackupUser] FOR LOGIN [BackupUser] WITH DEFAULT_SCHEMA=[dbo]"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_owner', N'BackupUser'"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_accessadmin', N'BackupUser'"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_securityadmin', N'BackupUser'"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_backupoperator', N'BackupUser'"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_datareader', N'BackupUser'"); + await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_datawriter', N'BackupUser'"); + } + } + + #endregion + + #region Versions + + public async Task UpgradeMachineStudioVersion(IWebAppBase sourceApp, IWebAppBase targetApp) + { + var latestMachineStudioVersion = await GetLatestMachineStudioVersion(sourceApp); + var targetDataSource = (await targetApp.GetMachineServiceSettingsAsync()).ToDataSource(); + + OnProgress(AzureUtilsStage.Database, $"Adding machine studio database entry for version '{latestMachineStudioVersion.Version}'..."); + + using (var db = ObservablesContext.CreateDefault(targetDataSource)) + { + db.MachineStudioVersions.Add(latestMachineStudioVersion); + await db.SaveChangesAsync(); + } + } + + public async Task UpgradePPCVersion(IWebAppBase sourceApp, IWebAppBase targetApp) + { + var latestPPCVersion = await GetLatestPPCVersion(sourceApp); + + var targetDataSource = (await targetApp.GetMachineServiceSettingsAsync()).ToDataSource(); + + OnProgress(AzureUtilsStage.Database, $"Adding machine studio database entry for version '{latestPPCVersion.Version}'..."); + + using (var db = ObservablesContext.CreateDefault(targetDataSource)) + { + db.TangoVersions.Add(latestPPCVersion); + await db.SaveChangesAsync(); + } + } + + public async Task<MachineStudioVersion> GetLatestMachineStudioVersion(IWebAppBase app) + { + OnProgress(AzureUtilsStage.Database, $"Getting latest machine studio version on '{app.Name}'..."); + + MachineServiceSettings settings = null; + + try + { + settings = await app.GetMachineServiceSettingsAsync(); + } + catch (Exception ex) + { + throw new ArgumentException("Could not fetch machine service settings. Please check that all settings are available.", ex); + } + + try + { + DataSource dataSource = settings.ToDataSource(); + + using (var db = ObservablesContext.CreateDefault(dataSource)) + { + var versions = await db.MachineStudioVersions.ToListAsync(); + var latest_machine_version = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); + return latest_machine_version; + } + } + catch (Exception ex) + { + throw new InvalidDataException($"Could not retrieve '{app.Name}' latest Machine Studio version from database.", ex); + } + } + + public async Task<TangoVersion> GetLatestPPCVersion(IWebAppBase app) + { + OnProgress(AzureUtilsStage.Database, $"Getting latest PPC version on '{app.Name}'..."); + + MachineServiceSettings settings = null; + + try + { + settings = await app.GetMachineServiceSettingsAsync(); + } + catch (Exception ex) + { + throw new ArgumentException("Could not fetch machine service settings. Please check that all settings are available.", ex); + } + + try + { + DataSource dataSource = settings.ToDataSource(); + + using (var db = ObservablesContext.CreateDefault(dataSource)) + { + var versions = await db.TangoVersions.ToListAsync(); + var latest_machine_version = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); + return latest_machine_version; + } + } + catch (Exception ex) + { + throw new InvalidDataException($"Could not retrieve '{app.Name}' latest PPC version from database.", ex); + } + } + + public async Task ValidateMachineStudioDatabaseUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Validating, "Validating machine studio database upgrade..."); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + var latestSourceMachineStudioVersion = await GetLatestMachineStudioVersion(sourceApp); + + //Check if there is any source machine studio version. + if (latestSourceMachineStudioVersion == null) + { + throw new ValidationException("Could not locate a Machine Studio version entry on the source database."); + } + + var latestTargetMachineStudioVersion = await GetLatestMachineStudioVersion(targetApp); + + //Check target latest machine studio version is older if there is any. + if (latestTargetMachineStudioVersion != null && Version.Parse(latestSourceMachineStudioVersion.Version) <= Version.Parse(latestTargetMachineStudioVersion.Version)) + { + throw new ValidationException($"Machine Studio source version is '{latestSourceMachineStudioVersion.Version}' while target version is '{latestTargetMachineStudioVersion.Version}'."); + } + } + + public async Task ValidatePPCDatabaseUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Validating, "Validating PPC database upgrade..."); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + var latestSourcePPCVersion = await GetLatestPPCVersion(sourceApp); + + //Check if there is any source PPC version. + if (latestSourcePPCVersion == null) + { + throw new ValidationException("Could not locate a PPC version entry on the source database."); + } + + var latestTargetPPCVersion = await GetLatestPPCVersion(targetApp); + + //Check target latest PPC version is older if there is any. + if (latestTargetPPCVersion != null && Version.Parse(latestSourcePPCVersion.Version) <= Version.Parse(latestTargetPPCVersion.Version)) + { + throw new ValidationException($"PPC source version is '{latestSourcePPCVersion.Version}' while target version is '{latestTargetPPCVersion.Version}'."); + } + } + + #endregion + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/DeploymentManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/DeploymentManager.cs index 2500b293c..5f1eb28fd 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/DeploymentManager.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/DeploymentManager.cs @@ -22,461 +22,59 @@ using Tango.Core; using Tango.Core.Helpers; using Microsoft.Azure.Management.AppService.Fluent.DeploymentSlot.Definition; using Microsoft.Azure.Management.AppService.Fluent.WebAppBase.Definition; +using Tango.AzureUtils.Environment; namespace Tango.AzureUtils.Deployment { public class DeploymentManager : AzureUtilsComponentBase { - private IProgress<FtpProgress> _ftpDownloadProgress; - private IProgress<FtpProgress> _ftpUploadProgress; - - //TODO: Embedded TFP injection to current package! - - #region Properties - - private UpgradeConfiguration _upgradeConfiguration; - public UpgradeConfiguration UpgradeConfiguration - { - get { return _upgradeConfiguration; } - set { _upgradeConfiguration = value; RaisePropertyChangedAuto(); } - } - - #endregion - #region Constructors public DeploymentManager(IAzure azure) : base(azure) { - UpgradeConfiguration = new UpgradeConfiguration(); - - _ftpDownloadProgress = new Progress<FtpProgress>((p) => - { - OnProgress(AzureUtilsStage.Downloading, $"Downloading {p.RemotePath}...", p.Progress, 100, false); - }); - - _ftpUploadProgress = new Progress<FtpProgress>((p) => - { - OnProgress(AzureUtilsStage.Uploading, $"Uploading {p.LocalPath}...", p.Progress, 100, false); - }); - } - - #endregion - - #region Helpers - - public async Task<List<IWebApp>> GetAllWebAppsAsync() - { - return (await Azure.WebApps.ListAsync()).ToList(); - } - - #endregion - - #region Full Upgrade - - public async Task PerformFullUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) - { - await ValidateUpgrade(sourceApp, targetApp); - - await OpenSQLExaminerSchema(sourceApp, targetApp); - await OpenSQLExaminerData(sourceApp, targetApp); - await UpgradeStorage(sourceApp, targetApp); - await UpgradeVersions(sourceApp, targetApp); - - if (UpgradeConfiguration.UpgradeMachineService) - { - await UpgradeMachineService(sourceApp, targetApp); - } - } - - #endregion - - #region SQLExaminer - - public async Task OpenSQLExaminerSchema(IWebAppBase sourceApp, IWebAppBase targetApp) - { - String projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Deployment", "GENERAL_ENV_UPGRADE.seproj"); - - using (Stream stream = GetFileStream(projectFile)) - { - XElement projectXml = XElement.Load(stream); - var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); - var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); - ApplyDatabaseSettingsToProjectXml(projectXml, sourceSettings, targetSettings); - - var tempFile = TemporaryManager.CreateImaginaryFile(".seproj"); - tempFile.Persist = true; - - File.WriteAllText(tempFile, projectXml.ToString()); - - Process.Start(tempFile); - - await RequestConfirmation($"Please synchronize the database schema between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); - OnProgress(AzureUtilsStage.Database, $"Waiting for database schema synchronization..."); - } - } - - public async Task OpenSQLExaminerData(IWebAppBase sourceApp, IWebAppBase targetApp, bool forCreation = false) - { - String projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Deployment", "GENERAL_ENV_UPGRADE.sdeproj"); - - if (forCreation) - { - projectFile = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), "Deployment", "GENERAL_ENV_CREATE.sdeproj"); - } - - using (Stream stream = GetFileStream(projectFile)) - { - XElement projectXml = XElement.Load(stream); - var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); - var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); - ApplyDatabaseSettingsToProjectXml(projectXml, sourceSettings, targetSettings); - - var tempFile = TemporaryManager.CreateImaginaryFile(".sdeproj"); - tempFile.Persist = true; - - File.WriteAllText(tempFile, projectXml.ToString()); - - Process.Start(tempFile); - - await RequestConfirmation($"Please synchronize the database static collections between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); - OnProgress(AzureUtilsStage.Database, $"Waiting for database static collections synchronization..."); - } - } - - private Stream GetFileStream(String projectFile) - { - byte[] projectBytes = File.ReadAllBytes(projectFile); - MemoryStream stream = new MemoryStream(projectBytes); - return stream; - } - - private void ApplyDatabaseSettingsToProjectXml(XElement projectXml, MachineServiceSettings sourceSettings, MachineServiceSettings targetSettings) - { - var sourceElement = projectXml.Elements().SelectMany(x => x.Descendants()).SingleOrDefault(x => x.Name == "Source" && x.Attributes().SingleOrDefault(y => y.Name == "id").Value == "1"); - var targetElement = projectXml.Elements().SelectMany(x => x.Descendants()).SingleOrDefault(x => x.Name == "Source" && x.Attributes().SingleOrDefault(y => y.Name == "id").Value == "2"); - - ApplyDatabaseSettingsToSourceElement(sourceElement, sourceSettings); - ApplyDatabaseSettingsToSourceElement(targetElement, targetSettings); - } - - private void ApplyDatabaseSettingsToSourceElement(XElement sourceElement, MachineServiceSettings settings) - { - sourceElement.Element("ServerName").SetValue(settings.DB_ADDRESS); - sourceElement.Element("Database").SetValue(settings.DB_CATALOG); - sourceElement.Element("Login").SetValue(settings.DB_USER_NAME); - sourceElement.Element("Password").SetValue(settings.DB_PASSWORD); - } - - #endregion - - #region FTP - - private async Task<List<FtpResult>> DownloadWebAppFiles(IWebAppBase app, String targetFolder) - { - var profile = await app.GetPublishingProfileAsync(); - - using (var ftp = new FtpClient(profile.FtpUrl, profile.FtpUsername, profile.FtpPassword)) - { - var downloadResults = await ftp.DownloadDirectoryAsync(targetFolder, "/site/wwwroot", progress: _ftpDownloadProgress); - - foreach (var downloadResult in downloadResults) - { - if (downloadResult.IsFailed) - { - throw downloadResult.Exception; - } - } - - return downloadResults; - } - } - - private async Task<List<FtpResult>> UploadWebAppFiles(IWebAppBase app, String sourceFolder) - { - var profile = await app.GetPublishingProfileAsync(); - - using (var ftp = new FtpClient(profile.FtpUrl, profile.FtpUsername, profile.FtpPassword)) - { - var uploadResults = await ftp.UploadDirectoryAsync(sourceFolder, "/site/wwwroot", existsMode: FtpRemoteExists.Overwrite, progress: _ftpUploadProgress); - - foreach (var uploadResult in uploadResults) - { - if (uploadResult.IsFailed) - { - throw uploadResult.Exception; - } - } - - return uploadResults; - } - } - - #endregion - - #region Machine Service - - public async Task UpgradeMachineService(IWebAppBase sourceApp, IWebAppBase targetApp) - { - var webAppFilesTempFolder = TemporaryManager.CreateFolder(); - var downloadResults = await DownloadWebAppFiles(sourceApp, webAppFilesTempFolder); - var uploadResults = await UploadWebAppFiles(targetApp, webAppFilesTempFolder + "\\wwwroot"); - } - - #endregion - - #region Applications Versions & Storage Blobs - - public async Task<MachineStudioVersion> GetLatestMachineStudioVersion(IWebAppBase app) - { - MachineServiceSettings settings = null; - - try - { - settings = await app.GetMachineServiceSettingsAsync(); - } - catch (Exception ex) - { - throw new ArgumentException("Could not fetch machine service settings. Please check that all settings are available.", ex); - } - - try - { - DataSource dataSource = settings.ToDataSource(); - - using (var db = ObservablesContext.CreateDefault(dataSource)) - { - var versions = await db.MachineStudioVersions.ToListAsync(); - var latest_machine_version = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); - return latest_machine_version; - } - } - catch (Exception ex) - { - throw new InvalidDataException($"Could not retrieve '{app.Name}' latest Machine Studio version from database.", ex); - } - } - - public async Task<TangoVersion> GetLatestPPCVersion(IWebAppBase app) - { - MachineServiceSettings settings = null; - - try - { - settings = await app.GetMachineServiceSettingsAsync(); - } - catch (Exception ex) - { - throw new ArgumentException("Could not fetch machine service settings. Please check that all settings are available.", ex); - } - - try - { - DataSource dataSource = settings.ToDataSource(); - - using (var db = ObservablesContext.CreateDefault(dataSource)) - { - var versions = await db.TangoVersions.ToListAsync(); - var latest_machine_version = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); - return latest_machine_version; - } - } - catch (Exception ex) - { - throw new InvalidDataException($"Could not retrieve '{app.Name}' latest PPC version from database.", ex); - } - } - - public async Task UpgradeStorage(IWebAppBase sourceApp, IWebAppBase targetApp) - { - await ValidateUpgrade(sourceApp, targetApp); - - var latestMachineStudioVersion = await GetLatestMachineStudioVersion(sourceApp); - var latestPPCVersion = await GetLatestPPCVersion(sourceApp); - - var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); - var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); - - var sourceAccount = CloudStorageAccount.Parse(sourceSettings.STORAGE_ACCOUNT); - var sourceClient = sourceAccount.CreateCloudBlobClient(); - - var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); - var targetClient = targetAccount.CreateCloudBlobClient(); - - var sourceMachineStudioContainer = sourceClient.GetContainerReference(sourceSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); - var targetMachineStudioContainer = targetClient.GetContainerReference(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); - - var sourcePPCContainer = sourceClient.GetContainerReference(sourceSettings.TANGO_VERSIONS_CONTAINER); - var targetPPCContainer = targetClient.GetContainerReference(targetSettings.TANGO_VERSIONS_CONTAINER); - - var sourceMachineStudioBlob = sourceMachineStudioContainer.GetBlockBlobReference(latestMachineStudioVersion.BlobName); - var sourceMachineStudioInstallerBlob = sourceMachineStudioContainer.GetBlockBlobReference(latestMachineStudioVersion.InstallerBlobName); - - var targetMachineStudioBlob = CreateEmptyBlob(targetMachineStudioContainer, sourceMachineStudioBlob.Name); - var targetMachineStudioInstallerBlob = CreateEmptyBlob(targetMachineStudioContainer, sourceMachineStudioInstallerBlob.Name); - - await Task.Factory.StartNew(() => - { - targetMachineStudioBlob.StartCopy(sourceMachineStudioBlob); - targetMachineStudioInstallerBlob.StartCopy(sourceMachineStudioInstallerBlob); - }); - } - - public async Task UpgradeVersions(IWebAppBase sourceApp, IWebAppBase targetApp) - { - await ValidateUpgrade(sourceApp, targetApp); - - if (UpgradeConfiguration.UpgradeMachineStudio) - { - await UpgradeMachineStudioVersion(sourceApp, targetApp); - } - - if (UpgradeConfiguration.UpgradePPC) - { - await UpgradePPCVersion(sourceApp, targetApp); - } - } - - private async Task UpgradeMachineStudioVersion(IWebAppBase sourceApp, IWebAppBase targetApp) - { - var latestMachineStudioVersion = await GetLatestMachineStudioVersion(sourceApp); - - var targetDataSource = (await targetApp.GetMachineServiceSettingsAsync()).ToDataSource(); - - using (var db = ObservablesContext.CreateDefault(targetDataSource)) - { - db.MachineStudioVersions.Add(latestMachineStudioVersion); - await db.SaveChangesAsync(); - } - } - - private async Task UpgradePPCVersion(IWebAppBase sourceApp, IWebAppBase targetApp) - { - var latestPPCVersion = await GetLatestPPCVersion(sourceApp); - - var targetDataSource = (await targetApp.GetMachineServiceSettingsAsync()).ToDataSource(); - - using (var db = ObservablesContext.CreateDefault(targetDataSource)) - { - db.TangoVersions.Add(latestPPCVersion); - await db.SaveChangesAsync(); - } - } - - private CloudBlockBlob CreateEmptyBlob(CloudBlobContainer container, String name) - { - CloudBlockBlob targetBlob = container.GetBlockBlobReference(name); - using (MemoryStream ms = new MemoryStream()) - { - targetBlob.UploadFromStream(ms);//Empty memory stream. Will create an empty blob. - } - - return targetBlob; + } #endregion - #region Validation - - public async Task ValidateUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + public async Task<IDeploymentSlot> CreateDeploymentSlot(IWebApp app, IDeploymentSlot sourceSlot, String slotName) { - if (sourceApp == targetApp) - { - throw new InvalidOperationException("Invalid upgrade configuration. source app and target app are identical."); - } + OnProgress(AzureUtilsStage.Initializing, $"Retrieving '{sourceSlot.Name}' settings..."); - if (UpgradeConfiguration.UpgradeMachineStudio) - { - await ValidateMachineStudioUpgrade(sourceApp, targetApp); - } + var sourceSettings = await sourceSlot.GetMachineServiceSettingsAsync(); + var targetSettings = EnvironmentSettings.FromSlotName(app.Name, slotName); - if (UpgradeConfiguration.UpgradePPC) - { - await ValidatePPCUpgrade(sourceApp, targetApp); - } - } + OnProgress(AzureUtilsStage.Deployment, $"Creating new deployment slot '{targetSettings.SLOT_NAME}'..."); - private async Task ValidateMachineStudioUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) - { - var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); - var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); - - var latestSourceMachineStudioVersion = await GetLatestMachineStudioVersion(sourceApp); - - //Check if there is any source machine studio version. - if (latestSourceMachineStudioVersion == null) - { - throw new ValidationException("Could not locate a Machine Studio version entry on the source database."); - } - - var latestTargetMachineStudioVersion = await GetLatestMachineStudioVersion(targetApp); - - //Check target latest machine studio version is older if there is any. - if (latestTargetMachineStudioVersion != null && Version.Parse(latestSourceMachineStudioVersion.Version) <= Version.Parse(latestTargetMachineStudioVersion.Version)) - { - throw new ValidationException($"Machine Studio source version is '{latestSourceMachineStudioVersion.Version}' while target version is '{latestTargetMachineStudioVersion.Version}'."); - } + var dictionary = new Dictionary<string, string>(); + dictionary.Add(nameof(MachineServiceSettings.DB_ADDRESS), sourceSettings.DB_ADDRESS); + dictionary.Add(nameof(MachineServiceSettings.DB_CATALOG), targetSettings.DB_CATALOG); + dictionary.Add(nameof(MachineServiceSettings.DB_PASSWORD), sourceSettings.DB_PASSWORD); + dictionary.Add(nameof(MachineServiceSettings.DB_USER_NAME), sourceSettings.DB_USER_NAME); + dictionary.Add(nameof(MachineServiceSettings.DEPLOYMENT_SLOT), slotName); + dictionary.Add(nameof(MachineServiceSettings.ENFORCE_MACHINE_STUDIO_VERSION), sourceSettings.ENFORCE_MACHINE_STUDIO_VERSION); + dictionary.Add(nameof(MachineServiceSettings.ENVIRONMENT_GROUP), targetSettings.ENVIRONMENT_GROUP); + dictionary.Add(nameof(MachineServiceSettings.STORAGE_ACCOUNT), sourceSettings.STORAGE_ACCOUNT); + dictionary.Add(nameof(MachineServiceSettings.MACHINE_STUDIO_VERSIONS_CONTAINER), targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); + dictionary.Add(nameof(MachineServiceSettings.TANGO_VERSIONS_CONTAINER), targetSettings.TANGO_VERSIONS_CONTAINER); - var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); - var targetClient = targetAccount.CreateCloudBlobClient(); + var slot = await app.DeploymentSlots + .Define(targetSettings.SLOT_NAME) + .WithBrandNewConfiguration() + .WithWebAppAlwaysOn(true) + .WithWebSocketsEnabled(true) + .WithStickyAppSettings(dictionary) + .WithStickyConnectionString(targetSettings.DB_CATALOG, $"Server=tcp:twine.database.windows.net,1433;Initial Catalog={targetSettings.DB_CATALOG};Persist Security Info=False;User ID=BackupUser;Password=Aa123456;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", Microsoft.Azure.Management.AppService.Fluent.Models.ConnectionStringType.SQLAzure) + .CreateAsync(); - var targetMachineStudioContainer = targetClient.GetContainerReference(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); - - //Check machine studio binaries blob not exists on the target. - var targetMachineStudioBlob = targetMachineStudioContainer.GetBlockBlobReference(latestSourceMachineStudioVersion.BlobName); - if (await targetMachineStudioBlob.ExistsAsync()) - { - throw new ValidationException($"Machine Studio Block blob '{latestSourceMachineStudioVersion.BlobName}' already exists on the target storage."); - } - - //Check machine studio installer blob not exists on the target. - var targetMachineStudioInstallerBlob = targetMachineStudioContainer.GetBlockBlobReference(latestSourceMachineStudioVersion.InstallerBlobName); - if (await targetMachineStudioInstallerBlob.ExistsAsync()) - { - throw new ValidationException($"Machine Studio Block blob '{latestSourceMachineStudioVersion.InstallerBlobName}' already exists on the target storage."); - } + return slot; } - private async Task ValidatePPCUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + public async Task<IDeploymentSlot> GetDeploymentSlot(IWebApp app, String slotName) { - var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); - var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); - - var latestSourcePPCVersion = await GetLatestPPCVersion(sourceApp); - - //Check if there is any source PPC version. - if (latestSourcePPCVersion == null) - { - throw new ValidationException("Could not locate a PPC version entry on the source database."); - } - - var latestTargetPPCVersion = await GetLatestPPCVersion(targetApp); - - //Check target latest PPC version is older if there is any. - if (latestTargetPPCVersion != null && Version.Parse(latestSourcePPCVersion.Version) <= Version.Parse(latestTargetPPCVersion.Version)) - { - throw new ValidationException($"PPC source version is '{latestSourcePPCVersion.Version}' while target version is '{latestTargetPPCVersion.Version}'."); - } - - var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); - var targetClient = targetAccount.CreateCloudBlobClient(); - - var targetPPCContainer = targetClient.GetContainerReference(targetSettings.TANGO_VERSIONS_CONTAINER); - - //Check PPC binaries blob not exists on the target. - var targetPPCBlob = targetPPCContainer.GetBlockBlobReference(latestSourcePPCVersion.BlobName); - if (await targetPPCBlob.ExistsAsync()) - { - throw new ValidationException($"PPC Block blob '{latestSourcePPCVersion.BlobName}' already exists on the target storage."); - } - - //Check PPC installer blob not exists on the target. - var targetPPCInstallerBlob = targetPPCContainer.GetBlockBlobReference(latestSourcePPCVersion.InstallerBlobName); - if (await targetPPCInstallerBlob.ExistsAsync()) - { - throw new ValidationException($"PPC Block blob '{latestSourcePPCVersion.InstallerBlobName}' already exists on the target storage."); - } + OnProgress(AzureUtilsStage.Deployment, $"Checking '{app.Name}' deployment slots..."); + IDeploymentSlot slot = (await app.DeploymentSlots.ListAsync()).ToList().SingleOrDefault(x => x.Name == slotName); + return slot; } - - #endregion } } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/UpgradeConfiguration.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/UpgradeConfiguration.cs deleted file mode 100644 index ab1099617..000000000 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/Deployment/UpgradeConfiguration.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Tango.Core; - -namespace Tango.AzureUtils.Deployment -{ - public class UpgradeConfiguration : ExtendedObject - { - private bool _UpgradeMachineService; - public bool UpgradeMachineService - { - get { return _UpgradeMachineService; } - set { _UpgradeMachineService = value; RaisePropertyChangedAuto(); } - } - - private bool _UpgradeMachineStudio; - public bool UpgradeMachineStudio - { - get { return _UpgradeMachineStudio; } - set { _UpgradeMachineStudio = value; RaisePropertyChangedAuto(); } - } - - private bool _UpgradePPC; - public bool UpgradePPC - { - get { return _UpgradePPC; } - set { _UpgradePPC = value; RaisePropertyChangedAuto(); } - } - } -} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/CreateEnvironmentConfiguration.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/CreateEnvironmentConfiguration.cs new file mode 100644 index 000000000..bf1c2cb31 --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/CreateEnvironmentConfiguration.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.AzureUtils.Environment +{ + public class CreateEnvironmentConfiguration + { + public String Email { get; set; } + public String Password { get; set; } + + public bool CreateEnvironmentGroup { get; set; } = true; + public bool AddEnvironmentGroupAdminUser { get; set; } = true; + public bool CloneEnvironmentGroupUsers { get; set; } = true; + public bool CreateDeploymentSlot { get; set; } = true; + public bool CreateDatabase { get; set; } = true; + public bool AddDatabasePermissionsForEnvironmentGroup { get; set; } = true; + public bool CreateDatabaseBackupUser { get; set; } = true; + public bool SynchronizeDatabaseSchema { get; set; } = true; + public bool SynchronizeDatabaseData { get; set; } = true; + public bool CreateStorageContainers { get; set; } = true; + public bool CopyStorageBlobs { get; set; } = true; + public bool CopyMachineServiceFiles { get; set; } = true; + public bool IgnoreExistingSlot { get; set; } = false; + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentManager.cs index 822813742..f30a51671 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentManager.cs +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentManager.cs @@ -10,7 +10,10 @@ using Microsoft.Azure.Management.RecoveryServices.Backup; using Microsoft.Azure.Management.Sql.Fluent.Models; using Microsoft.WindowsAzure.Storage; using Tango.AzureUtils.ActiveDirectory; +using Tango.AzureUtils.Database; using Tango.AzureUtils.Deployment; +using Tango.AzureUtils.FTP; +using Tango.AzureUtils.Storage; using Tango.BL; using Tango.Core; using Tango.Core.DB; @@ -21,197 +24,314 @@ namespace Tango.AzureUtils.Environment { private ActiveDirectoryManager _adManager; private DeploymentManager _deploymentManager; + private DatabaseManager _databaseManager; + private StorageManager _storageManager; + private FtpManager _ftpManager; public EnvironmentManager(IAzure azure) : base(azure) { - _adManager = new ActiveDirectoryManager(azure, AzureUtilsAuthenticationFactory.GetCredentials()); - _adManager.ConfirmationRequired += (x, e) => OnRequestConfirmation(e); - _adManager.Progress += (x, e) => OnProgress(e); - - _deploymentManager = new DeploymentManager(azure); - _deploymentManager.ConfirmationRequired += (x, e) => OnRequestConfirmation(e); - _deploymentManager.Progress += (x, e) => OnProgress(e); + _adManager = CreateManager<ActiveDirectoryManager>(); + _deploymentManager = CreateManager<DeploymentManager>(); + _databaseManager = CreateManager<DatabaseManager>(); + _storageManager = CreateManager<StorageManager>(); + _ftpManager = CreateManager<FtpManager>(); } - #region Deployment Slots + #region Creation - public async Task<IDeploymentSlot> CreateDeploymentSlot(IWebApp app, IDeploymentSlot sourceSlot, String name, String adEmail, String adPassword) + public async Task<IDeploymentSlot> CreateEnvironment(IWebApp app, IDeploymentSlot sourceSlot, String name, CreateEnvironmentConfiguration config) { - String dbCatalog = $"Tango_{name}"; - String machineStudioContainerName = $"machine-studio-versions-{name.ToLower()}"; - String ppcContainerName = $"tango-versions-{name.ToLower()}"; - String machineServiceBackupsContainerName = $"machine-service-backups-{name.ToLower()}"; - String machineServiceLogsContainerName = $"machine-service-logs-{name.ToLower()}"; - String environmentGroupName = $"Tango {name}"; - String slotName = app.Name + "-" + name; + OnProgress(AzureUtilsStage.Environment, $"Retrieving '{sourceSlot.Name}' settings..."); + + var sourceSettings = await sourceSlot.GetMachineServiceSettingsAsync(); + var targetSettings = EnvironmentSettings.FromSlotName(app.Name, name); + + if (!config.IgnoreExistingSlot) + { + var deploymentSlots = await app.DeploymentSlots.ListAsync(); + if (deploymentSlots.ToList().Exists(x => x.Name.ToLower() == targetSettings.SLOT_NAME.ToLower())) + { + throw new InvalidOperationException($"Deployment slot '{targetSettings.SLOT_NAME}' already exists."); + } + } + + //Add Environment Group + await _adManager.Authenticate(config.Email, config.Password); + + if (config.CreateEnvironmentGroup) + { + if (!await _adManager.IsGroupExists(targetSettings.ENVIRONMENT_GROUP)) + { + await _adManager.AddGroup(targetSettings.ENVIRONMENT_GROUP); + + if (config.AddEnvironmentGroupAdminUser) + { + await _adManager.AddUserToGroup(targetSettings.ENVIRONMENT_GROUP, config.Email); + } + } + else + { + await RequestConfirmation($"Environment group '{targetSettings.ENVIRONMENT_GROUP}' already exists. Do you wish to continue?"); + } + } - OnProgress(AzureUtilsStage.Initializing, $"Retrieving '{sourceSlot.Name}' settings..."); - var settings = await sourceSlot.GetMachineServiceSettingsAsync(); - OnProgress(AzureUtilsStage.EnvironmentGroup, $"Authenticating with active directory graph..."); - await _adManager.Authenticate(adEmail, adPassword); + //Add Deployment Slot + var slot = await _deploymentManager.GetDeploymentSlot(app, targetSettings.SLOT_NAME); - if (!await _adManager.IsGroupExists(environmentGroupName)) + if (config.CreateDeploymentSlot) { - OnProgress(AzureUtilsStage.EnvironmentGroup, $"Creating environment group '{environmentGroupName}'..."); - await _adManager.AddGroup(environmentGroupName); + if (slot == null) + { + slot = await _deploymentManager.CreateDeploymentSlot(app, sourceSlot, targetSettings.SLOT_NAME); + } + else + { + await RequestConfirmation($"Deployment slot '{targetSettings.SLOT_NAME}' already exists. Do you wish to continue."); + } + } - OnProgress(AzureUtilsStage.EnvironmentGroup, $"Adding environment group user '{adEmail}'..."); - await _adManager.AddUserToGroup(environmentGroupName, adEmail); + //Add Database + if (config.CreateDatabase) + { + if (!await _databaseManager.IsDatabaseExists("twine", targetSettings.DB_CATALOG)) + { + await _databaseManager.AddDatabase("twine", targetSettings.DB_CATALOG); + } + else + { + await RequestConfirmation($"Database '{targetSettings.DB_CATALOG}' already exists. Do you wish to continue?"); + } } - else + + //Add permissions for environment group on database + if (config.AddDatabasePermissionsForEnvironmentGroup) { - await RequestConfirmation($"Environment group '{environmentGroupName}' already exists. Do you wish to continue?"); + try + { + await _databaseManager.AddEnvironmentGroupPermissions(sourceSettings.DB_ADDRESS, targetSettings.DB_CATALOG, targetSettings.ENVIRONMENT_GROUP, config.Email, config.Password); + } + catch (Exception ex) + { + await RequestConfirmation($"Error creating/adding permissions for environment group '{targetSettings.ENVIRONMENT_GROUP}' on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); + } } - OnProgress(AzureUtilsStage.DeploymentSlot, $"Checking '{app.Name}' deployment slots..."); - IDeploymentSlot slot = (await app.DeploymentSlots.ListAsync()).ToList().SingleOrDefault(x => x.Name == slotName); + //Create backup user + if (config.CreateDatabaseBackupUser) + { + try + { + await _databaseManager.AddBackupUser(sourceSettings.DB_ADDRESS, targetSettings.DB_CATALOG, sourceSettings.DB_USER_NAME, sourceSettings.DB_PASSWORD); + } + catch (Exception ex) + { + + await RequestConfirmation($"Error creating/adding permissions for BackupUser on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); + } + } - if (slot == null) + //Synchronize Schema + if (config.SynchronizeDatabaseSchema) { - //Add Slot - OnProgress(AzureUtilsStage.DeploymentSlot, $"Creating new deployment slot '{slotName}'..."); + await _databaseManager.OpenSQLExaminerSchema(sourceSlot, slot); + await RequestConfirmation($"Please synchronize the database schema between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); + OnProgress(AzureUtilsStage.Environment, $"Waiting for database schema synchronization..."); + } - var dictionary = new Dictionary<string, string>(); - dictionary.Add(nameof(MachineServiceSettings.DB_ADDRESS), settings.DB_ADDRESS); - dictionary.Add(nameof(MachineServiceSettings.DB_CATALOG), dbCatalog); - dictionary.Add(nameof(MachineServiceSettings.DB_PASSWORD), settings.DB_PASSWORD); - dictionary.Add(nameof(MachineServiceSettings.DB_USER_NAME), settings.DB_USER_NAME); - dictionary.Add(nameof(MachineServiceSettings.DEPLOYMENT_SLOT), name); - dictionary.Add(nameof(MachineServiceSettings.ENFORCE_MACHINE_STUDIO_VERSION), settings.ENFORCE_MACHINE_STUDIO_VERSION); - dictionary.Add(nameof(MachineServiceSettings.ENVIRONMENT_GROUP), environmentGroupName); - dictionary.Add(nameof(MachineServiceSettings.STORAGE_ACCOUNT), settings.STORAGE_ACCOUNT); - dictionary.Add(nameof(MachineServiceSettings.MACHINE_STUDIO_VERSIONS_CONTAINER), machineStudioContainerName); - dictionary.Add(nameof(MachineServiceSettings.TANGO_VERSIONS_CONTAINER), ppcContainerName); - slot = await app.DeploymentSlots - .Define(slotName) - .WithBrandNewConfiguration() - .WithWebAppAlwaysOn(true) - .WithWebSocketsEnabled(true) - .WithStickyAppSettings(dictionary) - .WithStickyConnectionString(dbCatalog, $"Server=tcp:twine.database.windows.net,1433;Initial Catalog={dbCatalog};Persist Security Info=False;User ID=BackupUser;Password=Aa123456;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", Microsoft.Azure.Management.AppService.Fluent.Models.ConnectionStringType.SQLAzure) - .CreateAsync(); + //Synchronize Data + if (config.SynchronizeDatabaseData) + { + await _databaseManager.OpenSQLExaminerData(sourceSlot, slot, true); + await RequestConfirmation($"Please synchronize the database static collections between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); + OnProgress(AzureUtilsStage.Environment, $"Waiting for database static collections synchronization..."); } - else + + + //Add Storage Containers + await _storageManager.Connect(sourceSettings.STORAGE_ACCOUNT); + + if (config.CreateStorageContainers) { - await RequestConfirmation($"Deployment slot '{slotName}' already exists. Do you wish to continue."); + await _storageManager.CreateContainer(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); + await _storageManager.CreateContainer(targetSettings.TANGO_VERSIONS_CONTAINER); + await _storageManager.CreateContainer(targetSettings.MACHINE_SERVICE_BACKUPS_CONTAINER); + await _storageManager.CreateContainer(targetSettings.MACHINE_SERVICE_LOGS_CONTAINER); } - //Add Database - OnProgress(AzureUtilsStage.Database, $"Checking twine database server..."); - var sqlServer = (await Azure.SqlServers.ListAsync()).SingleOrDefault(x => x.Name == "twine"); + if (config.CopyStorageBlobs) + { + //Add Machine Studio storage versions. + try + { + await _storageManager.ValidateMachineStudioStorageUpgrade(sourceSlot, slot); + await _storageManager.UpgradeMachineStudioStorage(sourceSlot, slot); + } + catch (Exception ex) + { + await RequestConfirmation($"Issues encountered with upgrading machine studio storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); + } - var existingDatabase = (await sqlServer.Databases.ListAsync()).SingleOrDefault(x => x.Name == dbCatalog); + //Add PPC storage versions. + try + { + await _storageManager.ValidatePPCStorageUpgrade(sourceSlot, slot); + await _storageManager.UpgradePPCStorage(sourceSlot, slot); + } + catch (Exception ex) + { + await RequestConfirmation($"Issues encountered with upgrading PPC storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); + } + } - if (existingDatabase == null) + //Copy Website files. + if (config.CopyMachineServiceFiles) { - OnProgress(AzureUtilsStage.Database, $"Creating new database '{dbCatalog}'..."); - var database = await sqlServer.Databases.Define(dbCatalog).WithEdition(DatabaseEdition.Standard).CreateAsync(); + await _ftpManager.CopyAppFiles(sourceSlot, slot); } - else + + //Restart slot. + OnProgress(AzureUtilsStage.Environment, "Restarting deployment slot..."); + await slot.RestartAsync(); + + OnCompleted("Environment created successfully."); + + return slot; + } + + public async Task UpgradeEnvironment(IWebAppBase sourceApp, IWebAppBase targetApp, UpgradeEnvironmentConfiguration config) + { + await ValidateEnvironmentUpgrade(sourceApp, targetApp, config); + + OnProgress(AzureUtilsStage.Environment, $"Retrieving '{sourceApp.Name}' settings..."); + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + + //Synchronize Schema + if (config.SynchronizeDatabaseSchema) { - await RequestConfirmation($"Database '{dbCatalog}' already exists. Do you wish to continue?"); + await _databaseManager.OpenSQLExaminerSchema(sourceApp, targetApp); + await RequestConfirmation($"Please synchronize the database schema between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); + OnProgress(AzureUtilsStage.Environment, $"Waiting for database schema synchronization..."); } - //Add permissions for environment group on database - OnProgress(AzureUtilsStage.Database, $"Adding environment group permissions on '{dbCatalog}'..."); - using (DbManager db = DbManager.FromDataSource(new DataSource() + //Synchronize Data + if (config.SynchronizeDatabaseData) { - Type = DataSourceType.Azure, - Address = settings.DB_ADDRESS, - Catalog = dbCatalog, - UserName = adEmail, - Password = adPassword, - IntegratedSecurity = false - })) + await _databaseManager.OpenSQLExaminerData(sourceApp, targetApp); + await RequestConfirmation($"Please synchronize the database static collections between '{sourceSettings.DB_CATALOG}' and '{targetSettings.DB_CATALOG}'.\nPlease confirm in order to continue."); + OnProgress(AzureUtilsStage.Environment, $"Waiting for database static collections synchronization..."); + } + + //Add Machine Studio storage versions. + if (config.CopyMachineStudioStorageBlobs) { try { - await db.ExecuteCommandAsync($"CREATE USER [{environmentGroupName}] FROM EXTERNAL PROVIDER"); - await db.ExecuteCommandAsync($"ALTER ROLE db_datareader ADD MEMBER [{environmentGroupName}];"); - await db.ExecuteCommandAsync($"ALTER ROLE db_datawriter ADD MEMBER [{environmentGroupName}];"); + await _storageManager.ValidateMachineStudioStorageUpgrade(sourceApp, targetApp); + await _storageManager.UpgradeMachineStudioStorage(sourceApp, targetApp); } catch (Exception ex) { - await RequestConfirmation($"Error creating/adding permissions for environment group '{environmentGroupName}' on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); + await RequestConfirmation($"Issues encountered with upgrading machine studio storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } - OnProgress(AzureUtilsStage.Database, $"Adding BackupUser permissions on '{dbCatalog}'..."); - //Create backup user - using (DbManager db = DbManager.FromCredentials(settings.DB_ADDRESS, dbCatalog, settings.DB_USER_NAME, settings.DB_PASSWORD)) + //Add PPC storage versions. + if (config.CopyPPCStorageBlobs) { try { - await db.ExecuteCommandAsync("CREATE USER [BackupUser] FOR LOGIN [BackupUser] WITH DEFAULT_SCHEMA=[dbo]"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_owner', N'BackupUser'"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_accessadmin', N'BackupUser'"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_securityadmin', N'BackupUser'"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_backupoperator', N'BackupUser'"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_datareader', N'BackupUser'"); - await db.ExecuteCommandAsync("EXEC sp_addrolemember N'db_datawriter', N'BackupUser'"); + await _storageManager.ValidatePPCStorageUpgrade(sourceApp, targetApp); + await _storageManager.UpgradePPCStorage(sourceApp, targetApp); } catch (Exception ex) { - await RequestConfirmation($"Error creating/adding permissions for BackupUser on database.\n{ex.FlattenMessage()}\n\nDo you wish to continue?"); + await RequestConfirmation($"Issues encountered with upgrading PPC storage versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); } } - //Synchronize Schema - //await _deploymentManager.OpenSQLExaminerSchema(sourceSlot, slot); - //await RequestConfirmation($"Please synchronize the database schema between '{settings.DB_CATALOG}' and '{dbCatalog}'.\nPlease confirm in order to continue."); - //OnProgress(AzureUtilsStage.Database, $"Waiting for database schema synchronization..."); + //Upgrade machine studio database version. + if (config.UpgradeMachineStudioDatabaseVersion) + { + try + { + await _databaseManager.ValidateMachineStudioDatabaseUpgrade(sourceApp, targetApp); + await _databaseManager.UpgradeMachineStudioVersion(sourceApp, targetApp); + } + catch (Exception ex) + { + await RequestConfirmation($"Issues encountered with upgrading Machine Studio database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); + } + } - //await _deploymentManager.OpenSQLExaminerData(sourceSlot, slot, true); - //await RequestConfirmation($"Please synchronize the database static collections between '{settings.DB_CATALOG}' and '{dbCatalog}'.\nPlease confirm in order to continue."); - //OnProgress(AzureUtilsStage.Database, $"Waiting for database static collections synchronization..."); + //Upgrade PPC database version. + if (config.UpgradePPCDatabaseVersion) + { + try + { + await _databaseManager.ValidatePPCDatabaseUpgrade(sourceApp, targetApp); + await _databaseManager.UpgradePPCVersion(sourceApp, targetApp); + } + catch (Exception ex) + { + await RequestConfirmation($"Issues encountered with upgrading PPC database versions.\n{ex.FlattenMessage()}\nDo you wish to continue?"); + } + } - //OnProgress(AzureUtilsStage.Database, $"Retrieving latest Machine Studio version..."); - //var latestMsVersion = await _deploymentManager.GetLatestMachineStudioVersion(sourceSlot); - //OnProgress(AzureUtilsStage.Database, $"Retrieving latest PPC version..."); - //var latestPPCVersion = await _deploymentManager.GetLatestPPCVersion(sourceSlot); + //Copy Website files. + if (config.CopyMachineServiceFiles) + { + await _ftpManager.CopyAppFiles(sourceApp, targetApp); + } - //var targetSettings = await slot.GetMachineServiceSettingsAsync(); + //Restart slot. + OnProgress(AzureUtilsStage.Environment, "Restarting traget deployment slot..."); + await targetApp.RestartAsync(); - //using (ObservablesContext db = ObservablesContext.CreateDefault(targetSettings.ToDataSource())) - //{ - // db.MachineStudioVersions.Add(latestMsVersion); - // db.TangoVersions.Add(latestPPCVersion); - //} + OnCompleted("Environment upgraded successfully."); + } - //Add Storage Containers - OnProgress(AzureUtilsStage.Storage, $"Connecting to storage account..."); - var targetAccount = CloudStorageAccount.Parse(settings.STORAGE_ACCOUNT); - var targetClient = targetAccount.CreateCloudBlobClient(); + public async Task ValidateEnvironmentUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp, UpgradeEnvironmentConfiguration config) + { + if (sourceApp == targetApp || sourceApp.Name == targetApp.Name) + { + throw new InvalidOperationException("Source slot and target slot are identical."); + } - OnProgress(AzureUtilsStage.Storage, $"Creating storage container '{machineStudioContainerName}'..."); - var machineStudioContainer = targetClient.GetContainerReference(machineStudioContainerName); - await machineStudioContainer.CreateIfNotExistsAsync(); + //Add Machine Studio storage versions. + if (config.CopyMachineStudioStorageBlobs) + { + await _storageManager.ValidateMachineStudioStorageUpgrade(sourceApp, targetApp); + } - OnProgress(AzureUtilsStage.Storage, $"Creating storage container '{ppcContainerName}'..."); - var ppcContainer = targetClient.GetContainerReference(ppcContainerName); - await ppcContainer.CreateIfNotExistsAsync(); + //Add PPC storage versions. + if (config.CopyPPCStorageBlobs) + { + await _storageManager.ValidatePPCStorageUpgrade(sourceApp, targetApp); + } - OnProgress(AzureUtilsStage.Storage, $"Creating storage container '{machineServiceBackupsContainerName}'..."); - var machineServiceBackupsContainer = targetClient.GetContainerReference(machineServiceBackupsContainerName); - await machineServiceBackupsContainer.CreateIfNotExistsAsync(); + //Upgrade machine studio database version. + if (config.UpgradeMachineStudioDatabaseVersion) + { + await _databaseManager.ValidateMachineStudioDatabaseUpgrade(sourceApp, targetApp); + } - OnProgress(AzureUtilsStage.Storage, $"Creating storage container '{machineServiceLogsContainerName}'..."); - var machineServiceLogsContainer = targetClient.GetContainerReference(machineServiceLogsContainerName); - await machineServiceLogsContainer.CreateIfNotExistsAsync(); + //Upgrade PPC database version. + if (config.UpgradePPCDatabaseVersion) + { + await _databaseManager.ValidatePPCDatabaseUpgrade(sourceApp, targetApp); + } + } - _deploymentManager.UpgradeConfiguration.UpgradeMachineService = true; - _deploymentManager.UpgradeConfiguration.UpgradeMachineStudio = true; - _deploymentManager.UpgradeConfiguration.UpgradePPC = true; + #endregion - await _deploymentManager.PerformFullUpgrade(sourceSlot, slot); + #region Upgrade - OnProgress(AzureUtilsStage.Ready, "Deployment slot created successfully.", 0, 100, false); - return slot; - } #endregion } diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentSettings.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentSettings.cs new file mode 100644 index 000000000..8b8e3a757 --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/EnvironmentSettings.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.AzureUtils.Environment +{ + public class EnvironmentSettings + { + public String SLOT_NAME { get; set; } + public String DB_CATALOG { get; set; } + public String MACHINE_STUDIO_VERSIONS_CONTAINER { get; set; } + public String TANGO_VERSIONS_CONTAINER { get; set; } + public String MACHINE_SERVICE_BACKUPS_CONTAINER { get; set; } + public String MACHINE_SERVICE_LOGS_CONTAINER { get; set; } + public String ENVIRONMENT_GROUP { get; set; } + + public static EnvironmentSettings FromSlotName(String appName, String name) + { + EnvironmentSettings settings = new EnvironmentSettings(); + + settings.SLOT_NAME = appName + "-" + name; + settings.DB_CATALOG = $"Tango_{name}"; + settings.MACHINE_STUDIO_VERSIONS_CONTAINER = $"machine-studio-versions-{name.ToLower()}"; + settings.TANGO_VERSIONS_CONTAINER = $"tango-versions-{name.ToLower()}"; + settings.MACHINE_SERVICE_BACKUPS_CONTAINER = $"machine-service-backups-{name.ToLower()}"; + settings.MACHINE_SERVICE_LOGS_CONTAINER = $"machine-service-logs-{name.ToLower()}"; + settings.ENVIRONMENT_GROUP = $"Tango {name}"; + + return settings; + } + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/UpgradeEnvironmentConfiguration.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/UpgradeEnvironmentConfiguration.cs new file mode 100644 index 000000000..537056701 --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Environment/UpgradeEnvironmentConfiguration.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.AzureUtils.Environment +{ + public class UpgradeEnvironmentConfiguration + { + public bool SynchronizeDatabaseSchema { get; set; } = true; + public bool SynchronizeDatabaseData { get; set; } = true; + public bool CopyMachineStudioStorageBlobs { get; set; } = true; + public bool UpgradeMachineStudioDatabaseVersion { get; set; } = true; + public bool CopyPPCStorageBlobs { get; set; } = true; + public bool UpgradePPCDatabaseVersion { get; set; } = true; + public bool CopyMachineServiceFiles { get; set; } = true; + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/FTP/FtpManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/FTP/FtpManager.cs new file mode 100644 index 000000000..5a174dcb2 --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/FTP/FtpManager.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Authentication; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using FluentFTP; +using Microsoft.Azure.Management.AppService.Fluent; +using Microsoft.Azure.Management.Fluent; + +namespace Tango.AzureUtils.FTP +{ + public class FtpManager : AzureUtilsComponentBase + { + private IProgress<FtpProgress> _ftpDownloadProgress; + private IProgress<FtpProgress> _ftpUploadProgress; + private String _currentDownloadFile; + + public FtpManager(IAzure azure) : base(azure) + { + _ftpDownloadProgress = new Progress<FtpProgress>((p) => + { + OnProgress(AzureUtilsStage.FtpDownload, $"Downloading {_currentDownloadFile}...", p.Progress, 100, false); + }); + + _ftpUploadProgress = new Progress<FtpProgress>((p) => + { + OnProgress(AzureUtilsStage.FtpUpload, $"Uploading {_currentDownloadFile}...", p.Progress, 100, false); + }); + } + + private FtpClient CreateFtpClient(String address, String userName, String password) + { + Uri uri = new Uri("ftp://" + address); + string host = uri.Host; + + FtpClient client = new FtpClient(host, userName, password); + //client.EncryptionMode = FtpEncryptionMode.Explicit; + client.SslProtocols = SslProtocols.Tls; + client.ValidateAnyCertificate = true; + client.DataConnectionType = FtpDataConnectionType.PASV; + client.DownloadDataType = FtpDataType.Binary; + client.RetryAttempts = 5; + client.SocketPollInterval = 1000; + client.ConnectTimeout = 2000; + client.ReadTimeout = 2000; + client.DataConnectionConnectTimeout = 2000; + client.DataConnectionReadTimeout = 2000; + client.OnLogEvent = (ev, msg) => + { + if (msg.Contains("DownloadFileAsync") || msg.Contains("UploadFileAsync")) + { + var matches = Regex.Matches(msg, "(?<=\").+?(?=\")"); + var match = matches.OfType<Match>().LastOrDefault(); + + if (match != null) + { + _currentDownloadFile = match.ToString(); + } + } + }; + + return client; + } + + public async Task<List<FtpResult>> DownloadWebAppFiles(IWebAppBase app, String targetFolder) + { + OnProgress(AzureUtilsStage.FtpDownload, $"Downloading web app files for '{app.Name}'..."); + + var profile = await app.GetPublishingProfileAsync(); + + using (var ftp = CreateFtpClient(profile.FtpUrl, profile.FtpUsername, profile.FtpPassword)) + { + await ftp.ConnectAsync(); + var downloadResults = await ftp.DownloadDirectoryAsync(targetFolder, "/site/wwwroot", progress: _ftpDownloadProgress); + + foreach (var downloadResult in downloadResults) + { + if (downloadResult.IsFailed) + { + throw downloadResult.Exception; + } + } + + return downloadResults; + } + } + + public async Task<List<FtpResult>> UploadWebAppFiles(IWebAppBase app, String sourceFolder) + { + OnProgress(AzureUtilsStage.FtpUpload, $"Uploading web app files for '{app.Name}'..."); + + var profile = await app.GetPublishingProfileAsync(); + + using (var ftp = CreateFtpClient(profile.FtpUrl, profile.FtpUsername, profile.FtpPassword)) + { + var uploadResults = await ftp.UploadDirectoryAsync(sourceFolder, "/site/wwwroot", existsMode: FtpRemoteExists.Overwrite, progress: _ftpUploadProgress); + + foreach (var uploadResult in uploadResults) + { + if (uploadResult.IsFailed) + { + throw uploadResult.Exception; + } + } + + return uploadResults; + } + } + + public async Task CopyAppFiles(IWebAppBase sourceApp, IWebAppBase targetApp) + { + var webAppFilesTempFolder = TemporaryManager.CreateFolder(); + var downloadResults = await DownloadWebAppFiles(sourceApp, webAppFilesTempFolder); + var uploadResults = await UploadWebAppFiles(targetApp, webAppFilesTempFolder); + } + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Storage/StorageManager.cs b/Software/Visual_Studio/Azure/Tango.AzureUtils/Storage/StorageManager.cs new file mode 100644 index 000000000..910f4b84f --- /dev/null +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Storage/StorageManager.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Azure.Management.AppService.Fluent; +using Microsoft.Azure.Management.Fluent; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Blob; +using Tango.AzureUtils.Database; + +namespace Tango.AzureUtils.Storage +{ + public class StorageManager : AzureUtilsComponentBase + { + private String _account; + private CloudBlobClient _client; + private DatabaseManager _databaseManager; + + public StorageManager(IAzure azure) : base(azure) + { + _databaseManager = CreateManager<DatabaseManager>(); + } + + public Task Connect(String account) + { + OnProgress(AzureUtilsStage.Storage, $"Connecting to storage account..."); + _account = account; + var targetAccount = CloudStorageAccount.Parse(_account); + _client = targetAccount.CreateCloudBlobClient(); + + return Task.FromResult(true); + } + + private async Task<CloudBlockBlob> CreateEmptyBlob(CloudBlobContainer container, String name) + { + OnProgress(AzureUtilsStage.Storage, $"Creating blob '{name}'..."); + + CloudBlockBlob targetBlob = container.GetBlockBlobReference(name); + using (MemoryStream ms = new MemoryStream()) + { + await targetBlob.UploadFromStreamAsync(ms);//Empty memory stream. Will create an empty blob. + } + + return targetBlob; + } + + public async Task CreateContainer(String name) + { + OnProgress(AzureUtilsStage.Storage, $"Creating storage container '{name}'..."); + var container = _client.GetContainerReference(name); + await container.CreateIfNotExistsAsync(); + } + + public async Task UpgradePPCStorage(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Storage, $"Retrieving source and target settings..."); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + var latestPPCVersion = await _databaseManager.GetLatestPPCVersion(sourceApp); + + OnProgress(AzureUtilsStage.Storage, $"Upgrading PPC version storage..."); + + var sourceAccount = CloudStorageAccount.Parse(sourceSettings.STORAGE_ACCOUNT); + var sourceClient = sourceAccount.CreateCloudBlobClient(); + + var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); + var targetClient = targetAccount.CreateCloudBlobClient(); + + var sourcePPCContainer = sourceClient.GetContainerReference(sourceSettings.TANGO_VERSIONS_CONTAINER); + var targetPPCContainer = targetClient.GetContainerReference(targetSettings.TANGO_VERSIONS_CONTAINER); + + var sourcePPCBlob = sourcePPCContainer.GetBlockBlobReference(latestPPCVersion.BlobName); + var sourcePPCInstallerBlob = sourcePPCContainer.GetBlockBlobReference(latestPPCVersion.InstallerBlobName); + + var targetPPCBlob = await CreateEmptyBlob(targetPPCContainer, sourcePPCBlob.Name); + var targetPPCInstallerBlob = await CreateEmptyBlob(targetPPCContainer, sourcePPCInstallerBlob.Name); + + await Task.Factory.StartNew(() => + { + targetPPCBlob.StartCopy(sourcePPCBlob); + targetPPCInstallerBlob.StartCopy(sourcePPCInstallerBlob); + }); + } + + public async Task UpgradeMachineStudioStorage(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Storage, $"Retrieving source and target settings..."); + + var latestMachineStudioVersion = await _databaseManager.GetLatestMachineStudioVersion(sourceApp); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + OnProgress(AzureUtilsStage.Storage, $"Upgrading Machine Studio version storage..."); + + var sourceAccount = CloudStorageAccount.Parse(sourceSettings.STORAGE_ACCOUNT); + var sourceClient = sourceAccount.CreateCloudBlobClient(); + + var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); + var targetClient = targetAccount.CreateCloudBlobClient(); + + var sourceMachineStudioContainer = sourceClient.GetContainerReference(sourceSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); + var targetMachineStudioContainer = targetClient.GetContainerReference(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); + + var sourceMachineStudioBlob = sourceMachineStudioContainer.GetBlockBlobReference(latestMachineStudioVersion.BlobName); + var sourceMachineStudioInstallerBlob = sourceMachineStudioContainer.GetBlockBlobReference(latestMachineStudioVersion.InstallerBlobName); + + var targetMachineStudioBlob = await CreateEmptyBlob(targetMachineStudioContainer, sourceMachineStudioBlob.Name); + var targetMachineStudioInstallerBlob = await CreateEmptyBlob(targetMachineStudioContainer, sourceMachineStudioInstallerBlob.Name); + + await Task.Factory.StartNew(() => + { + targetMachineStudioBlob.StartCopy(sourceMachineStudioBlob); + targetMachineStudioInstallerBlob.StartCopy(sourceMachineStudioInstallerBlob); + }); + } + + public async Task ValidatePPCStorageUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Validating, "Validating PPC database upgrade..."); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + var latestSourcePPCVersion = await _databaseManager.GetLatestPPCVersion(sourceApp); + var latestTargetPPCVersion = await _databaseManager.GetLatestPPCVersion(targetApp); + + var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); + var targetClient = targetAccount.CreateCloudBlobClient(); + + var targetPPCContainer = targetClient.GetContainerReference(targetSettings.TANGO_VERSIONS_CONTAINER); + + //Check PPC binaries blob not exists on the target. + var targetPPCBlob = targetPPCContainer.GetBlockBlobReference(latestSourcePPCVersion.BlobName); + if (await targetPPCBlob.ExistsAsync()) + { + throw new ValidationException($"PPC Block blob '{latestSourcePPCVersion.BlobName}' already exists on the target storage."); + } + + //Check PPC installer blob not exists on the target. + var targetPPCInstallerBlob = targetPPCContainer.GetBlockBlobReference(latestSourcePPCVersion.InstallerBlobName); + if (await targetPPCInstallerBlob.ExistsAsync()) + { + throw new ValidationException($"PPC Block blob '{latestSourcePPCVersion.InstallerBlobName}' already exists on the target storage."); + } + } + + public async Task ValidateMachineStudioStorageUpgrade(IWebAppBase sourceApp, IWebAppBase targetApp) + { + OnProgress(AzureUtilsStage.Validating, "Validating machine studio storage upgrade..."); + + var sourceSettings = await sourceApp.GetMachineServiceSettingsAsync(); + var targetSettings = await targetApp.GetMachineServiceSettingsAsync(); + + var latestSourceMachineStudioVersion = await _databaseManager.GetLatestMachineStudioVersion(sourceApp); + var latestTargetMachineStudioVersion = await _databaseManager.GetLatestMachineStudioVersion(targetApp); + + var targetAccount = CloudStorageAccount.Parse(targetSettings.STORAGE_ACCOUNT); + var targetClient = targetAccount.CreateCloudBlobClient(); + + var targetMachineStudioContainer = targetClient.GetContainerReference(targetSettings.MACHINE_STUDIO_VERSIONS_CONTAINER); + + //Check machine studio binaries blob not exists on the target. + var targetMachineStudioBlob = targetMachineStudioContainer.GetBlockBlobReference(latestSourceMachineStudioVersion.BlobName); + if (await targetMachineStudioBlob.ExistsAsync()) + { + throw new ValidationException($"Machine Studio Block blob '{latestSourceMachineStudioVersion.BlobName}' already exists on the target storage."); + } + + //Check machine studio installer blob not exists on the target. + var targetMachineStudioInstallerBlob = targetMachineStudioContainer.GetBlockBlobReference(latestSourceMachineStudioVersion.InstallerBlobName); + if (await targetMachineStudioInstallerBlob.ExistsAsync()) + { + throw new ValidationException($"Machine Studio Block blob '{latestSourceMachineStudioVersion.InstallerBlobName}' already exists on the target storage."); + } + } + } +} diff --git a/Software/Visual_Studio/Azure/Tango.AzureUtils/Tango.AzureUtils.csproj b/Software/Visual_Studio/Azure/Tango.AzureUtils/Tango.AzureUtils.csproj index f7a0e2436..b626a3b09 100644 --- a/Software/Visual_Studio/Azure/Tango.AzureUtils/Tango.AzureUtils.csproj +++ b/Software/Visual_Studio/Azure/Tango.AzureUtils/Tango.AzureUtils.csproj @@ -227,26 +227,31 @@ <Compile Include="AzureUtilsComponentBase.cs" /> <Compile Include="AzureUtilsCredentials.cs" /> <Compile Include="AzureUtilsConfirmationEventArgs.cs" /> + <Compile Include="Database\DatabaseManager.cs" /> <Compile Include="Deployment\DeploymentManager.cs" /> <Compile Include="AzureUtilsProgressEventArgs.cs" /> <Compile Include="AzureUtilsStage.cs" /> + <Compile Include="Environment\CreateEnvironmentConfiguration.cs" /> + <Compile Include="Environment\EnvironmentSettings.cs" /> + <Compile Include="Environment\UpgradeEnvironmentConfiguration.cs" /> <Compile Include="ExtensionMethods.cs" /> + <Compile Include="FTP\FtpManager.cs" /> <Compile Include="MachineServiceSettings.cs" /> - <Compile Include="Deployment\UpgradeConfiguration.cs" /> <Compile Include="Environment\EnvironmentManager.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Storage\StorageManager.cs" /> </ItemGroup> <ItemGroup> <Content Include="..\..\..\DB\SQLExaminer Projects\GENERAL_ENV_UPGRADE.sdeproj"> - <Link>Deployment\GENERAL_ENV_UPGRADE.sdeproj</Link> + <Link>Database\GENERAL_ENV_UPGRADE.sdeproj</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="..\..\..\DB\SQLExaminer Projects\GENERAL_ENV_UPGRADE.seproj"> - <Link>Deployment\GENERAL_ENV_UPGRADE.seproj</Link> + <Link>Database\GENERAL_ENV_UPGRADE.seproj</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="..\..\..\DB\SQLExaminer Projects\GENERAL_ENV_CREATE.sdeproj"> - <Link>Deployment\GENERAL_ENV_CREATE.sdeproj</Link> + <Link>Database\GENERAL_ENV_CREATE.sdeproj</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <None Include="app.config" /> @@ -266,5 +271,6 @@ <Name>Tango.Logging</Name> </ProjectReference> </ItemGroup> + <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file |
