/// <summary> /// Get list of installed application templates. /// </summary> /// <returns></returns> public List <InstalledApplication> GetInstalledApplicationTemplates(string identifiers = null) { List <InstalledApplication> apps = new List <InstalledApplication>(); var installedAppsFolder = this.GlobalSettings.applicationTemplateDir; var difo = new DirectoryInfo(installedAppsFolder); if (!difo.Exists) { throw new Exception($"Non existent installed applications folder: {installedAppsFolder}"); } foreach (var file in difo.EnumerateFiles()) { var installedApp = new InstalledApplication(); installedApp.ParseFromString(File.ReadAllText(file.FullName)); if (string.IsNullOrWhiteSpace(identifiers) || this.ExplodeAndCleanList(identifiers, ",").Contains(installedApp.GetId())) { apps.Add(installedApp); } } return(apps); }
/// <summary> /// Deploys an installed app. /// </summary> /// <param name="app"></param> /// <param name="force"></param> /// <param name="buildId"></param> /// <param name="sync"></param> protected Deployment _DeployApp( Application app, bool force = false, string buildId = null, bool sync = false) { DateTime start = DateTime.Now; // The parent application to inherit from (if needed) InstalledApplication parentApp = null; // Lo primero es ver si hay algo nuevo... var downloader = this.installedAppSettings.GetDownloader(this.GlobalSettings, this.Logger); if (!string.IsNullOrWhiteSpace(buildId)) { this.Logger.LogInfo(true, "Deploying specific version build: '{0}'", buildId); } string nextArtifactId; // Next artifact id might be pulled from a remote location, and this prompt to random failures (network, etc.) // so wrap this in a try/catch try { nextArtifactId = downloader.GetNextId(buildId == "latest" ? null : buildId); } catch (Exception e) { this.Logger.LogException(new Exception("Failure while looking for next build ID", e), EventLogEntryType.Warning); return(this.DeploymentActive); } string currentArtifactId = this.DeploymentActive != null ? this.DeploymentActive.artifact.id : string.Empty; bool isNew = this.DeploymentActive == null || this.DeploymentActive.artifact.id != nextArtifactId; // Check that Inherit application exists if (!string.IsNullOrEmpty(this.installedAppSettings.GetInherit())) { parentApp = app.GetInstalledApp(this.installedAppSettings.GetInherit()); if (parentApp == null) { throw new Exception( $"Application from inheritation: {this.installedAppSettings.GetInherit()}, can not be found"); } this.Logger.LogInfo(true, "Application configured to inherit from parent application '{0}'. Sync:{1}", parentApp.GetId(), sync ? "Yes" : "No"); } // Si no es nuevo y no estamos forzando, no hacer deploy. if (!isNew && !force) { this.Logger.LogInfo(true, "No new version found for Application {0}", this.installedAppSettings.GetId()); return(this.DeploymentActive); } // There is an existing deployment that had a manually enforced BuildId if (!string.IsNullOrEmpty(this.DeploymentActive?.enforceBuildId)) { if (force) { if (!string.IsNullOrWhiteSpace(buildId)) { // If there is a force and a buildId has been specified, override the next artifactId // with the requested BuildId, or if latest was specified use that. if (buildId != "latest") { nextArtifactId = buildId; } } else { // If no specific build was requested, override the nextArtifactId with // the stored build nextArtifactId = this.DeploymentActive.enforceBuildId; this.Logger.LogWarning(true, "Deploying stored version {0}", nextArtifactId); } } else if (buildId != this.DeploymentActive.enforceBuildId && buildId != "latest") { this.Logger.LogWarning(true, $"Deployment was skipped because previous deployment was a version-specific deployment. Previous buildId='{this.DeploymentActive.enforceBuildId}'. Requested buildId='{buildId}'. Use buildId='latest' to force deploying the latest succesful build or -Force to deploy this version."); return(this.DeploymentActive); } } this.Logger.LogInfo(false, "@@ Starting deployment for app: '{0}'", this.installedAppSettings.GetId()); this.Logger.LogInfo(false, "Current artifact: '{0}' || Previous artifact: '{1}'", nextArtifactId, currentArtifactId); // Specify a local temporary artifact location, in case this is supported by the downloader... // final path should be retrieved from artifact.localPath string preferredLocalArtifactPath = UtilsSystem.EnsureDirectoryExists( UtilsSystem.CombinePaths( this.GlobalSettings.GetDefaultApplicationStorage().path, "_tmp", this.installedAppSettings.GetId(), UtilsEncryption.GetShortHash(nextArtifactId, 12)), true); // Get from the ID... Artifact artifact = downloader.PullFromId(nextArtifactId, preferredLocalArtifactPath); if (string.IsNullOrWhiteSpace(this.GlobalSettings.id)) { throw new Exception("Environment settings cannot have an empty ID."); } this.Logger.LogInfo(false, "Environment id: '{0}'", this.GlobalSettings.id); this.Logger.LogInfo(false, "Environment options/tags: '{0}'", string.Join(",", this.GlobalSettings.getOptions())); this.Logger.LogInfo(false, "Pull artifact lapsed: {0}s", (DateTime.Now - start).TotalSeconds); start = DateTime.Now; // Look for a configuration file that fits this environment. string chefsettingsdir = UtilsSystem.CombinePaths(artifact.localPath, "chef"); // The final chef configuration files is a combination of Chef files var appSettings = this.LoadApplicationSettings( chefsettingsdir, this.GlobalSettings.id, artifact.artifactSettings.branch, out var loadedConfigurationFiles); // Storage for current deployment. Includes all possible environment data // in order to provide traceability + rollback capabilities. Deployment deployment = new Deployment( appSettings, this.GlobalSettings, artifact, this.installedAppSettings, parentApp); deployment.SetPreviousDeployment(this.DeploymentActive); // Check the deployment windows! var deploymentSettings = deployment.appSettings.getDeploymentSettings(); if (deploymentSettings != null) { if (deploymentSettings.deployment_windows != null && deploymentSettings.deployment_windows.Any()) { bool canDeploy = false; foreach (var deploymentWindow in deploymentSettings.deployment_windows) { TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(deploymentWindow.Value.timezone); TimeSpan dtStart = TimeSpan.Parse(deploymentWindow.Value.start); TimeSpan dtEnd = TimeSpan.Parse(deploymentWindow.Value.end); DateTimeOffset localServerTime = DateTimeOffset.Now; DateTimeOffset windowTimeZone = TimeZoneInfo.ConvertTime(localServerTime, info); TimeSpan dtNow = windowTimeZone.TimeOfDay; if (dtStart <= dtEnd) { // start and stop times are in the same day if (dtNow >= dtStart && dtNow <= dtEnd) { // current time is between start and stop canDeploy = true; break; } } else { // start and stop times are in different days if (dtNow >= dtStart || dtNow <= dtEnd) { // current time is between start and stop canDeploy = true; break; } } } // Even if we are not in a deployment windows, // if we are forcing the deployment continue. if (!canDeploy && !force) { this.Logger.LogInfo(false, "Application deployment skipped. Current time not within allowed publishing windows."); return(this.DeploymentActive); } } } // Inform about the confiugration files that where used for loading deployment.SetRuntimeSetting("deployment.loaded_configuration_files", string.Join(",", loadedConfigurationFiles)); deployment.enforceBuildId = buildId == "latest" ? null : buildId; var deployersActive = this.DeploymentActive != null?this.DeploymentActive.GrabDeployers(this.Logger) : new DeployerCollection(this.GlobalSettings, null, this.Logger, parentApp); var servicesActive = this.DeploymentActive != null?this.DeploymentActive.GrabServices(this.Logger) : new DeployerCollection(this.GlobalSettings, null, this.Logger, parentApp); var deployers = deployment.GrabDeployers(this.Logger); var services = deployment.GrabServices(this.Logger); this.Logger.LogInfo(false, "Deployers and services gathered. Starting installation..."); var settingsConverter = new JObjectToKeyValueConverter(); try { // Deploy the application base storage (logs, runtime, etc.) deployers.DeployAll(); services.DeployAll(); // Move the application settings to runtime settings var userApplicationSettings = appSettings.getApplicationSettings(); foreach (var k in settingsConverter.NestedToKeyValue(userApplicationSettings)) { deployment.SetRuntimeSetting("app_settings." + k.Key, k.Value); } // Sync if (sync) { deployers.SyncAll(); services.SyncAll(); } // Time to hot switch the sites... we need to waitPauseMs for all // current requests to finish... because that way we ensure // that underlying storage updates will not collide if updates // are being deployed. servicesActive.StopAll(); deployersActive.StopAll(); // Some stuff requires the old services to be stopped in order to be deployed, such as IIS bindings and certificates deployers.BeforeDoneAll(); services.BeforeDoneAll(); var settingsToDeploy = deployment.GetRuntimeSettingsToDeploy(); // Store Key-Value settings in a JSON object (with keys as var jsonSettings = JsonConvert.SerializeObject( settingsToDeploy, Formatting.Indented); var jsonSettingsNested = JsonConvert.SerializeObject( settingsConverter.keyValueToNested(settingsToDeploy), Formatting.Indented); // Make sure we persist the settings AFTER all deployers have finished thri job deployers.DeploySettingsAll(jsonSettings, jsonSettingsNested); services.DeploySettingsAll(jsonSettings, jsonSettingsNested); // Time to start! deployers.StartAll(); services.StartAll(); DateTime dtStart = DateTime.Now; // Replace active configuration settings deployment.StoreInPath(this.activeDeploymentPathStorage); // Quitar el deployment anterior y si hay error seguir, // ya que los datos del deployment actual YA están guardados! servicesActive.UndeployAll(true); deployersActive.UndeployAll(true); // The done "event" is called on deployers // once everything is completed correctly. deployers.DoneAll(true); services.DoneAll(true); // Make sure that at least 2 seconds pass after deployment before // doing an OK to let IIS reconfigure. while ((DateTime.Now - dtStart).TotalSeconds < 1) { System.Threading.Thread.Sleep(500); } } catch (Exception e) { // Just in case.... log this ASAP this.Logger.LogException( new Exception("Error deploying APP: " + deployment.installedApplicationSettings.GetId(), e)); deployers.StopAll(true); deployers.UndeployAll(true); // Aquí hacemos un continue on error porque... estamos repescando algo que ya funcionaba // a toda costa queremos levantarlo! deployersActive.StartAll(true); servicesActive.StopAll(true); // In unit test rethrow to preserve stack trace in GUI if (UnitTestDetector.IsRunningInTests) { System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e) .Throw(); } else { throw new AlreadyHandledException(e.Message, e); } } finally { // Run cleanup, dot not fail if cleanup fails, it's just an extra... deployers.CleanupAll(true); services.CleanupAll(true); } // Done! this.Logger.LogInfo(false, "Deployment lapsed: {0}s", (DateTime.Now - start).TotalSeconds); return(deployment); }