示例#1
0
        public async Task Handle(RunAdded notification, CancellationToken cancellationToken)
        {
            var run = await _db.Runs
                      .Include(r => r.Workspace)
                      .ThenInclude(w => w.Directory)
                      .Include(r => r.Workspace)
                      .ThenInclude(w => w.Host)
                      .SingleOrDefaultAsync(x => x.Id == notification.RunId);

            try
            {
                var isError = await DoWork(run);

                _plan.Output = _output.Content;
                _plan.Status = !isError ? PlanStatus.Planned : PlanStatus.Failed;
                run.Status   = !isError ? RunStatus.Planned : RunStatus.Failed;

                _output.SetCompleted();
                await _db.SaveChangesAsync();

                _outputService.RemoveOutput(_plan.Id);
                await _mediator.Publish(new RunUpdated(run.Id));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Error in {nameof(RunAddedHandler)}.Handle");
                run.Status = Domain.Models.RunStatus.Failed;
                await _db.SaveChangesAsync();

                await _mediator.Publish(new RunUpdated(run.Id));
            }
        }
示例#2
0
            protected override async Task Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var workspace = await _db.Workspaces.FindAsync(request.Id);

                if (workspace == null)
                {
                    throw new EntityNotFoundException <Workspace>();
                }

                using (var lockResult = await _lockService.GetWorkspaceLock(request.Id).LockAsync(0))
                {
                    if (!lockResult.AcquiredLock)
                    {
                        throw new WorkspaceConflictException();
                    }

                    if (workspace.GetState().GetResources().Any())
                    {
                        throw new ConflictException("Cannot delete a Workspace with deployed Resources.");
                    }

                    if (await _db.AnyIncompleteRuns(request.Id))
                    {
                        throw new ConflictException("Cannot delete a Workspace with pending Runs.");
                    }

                    _db.Workspaces.Remove(workspace);
                    await _db.SaveChangesAsync(cancellationToken);
                }
            }
示例#3
0
            protected override async Task Handle(Command request, CancellationToken cancellationToken)
            {
                Caster.Api.Domain.Models.UserPermission entry;

                if (!(await _authorizationService.AuthorizeAsync(_user, null, new FullRightsRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                if (request.Id != null)
                {
                    entry = _db.UserPermissions.FirstOrDefault(e => e.Id == request.Id);
                }
                else
                {
                    entry = _db.UserPermissions.FirstOrDefault(e => e.UserId == request.UserId && e.PermissionId == request.PermissionId);
                }

                if (entry == null)
                {
                    throw new EntityNotFoundException <UserPermission>();
                }

                _db.UserPermissions.Remove(entry);
                await _db.SaveChangesAsync(cancellationToken);
            }
示例#4
0
            protected override async Task Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var directory = await _db.Directories.FindAsync(request.Id);

                if (directory == null)
                {
                    throw new EntityNotFoundException <Directory>();
                }

                var workspaces = await CheckForResources(directory);

                if (workspaces.Any())
                {
                    string errorMessage = "Cannot delete this Directory due to existing Resources in the following Workspaces:";

                    foreach (var workspace in workspaces)
                    {
                        errorMessage += $"\n Name: {workspace.Name}, Id: {workspace.Id} in Directory: {workspace.Directory.Name}, {workspace.DirectoryId}";
                    }

                    throw new ConflictException(errorMessage);
                }

                _db.Directories.Remove(directory);
                await _db.SaveChangesAsync(cancellationToken);
            }
示例#5
0
            public async Task <FileVersion[]> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var dateTagged = DateTime.UtcNow;
                var tag        = request.Tag;

                var files = await _db.Files
                            .Where(f => request.FileIds.Contains(f.Id))
                            .ToArrayAsync();

                foreach (var fileId in request.FileIds)
                {
                    var file = files.Where(f => f.Id == fileId).FirstOrDefault();
                    if (file == null)
                    {
                        throw new EntityNotFoundException <File>($"File {fileId} could not be found.");
                    }

                    file.Tag(tag, _user.GetId(), dateTagged);
                }

                await _db.SaveChangesAsync(cancellationToken);

                return(await _db.FileVersions
                       .Where(fileVersion => fileVersion.Tag == request.Tag)
                       .ProjectTo <FileVersion>(_mapper.ConfigurationProvider)
                       .ToArrayAsync());
            }
        protected async Task <File> Handle(IFileCommand request, CancellationToken cancellationToken)
        {
            await this.Authorize();

            using (var lockResult = await _lockService.GetFileLock(request.Id).LockAsync(0))
            {
                if (!lockResult.AcquiredLock)
                {
                    throw new FileConflictException();
                }

                var file = await _db.Files.FindAsync(request.Id);

                if (file == null)
                {
                    throw new EntityNotFoundException <File>();
                }

                await this.PerformOperation(file);

                await _db.SaveChangesAsync(cancellationToken);
            }

            return(await _fileQuery.ExecuteAsync(request.Id));
        }
示例#7
0
        private async Task <User> ValidateUser(Guid subClaim, string nameClaim, bool update)
        {
            var user = await _context.Users
                       .Where(u => u.Id == subClaim)
                       .FirstOrDefaultAsync();

            var anyUsers = await _context.Users.AnyAsync();

            if (update)
            {
                if (user == null)
                {
                    user = new User
                    {
                        Id   = subClaim,
                        Name = nameClaim ?? "Anonymous"
                    };

                    // First user is default SystemAdmin
                    if (!anyUsers)
                    {
                        var systemAdminPermission = await _context.Permissions.Where(p => p.Key == nameof(CasterClaimTypes.SystemAdmin)).FirstOrDefaultAsync();

                        if (systemAdminPermission != null)
                        {
                            user.UserPermissions.Add(new UserPermission(user.Id, systemAdminPermission.Id));
                        }
                    }

                    _context.Users.Add(user);
                    await _context.SaveChangesAsync();
                }
                else
                {
                    if (nameClaim != null && user.Name != nameClaim)
                    {
                        user.Name = nameClaim;
                        _context.Update(user);
                        await _context.SaveChangesAsync();
                    }
                }
            }

            return(user);
        }
示例#8
0
            private async Task <Domain.Models.Run> DoWork(Command request)
            {
                var run = _mapper.Map <Domain.Models.Run>(request);
                await _db.Runs.AddAsync(run);

                await _db.SaveChangesAsync();

                return(run);
            }
示例#9
0
        private async Task <User> ValidateUser(Guid subClaim, string nameClaim, bool update)
        {
            var userQuery = _context.Users.Where(u => u.Id == subClaim).Future();
            var anyUsers  = _context.Users.DeferredAny().FutureValue();
            var user      = (await userQuery.ToListAsync()).SingleOrDefault();

            if (update)
            {
                if (user == null)
                {
                    user = new User
                    {
                        Id   = subClaim,
                        Name = nameClaim ?? "Anonymous"
                    };

                    // First user is default SystemAdmin
                    if (!(await anyUsers.ValueAsync()))
                    {
                        var systemAdminPermission = await _context.Permissions.Where(p => p.Key == CasterClaimTypes.SystemAdmin.ToString()).FirstOrDefaultAsync();

                        if (systemAdminPermission != null)
                        {
                            user.UserPermissions.Add(new UserPermission(user.Id, systemAdminPermission.Id));
                        }
                    }

                    _context.Users.Add(user);
                    await _context.SaveChangesAsync();
                }
                else
                {
                    if (nameClaim != null && user.Name != nameClaim)
                    {
                        user.Name = nameClaim;
                        _context.Update(user);
                        await _context.SaveChangesAsync();
                    }
                }
            }

            return(user);
        }
示例#10
0
            private async Task <Domain.Models.Run> DoWork(Command request)
            {
                var run = _mapper.Map <Domain.Models.Run>(request);

                run.CreatedById = _user.GetId();
                run.Modify(_user.GetId());
                await _db.Runs.AddAsync(run);

                await _db.SaveChangesAsync();

                return(run);
            }
示例#11
0
            public async Task <Exercise> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var exercise = _mapper.Map <Domain.Models.Exercise>(request);
                await _db.Exercises.AddAsync(exercise);

                await _db.SaveChangesAsync();

                return(_mapper.Map <Exercise>(exercise));
            }
示例#12
0
            public async Task <User> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new FullRightsRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var user = _mapper.Map <Domain.Models.User>(request);
                await _db.Users.AddAsync(user);

                await _db.SaveChangesAsync();

                return(_mapper.Map <User>(user));
            }
示例#13
0
            public async Task <Run> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var run = await _db.Runs
                          .Include(r => r.Apply)
                          .Include(r => r.Workspace)
                          .Where(r => r.Id == request.RunId)
                          .FirstOrDefaultAsync(cancellationToken);

                if (run == null)
                {
                    throw new EntityNotFoundException <Run>();
                }

                using (var lockResult = await _lockService.GetWorkspaceLock(run.WorkspaceId).LockAsync(0))
                {
                    if (!lockResult.AcquiredLock)
                    {
                        throw new WorkspaceConflictException();
                    }

                    await ValidateRun(run, cancellationToken);

                    var workingDir     = run.Workspace.GetPath(_options.RootWorkingDirectory);
                    var stateRetrieved = await run.Workspace.RetrieveState(workingDir);

                    if (stateRetrieved)
                    {
                        run.Apply.Status = run.Apply.Status == ApplyStatus.Applied_StateError ? ApplyStatus.Applied : ApplyStatus.Failed;
                        run.Status       = run.Status == RunStatus.Applied_StateError ? RunStatus.Applied : RunStatus.Failed;

                        await _db.SaveChangesAsync(cancellationToken);

                        await _mediator.Publish(new RunUpdated(run.Id));

                        await _mediator.Publish(new ApplyCompleted(run.Workspace));

                        run.Workspace.CleanupFileSystem(_options.RootWorkingDirectory);
                    }
                }

                return(await _db.Runs
                       .ProjectTo <Run>(_mapper.ConfigurationProvider)
                       .SingleOrDefaultAsync(x => x.Id == run.Id, cancellationToken));
            }
示例#14
0
            public async Task <Workspace> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var workspace = await _db.Workspaces.FindAsync(request.Id);

                await ValidateEntities(workspace, request.DirectoryId);

                _mapper.Map(request, workspace);
                await _db.SaveChangesAsync();

                return(_mapper.Map <Workspace>(workspace));
            }
示例#15
0
            protected override async Task Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var entry = await _db.Modules.FirstOrDefaultAsync(m => m.Id == request.Id);

                if (entry == null)
                {
                    throw new EntityNotFoundException <Module>();
                }

                _db.Modules.Remove(entry);
                await _db.SaveChangesAsync(cancellationToken);
            }
示例#16
0
            protected override async Task Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new FullRightsRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var entry = _db.Users.FirstOrDefault(e => e.Id == request.Id);

                if (entry == null)
                {
                    throw new EntityNotFoundException <User>();
                }

                _db.Users.Remove(entry);
                await _db.SaveChangesAsync(cancellationToken);
            }
示例#17
0
        private async Task ProcessRemovedResources(Workspace workspace)
        {
            var removedResources = workspace.GetRemovedResources();
            var resourcesToSync  = removedResources
                                   .Where(r => r.IsVirtualMachine())
                                   .Select(r => new RemovedResource {
                Id = r.Id
            });

            await _dbContext.RemovedResources.AddRangeAsync(resourcesToSync);

            await _dbContext.SaveChangesAsync();

            if (resourcesToSync.Any())
            {
                _playerSyncService.CheckRemovedResources();
            }
        }
示例#18
0
            public async Task <User> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new FullRightsRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var user = await _db.Users.FindAsync(request.Id);

                if (user == null)
                {
                    throw new EntityNotFoundException <User>();
                }

                _mapper.Map(request, user);
                await _db.SaveChangesAsync();

                return(_mapper.Map <User>(user));
            }
示例#19
0
            public async Task <Directory> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                await ValidateProject(request.ProjectId);

                var directory = _mapper.Map <Domain.Models.Directory>(request);

                await SetPath(directory);

                await _db.Directories.AddAsync(directory, cancellationToken);

                await _db.SaveChangesAsync(cancellationToken);

                return(_mapper.Map <Directory>(directory));
            }
示例#20
0
            public async Task <Run> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var workspaceId = await _db.Runs.Where(r => r.Id == request.Id).Select(r => r.WorkspaceId).FirstOrDefaultAsync();

                using (var lockResult = await _lockService.GetWorkspaceLock(workspaceId).LockAsync(0))
                {
                    if (!lockResult.AcquiredLock)
                    {
                        throw new WorkspaceConflictException();
                    }

                    var run = await _db.Runs
                              .Include(r => r.Plan)
                              .Include(r => r.Apply)
                              .Include(r => r.Workspace)
                              .FirstOrDefaultAsync(r => r.Id == request.Id);

                    ValidateRun(run);

                    run.Workspace.CleanupFileSystem(_terraformOptions.RootWorkingDirectory);

                    run.Status = RunStatus.Rejected;

                    if (run.Plan != null)
                    {
                        run.Plan.Status = PlanStatus.Rejected;
                    }

                    run.Modify(_user.GetId());
                    await _db.SaveChangesAsync();

                    await _mediator.Publish(new RunUpdated(run.Id));

                    return(await _db.Runs
                           .ProjectTo <Run>(_mapper.ConfigurationProvider)
                           .SingleOrDefaultAsync(x => x.Id == run.Id, cancellationToken));
                }
            }
示例#21
0
            public async Task <Exercise> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var exercise = await _db.Exercises.FindAsync(request.Id);

                if (exercise == null)
                {
                    throw new EntityNotFoundException <Exercise>();
                }

                _mapper.Map(request, exercise);
                await _db.SaveChangesAsync();

                return(_mapper.Map <Exercise>(exercise));
            }
示例#22
0
            public async Task <Apply> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var workspaceId = await _db.Runs.Where(r => r.Id == request.RunId).Select(r => r.WorkspaceId).FirstOrDefaultAsync();

                Domain.Models.Apply apply = null;

                using (var lockResult = await _lockService.GetWorkspaceLock(workspaceId).LockAsync(0))
                {
                    if (!lockResult.AcquiredLock)
                    {
                        throw new WorkspaceConflictException();
                    }

                    var run = await _db.Runs
                              .Include(r => r.Plan)
                              .Include(r => r.Apply)
                              .SingleOrDefaultAsync(r => r.Id == request.RunId);

                    ValidateRun(run);

                    apply = new Domain.Models.Apply
                    {
                        RunId  = run.Id,
                        Status = ApplyStatus.Queued
                    };

                    await _db.Applies.AddAsync(apply);

                    run.Modify(_user.GetId());
                    await _db.SaveChangesAsync();
                }

                await _mediator.Publish(new ApplyCreated { ApplyId = apply.Id });

                await _mediator.Publish(new RunUpdated(apply.RunId));

                return(_mapper.Map <Apply>(apply));
            }
示例#23
0
            public async Task <File> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                await ValidateEntities(request.DirectoryId, request.WorkspaceId);

                var file = _mapper.Map <Domain.Models.File>(request);

                file.Save(_user.GetId(), isAdmin: (await _identityResolver.IsAdminAsync()), bypassLock: true);

                await _db.Files.AddAsync(file, cancellationToken);

                await _db.SaveChangesAsync(cancellationToken);

                return(await _fileQuery.ExecuteAsync(file.Id));
            }
示例#24
0
            public async Task <Workspace> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var directory = await this.GetDirectory(request.DirectoryId, cancellationToken);

                var workspace = _mapper.Map <Domain.Models.Workspace>(request);

                workspace.TerraformVersion = !string.IsNullOrEmpty(request.TerraformVersion) ?
                                             request.TerraformVersion :
                                             await GetTerraformVersion(directory.Id, cancellationToken);

                await _db.Workspaces.AddAsync(workspace, cancellationToken);

                await _db.SaveChangesAsync(cancellationToken);

                return(_mapper.Map <Workspace>(workspace));
            }
示例#25
0
            public async Task <ImportProjectResult> Handle(Command request, CancellationToken cancellationToken)
            {
                if (!(await _authorizationService.AuthorizeAsync(_user, null, new ContentDeveloperRequirement())).Succeeded)
                {
                    throw new ForbiddenException();
                }

                var project = await _db.Projects
                              .Include(e => e.Directories)
                              .ThenInclude(d => d.Workspaces)
                              .Include(e => e.Directories)
                              .ThenInclude(d => d.Files)
                              .SingleOrDefaultAsync(e => e.Id == request.Id, cancellationToken);

                if (project == null)
                {
                    throw new EntityNotFoundException <Project>();
                }

                Domain.Models.Project extractedProject;

                using (var memStream = new System.IO.MemoryStream())
                {
                    await request.Archive.CopyToAsync(memStream, cancellationToken);

                    memStream.Position = 0;
                    extractedProject   = _archiveService.ExtractProject(memStream, request.Archive.FileName);
                }

                var importResult = await _importService.ImportProject(project, extractedProject, request.PreserveIds);

                var entries = _db.GetUpdatedEntries();
                await _db.SaveChangesAsync(cancellationToken);

                await this.PublishEvents(entries);

                return(_mapper.Map <ImportProjectResult>(importResult));
            }
示例#26
0
        private async Task <ResourceCommandResult> OperationDoWork(Workspace workspace, ResourceOperation operation, string[] addresses, string args)
        {
            var         errors     = new List <string>();
            JsonElement?outputs    = null;
            var         workingDir = workspace.GetPath(_terraformOptions.RootWorkingDirectory);
            var         files      = await _db.GetWorkspaceFiles(workspace, workspace.Directory);

            await workspace.PrepareFileSystem(workingDir, files);

            var initResult = _terraformService.InitializeWorkspace(workspace, null);

            var statePath = string.Empty;

            if (!workspace.IsDefault)
            {
                statePath = workspace.GetStatePath(workingDir, backupState: false);
            }

            if (!initResult.IsError)
            {
                TerraformResult result = null;

                switch (operation)
                {
                case ResourceOperation.taint:
                case ResourceOperation.untaint:
                    foreach (string address in addresses)
                    {
                        TerraformResult taintResult = null;

                        switch (operation)
                        {
                        case ResourceOperation.taint:
                            taintResult = _terraformService.Taint(workspace, address, statePath);
                            break;

                        case ResourceOperation.untaint:
                            taintResult = _terraformService.Untaint(workspace, address, statePath);
                            break;
                        }

                        if (taintResult != null && taintResult.IsError)
                        {
                            errors.Add(taintResult.Output);
                        }
                    }
                    break;

                case ResourceOperation.refresh:
                    result = _terraformService.Refresh(workspace, statePath);
                    break;

                case ResourceOperation.remove:
                    result = _terraformService.RemoveResources(workspace, addresses, statePath);
                    break;

                case ResourceOperation.import:
                    result = _terraformService.Import(workspace, addresses[0], args, statePath);
                    break;

                case ResourceOperation.output:
                    result  = _terraformService.GetOutputs(workspace, statePath);
                    outputs = JsonDocument.Parse(result.Output).RootElement;
                    break;
                }

                if (result != null && result.IsError)
                {
                    errors.Add(result.Output);
                }

                await workspace.RetrieveState(workingDir);

                await _db.SaveChangesAsync();

                workspace.CleanupFileSystem(_terraformOptions.RootWorkingDirectory);
            }

            return(new ResourceCommandResult
            {
                Resources = _mapper.Map <Resource[]>(workspace.GetState().GetResources(), opts => opts.ExcludeMembers(nameof(Resource.Attributes))),
                Errors = errors.ToArray(),
                Outputs = outputs
            });
        }
示例#27
0
        private async Task <Workspace> OperationDoWork(Workspace workspace, ResourceOperation operation, string[] addresses)
        {
            var workingDir = workspace.GetPath(_terraformOptions.RootWorkingDirectory);
            var files      = await _db.GetWorkspaceFiles(workspace, workspace.Directory);

            await workspace.PrepareFileSystem(workingDir, files);

            var initResult = _terraformService.InitializeWorkspace(
                workingDir, workspace.Name, workspace.IsDefault, null);

            var statePath = string.Empty;

            if (!workspace.IsDefault)
            {
                statePath = workspace.GetStatePath(workingDir, backupState: false);
            }

            if (!initResult.IsError)
            {
                switch (operation)
                {
                case ResourceOperation.taint:
                case ResourceOperation.untaint:
                    foreach (string address in addresses)
                    {
                        TerraformResult taintResult = null;

                        switch (operation)
                        {
                        case ResourceOperation.taint:
                            taintResult = _terraformService.Taint(workingDir, address, statePath);
                            break;

                        case ResourceOperation.untaint:
                            taintResult = _terraformService.Untaint(workingDir, address, statePath);
                            break;
                        }

                        if (taintResult != null && taintResult.IsError)
                        {
                            _logger.LogError(taintResult.Output);
                        }
                    }
                    break;

                case ResourceOperation.refresh:
                    TerraformResult refreshResult = _terraformService.Refresh(workingDir, statePath);
                    if (refreshResult.IsError)
                    {
                        _logger.LogError(refreshResult.Output);
                    }
                    break;
                }

                await workspace.RetrieveState(workingDir);

                await _db.SaveChangesAsync();

                workspace.CleanupFileSystem(_terraformOptions.RootWorkingDirectory);
            }

            return(workspace);
        }
        private async Task UpdateApply()
        {
            await _db.SaveChangesAsync();

            await _mediator.Publish(new RunUpdated(_apply.RunId));
        }
        public async Task <bool> GetModulesAsync(
            bool forceUpdate,
            CancellationToken cancellationToken)
        {
            var      requestTime = DateTime.UtcNow;
            DateTime updateCutoffDate;

            if (forceUpdate)
            {
                // force update of all modules
                updateCutoffDate = DateTime.MinValue;
            }
            else
            {
                // set the cutoff date to the most recxent DateModifed
                var dbDateModified = await _db.Modules.Select(m => m.DateModified).MaxAsync <DateTime?>(cancellationToken);

                updateCutoffDate = dbDateModified == null ? DateTime.MinValue : (DateTime)dbDateModified;
            }
            _httpClient = _httpClientFactory.CreateClient("gitlab");
            _token      = _terraformOptions.CurrentValue.GitlabToken;
            var groupId = _terraformOptions.CurrentValue.GitlabGroupId;

            if (!groupId.HasValue)
            {
                var groupIdName = nameof(_terraformOptions.CurrentValue.GitlabGroupId);
                throw new ArgumentNullException(groupIdName, $"{groupIdName} must be set in order to retrieve Modules");
            }

            var response = await _httpClient.GetAsync($"groups/{groupId}/projects?private_token={_token}&include_subgroups=true");

            ValidateResponse(response);
            var json = await response.Content.ReadAsByteArrayAsync();

            var gitlabModules = JsonSerializer
                                .Deserialize <GitlabModule[]>(
                json,
                DefaultJsonSettings.Settings);

            foreach (var gitlabModule in gitlabModules)
            {
                if (DateTime.Compare(gitlabModule.LastActivityAt, updateCutoffDate) > 0)
                {
                    var module = gitlabModule.ToModule(requestTime);

                    var existingModule = await _db.Modules.AsNoTracking().FirstOrDefaultAsync(m => m.Path == module.Path);

                    if (existingModule == null)
                    {
                        // add the module
                        _db.Modules.Add(module);
                        await _db.SaveChangesAsync(cancellationToken);
                    }
                    else
                    {
                        // attach and update the module
                        module.Id = existingModule.Id;
                        _db.Modules.Update(module);
                        await _db.SaveChangesAsync(cancellationToken);
                    }

                    // get versions from Gitlab and update the database
                    await GetVersionsAsync(gitlabModule.Id, module.Id, gitlabModule.RepoUrl, cancellationToken);
                }
            }

            return(true);
        }