Ejemplo n.º 1
0
        private async ValueTask <LFSResponse.LFSObject> HandleDownload(LfsProject project, LFSRequest.LFSObject obj)
        {
            var existingObject = await database.LfsObjects
                                 .Where(o => o.LfsOid == obj.Oid && o.LfsProjectId == project.Id).Include(o => o.LfsProject)
                                 .FirstOrDefaultAsync();

            if (existingObject == null)
            {
                logger.LogWarning("Non-existing OID requested: {Oid}", obj.Oid);

                return(new LFSResponse.LFSObject(obj.Oid, obj.Size,
                                                 new LFSResponse.LFSObject.ErrorInfo(StatusCodes.Status404NotFound, "OID not found")));
            }

            var createdUrl = downloadUrls.CreateDownloadFor(existingObject, DownloadUrlExpireTime);

            return(new LFSResponse.LFSObject(obj.Oid, obj.Size)
            {
                Actions = new Dictionary <string, LFSResponse.LFSObject.Action>()
                {
                    {
                        "download", new LFSResponse.LFSObject.DownloadAction()
                        {
                            Href = createdUrl,
                            ExpiresIn = (int)DownloadExpireTime.TotalSeconds
                        }
                    }
                }
            });
        }
        public static async Task BuildFileTree(ILocalTempFileLocks tempFiles, ApplicationDbContext database,
                                               LfsProject project, ILogger logger, CancellationToken cancellationToken)
        {
            var semaphore = tempFiles.GetTempFilePath($"gitFileTrees/{project.Slug}", out string tempPath);

            await semaphore.WaitAsync(TimeSpan.FromMinutes(10), cancellationToken);

            try
            {
                await GitRunHelpers.EnsureRepoIsCloned(project.CloneUrl, tempPath, true, cancellationToken);

                try
                {
                    await GitRunHelpers.Checkout(tempPath, project.BranchToBuildFileTreeFor, true, cancellationToken);
                }
                catch (Exception)
                {
                    // In case the branch refers to a new branch
                    await GitRunHelpers.Fetch(tempPath, true, cancellationToken);

                    await GitRunHelpers.Checkout(tempPath, project.BranchToBuildFileTreeFor, true, cancellationToken);
                }

                await GitRunHelpers.Pull(tempPath, true, cancellationToken, true);

                // Skip if commit has not changed
                var newCommit = await GitRunHelpers.GetCurrentCommit(tempPath, cancellationToken);

                if (newCommit == project.FileTreeCommit)
                {
                    logger.LogDebug("Commit is still the same ({FileTreeCommit}), skipping tree update " +
                                    "for {Id}",
                                    project.FileTreeCommit, project.Id);
                    return;
                }

                logger.LogInformation("New commit {NewCommit} to build file tree from (previous: {FileTreeCommit}) " +
                                      "for project {Id}", newCommit, project.FileTreeCommit, project.Id);

                project.FileTreeCommit = newCommit;

                // Make sure we don't have any extra files locally
                await GitRunHelpers.Clean(tempPath, cancellationToken);

                // And then make sure the DB file tree entries are fine
                await UpdateFileTreeForProject(database, tempPath, project, cancellationToken);
            }
            finally
            {
                semaphore.Release();
            }

            project.FileTreeUpdated = DateTime.UtcNow;
            await database.SaveChangesAsync(cancellationToken);
        }
Ejemplo n.º 3
0
        private async Task <List <LFSResponse.LFSObject> > HandleLFSObjectRequests(LfsProject project,
                                                                                   LFSRequest.OperationType operation,
                                                                                   IEnumerable <LFSRequest.LFSObject> objects)
        {
            switch (operation)
            {
            case LFSRequest.OperationType.Download:
                return(await objects.ToAsyncEnumerable().SelectAwait(o => HandleDownload(project, o)).ToListAsync());

            case LFSRequest.OperationType.Upload:
                return(await objects.ToAsyncEnumerable().SelectAwait(o => HandleUpload(project, o)).ToListAsync());

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
        private static async Task UpdateFileTreeForProject(ApplicationDbContext database, string folder,
                                                           LfsProject project, CancellationToken cancellationToken)
        {
            // Folders that should exist and how many items they contain
            var foldersThatShouldExist = new Dictionary <string, int>();

            var existingEntries = await database.ProjectGitFiles.Where(f => f.LfsProjectId == project.Id)
                                  .ToListAsync(cancellationToken);

            var itemsThatShouldExist = new HashSet <ProjectGitFile>();

            // Note that all paths need to start with a "/"

            // Create new files
            foreach (var entry in Directory.EnumerateFileSystemEntries(folder, "*", SearchOption.AllDirectories))
            {
                // Skip .git folder
                if (entry.Contains(".git"))
                {
                    continue;
                }

                var justRepoPath = entry.Substring(folder.Length);

                if (!justRepoPath.StartsWith('/'))
                {
                    throw new Exception("Generated file path doesn't start with a slash");
                }

                var parentFolder = GetParentPath(justRepoPath);

                // Don't create the root folder item
                if (parentFolder != "/")
                {
                    foldersThatShouldExist.TryGetValue(parentFolder, out int existingItemCount);

                    foldersThatShouldExist[parentFolder] = existingItemCount + 1;
                }

                // Only add folders to their parent folder's count of items
                if (Directory.Exists(entry))
                {
                    continue;
                }

                var name = Path.GetFileName(justRepoPath);

                // Detect if this is an LFS file
                int    size = (int)new FileInfo(entry).Length;
                string?oid  = null;

                var(detectedOid, detectedSize) = await DetectLFSFile(entry, cancellationToken);

                if (detectedOid != null)
                {
                    oid  = detectedOid;
                    size = detectedSize !.Value;
                }

                // For files there needs to be an entry
                var existing = existingEntries.FirstOrDefault(f =>
                                                              f.FType == FileType.File && f.Name == name && f.Path == justRepoPath);

                if (existing != null)
                {
                    existing.Size   = size;
                    existing.LfsOid = oid;

                    itemsThatShouldExist.Add(existing);
                }
                else
                {
                    await database.ProjectGitFiles.AddAsync(new ProjectGitFile()
                    {
                        FType        = FileType.File,
                        LfsProjectId = project.Id,
                        Name         = name,
                        Path         = parentFolder,
                        Size         = size,
                        LfsOid       = oid,
                    }, cancellationToken);
                }
            }

            // Create / update folders
            foreach (var(processedFolder, size) in foldersThatShouldExist)
            {
                var name = Path.GetFileName(processedFolder);
                var path = GetParentPath(processedFolder);

                var existing =
                    existingEntries.FirstOrDefault(f => f.FType == FileType.Folder && f.Name == name && f.Path == path);

                if (existing != null)
                {
                    existing.Size = size;

                    itemsThatShouldExist.Add(existing);
                }
                else
                {
                    await database.ProjectGitFiles.AddAsync(new ProjectGitFile()
                    {
                        FType        = FileType.Folder,
                        LfsProjectId = project.Id,
                        Name         = name,
                        Path         = path,
                        Size         = size,
                    }, cancellationToken);
                }
            }

            // Delete files and folders that shouldn't exist
            database.ProjectGitFiles.RemoveRange(existingEntries.Except(itemsThatShouldExist));
        }
Ejemplo n.º 5
0
        private async ValueTask <LFSResponse.LFSObject> HandleUpload(LfsProject project, LFSRequest.LFSObject obj)
        {
            var existingObject = await database.LfsObjects
                                 .Where(o => o.LfsOid == obj.Oid && o.LfsProjectId == project.Id).Include(o => o.LfsProject)
                                 .FirstOrDefaultAsync();

            if (existingObject != null)
            {
                // We already have this object
                return(new LFSResponse.LFSObject(obj.Oid, obj.Size)
                {
                    Actions = null,
                    Authenticated = null
                });
            }

            if (obj.Size > AppInfo.MaxLfsUploadSize)
            {
                return(new LFSResponse.LFSObject(obj.Oid, obj.Size,
                                                 new LFSResponse.LFSObject.ErrorInfo(StatusCodes.Status422UnprocessableEntity, "File is too large")));
            }

            logger.LogTrace("Requesting auth because new object is to be uploaded {Oid} for project {Name}", obj.Oid,
                            project.Name);

            // New object. User must have write access
            if (!RequireWriteAccess(out var result))
            {
                throw new InvalidAccessException(result !);
            }

            // We don't yet create the LfsObject here to guard against upload failures
            // instead the verify callback does that

            // The uploads prefix is used here to ensure the user can't overwrite the file after uploading and
            // verification
            var storagePath = "uploads/" + LfsDownloadUrls.OidStoragePath(project, obj.Oid);

            if (!bucketChecked)
            {
                try
                {
                    if (!await remoteStorage.BucketExists())
                    {
                        throw new Exception("bucket doesn't exist");
                    }
                }
                catch (Exception e)
                {
                    logger.LogWarning("Bucket check failed: {@E}", e);
                    var error = "remote storage is inaccessible";
                    throw new HttpResponseException()
                          {
                              Status      = StatusCodes.Status500InternalServerError,
                              ContentType = AppInfo.GitLfsContentType,
                              Value       = new GitLFSErrorResponse()
                              {
                                  Message = error
                              }.ToString()
                          };
                }

                bucketChecked = true;
            }

            var verifyUrl = QueryHelpers.AddQueryString(
                new Uri(configuration.GetBaseUrl(), $"api/v1/lfs/{project.Slug}/verify").ToString(),
                "token", GenerateUploadVerifyToken(obj));

            return(new LFSResponse.LFSObject(obj.Oid, obj.Size)
            {
                Actions = new Dictionary <string, LFSResponse.LFSObject.Action>()
                {
                    {
                        "upload", new LFSResponse.LFSObject.UploadAction()
                        {
                            Href = remoteStorage.CreatePresignedUploadURL(storagePath, S3UploadValidTime),
                            ExpiresIn = (int)UploadValidTime.TotalSeconds
                        }
                    },
                    {
                        "verify", new LFSResponse.LFSObject.UploadAction()
                        {
                            Href = verifyUrl,
                            ExpiresIn = (int)UploadValidTime.TotalSeconds
                        }
                    }
                }
            });
        }
 public static string OidStoragePath(LfsProject project, string oid)
 {
     return($"{project.Slug}/objs/{oid[0..1]}/{oid[2..3]}/{oid[4..]}");