/// <summary>
        /// All disk storage for this application is pointed to this directory.
        /// </summary>
        /// <param name="settings"></param>
        /// <returns></returns>
        protected string GetStoragePath(DiskServiceSettings settings)
        {
            var storage = this.GlobalSettings.GetDefaultContentStorage();

            // We can have an app_setting configuration
            // to route a whole application to a specific sql server
            string diskTarget;

            if (this.Deployment.installedApplicationSettings.configuration["disktarget"] != null)
            {
                diskTarget = Convert.ToString(this.Deployment.installedApplicationSettings.configuration["disktarget"]);
                this.Logger.LogInfo(true, "Custom disk target: " + diskTarget);

                if (!Directory.Exists(diskTarget))
                {
                    throw new Exception("Invalid custom disk target: " + diskTarget);
                }
            }
            else
            {
                // Generate a unique "virtual disk" (directory) for this application
                diskTarget = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(
                                                                   storage.path,
                                                                   "store_" + this.Deployment.installedApplicationSettings.GetId()));
            }

            return(diskTarget);
        }
Example #2
0
        /// <summary>
        /// Grab from a settings file.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="logger"></param>
        public void PopulateFromSettingsFile(string path, ILoggerInterface logger)
        {
            string file = UtilsSystem.CombinePaths(path, "artifact-settings.yml");

            if (!File.Exists(file))
            {
                return;
            }

            var configfile = new Configuration.YamlConfigurationFile();

            try
            {
                // This file might be malformed, do not crash and let other
                // environment information sources have their chance
                configfile.ParseFromFile(file);
            }
            catch (Exception e)
            {
                logger.LogException(new Exception("Error parsing file: " + file, e));
                return;
            }

            // Parse the artifact settings...
            this.branch     = configfile.GetStringValue("repo-branch", null);
            this.commit_sha = configfile.GetStringValue("repo-commit", null);
            this.version    = configfile.GetStringValue("build-id", null);
        }
        /// <summary>
        /// Get a deployer for the installed application.
        /// </summary>
        /// <param name="globalSettings">The global settings.</param>
        /// <param name="installedApplicationSettings">The installed application settings.</param>
        /// <param name="logger">The logger.</param>
        public ApplicationDeployer(
            EnvironmentSettings globalSettings,
            InstalledApplication installedApplicationSettings,
            ILoggerInterface logger)
        {
            this.GlobalSettings       = globalSettings;
            this.installedAppSettings = installedApplicationSettings;
            this.Logger = logger;

            if (this.GlobalSettings == null)
            {
                throw new InvalidDataException("settings argument cannot be null.");
            }

            if (this.installedAppSettings == null)
            {
                throw new Exception("installedApplicationSettings argument cannot be null.");
            }

            // Try to grab previous deployment...
            this.activeDeploymentPathStorage = UtilsSystem.CombinePaths(globalSettings.activeDeploymentDir, "active." + this.installedAppSettings.GetId() + ".json");

            if (File.Exists(this.activeDeploymentPathStorage))
            {
                this.DeploymentActive = Deployment.InstanceFromPath(this.activeDeploymentPathStorage, globalSettings);
            }
        }
        public override void beforeDone()
        {
            // We also have a canonical access to the deployed app through a symlink
            string basePath      = this.Deployment.GetSetting("appstorage.base", (string)null, this.Logger);
            string canonicalPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, "_" + this.Deployment.installedApplicationSettings.GetId());

            UtilsJunction.EnsureLink(canonicalPath, basePath, this.Logger, false, true);
            this.Deployment.SetSetting("appstorage.canonical", canonicalPath);
        }
Example #5
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        protected string GetGlobalStoragePath(string filename)
        {
            var environmentSettingsFile =
                UtilsSystem.EnsureDirectoryExists(
                    UtilsSystem.CombinePaths(
                        Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                        "iischef",
                        "config",
                        filename));

            return(environmentSettingsFile);
        }
Example #6
0
        /// <summary>
        /// Get an instance of AppVeyorClient
        /// </summary>
        /// <param name="token">API Token</param>
        /// <param name="baseUri">Base URI</param>
        /// <param name="logger"></param>
        /// <param name="tempDir"></param>
        public Client(
            string token,
            string baseUri,
            ILoggerInterface logger,
            string tempDir)
        {
            string apiTempDir =
                UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(tempDir, "_appveyor", "api"), true);

            this.TempDir     = tempDir;
            this.Token       = token;
            this.Logger      = logger;
            this.BaseUri     = baseUri;
            this.SimpleStore = new SimpleStore(apiTempDir);
        }
Example #7
0
        /// <summary>
        /// Find the matching deployer of the parent application when
        /// inheritance is configured for this application.
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <returns></returns>
        public TType getDeployerFromParentApp <TType>()
            where TType : DeployerBase
        {
            // We need a parent application for this to work.
            if (this.ParentApp == null)
            {
                return(null);
            }

            // Try to grab parent deployment...
            Deployment parentDeployment;
            string     activeDeploymentPathStorage = UtilsSystem.CombinePaths(this.GlobalSettings.activeDeploymentDir, "active." + this.ParentApp.GetId() + ".json");

            if (File.Exists(activeDeploymentPathStorage))
            {
                parentDeployment = Deployment.InstanceFromPath(activeDeploymentPathStorage, this.GlobalSettings);
                DeployerSettingsBase      ourSettings          = this.DeployerSettings.castTo <DeployerSettingsBase>();
                List <IDeployerInterface> deployersAndServices = new List <IDeployerInterface>();
                deployersAndServices.AddRange(parentDeployment.GrabServices(this.Logger));
                deployersAndServices.AddRange(parentDeployment.GrabDeployers(this.Logger));

                // Only keep those that match our type
                deployersAndServices = deployersAndServices.Where(s => s.GetType() == typeof(TType)).ToList();

                // Filter by ID
                foreach (TType t in deployersAndServices)
                {
                    if (t.DeployerSettings.castTo <DeployerSettingsBase>().id == ourSettings.id)
                    {
                        return(t);
                    }
                }

                return(null);
            }
            else
            {
                return(null);
            }
        }
Example #8
0
        /// <summary>
        /// Grab from local GIT repo.
        /// </summary>
        /// <param name="path"></param>
        public void PopulateFromGit(string path)
        {
            // Crawl up to find the first directory covered by GIT. There might be a difference
            // between the artifact folder structure and the repository (local working copy) itself...
            DirectoryInfo difo    = new DirectoryInfo(path);
            string        gitpath = null;

            while (difo != null && difo.Exists)
            {
                if (Directory.Exists(UtilsSystem.CombinePaths(difo.FullName, ".git")))
                {
                    gitpath = difo.FullName;
                    break;
                }

                difo = difo.Parent;
            }

            if (gitpath == null)
            {
                return;
            }

            try
            {
                // Try to get information directly from GIT??
                var repo = new LibGit2Sharp.Repository(gitpath, new LibGit2Sharp.RepositoryOptions()
                {
                });

                this.branch     = repo.Head.FriendlyName;
                this.commit_sha = repo.Commits.First().Sha;
                this.version    = this.commit_sha;
            }
            catch (Exception e)
            {
                // Trying to read settings from GIT can be delicate. Such as...
                // https://github.com/GitTools/GitVersion/issues/1043
            }
        }
Example #9
0
        /// <summary>
        /// Get the webroot for the CDN site, initialized with a base web.config prepared for URL REWRITING
        /// </summary>
        /// <returns></returns>
        public string GetCdnWebConfigPathInitialized()
        {
            var basedir =
                UtilsSystem.EnsureDirectoryExists(
                    UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, "__chef_cdn"), true);

            var webconfigfilepath = UtilsSystem.CombinePaths(basedir, "web.config");

            // Si no hay un web.config plantilla, crearlo ahora.
            if (!File.Exists(webconfigfilepath))
            {
                File.WriteAllText(webconfigfilepath, @"
                        <configuration>
                          <system.webServer>
                            <rewrite>
                              <rules>
                              </rules>
                              <outboundRules>
                              </outboundRules>
                            </rewrite>
                          </system.webServer>
                        </configuration>
                        ");
            }

            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(new SecurityIdentifier(UtilsWindowsAccounts.WELL_KNOWN_SID_USERS), basedir, FileSystemRights.ReadAndExecute);

            // Make sure that the site exists
            using (ServerManager manager = new ServerManager())
            {
                bool configChanged = false;

                var site = UtilsIis.FindSiteWithName(manager, this.CstChefCndSiteName, this.Logger)
                           .FirstOrDefault();

                if (site == null)
                {
                    manager.Sites.Add(this.CstChefCndSiteName, "http", $"{UtilsIis.LOCALHOST_ADDRESS}:80:{this.CstChefInternalHostname}", basedir);

                    configChanged = true;
                }
                else
                {
                    if (site.Applications.First().VirtualDirectories.First().PhysicalPath != basedir)
                    {
                        site.Applications.First().VirtualDirectories.First().PhysicalPath = basedir;
                        configChanged = true;
                    }
                }

                if (configChanged)
                {
                    UtilsIis.CommitChanges(manager);
                }
            }

            this.UtilsHosts.AddHostsMapping(UtilsIis.LOCALHOST_ADDRESS, this.CstChefInternalHostname, "chf_IISDeployer_CDN");

            // Add a cross domain file
            var crossdomainfilepath = UtilsSystem.CombinePaths(Path.GetDirectoryName(webconfigfilepath), "crossdomain.xml");

            File.WriteAllText(crossdomainfilepath, UtilsSystem.GetEmbededResourceAsString(Assembly.GetExecutingAssembly(), "IIS.crossdomain.xml"));

            // Add common proxy headers
            UtilsIis.AddAllowedServerVariablesForUrlRewrite(
                this.CstChefCndSiteName,
                "HTTP_X_FORWARDED_FOR",
                "HTTP_X_FORWARDED_PROTO",
                "HTTP_X_FORWARDED_HOST");

            return(webconfigfilepath);
        }
Example #10
0
        /// <inheritdoc cref="DeployerInterface"/>
        public void deploy()
        {
            var settings = this.Settings;

            string cronId = this.Deployment.shortid + "_" + settings.id;

            string pwfile = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "cronjobs_" + settings.id + ".ps1");

            // Necesitamos un Bat que llame al powershel, este siempre tiene el mismo aspecto.
            string batfile = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "cronjobs_" + settings.id + ".bat");

            Encoding enc = Encoding.GetEncoding("Windows-1252");

            File.WriteAllText(batfile, "powershell " + pwfile, enc);

            StringBuilder command = new StringBuilder();

            // Add path to environment.
            command.AppendLine(
                $"$env:Path = \"{UtilsSystem.CombinePaths(this.Deployment.runtimePath, "include_path")};\" + $env:Path");

            // Move to runtime.
            command.AppendLine($"cd \"{UtilsSystem.CombinePaths(this.Deployment.appPath)}\"");

            // Add path of project to the enviroment
            command.AppendLine($"$env:AppPath = \"{UtilsSystem.CombinePaths(this.Deployment.appPath)}\"");

            // Whatever deployers wanna do...
            var logger    = new logger.NullLogger();
            var deployers = this.Deployment.GrabDeployers(logger);

            foreach (var deployer in deployers)
            {
                deployer.deployConsoleEnvironment(command);
            }

            // Drop the user commands
            if (!string.IsNullOrWhiteSpace(settings.command))
            {
                command.AppendLine(settings.command);
            }

            if (settings.commands != null)
            {
                foreach (var cmd in settings.commands)
                {
                    command.AppendLine(cmd);
                }
            }

            File.WriteAllText(pwfile, command.ToString());

            // Nuestro scheduler tiene un nombre
            // definido.
            using (TaskService ts = new TaskService())
            {
                // Create a new task definition and assign properties
                TaskDefinition td = ts.NewTask();

                // Run with highest level to avoid UAC issues
                // https://www.devopsonwindows.com/create-scheduled-task/
                td.Principal.RunLevel = TaskRunLevel.Highest;

                string password = settings.taskUserPassword;

                if (settings.taskLogonType.HasValue)
                {
                    td.Principal.LogonType = (TaskLogonType)settings.taskLogonType.Value;
                }

                if (settings.taskUserId == "auto")
                {
                    td.Principal.UserId    = this.Deployment.WindowsUsernameFqdn();
                    td.Principal.LogonType = TaskLogonType.Password;
                    password = this.Deployment.GetWindowsPassword();

                    // Make sure that the user has the LogonAsBatchRight
                    UtilsWindowsAccounts.SetRight(this.Deployment.WindowsUsernameFqdn(), "SeBatchLogonRight", logger);
                }

                // Default to the SYSTEM account.
                else if (string.IsNullOrWhiteSpace(settings.taskUserId))
                {
                    td.Principal.UserId    = "SYSTEM";
                    td.Principal.LogonType = TaskLogonType.ServiceAccount;
                    password = null;
                }

                td.RegistrationInfo.Description = cronId;

                // Create a trigger that will fire the task every 5 minutes.
                var trigger = new DailyTrigger();

                // Habilitada...
                trigger.Enabled = true;

                // Repetir cada 24 horas.
                trigger.DaysInterval = 1;

                // Repetir durante 24 horas en la frecuencia establecida.
                trigger.Repetition = new RepetitionPattern(new TimeSpan(0, settings.frequency, 0), new TimeSpan(24, 0, 0), true);

                // Para que arranque dos minutos después del deploy.
                trigger.StartBoundary = DateTime.Now.AddMinutes(2);

                // Enablin/disabling will happen during start/stop of service
                td.Settings.Enabled = false;

                // Un solo trigger.
                td.Triggers.Add(trigger);

                // Create an action that will launch the bat launcher.
                td.Actions.Add(new ExecAction(batfile, null, null));

                TaskFolder f = this.GetFolder(ts);

                // Register the task in the root folder
                if (!string.IsNullOrWhiteSpace(password) && td.Principal.LogonType == TaskLogonType.Password)
                {
                    f.RegisterTaskDefinition(td.RegistrationInfo.Description, td, TaskCreation.Create, td.Principal.UserId, this.Deployment.GetWindowsPassword(), td.Principal.LogonType);
                }
                else
                {
                    f.RegisterTaskDefinition(td.RegistrationInfo.Description, td, TaskCreation.Create, td.Principal.UserId);
                }
            }
        }
Example #11
0
 /// <summary>
 /// A local writable temporary directory
 /// </summary>
 /// <returns></returns>
 protected string GetSysTempDir()
 {
     return(UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.Deployment.tempPathSys, "sys_temp_dir"), true));
 }
        /// <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);
        }
Example #13
0
        public override void _sync(object input)
        {
            var sqlSettings = this.DeployerSettings.castTo <SQLServiceSettings>();

            SQLService parent = (SQLService)input;

            string database       = this.Deployment.GetRuntimeSettingsToDeploy()["services." + this.DeployerSettings.castTo <SQLServiceSettings>().id + ".database"];
            string parentDatabase = parent.Deployment.GetRuntimeSettingsToDeploy()["services." + parent.DeployerSettings.castTo <SQLServiceSettings>().id + ".database"];

            var sqlServer = this.GetSqlServer(sqlSettings.id);

            using (SqlConnection connection = new SqlConnection(sqlServer.connectionString))
            {
                connection.Open();

                ServerConnection serv = new ServerConnection(connection);

                smo.Server serverTemp = new smo.Server(serv);

                string backupDir = serverTemp.BackupDirectory;
                string dataDir   = serverTemp.MasterDBPath;

                this.Logger.LogInfo(true, "SQL Server Version: " + serverTemp.VersionString);
                this.Logger.LogInfo(true, "SQL Server Edition: " + serverTemp.Edition);

                var backupName = database + DateTime.Now.ToString("yyyyMMddHHmmssffff");
                var backupFile = UtilsSystem.CombinePaths(backupDir, backupName + ".bak");

                SqlCommand cmd;

                // Timeout for long running processes (i.e. backup and restore)
                int longProcessTimeout = 120;

                try
                {
                    string query = null;

                    // EngineEdition Database Engine edition of the instance of SQL Server installed on the server.
                    //  1 = Personal or Desktop Engine(Not available in SQL Server 2005 and later versions.)
                    //  2 = Standard(This is returned for Standard, Web, and Business Intelligence.)
                    //  3 = Enterprise(This is returned for Evaluation, Developer, and both Enterprise editions.)
                    //  4 = Express(This is returned for Express, Express with Tools and Express with Advanced Services)
                    //  5 = SQL Database
                    //  6 - SQL Data Warehouse

                    bool supportsCompression = serverTemp.EngineEdition != smo.Edition.Express;

                    string compressionOption = supportsCompression ? "COMPRESSION," : string.Empty;

                    query = string.Format(
                        "BACKUP DATABASE [{0}] to disk = '{1}' WITH {3} name = '{2}'",
                        parentDatabase,
                        backupFile,
                        backupName,
                        compressionOption);

                    this.Logger.LogInfo(true, "CMD: {0}", query);
                    cmd = new SqlCommand(query, connection);
                    cmd.CommandTimeout = longProcessTimeout;
                    cmd.ExecuteNonQuery();

                    query = string.Format(
                        @"DECLARE @kill varchar(8000) = '';  
                        SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), session_id) + ';'  
                        FROM sys.dm_exec_sessions
                        WHERE database_id  = db_id('{0}')",
                        database);

                    this.Logger.LogInfo(true, "CMD: {0}", query);
                    cmd = new SqlCommand(query, connection);
                    cmd.ExecuteNonQuery();

                    query = string.Format("ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE", database);

                    this.Logger.LogInfo(true, "CMD: {0}", query);
                    cmd = new SqlCommand(query, connection);
                    cmd.ExecuteNonQuery();

                    query = string.Format("RESTORE filelistonly FROM disk='{0}'", backupFile);

                    // Before restoring, print out logical file names
                    cmd = new SqlCommand(query, connection);
                    Dictionary <string, string> info = new Dictionary <string, string>();
                    using (var reader = cmd.ExecuteReader())
                    {
                        int row = 0;

                        while (reader.Read())
                        {
                            for (var i = 0; i < reader.FieldCount; i++)
                            {
                                var columname   = reader.GetName(i);
                                var columnvalue = Convert.ToString(reader[i]);
                                info.Add(row.ToString() + "_" + columname, columnvalue);
                            }

                            row++;
                        }
                    }

                    // logger.LogInfo(true, "BACKUP DETAILS: {0}", Newtonsoft.Json.JsonConvert.SerializeObject(info, Newtonsoft.Json.Formatting.Indented));

                    Dictionary <string, string> dataFilesToMove = new Dictionary <string, string>();

                    for (int x = 0; x < 500; x++)
                    {
                        if (!info.ContainsKey($"{x}_LogicalName"))
                        {
                            break;
                        }

                        var logicalName  = info[$"{x}_LogicalName"];
                        var physicalName = info[$"{x}_PhysicalName"];
                        var fileName     = System.IO.Path.GetFileName(physicalName);

                        dataFilesToMove.Add(logicalName, $"{dataDir}\\2_{fileName}");
                    }

                    List <string> fileMoves = new List <string>();

                    foreach (var dataFile in dataFilesToMove)
                    {
                        fileMoves.Add($"MOVE '{dataFile.Key}' TO '{dataFile.Value}'");
                    }

                    query = string.Format($"RESTORE DATABASE [{database}] FROM DISK = '{backupFile}' WITH REPLACE, {string.Join(",", fileMoves)};");

                    this.Logger.LogInfo(true, "CMD: {0}", query);
                    cmd = new SqlCommand(query, connection);
                    cmd.CommandTimeout = longProcessTimeout;
                    cmd.ExecuteNonQuery();

                    query = $"ALTER DATABASE [{database}] SET MULTI_USER";

                    this.Logger.LogInfo(true, "CMD: {0}", query);
                    cmd = new SqlCommand(query, connection);
                    cmd.ExecuteNonQuery();

                    serverTemp.ConnectionContext.ExecuteNonQuery("EXEC sp_configure 'show advanced options', 1");
                    serverTemp.ConnectionContext.ExecuteNonQuery("RECONFIGURE");
                    serverTemp.ConnectionContext.ExecuteNonQuery("EXEC sp_configure 'xp_cmdshell', 1");
                    serverTemp.ConnectionContext.ExecuteNonQuery("RECONFIGURE");
                    serverTemp.ConnectionContext.ExecuteNonQuery($"xp_cmdshell 'del \"{backupFile}\"'");
                    serverTemp.ConnectionContext.ExecuteNonQuery("EXEC sp_configure 'xp_cmdshell', 0");
                    serverTemp.ConnectionContext.ExecuteNonQuery("EXEC sp_configure 'show advanced options', 0");
                    serverTemp.ConnectionContext.ExecuteNonQuery("RECONFIGURE");
                }
                finally
                {
                    // Make sure that we remove single_user_mode
                    try
                    {
                        string query = null;

                        query = $"ALTER DATABASE [{database}] SET MULTI_USER";
                        cmd   = new SqlCommand(query, connection);
                        cmd.ExecuteNonQuery();
                    }
                    catch
                    {
                        // ignored
                    }
                }

                connection.Close();
            }

            // After doing the SYNC we need to "re-deploy" the database so that
            // user accounts are properly setup for the new application.
            this.deploy();
        }
 /// <summary>
 /// A file name that will be used for physical file locks
 /// </summary>
 /// <returns></returns>
 protected string LockPathForApplication()
 {
     return(UtilsSystem.EnsureDirectoryExists(
                UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, "_chef_locks", this.GlobalSettings.id, "application." + this.installedAppSettings.GetId() + ".lock")));
 }
Example #15
0
        /// <summary>
        /// Path to the environment settings folder
        /// </summary>
        /// <param name="settingsFile"></param>
        public void Initialize(string settingsFile = null, string options = null)
        {
            var environmentSettingsFile = this.GetGlobalStorageVariable("environment-file-path");

            if (settingsFile == null && !File.Exists(environmentSettingsFile))
            {
                throw new Exception("To start the deployer you need to provide a valid environment configuration file. The default location is: " + environmentSettingsFile);
            }

            if (settingsFile != null)
            {
                environmentSettingsFile = settingsFile;
            }

            var serverSettingsContent = File.ReadAllText(environmentSettingsFile);

            this.GlobalSettings = JsonConvert.DeserializeObject <EnvironmentSettings>(serverSettingsContent);

            // Ensure we have a salt
            if (string.IsNullOrWhiteSpace(this.GlobalSettings.installationSalt))
            {
                this.Logger.LogWarning(true, "Global parameter 'installationSalt' no defined, using default salt.");
                this.GlobalSettings.installationSalt = "default-salt";
            }

            // Initialize the settings directory
            if (string.IsNullOrEmpty(this.GlobalSettings.settingsDir))
            {
                this.GlobalSettings.settingsDir = Path.GetDirectoryName(environmentSettingsFile);
                this.Logger.LogInfo(true, "No 'settingsDir' directory specified. Using default: {0}", environmentSettingsFile);
            }

            // Active deployment directory
            if (string.IsNullOrWhiteSpace(this.GlobalSettings.activeDeploymentDir))
            {
                this.GlobalSettings.activeDeploymentDir = UtilsSystem.CombinePaths(this.GlobalSettings.settingsDir, "deployments");

                // Initialize storage
                if (!Directory.Exists(this.GlobalSettings.activeDeploymentDir))
                {
                    Directory.CreateDirectory(this.GlobalSettings.activeDeploymentDir);
                }

                this.Logger.LogInfo(true, "No 'activeDeploymentDir' directory specified. Using default: {0}", this.GlobalSettings.activeDeploymentDir);
            }

            // Template directory
            if (string.IsNullOrWhiteSpace(this.GlobalSettings.applicationTemplateDir))
            {
                this.GlobalSettings.applicationTemplateDir = UtilsSystem.CombinePaths(this.GlobalSettings.settingsDir, "installed_apps");

                // Initialize storage
                if (!Directory.Exists(this.GlobalSettings.applicationTemplateDir))
                {
                    Directory.CreateDirectory(this.GlobalSettings.applicationTemplateDir);
                }

                this.Logger.LogInfo(true, "No 'applicationTemplateDir' directory specified. Using default: {0}", this.GlobalSettings.applicationTemplateDir);
            }

            if (this.GlobalSettings.options == null)
            {
                this.GlobalSettings.options = new List <string>();
            }

            if (options != null)
            {
                foreach (var option in options.Split(",".ToCharArray()))
                {
                    if (!this.GlobalSettings.options.Contains(option))
                    {
                        this.GlobalSettings.options.Add(option);
                    }
                }
            }

            // Now move to a file based logger
            // and keep track of original logger.
            this.parentLogger = this.Logger;
            this.Logger       = new FileLogger(UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultLogStorage().path, $"chef-application-{this.GlobalSettings.id}.log"));
        }
        /// <summary>
        /// Deploy the application runtime settings.
        /// </summary>
        /// <param name="jsonSettings"></param>
        /// <param name="jsonSettingsArray"></param>
        /// <param name="replacer"></param>
        public void deploySettings(
            string jsonSettings,
            string jsonSettingsArray,
            RuntimeSettingsReplacer replacer)
        {
            // Write the settings in a directory in the application folder itself.
            // When deployed as web app or similar, the other deployers must hide this directory...
            var settingsFile = UtilsSystem.EnsureDirectoryExists(
                Path.Combine(this.Deployment.runtimePath, "chef-settings.json"));

            File.WriteAllText(settingsFile, jsonSettings);

            var settingsFileNested = UtilsSystem.EnsureDirectoryExists(
                Path.Combine(this.Deployment.runtimePath, "chef-settings-nested.json"));

            File.WriteAllText(settingsFileNested, jsonSettingsArray);

            // Why don't we write the settings directly to the AppRoot? Because it might
            // be exposed to the public if the application is mounted as a web application...
            // So we just hint to the location of the runtime, and the application
            // must implement the code needed to load the settings.
            var hintFile = UtilsSystem.EnsureDirectoryExists(
                UtilsSystem.CombinePaths(this.Deployment.appPath, "chef-runtime.path"));

            // We hint to the runtime path, not the specific file
            File.WriteAllText(hintFile, this.Deployment.runtimePath);

            // Dump the configuration files if requested to do so...
            foreach (var kvp in this.GetSettings().configuration_dump_paths ?? new Dictionary <string, string>())
            {
                var destinationDir = UtilsSystem.CombinePaths(this.Deployment.appPath, kvp.Value);
                if (!Directory.Exists(destinationDir))
                {
                    Directory.CreateDirectory(destinationDir);
                }

                var settingsFileDump = UtilsSystem.EnsureDirectoryExists(
                    Path.Combine(destinationDir, "chef-settings.json"));

                File.WriteAllText(settingsFileDump, jsonSettings);

                var settingsFileNestedDump = UtilsSystem.EnsureDirectoryExists(
                    Path.Combine(destinationDir, "chef-settings-nested.json"));

                File.WriteAllText(settingsFileNestedDump, jsonSettingsArray);

                var settingsFileNestedYaml = UtilsSystem.EnsureDirectoryExists(
                    Path.Combine(destinationDir, "chef-settings-nested.yml"));

                File.WriteAllText(settingsFileNestedYaml, UtilsYaml.JsonToYaml(jsonSettingsArray));
            }

            // Now replace the settings in the configuration templates
            foreach (var kvp in this.GetSettings().configuration_replacement_files ?? new Dictionary <string, string>())
            {
                var sourcePath      = UtilsSystem.CombinePaths(this.Deployment.appPath, kvp.Key);
                var destinationPath = UtilsSystem.CombinePaths(this.Deployment.appPath, kvp.Value);

                var contents = File.ReadAllText(sourcePath);

                if (destinationPath == sourcePath)
                {
                    throw new Exception("Destination and source for configuration settings replacements cannot be the same.");
                }

                contents = replacer.DoReplace(contents);

                File.WriteAllText(destinationPath, contents);
            }
        }
Example #17
0
 protected string GetIniFilePath()
 {
     return(UtilsSystem.CombinePaths(this.Deployment.runtimePath, "php", "php.ini"));
 }
        /// <inheritdoc cref="DeployerBase"/>
        public void deploy()
        {
            var settings = this.GetSettings();

            this.Deployment.windowsUsername = "******" + this.Deployment.installedApplicationSettings.GetId();

            if (this.Deployment.GetPreviousDeployment() != null && this.Deployment.GetPreviousDeployment().windowsUsername != this.Deployment.windowsUsername)
            {
                this.Logger.LogWarning(
                    false,
                    "Windows account username has changed from '{0}' to '{1}', removal of account and granted permissions must be performed manually.",
                    this.Deployment.GetPreviousDeployment()?.windowsUsername,
                    this.Deployment.windowsUsername);
            }

            UtilsWindowsAccounts.EnsureUserExists(this.Deployment.WindowsUsernameFqdn(), this.Deployment.GetWindowsPassword(), this.Deployment.installedApplicationSettings.GetId(), this.Logger, this.GlobalSettings.directoryPrincipal);

            // Legacy behaviour, if no userGroups defined, create a chef_users groups and add the users
            // to it
            if (!(this.GlobalSettings.userGroups ?? new List <string>()).Any())
            {
                UtilsWindowsAccounts.EnsureGroupExists(LEGACY_CHEF_USERS_GROUPNAME, this.GlobalSettings.directoryPrincipal);
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), LEGACY_CHEF_USERS_GROUPNAME, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to the user groups
            foreach (var groupIdentifier in this.GlobalSettings.userGroups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to any user groups defined at the application level
            foreach (var groupIdentifier in settings.user_groups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add any privileges if requested
            foreach (var privilegeName in settings.privileges ?? new List <string>())
            {
                UtilsWindowsAccounts.SetRight(this.Deployment.WindowsUsernameFqdn(), privilegeName, this.Logger);
            }

            // Getting security right at the OS level here is a little bit picky...
            // in order to have REALPATH to work in PHP we need to be able to read all directories
            // in a path i.e. D:\webs\chef\appnumber1\
            // What we will do is disconnect the USERS account here...
            string basePath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, this.Deployment.getShortId());

            UtilsSystem.EnsureDirectoryExists(basePath, true);

            UtilsWindowsAccounts.DisablePermissionInheritance(basePath);
            UtilsWindowsAccounts.RemoveAccessRulesForIdentity(new SecurityIdentifier(UtilsWindowsAccounts.WELL_KNOWN_SID_USERS), basePath, this.Logger);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), basePath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);

            // Store this in the application storage location.
            this.Deployment.runtimePath = UtilsSystem.CombinePaths(basePath, "runtime");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.runtimePath, true);

            this.Deployment.runtimePathWritable = UtilsSystem.CombinePaths(basePath, "runtime_writable");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.runtimePathWritable, true);

            // Due to compatibility reasons with environments such as PHP (that do not play well with network file URIs such as shared folders)
            // by default these two directories are symlinked to a local path if they are network paths.

            // Temp dir
            string localTempPath  = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "temp");
            string remoteTempPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultTempStorage().path, this.Deployment.installedApplicationSettings.GetId());

            UtilsSystem.EnsureDirectoryExists(remoteTempPath, true);
            UtilsJunction.EnsureLink(localTempPath, remoteTempPath, this.Logger, false);
            this.Deployment.tempPath = localTempPath;

            // Temp dir sys
            this.Deployment.tempPathSys = UtilsSystem.CombinePaths(this.Deployment.runtimePathWritable, "_tmp");
            UtilsSystem.EnsureDirectoryExists(this.Deployment.tempPathSys, true);

            // Log dir
            string localLogPath  = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "log");
            string remoteLogPath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultLogStorage().path, this.Deployment.installedApplicationSettings.GetId());

            UtilsSystem.EnsureDirectoryExists(remoteLogPath, true);
            UtilsJunction.EnsureLink(localLogPath, remoteLogPath, this.Logger, false);
            this.Deployment.logPath = localLogPath;

            this.Deployment.SetSetting("appstorage.base", basePath);
            this.Deployment.SetSetting("appstorage.temp", this.Deployment.tempPath);
            this.Deployment.SetSetting("appstorage.log", this.Deployment.logPath);

            this.Deployment.SetSetting("appstorage.remote_temp", remoteTempPath);
            this.Deployment.SetSetting("appstorage.remote_log", remoteLogPath);

            // We use this flag to detect transient storage
            // that must be removed when the deployer is "undeployed".
            AppBaseStorageType appBaseStorageType = AppBaseStorageType.Original;

            // TODO: Make this configurable through the chef.yml settings file.
            string ignoreOnDeployPattern = "^\\.git\\\\|^chef\\\\|^\\.vs\\\\";

            switch (this.Deployment.installedApplicationSettings.GetApplicationMountStrategy())
            {
            case ApplicationMountStrategy.Copy:
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");

                // TODO: We should consider the ability to symlink the code here, or to point/mount directly
                // to the original source path. This would probably require delegating this step to the artifact downloader
                // (artifact.getDownloader()) or having the downloader tell us how to deal with this (symlinks, direct, whatever)
                this.Logger.LogInfo(true, "Copying artifact files...");
                UtilsSystem.CopyFilesRecursivelyFast(this.Deployment.artifact.localPath, this.Deployment.appPath, false, ignoreOnDeployPattern, this.Logger);
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUserPrincipalName(), this.Deployment.appPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.artifact.DeleteIfRemote(this.Logger);
                appBaseStorageType = AppBaseStorageType.Transient;
                break;

            case ApplicationMountStrategy.Move:
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");

                // TODO: We should consider the ability to symlink the code here, or to point/mount directly
                // to the original source path. This would probably require delegating this step to the artifact downloader
                // (artifact.getDownloader()) or having the downloader tell us how to deal with this (symlinks, direct, whatever)
                this.Logger.LogInfo(true, "Moving artifact files...");
                UtilsSystem.MoveDirectory(this.Deployment.artifact.localPath, this.Deployment.appPath, this.Logger, ignoreOnDeployPattern);

                // We had issues in appveyor where _webs location is in C drive and thus not giving
                // permissions here would make tests fail.
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.appPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.artifact.DeleteIfRemote(this.Logger);
                appBaseStorageType = AppBaseStorageType.Transient;
                break;

            case ApplicationMountStrategy.Link:
                this.Logger.LogInfo(true, "Linking artifact files...");
                this.Deployment.appPath = UtilsSystem.CombinePaths(basePath, "app");
                UtilsJunction.EnsureLink(this.Deployment.appPath, this.Deployment.artifact.localPath, this.Logger, false);
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.artifact.localPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                appBaseStorageType = AppBaseStorageType.Symlink;
                break;

            case ApplicationMountStrategy.Original:
                this.Logger.LogInfo(true, "Ensure app has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.artifact.localPath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
                this.Deployment.appPath = UtilsSystem.CombinePaths(this.Deployment.artifact.localPath);
                appBaseStorageType      = AppBaseStorageType.Original;
                break;

            default:
                throw new NotImplementedException("The requested mount strategy for the application is not available: " + this.Deployment.installedApplicationSettings.GetApplicationMountStrategy());
            }

            this.Deployment.SetRuntimeSetting("deployment.appPath", this.Deployment.appPath);
            this.Deployment.SetRuntimeSetting("deployment.logPath", this.Deployment.logPath);
            this.Deployment.SetRuntimeSetting("deployment.tempPath", this.Deployment.tempPath);

            this.Deployment.SetSetting("appstorage.appBaseStorageType", appBaseStorageType);

            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), remoteTempPath, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), remoteLogPath, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.runtimePath, FileSystemRights.ReadAndExecute, this.GlobalSettings.directoryPrincipal);
            UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), this.Deployment.runtimePathWritable, FileSystemRights.Write | FileSystemRights.Read | FileSystemRights.Delete, this.GlobalSettings.directoryPrincipal);

            this.DeployFonts(settings);
        }
        public void undeploy(bool isUninstall = false)
        {
            if (this.Deployment == null)
            {
                return;
            }

            var    strategy = this.Deployment.installedApplicationSettings.GetApplicationMountStrategy();
            string basePath = UtilsSystem.CombinePaths(this.GlobalSettings.GetDefaultApplicationStorage().path, this.Deployment.getShortId());

            switch (strategy)
            {
            case ApplicationMountStrategy.Copy:
            case ApplicationMountStrategy.Move:
                UtilsSystem.DeleteDirectoryAndCloseProcesses(basePath, this.Logger, UtilsSystem.DefaultProcessWhitelist, 100);
                break;

            case ApplicationMountStrategy.Link:
                UtilsJunction.RemoveJunction(this.Deployment.appPath);
                UtilsSystem.DeleteDirectoryAndCloseProcesses(basePath, this.Logger, UtilsSystem.DefaultProcessWhitelist, 100);
                break;

            case ApplicationMountStrategy.Original:
                // Do nothing!
                break;

            default:
                throw new Exception("Option not supported.");
            }

            if (!isUninstall)
            {
                return;
            }

            var canonicalPath = this.Deployment.GetSetting <string>("appstorage.canonical", null, this.Logger);

            if (Directory.Exists(canonicalPath) && UtilsJunction.IsJunctionOrSymlink(canonicalPath))
            {
                Directory.Delete(canonicalPath);
            }

            // Usually the IIS site has been closed a few fractions of a second
            // before this is called, so the folders have probably not yet
            // been released, waitPauseMs at least 10 seconds.
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.temp", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.log", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.remote_temp", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);
            UtilsSystem.DeleteDirectoryAndCloseProcesses(this.Deployment.GetSetting <string>("appstorage.remote_log", null, this.Logger), this.Logger, UtilsSystem.DefaultProcessWhitelist, 60);

            var settings = this.GetSettings();

            var groups = this.GlobalSettings.userGroups ?? new List <string>();

            // add legacy group chef_users
            groups.Add(LEGACY_CHEF_USERS_GROUPNAME);

            // Remove user from all groups before deleting
            foreach (var groupIdentifier in groups)
            {
                UtilsWindowsAccounts.EnsureUserNotInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            // Add the user to any user groups defined at the application level
            foreach (var groupIdentifier in settings.user_groups ?? new List <string>())
            {
                UtilsWindowsAccounts.EnsureUserNotInGroup(this.Deployment.WindowsUsernameFqdn(), groupIdentifier, this.Logger, this.GlobalSettings.directoryPrincipal);
            }

            UtilsWindowsAccounts.DeleteUser(this.Deployment.WindowsUsernameFqdn(), this.GlobalSettings.directoryPrincipal);
        }
Example #20
0
        /// <summary>
        /// Provisions a certificate in the central store
        /// </summary>
        /// <param name="hostName">Domain to register</param>
        /// <param name="email">Registration e-mail</param>
        /// <param name="bindingInfo">IIS binding info</param>
        /// <param name="ownerSiteName">The site that owns the binding, used to assign identity and application pool permissions.</param>
        /// <param name="forceSelfSigned">Force a self-signed certificate</param>
        /// <param name="forceRenewal">Force renewal, even if renewal conditions are not met</param>
        /// <returns>The certificate's friendly name, ready to be bound in IIS</returns>
        public void ProvisionCertificateInIis(
            string hostName,
            string email,
            string bindingInfo,
            string ownerSiteName,
            bool forceSelfSigned = false,
            bool forceRenewal    = false)
        {
            if (hostName.Contains("*"))
            {
                throw new Exception($"Provisioning certificates for wildcard host name '{hostName}' is not supported.");
            }

            var    currentCertificate       = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _);
            double remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0;

            this.Logger.LogInfo(true, "Total days remaining for certificate expiration: '{0}'", (int)Math.Floor(remainingCertificateDays));

            // Trigger renovation. Do this differently on mock/prod environment.
            // Next renewal attempt is calculated based on previous renewal attempt
            var renewalState = this.GetCertificateRenewalState(hostName);

            // Legacy apps don't have this set, or when a certificate has been manually placed
            if (renewalState.NextRenewal == null && remainingCertificateDays > 1)
            {
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
            }

            int remainingDaysForNextRenewal = renewalState.NextRenewal == null ? 0 : (int)(renewalState.NextRenewal - DateTime.UtcNow).Value.TotalDays;
            int certificateTotalDuration    = currentCertificate == null ? 0 : (int)(currentCertificate.NotAfter - currentCertificate.NotBefore).TotalDays;

            this.Logger.LogInfo(true, "Next renewal attempt for this site SSL targeted in '{0}' days.", remainingDaysForNextRenewal);

            // Check that the validationfailed request rate is not exceeded for this domain
            if (!forceRenewal && renewalState.FailedValidations.AsIterable().Count(i => (DateTime.UtcNow - i).TotalHours < 48) >= 2)
            {
                // Make this message verbos so that it will not flood the logs, the failed validation message will get logged
                // anyways and is sufficient.
                this.Logger.LogWarning(true, "The hostname '{0}' has reached the limit of two failed validations in the last 48 hours.", hostName);
                return;
            }

            if (!forceRenewal && !forceSelfSigned && remainingDaysForNextRenewal > 0 && remainingCertificateDays > 0)
            {
                this.Logger.LogWarning(true, "Next renewal attempt date not reached, skipping SSL provisioning.");
                return;
            }

            if (!forceRenewal && remainingDaysForNextRenewal > 0 && (remainingDaysForNextRenewal > certificateTotalDuration * 0.5) && certificateTotalDuration > 0)
            {
                this.Logger.LogWarning(false, "Certificate has not yet been through at least 50% of it's lifetime so it will not be renewed.'");
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
                return;
            }

            // Check the general too many requests rate exceeded
            if (!forceRenewal && this.SimpleStoreRenewalStatus.Get <bool>("ssl-certificate-provider-too-many-requests", out var tooManyRequests))
            {
                this.Logger.LogWarning(false, "Certificate provisioning temporarily disabled due to a Too Many Requests ACME error. Flag stored in {0}", tooManyRequests.StorePath);
                return;
            }

            this.Logger.LogInfo(false, "Attempting SSL certificate renewal for site '{0}' and host '{1}'", ownerSiteName, hostName);

            // Clear old validation failed requests
            if (renewalState.FailedValidations?.Any() == true)
            {
                // Only keep failed validations that happen in the last 5 days
                renewalState.FailedValidations = renewalState.FailedValidations
                                                 .Where((i) => (DateTime.UtcNow - i).TotalDays < 5).ToList();
            }

            // This is a little bit inconvenient but... the most reliable and compatible
            // way to do this is to setup a custom IIS website that uses the binding during
            // provisioning.

            long        tempSiteId;
            List <Site> haltedSites = new List <Site>();

            var    tempSiteName  = "cert-" + this.AppId;
            var    tempSiteAppId = "cert-" + this.AppId;
            string tempHostName  = "localcert-" + hostName;

            this.Logger.LogInfo(true, "Preparing temp site: " + tempSiteName);

            List <Site> conflictingSites;

            // Prepare the site
            using (ServerManager sm = new ServerManager())
            {
                // Query the sites in a resilient way...
                conflictingSites = UtilsSystem.QueryEnumerable(
                    sm.Sites,
                    (s) => s.State == ObjectState.Started && s.Bindings.Any((i) => i.Host.Equals(hostName)),
                    (s) => s,
                    (s) => s.Name,
                    this.Logger);
            }

            // Stop the sites that might prevent this one from starting
            foreach (var s in conflictingSites)
            {
                this.Logger.LogInfo(true, "Stopping site {0} to avoid binding collision.", s.Name);
                this.AppPoolUtils.WebsiteAction(s.Name, AppPoolActionType.Stop, skipApplicationPools: true);
                haltedSites.Add(s);
            }

            using (ServerManager sm = new ServerManager())
            {
                // Make sure there is no other site (might be stuck?)
                var existingSite = (from p in sm.Sites
                                    where p.Name == tempSiteName
                                    select p).FirstOrDefault();

                var tempSite = existingSite ?? sm.Sites.Add(tempSiteName, this.GetAcmeTemporarySiteRootForApplication(), 80);

                // Propagate application pool usage so that permissions are properly handled.
                var ownerSite = sm.Sites.First((i) => i.Name == ownerSiteName);
                tempSite.Applications.First().ApplicationPoolName = ownerSite.Applications.First().ApplicationPoolName;

                // Delete all bindings
                tempSite.Bindings.Clear();

                tempSite.Bindings.Add(bindingInfo, "http");
                tempSite.Bindings.Add($"{UtilsIis.LOCALHOST_ADDRESS}:80:" + tempHostName, "http");
                tempSiteId = tempSite.Id;

                this.UtilsHosts.AddHostsMapping(UtilsIis.LOCALHOST_ADDRESS, tempHostName, tempSiteAppId);

                // Prepare the website contents
                var sourceDir = UtilsSystem.FindResourcePhysicalPath(typeof(IISDeployer), ".well-known");
                UtilsSystem.CopyFilesRecursively(new DirectoryInfo(sourceDir), new DirectoryInfo(this.GetWellKnownSharedPathForApplication()), true);

                UtilsIis.CommitChanges(sm);
            }

            UtilsIis.WaitForSiteToBeAvailable(tempSiteName, this.Logger);
            UtilsIis.ConfigureAnonymousAuthForIisApplication(tempSiteName, this.Deployment.WindowsUsernameFqdn(), this.Deployment.GetWindowsPassword());

            IAcmeSharpProvider provider = null;

            try
            {
                this.AppPoolUtils.WebsiteAction(tempSiteName, AppPoolActionType.Start);

                // Check that the site does work using the local binding
                var testDataUrl = $"http://{tempHostName}/.well-known/acme-challenge/test.html";
                this.Logger.LogInfo(true, "Validating local challenge setup at: {0}", testDataUrl);

                if (!string.Equals(UtilsSystem.DownloadUriAsText(testDataUrl), "test data"))
                {
                    throw new Exception($"Could not locally validate acme challenge site setup at {testDataUrl}");
                }

                // Ssl registration configuration only depends on the e-mail and is signed as such
                string sslSignerAndRegistrationStoragePath = UtilsSystem.EnsureDirectoryExists(
                    UtilsSystem.CombinePaths(this.StoragePath, "_ssl_config", StringFormating.Instance.ExtremeClean(email)), true);

                // Initialize the provider
                bool useMockProvider = this.MockEnvironment || forceSelfSigned;

                provider = useMockProvider
                    ? (IAcmeSharpProvider) new AcmeSharpProviderMock(this.Logger, tempHostName)
                    : this.GetAcmeProvider(this.Logger, hostName);

                var signerPath       = Path.Combine(this.StoragePath, "_signer.xml");
                var registrationPath = Path.Combine(sslSignerAndRegistrationStoragePath, "registration.json");

                provider.InitRegistration(signerPath, registrationPath, email);

                string challengeUrl;
                string challengeContent;
                string challengeFilePath;

                try
                {
                    provider.GenerateHttpChallenge(
                        out challengeUrl,
                        out challengeContent,
                        out challengeFilePath);
                }
                catch (AcmeClient.AcmeWebException acmeException)
                {
                    if (acmeException.Message.Contains("429"))
                    {
                        int waitHours = 1;
                        this.SimpleStoreRenewalStatus.Set("ssl-certificate-provider-too-many-requests", true, 60 * waitHours);
                        this.Logger.LogError("Let's encrypt too many requests issue. Certificate provisioning disabled for the next {0} hours.", waitHours);
                        this.Logger.LogException(acmeException, EventLogEntryType.Warning);
                        return;
                    }

                    throw;
                }

                // Write the challanege contents
                string challengeFullPath =
                    Path.Combine(this.GetAcmeTemporarySiteRootForApplication(), challengeFilePath);

                File.WriteAllText(
                    challengeFullPath,
                    challengeContent);

                this.Logger.LogInfo(false, $"Veryfing challenge at '{challengeUrl}'");

                try
                {
                    // Validate that we can actually access the challenge ourselves!
                    string contents = UtilsSystem.DownloadUriAsText(challengeUrl, false);

                    if (!string.Equals(contents, challengeContent))
                    {
                        throw new Exception(
                                  $"Could not validate ACME challenge, retrieved challenge '{contents}' does not match '{challengeContent}'");
                    }
                }
                catch (Exception e)
                {
                    this.Logger.LogWarning(true, "Cannot self-verify auth challenge, this can sometimes happeen under some DNS setups. {0}", e.Message + Environment.NewLine + e.InnerException?.Message);
                }

                var challengeValidated = false;

                try
                {
                    challengeValidated = provider.ValidateChallenge();
                }
                catch (Exception e)
                {
                    this.Logger.LogException(e, EventLogEntryType.Warning);
                }

                this.Logger.LogWarning(true, "Remote challenge validation success: " + (challengeValidated ? "Yes" : "No"));

                // Download the certificates to this temp location
                string           temporaryCertificatePath = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.StoragePath, this.AppId, "ssl_certificates", hostName), true);
                CertificatePaths certificatepaths         = null;

                // This is here for testing purposes
                if (Environment.GetEnvironmentVariable("TEST_FAIL_CHALLENGE_VALIDATION") == true.ToString())
                {
                    challengeValidated = false;
                }

                if (!challengeValidated)
                {
                    // There is a Failed Validation limit of 5 failures per account, per hostname, per hour.
                    renewalState.FailedValidations = renewalState.FailedValidations ?? new List <DateTime>();
                    renewalState.FailedValidations.Add(DateTime.UtcNow);

                    this.Logger.LogError(
                        "Challenge could not be validated at '{0}'. If behind a load balancer, make sure that the site is deployed in ALL nodes, remove the self-signed certificate from the store and redeploy the application.",
                        challengeUrl);

                    this.StoreCertificateRenewalState(renewalState);
                }
                else
                {
                    try
                    {
                        certificatepaths = provider.DownloadCertificate(
                            UtilsEncryption.GetMD5(hostName),
                            hostName,
                            temporaryCertificatePath);
                    }
                    catch (AcmeClient.AcmeWebException acmeException)
                    {
                        this.Logger.LogException(acmeException, EventLogEntryType.Warning);
                    }
                    catch (WebException webException)
                    {
                        this.Logger.LogException(webException, EventLogEntryType.Warning);
                    }
                    catch (Exception e)
                    {
                        this.Logger.LogException(e, EventLogEntryType.Warning);
                    }
                }

                if (certificatepaths == null && currentCertificate == null)
                {
                    this.Logger.LogWarning(false, "Unable to acquire certificate and site does not have a valid existing one, using self-signed fallback.");

                    provider = new AcmeSharpProviderMock(this.Logger, hostName);

                    certificatepaths = provider.DownloadCertificate(
                        UtilsEncryption.GetMD5(hostName),
                        hostName,
                        temporaryCertificatePath);
                }

                // Save this, use a fixed name certificate file
                if (certificatepaths != null)
                {
                    string certificateFilePath = Path.Combine(UtilsIis.CentralStorePath(this.Logger), hostName + ".pfx");

                    UtilsSystem.RetryWhile(
                        () => { File.Copy(certificatepaths.pfxPemFile, certificateFilePath, true); },
                        (e) => true,
                        2500,
                        this.Logger);

                    this.Logger.LogInfo(false, "Certificate file writen to '{0}'", certificateFilePath);

                    // TODO: Activate this refreshing when it's prooved to work
                    // UtilsIis.EnsureCertificateInCentralCertificateStoreIsRebound(hostName, this.Logger);
                }

                // Remove temporary certificates
                UtilsSystem.DeleteDirectory(temporaryCertificatePath, this.Logger);

                // Remove the already used challenge if it was validated. Otherwise keep it
                // for debugging purposes.
                if (challengeValidated && File.Exists(challengeFullPath))
                {
                    File.Delete(challengeFullPath);
                }

                // In the end, we always have a certificate. Program a renewal date according to the remaining expiration.
                currentCertificate       = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _);
                remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0;

                // Add some randomness in renewal dates to avoid all certificates being renewed at once and reaching api limits
                renewalState.LastRenewal = DateTime.UtcNow;
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
            }
            finally
            {
                this.Logger.LogInfo(true, "Disposing temporary verification setup");

                provider?.Dispose();

                this.UtilsHosts.RemoveHostsMapping(tempSiteAppId);

                // Restore the original state of IIS!!!
                using (ServerManager sm = new ServerManager())
                {
                    var site = sm.Sites.Single(i => i.Id == tempSiteId);
                    UtilsIis.RemoveSite(site, sm, this.Logger);
                    UtilsIis.CommitChanges(sm);
                }

                // Give IIS some time to reconfigure itself and free resources.
                Thread.Sleep(1000);

                // Start the sites
                foreach (var site in haltedSites)
                {
                    // Add some retry logic here because bringing the original sites online is critical
                    UtilsSystem.RetryWhile(() => { this.AppPoolUtils.WebsiteAction(site.Name, AppPoolActionType.Start); }, (e) => true, 5000, this.Logger);
                }
            }
        }
Example #21
0
 protected string GetPhpExe()
 {
     return(UtilsSystem.CombinePaths(this.Deployment.runtimePath, "php", "php.exe"));
 }
        public void deploy()
        {
            var diskSettings = this.DeployerSettings.castTo <DiskServiceSettings>();

            var baseStoragePath = this.GetStoragePath(diskSettings);

            if (diskSettings.mounts == null || !diskSettings.mounts.Any())
            {
                throw new Exception("You must specify at least a mount for a disk service.");
            }

            // Each one of these is to be mounted as a symlink/junction
            foreach (var mount in diskSettings.mounts)
            {
                if (string.IsNullOrWhiteSpace(diskSettings.id))
                {
                    throw new Exception("Disk settings must have an id");
                }

                if (string.IsNullOrWhiteSpace(mount.Value.id))
                {
                    throw new Exception("All mounts in disk configuration must have an id");
                }

                // Expand the local path..
                var mountDestination = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(baseStoragePath, mount.Value.path), true);
                this.Logger.LogInfo(true, "Mounting disk '{0}' at {1}", mount.Value.id, mountDestination);

                var settingkey = $"services.{diskSettings.id}.mount.{mount.Value.id}.path";

                // We might sometimes need to force a specific path in an environment...
                if (this.Deployment.installedApplicationSettings.GetRuntimeSettingsOverrides().ContainsKey(settingkey))
                {
                    string newMountDestination = this.Deployment.installedApplicationSettings.GetRuntimeSettingsOverrides()[settingkey];
                    if (Directory.Exists(newMountDestination))
                    {
                        this.Logger.LogInfo(false, "Default mount for '{0}' overriden with '{1}' from a default value of '{2}'.", settingkey, newMountDestination, mountDestination);
                        mountDestination = newMountDestination;
                    }
                    else
                    {
                        this.Logger.LogInfo(false, "Tried to override mount path ({0}) with a non-existent directory: '{1}'", settingkey, newMountDestination);
                    }
                }

                // Ensure proper permissions
                this.Logger.LogInfo(true, "Ensure mount has proper user permissions for account '{0}'", this.Deployment.WindowsUsernameFqdn());
                UtilsWindowsAccounts.AddPermissionToDirectoryIfMissing(this.Deployment.WindowsUsernameFqdn(), mountDestination, FileSystemRights.Modify, this.GlobalSettings.directoryPrincipal);

                string mountPath = null;

                if (!string.IsNullOrWhiteSpace(mount.Value.mountpath))
                {
                    mountPath = UtilsSystem.CombinePaths(this.Deployment.appPath, mount.Value.mountpath);
                    UtilsJunction.EnsureLink(mountPath, mountDestination, this.Logger, mount.Value.persist_on_deploy);
                }

                // Wether we requested or not a mountpath, make a link in the runtime folder to all disk stores
                var localMountPath = UtilsSystem.CombinePaths(this.Deployment.runtimePath, "disk", mount.Value.id);
                this.Logger.LogInfo(true, "Linking disk at local path {0}", localMountPath);
                UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.Deployment.runtimePath, "disk"), true);
                UtilsJunction.EnsureLink(localMountPath, mountDestination, this.Logger, mount.Value.persist_on_deploy);

                // Make only the local mount path visible to the application
                this.Deployment.SetRuntimeSetting(settingkey, localMountPath);

                this.Deployment.SetSettingCollection($"service.{diskSettings.id}", settingkey, new DiskStore()
                {
                    path             = localMountPath,
                    junction         = mountPath,
                    originalPath     = mountDestination,
                    junctionRealPath = UtilsJunction.ResolvePath(mountPath)
                });
            }
        }
Example #23
0
        /// <summary>
        /// Archive and cleanup the log directories...
        ///
        /// TODO: This is hardcoded here... should be a true cron
        /// that each deployer implements the way they want...
        /// </summary>
        protected void CleanupLogDirectories()
        {
            var logPath = this.GlobalSettings.GetDefaultLogStorage().path;

            this.Logger.LogInfo(true, "Cleaning log directories at path: {0}", logPath);

            var files = Directory.EnumerateFiles(logPath, "*", SearchOption.AllDirectories);

            foreach (string f in files)
            {
                try
                {
                    FileInfo info = new FileInfo(f);

                    // Delete any files not touched within the last six months.
                    if ((DateTime.Now - info.LastWriteTime).TotalDays > (30 * 6) &&
                        (info.FullName.EndsWith("_bak.zip") || info.Extension.ToLower() == ".log" ||
                         info.Extension.ToLower() == ".txt"))
                    {
                        File.Delete(f);
                        this.Logger.LogInfo(true, "Deleted log file: {0}", info.FullName);
                        continue;
                    }

                    // Zip any log files that are larger than 100Mb or
                    // have not been writen into in the last 30 days.
                    bool extensionCriteria = (info.Extension.ToLower() == ".log" ||
                                              info.Extension.ToLower() == ".txt");

                    bool timeCriteria = (DateTime.UtcNow - info.LastWriteTimeUtc).TotalDays > 30;
                    bool sizeCriteria = info.Length > 1024 * 1024 * 100;

                    if (extensionCriteria && (timeCriteria && sizeCriteria))
                    {
                        string name = info.FullName;
                        string extensionlessName = name.Replace(info.Extension, string.Empty);
                        string folderTemp        = extensionlessName;

                        Directory.CreateDirectory(extensionlessName);

                        info.MoveTo(UtilsSystem.CombinePaths(extensionlessName, info.Name));
                        string diff = info.CreationTime.ToString("yyyyMMddHHmmss");
                        ZipFile.CreateFromDirectory(folderTemp, extensionlessName + "_" + diff + "_bak.zip");
                        Directory.Delete(folderTemp, true);

                        this.Logger.LogInfo(true, "Archived log file: {0}", info.FullName);
                    }
                }
                catch (UnauthorizedAccessException e)
                {
                    this.Logger.LogException(e, EventLogEntryType.Warning);
                }
                catch (FileNotFoundException e)
                {
                    this.Logger.LogException(e, EventLogEntryType.Warning);
                }
                catch (Exception e)
                {
                    this.Logger.LogException(e, EventLogEntryType.Warning);
                }
            }
        }
        /// <summary>
        /// Execute the opreation...
        /// </summary>
        /// <param name="destination"></param>
        /// <param name="forceDownload"></param>
        protected void DoExecute(
            string destination,
            bool forceDownload = false)
        {
            var uri  = this.Config.uri;
            var maps = this.Config.maps;

            var filename = Path.GetFileName(uri);

            var tmpDir  = UtilsSystem.GetTempPath("iischef_cache", UtilsEncryption.GetMD5(uri));
            var tmpFile = UtilsSystem.CombinePaths(UtilsSystem.GetTempPath(), UtilsEncryption.GetMD5(uri) + "_" + filename);

            if (forceDownload && Directory.Exists(tmpDir))
            {
                Directory.Delete(tmpDir, true);
            }

            if (Directory.Exists(tmpDir))
            {
                var difo = new DirectoryInfo(tmpDir);
                if (!difo.EnumerateFiles("*", SearchOption.AllDirectories).Any())
                {
                    Directory.Delete(tmpDir, true);
                }
            }

            if (!Directory.Exists(tmpDir))
            {
                var parsedUri = new Uri(uri);
                if (parsedUri.Scheme.Equals("file", StringComparison.CurrentCultureIgnoreCase))
                {
                    var path = Path.Combine(this.LocalArtifactPath, parsedUri.LocalPath.TrimStart("\\".ToCharArray()));
                    File.Copy(path, tmpFile);
                }
                else
                {
                    using (var wc = new WebClient())
                    {
                        try
                        {
                            wc.Headers.Add(
                                "User-Agent",
                                "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.33 Safari/537.36");
                            wc.DownloadFile(uri, tmpFile);
                        }
                        catch (Exception ex)
                        {
                            throw new Exception("Could not download file: " + uri, ex);
                        }
                    }
                }

                UtilsSystem.EnsureDirectoryExists(tmpDir, true);

                if (tmpFile.EndsWith(".zip"))
                {
                    ZipFile.ExtractToDirectory(tmpFile, tmpDir);
                }
                else
                {
                    File.Move(tmpFile, UtilsSystem.CombinePaths(tmpDir, filename));
                }

                File.Delete(tmpFile);
            }

            // Move the files according to the maps
            foreach (var map in maps)
            {
                var files = (new DirectoryInfo(tmpDir)).GetFiles(map.Key, SearchOption.AllDirectories);

                if (!files.Any())
                {
                    throw new Exception(
                              string.Format(
                                  "No matching files found for pattern: {0} in package {1} ['{2}']",
                                  map.Key,
                                  uri,
                                  tmpDir));
                }

                if (files.Count() == 1)
                {
                    var dest = UtilsSystem.CombinePaths(destination, map.Value);
                    UtilsSystem.EnsureDirectoryExists(dest);
                    File.Copy(files.First().FullName, dest);
                }
                else
                {
                    foreach (var f in files)
                    {
                        var subpath = f.FullName.Replace((new DirectoryInfo(tmpDir)).FullName, string.Empty);
                        var dest    = UtilsSystem.CombinePaths(destination, map.Value, subpath);
                        UtilsSystem.EnsureDirectoryExists(dest);

                        try
                        {
                            File.Copy(f.FullName, dest);
                        }
                        catch (Exception e)
                        {
                            throw new Exception($"Error copying file '{f.FullName}' to '{dest}'");
                        }
                    }
                }
            }
        }
Example #25
0
        protected void DeployPhpRuntimeShortcut()
        {
            string command = $"{this.GetPhpExe()} -c \"{this.GetIniFilePath()}\" %*";

            var destionationDir = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.Deployment.runtimePath, "include_path"), true);

            File.WriteAllText(UtilsSystem.CombinePaths(destionationDir, "php.bat"), command);

            File.WriteAllText(
                UtilsSystem.CombinePaths(destionationDir, "setenv.bat"),
                string.Format(
                    @"
set path={0};%path%
cd /D ""{1}""
",
                    destionationDir.Replace("\"", "\"\""),
                    this.Deployment.appPath.Replace("\"", "\"\"")));

            File.WriteAllText(
                UtilsSystem.CombinePaths(destionationDir, "setenv.ps1"),
                string.Format(
                    @"
$Env:Path=""{0};$($Env:Path)"";
CD ""{1}""
",
                    destionationDir.Replace("\"", "\"\""),
                    this.Deployment.appPath.Replace("\"", "\"\"")));

            File.WriteAllText(
                UtilsSystem.CombinePaths(destionationDir, "launch_console.bat"),
                "cmd /k setenv.bat");

            File.WriteAllText(UtilsSystem.CombinePaths(destionationDir, "launch_console_admin_UAC.bat"),
                              @"

@echo off
set _SCRIPT_DRIVE=%~d0
set _SCRIPT_PATH=%~p0

call :isAdmin

if %errorlevel% == 0 (
   goto :run
) else (
   echo Requesting administrative privileges...
   goto :UACPrompt
)

exit /b

:isAdmin
   fsutil dirty query %systemdrive% >nul
exit /b

:run
 REM <YOUR BATCH CODE GOES HERE>
 %_SCRIPT_DRIVE%
 cd %_SCRIPT_PATH%
 cmd /k setenv.bat
exit /b

:UACPrompt
  echo Set UAC = CreateObject^(""Shell.Application""^) > ""%temp%\getadmin.vbs""
  echo UAC.ShellExecute ""cmd.exe"", ""/c %~s0 %~1"", """", ""runas"", 1 >> ""%temp%\getadmin.vbs""

  ""%temp%\getadmin.vbs""
  del ""%temp%\getadmin.vbs""
 exit / B`
");
        }
Example #26
0
        /// <summary>
        /// Downloads (And extracts) single artifacts from jobs.
        /// </summary>
        /// <param name="applicationId"></param>
        /// <param name="build"></param>
        /// <param name="artifactRegex"></param>
        /// <param name="destinationPath"></param>
        /// <param name="logger"></param>
        public void DownloadSingleArtifactFromBuild(
            string applicationId,
            Build build,
            string artifactRegex,
            string destinationPath,
            ILoggerInterface logger)
        {
            UtilsSystem.EnsureDirectoryExists(destinationPath, true);

            // Use the first job in the build...
            var job      = build.jobs.First();
            var artifact = this.FindDefaultArtifactForBuild(job, build, artifactRegex);

            var filename  = artifact.fileName;
            var extension = Path.GetExtension(filename);

            string downloadTemporaryDir =
                UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.TempDir, "_appveyor", "dld", applicationId), true);

            int artifactRetentionNum     = 5;
            int artifactAgeHoursForStale = 24;

            // Do not touch the latest artifactRetentionNum artifacts or artifacts that are not older than artifactAgeHoursForStale hours
            var staleFiles = Directory.EnumerateFiles(downloadTemporaryDir)
                             .Select((i) => new FileInfo(i))
                             .Where((i) => i.Extension.Equals(".zip", StringComparison.CurrentCultureIgnoreCase))
                             .OrderByDescending((i) => i.CreationTimeUtc)
                             .Skip(artifactRetentionNum)
                             .Where((i) => (DateTime.UtcNow - i.LastWriteTime).TotalHours > artifactAgeHoursForStale)
                             .ToList();

            foreach (var f in staleFiles)
            {
                // Make this fail proof, it's just a cleanup.
                try
                {
                    this.Logger.LogInfo(true, "Removing stale artifact cache file {0}", f.FullName);
                    f.Delete();
                }
                catch
                {
                    // ignored
                }
            }

            // Use a short hash as the temporary file name, because long paths can have issues...
            var tmpFile = UtilsSystem.CombinePaths(downloadTemporaryDir, UtilsEncryption.GetShortHash(JsonConvert.SerializeObject(build) + filename) + extension);

            if (Path.GetExtension(tmpFile)?.ToLower() != ".zip")
            {
                throw new NotImplementedException("AppVeyor artifacts should only be Zip Files.");
            }

            if (!File.Exists(tmpFile))
            {
                // Use an intermediate .tmp file just in case the files does not finish to download,
                // if it exists, clear it.
                string tmpFileDownload = tmpFile + ".tmp";
                if (File.Exists(tmpFileDownload))
                {
                    UtilsSystem.RetryWhile(() => File.Delete(tmpFileDownload), (e) => true, 4000, this.Logger);
                }

                var url = $"/api/buildjobs/{job.jobId}/artifacts/{filename}";
                logger.LogInfo(true, "Downloading artifact from: '{0}' to '{1}'", url, tmpFileDownload);
                this.ExecuteApiCallToFile(url, tmpFileDownload);

                // Rename to the final cached artifact file
                logger.LogInfo(true, "Download succesful, moving to '{0}'", tmpFile);
                UtilsSystem.RetryWhile(() => File.Move(tmpFileDownload, tmpFile), (e) => true, 4000, this.Logger);
            }
            else
            {
                logger.LogInfo(true, "Skipping artifact download, already in local cache: {0}", tmpFile);
            }

            logger.LogInfo(true, "Unzipping {1} file to '{0}'...", destinationPath, UtilsSystem.BytesToString(new FileInfo(tmpFile).Length));

            ZipFile.ExtractToDirectory(tmpFile, destinationPath);

            logger.LogInfo(true, "Unzipping finished.");
        }