Esempio n. 1
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            CompileJob cj = null;
            await databaseContextFactory.UseContext(async (db) =>
            {
                cj = await db
                     .CompileJobs
                     .AsQueryable()
                     .Where(x => x.Job.Instance.Id == metadata.Id)
                     .OrderByDescending(x => x.Job.StoppedAt)
                     .FirstOrDefaultAsync(cancellationToken)
                     .ConfigureAwait(false);
            })
            .ConfigureAwait(false);

            if (cj == default(CompileJob))
            {
                return;
            }
            await LoadCompileJob(cj, cancellationToken).ConfigureAwait(false);

            started = true;

            // we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet
        }
Esempio n. 2
0
        /// <inheritdoc />
        public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
        {
            if (newPath == null)
            {
                throw new ArgumentNullException(nameof(newPath));
            }
            if (instance.Online.Value)
            {
                throw new InvalidOperationException("Cannot move an online instance!");
            }
            var oldPath = instance.Path;
            await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);

            await databaseContextFactory.UseContext(db =>
            {
                var targetInstance = new Models.Instance
                {
                    Id = instance.Id
                };
                db.Instances.Attach(targetInstance);
                targetInstance.Path = newPath;
                return(db.Save(cancellationToken));
            }).ConfigureAwait(false);

            await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
        }
Esempio n. 3
0
        /// <inheritdoc />
        public async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

            CompileJob finalCompileJob = null;
            //now load the entire compile job tree
            await databaseContextFactory.UseContext(async db => finalCompileJob = await db.CompileJobs.Where(x => x.Id == job.Id)
                                                    .Include(x => x.Job).ThenInclude(x => x.StartedBy)
                                                    .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
                                                    .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
                                                    .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query

            if (finalCompileJob == null)
            {
                //lol git f****d
                return;
            }

            var newProvider = await FromCompileJob(finalCompileJob, cancellationToken).ConfigureAwait(false);

            if (newProvider == null)
            {
                return;
            }
            lock (this)
            {
                nextDmbProvider?.Dispose();
                nextDmbProvider = newProvider;
                newerDmbTcs.SetResult(nextDmbProvider);
                newerDmbTcs = new TaskCompletionSource <object>();
            }
        }
Esempio n. 4
0
 /// <inheritdoc />
 public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async(db) =>
 {
     //where complete clause not necessary, only successful COMPILEjobs get in the db
     var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id)
              .OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
     if (cj == default(CompileJob))
     {
         return;
     }
     await LoadCompileJob(cj, cancellationToken).ConfigureAwait(false);
     //we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet
 });
 /// <inheritdoc />
 public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
 {
     try
     {
         await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
         await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
         var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
                           .Include(x => x.RepositorySettings)
                           .Include(x => x.ChatSettings)
                           .ThenInclude(x => x.Channels)
                           .Include(x => x.DreamDaemonSettings)
                           .ToAsyncEnumerable();
         var tasks = new List <Task>();
         await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false);
         await Task.WhenAll(tasks).ConfigureAwait(false);
         logger.LogInformation("Instance manager ready!");
         application.Ready(null);
     }
     catch (OperationCanceledException)
     {
         logger.LogInformation("Cancelled instance manager initialization!");
     }
     catch (Exception e)
     {
         logger.LogCritical("Instance manager startup error! Exception: {0}", e);
         application.Ready(e);
     }
 });
Esempio n. 6
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            CompileJob cj = null;
            await databaseContextFactory.UseContext(async (db) =>
            {
                cj = await db
                     .MostRecentCompletedCompileJobOrDefault(instance, cancellationToken)
                     .ConfigureAwait(false);
            })
            .ConfigureAwait(false);

            if (cj == default(CompileJob))
            {
                return;
            }
            await LoadCompileJob(cj, cancellationToken).ConfigureAwait(false);

            // we dont do CleanUnusedCompileJobs here because the watchdog may have plans for them yet
        }
Esempio n. 7
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            await Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Configuration.StartAsync(cancellationToken), ByondManager.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), CompileJobConsumer.StartAsync(cancellationToken)).ConfigureAwait(false);

            //dependent on so many things, its just safer this way
            await Watchdog.StartAsync(cancellationToken).ConfigureAwait(false);

            CompileJob latestCompileJob = null;
            await databaseContextFactory.UseContext(async db =>
            {
                latestCompileJob = await db.CompileJobs.Where(x => x.Job.Instance.Id == metadata.Id && x.Job.ExceptionDetails == null).OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
            }).ConfigureAwait(false);

            await dmbFactory.CleanUnusedCompileJobs(latestCompileJob, cancellationToken).ConfigureAwait(false);
        }
Esempio n. 8
0
        /// <inheritdoc />
        // TODO: Decomplexify
                #pragma warning disable CA1506
        public async Task <string> Invoke(string arguments, ChatUser user, CancellationToken cancellationToken)
        {
            IEnumerable <Models.TestMerge> results = null;

            if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--REPO"))
            {
                string head;
                using (var repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
                {
                    if (repo == null)
                    {
                        return("Repository unavailable!");
                    }
                    head = repo.Head;
                }

                await databaseContextFactory.UseContext(
                    async db => results = await db
                    .RevisionInformations
                    .AsQueryable()
                    .Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
                    .SelectMany(x => x.ActiveTestMerges)
                    .Select(x => x.TestMerge)
                    .Select(x => new Models.TestMerge
                {
                    Number = x.Number,
                    PullRequestRevision = x.PullRequestRevision
                })
                    .ToListAsync(cancellationToken)
                    .ConfigureAwait(false))
                .ConfigureAwait(false);
            }
            else
            {
                if (!watchdog.Running)
                {
                    return("Server offline!");
                }
                results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List <Models.TestMerge>();
            }

            return(!results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))));
        }
Esempio n. 9
0
        /// <inheritdoc />
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            var reattachInfo = await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false);

            if (!autoStart && reattachInfo == null)
            {
                return;
            }

            long?adminUserId = null;

            await databaseContextFactory.UseContext(async db => adminUserId = await db.Users
                                                    .Where(x => x.CanonicalName == Api.Models.User.AdminName.ToUpperInvariant())
                                                    .Select(x => x.Id)
                                                    .FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);

            var job = new Models.Job
            {
                StartedBy = new Models.User
                {
                    Id = adminUserId.Value
                },
                Instance = new Models.Instance
                {
                    Id = instance.Id
                },
                Description      = "Instance startup watchdog launch",
                CancelRight      = (ulong)DreamDaemonRights.Shutdown,
                CancelRightsType = RightsType.DreamDaemon
            };
            await jobManager.RegisterOperation(job, async (j, databaseContext, progressFunction, ct) =>
            {
                using (await SemaphoreSlimContext.Lock(Semaphore, ct).ConfigureAwait(false))
                    await LaunchImplNoLock(true, true, reattachInfo, ct).ConfigureAwait(false);
            }, cancellationToken).ConfigureAwait(false);
        }
        /// <summary>
        /// Pull the repository and compile for every set of given <paramref name="minutes"/>
        /// </summary>
        /// <param name="minutes">How many minutes the operation should repeat. Does not include running time</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
        /// <returns>A <see cref="Task"/> representing the running operation</returns>
                #pragma warning disable CA1502 // TODO: Decomplexify
        async Task TimerLoop(uint minutes, CancellationToken cancellationToken)
        {
            while (true)
            {
                try
                {
                    await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false);

                    logger.LogInformation("Beginning auto update...");
                    await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, new List <string>(), cancellationToken).ConfigureAwait(false);

                    try
                    {
                        Models.User user = null;
                        await databaseContextFactory.UseContext(async (db) => user = await db.Users.Where(x => x.CanonicalName == Api.Models.User.AdminName.ToUpperInvariant()).FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);

                        var repositoryUpdateJob = new Job
                        {
                            Instance = new Models.Instance
                            {
                                Id = metadata.Id
                            },
                            Description      = "Scheduled repository update",
                            CancelRightsType = RightsType.Repository,
                            CancelRight      = (ulong)RepositoryRights.CancelPendingChanges,
                            StartedBy        = user
                        };

                        string deploySha = null;
                        await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) =>
                        {
                            var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);

                            // assume 5 steps with synchronize
                            const int ProgressSections = 7;
                            const int ProgressStep     = 100 / ProgressSections;

                            const int NumSteps = 3;
                            var doneSteps      = 0;

                            Action <int> NextProgressReporter()
                            {
                                var tmpDoneSteps = doneSteps;
                                ++doneSteps;
                                return(progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps));
                            }

                            using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false))
                            {
                                if (repo == null)
                                {
                                    logger.LogTrace("Aborting repo update, no repository!");
                                    return;
                                }

                                var startSha = repo.Head;
                                if (!repo.Tracking)
                                {
                                    logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!");
                                    deploySha = startSha;
                                    return;
                                }

                                var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);

                                // the main point of auto update is to pull the remote
                                await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                RevisionInformation currentRevInfo = null;
                                bool hasDbChanges = false;

                                Task <RevisionInformation> LoadRevInfo() => databaseContext.RevisionInformations
                                .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
                                .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
                                .FirstOrDefaultAsync(cancellationToken);

                                async Task UpdateRevInfo(string currentHead, bool onOrigin)
                                {
                                    if (currentRevInfo == null)
                                    {
                                        currentRevInfo = await LoadRevInfo().ConfigureAwait(false);
                                    }

                                    if (currentRevInfo == default)
                                    {
                                        logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead);
                                        onOrigin = true;
                                    }

                                    var attachedInstance = new Models.Instance
                                    {
                                        Id = metadata.Id
                                    };
                                    var oldRevInfo = currentRevInfo;
                                    currentRevInfo = new RevisionInformation
                                    {
                                        CommitSha       = currentHead,
                                        OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha,
                                        Instance        = attachedInstance
                                    };
                                    if (!onOrigin)
                                    {
                                        currentRevInfo.ActiveTestMerges = new List <RevInfoTestMerge>(oldRevInfo.ActiveTestMerges);
                                    }

                                    databaseContext.Instances.Attach(attachedInstance);
                                    databaseContext.RevisionInformations.Add(currentRevInfo);
                                    hasDbChanges = true;
                                }

                                // take appropriate auto update actions
                                bool shouldSyncTracked;
                                if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
                                {
                                    logger.LogTrace("Preserving test merges...");

                                    var currentRevInfoTask = LoadRevInfo();

                                    var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                    if (!result.HasValue)
                                    {
                                        throw new JobException("Merge conflict while preserving test merges!");
                                    }

                                    currentRevInfo = await currentRevInfoTask.ConfigureAwait(false);

                                    var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha;
                                    var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit;

                                    var currentHead = repo.Head;
                                    if (currentHead != startSha)
                                    {
                                        await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false);
                                        shouldSyncTracked = stillOnOrigin;
                                    }
                                    else
                                    {
                                        shouldSyncTracked = false;
                                    }
                                }
                                else
                                {
                                    logger.LogTrace("Not preserving test merges...");
                                    await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);

                                    var currentHead = repo.Head;

                                    currentRevInfo = await databaseContext.RevisionInformations
                                                     .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
                                                     .FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);

                                    if (currentHead != startSha && currentRevInfo != default)
                                    {
                                        await UpdateRevInfo(currentHead, true).ConfigureAwait(false);
                                    }

                                    shouldSyncTracked = true;
                                }

                                // synch if necessary
                                if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
                                {
                                    var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
                                    var currentHead  = repo.Head;
                                    if (currentHead != currentRevInfo.CommitSha)
                                    {
                                        await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false);
                                    }
                                }

                                if (hasDbChanges)
                                {
                                    try
                                    {
                                        await databaseContext.Save(cancellationToken).ConfigureAwait(false);
                                    }
                                    catch
                                    {
                                        await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false);
                                        throw;
                                    }
                                }

                                progressReporter(5 * ProgressStep);
                                deploySha = repo.Head;
                            }
                        }, cancellationToken).ConfigureAwait(false);

                        await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false);

                        if (deploySha == null)
                        {
                            logger.LogTrace("Aborting auto update, repository error!");
                            continue;
                        }

                        if (deploySha == LatestCompileJob()?.RevisionInformation.CommitSha)
                        {
                            logger.LogTrace("Aborting auto update, same revision as latest CompileJob");
                            continue;
                        }

                        // finally set up the job
                        var compileProcessJob = new Job
                        {
                            StartedBy        = user,
                            Instance         = repositoryUpdateJob.Instance,
                            Description      = "Scheduled code deployment",
                            CancelRightsType = RightsType.DreamMaker,
                            CancelRight      = (ulong)DreamMakerRights.CancelCompile
                        };

                        await jobManager.RegisterOperation(compileProcessJob, CompileProcess, cancellationToken).ConfigureAwait(false);

                        await jobManager.WaitForJobCompletion(compileProcessJob, user, cancellationToken, default).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                        logger.LogDebug("Cancelled auto update job!");
                        throw;
                    }
                    catch (Exception e)
                    {
                        logger.LogWarning("Error in auto update loop! Exception: {0}", e);
                        continue;
                    }
                }
                catch (OperationCanceledException)
                {
                    break;
                }
            }

            logger.LogTrace("Leaving auto update loop...");
        }
        /// <inheritdoc />
        public async Task StartDeployment(IRepository repository, CompileJob compileJob, CancellationToken cancellationToken)
        {
            if (repository == null)
            {
                throw new ArgumentNullException(nameof(repository));
            }
            if (compileJob == null)
            {
                throw new ArgumentNullException(nameof(compileJob));
            }

            if (!repository.IsGitHubRepository)
            {
                logger.LogTrace("Not managing deployment as this is not a GitHub repo");
                return;
            }

            logger.LogTrace("Starting deployment...");

            RepositorySettings repositorySettings = null;
            await databaseContextFactory.UseContext(
                async databaseContext =>
                repositorySettings = await databaseContext
                .RepositorySettings
                .AsQueryable()
                .Where(x => x.InstanceId == metadata.Id)
                .FirstAsync(cancellationToken)
                .ConfigureAwait(false))
            .ConfigureAwait(false);

            var gitHubClient = repositorySettings.AccessToken == null
                                ? gitHubClientFactory.CreateClient()
                                : gitHubClientFactory.CreateClient(repositorySettings.AccessToken);

            var repositoryTask = gitHubClient
                                 .Repository
                                 .Get(
                repository.GitHubOwner,
                repository.GitHubRepoName);

            if (repositorySettings.CreateGitHubDeployments.Value)
            {
                logger.LogTrace("Creating deployment...");
                var deployment = await gitHubClient
                                 .Repository
                                 .Deployment
                                 .Create(
                    repository.GitHubOwner,
                    repository.GitHubRepoName,
                    new NewDeployment(compileJob.RevisionInformation.CommitSha)
                {
                    AutoMerge             = false,
                    Description           = "TGS Game Deployment",
                    Environment           = $"TGS: {metadata.Name}",
                    ProductionEnvironment = true,
                    RequiredContexts      = new Collection <string>()
                })
                                 .WithToken(cancellationToken)
                                 .ConfigureAwait(false);

                compileJob.GitHubDeploymentId = deployment.Id;
                logger.LogDebug("Created deployment ID {0}", deployment.Id);

                await gitHubClient
                .Repository
                .Deployment
                .Status
                .Create(
                    repository.GitHubOwner,
                    repository.GitHubRepoName,
                    deployment.Id,
                    new NewDeploymentStatus(DeploymentState.InProgress)
                {
                    Description  = "The project is being deployed",
                    AutoInactive = false
                })
                .WithToken(cancellationToken)
                .ConfigureAwait(false);

                logger.LogTrace("In-progress deployment status created");
            }
            else
            {
                logger.LogTrace("Not creating deployment");
            }

            try
            {
                var gitHubRepo = await repositoryTask
                                 .WithToken(cancellationToken)
                                 .ConfigureAwait(false);

                compileJob.GitHubRepoId = gitHubRepo.Id;
                logger.LogTrace("Set GitHub ID as {0}", compileJob.GitHubRepoId);
            }
            catch (RateLimitExceededException ex) when(!repositorySettings.CreateGitHubDeployments.Value)
            {
                logger.LogWarning(ex, "Unable to set compile job repository ID!");
            }
        }
        /// <inheritdoc />
        public Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async(db) =>
        {
            if (reattachInformation == null)
            {
                throw new ArgumentNullException(nameof(reattachInformation));
            }

            logger.LogDebug("Saving reattach information: {0}...", reattachInformation);

            await db
            .ReattachInformations
            .AsQueryable()
            .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id)
            .DeleteAsync(cancellationToken)
            .ConfigureAwait(false);

            var dbReattachInfo = new Models.ReattachInformation
            {
                AccessIdentifier    = reattachInformation.AccessIdentifier,
                CompileJobId        = reattachInformation.Dmb.CompileJob.Id,
                Port                = reattachInformation.Port,
                ProcessId           = reattachInformation.ProcessId,
                RebootState         = reattachInformation.RebootState,
                LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel
            };

            db.ReattachInformations.Add(dbReattachInfo);
            await db.Save(cancellationToken).ConfigureAwait(false);
        });