Add API, CLI and web UI integration for objects

This change adds the API endpoints, the CLI commands and the web UI elements
needed to manage objects in GARMs internal storage.

This storage system is meant to be used to distribute the garm-agent and as a
single source of truth for provider binaries, when we will add the ability for GARM
to scale out.

Potentially, we can also use this in air gapped systems to distribute the runner binaries
for forges that don't have their own internal storage system (like GHES).

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-10-07 17:07:40 +00:00 committed by Gabriel
parent f66f95baff
commit 6c46cf9be1
138 changed files with 7911 additions and 267 deletions

View file

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"math"
"github.com/mattn/go-sqlite3"
"gorm.io/gorm"
@ -18,17 +19,18 @@ import (
"github.com/cloudbase/garm/util"
)
func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
// func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
func (s *sqlDatabase) CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (fileObjParam params.FileObject, err error) {
// Read first 8KB for type detection
buffer := make([]byte, 8192)
n, _ := io.ReadFull(reader, buffer)
fileType := util.DetectFileType(buffer[:n])
// Create document with pre-allocated blob
fileObj := FileObject{
Name: name,
FileType: fileType,
Size: size,
Content: make([]byte, size),
Name: param.Name,
Description: param.Description,
FileType: fileType,
Size: param.Size,
}
defer func() {
@ -37,10 +39,19 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
}
}()
// Create the file first, without any space allocated for the blob.
if err := s.conn.Create(&fileObj).Error; err != nil {
return params.FileObject{}, fmt.Errorf("failed to create file object: %w", err)
}
// allocate space for the blob using the zeroblob() function. This will allow us to avoid
// having to allocate potentially huge byte arrays in memory and writing that huge blob to
// disk.
query := fmt.Sprintf(`UPDATE %q SET content = zeroblob(?) WHERE id = ?`, fileObj.TableName())
if err := s.conn.Exec(query, param.Size, fileObj.ID).Error; err != nil {
return params.FileObject{}, fmt.Errorf("failed to allocate disk space: %w", err)
}
// Stream file to blob and compute SHA256
conn, err := s.sqlDB.Conn(ctx)
if err != nil {
@ -54,7 +65,7 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
blob, err := sqliteConn.Blob("main", fileObj.TableName(), "content", int64(fileObj.ID), 1)
if err != nil {
return err
return fmt.Errorf("failed to open blob: %w", err)
}
defer blob.Close()
@ -63,14 +74,14 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
// Write the buffered data first
if _, err := blob.Write(buffer[:n]); err != nil {
return err
return fmt.Errorf("failed to write blob initial buffer: %w", err)
}
hasher.Write(buffer[:n])
// Stream the rest with hash computation
_, err = io.Copy(io.MultiWriter(blob, hasher), reader)
if err != nil {
return err
return fmt.Errorf("failed to write blob: %w", err)
}
// Get final hash
@ -87,7 +98,7 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
}
// Create tag entries
for _, tag := range tags {
for _, tag := range param.Tags {
fileObjTag := FileObjectTag{
FileObjectID: fileObj.ID,
Tag: tag,
@ -129,6 +140,10 @@ func (s *sqlDatabase) UpdateFileObject(_ context.Context, objID uint, param para
fileObj.Name = *param.Name
}
if param.Description != nil {
fileObj.Description = *param.Description
}
// Update tags if provided
if param.Tags != nil {
// Delete existing tags
@ -239,9 +254,20 @@ func (s *sqlDatabase) SearchFileObjectByTags(_ context.Context, tags []string, p
offset := (page - 1) * pageSize
queryPageSize := math.MaxInt
if pageSize <= math.MaxInt {
queryPageSize = int(pageSize)
}
var queryOffset int
if offset <= math.MaxInt {
queryOffset = int(offset)
} else {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("offset excedes max int size: %d", math.MaxInt)
}
if err := query.
Limit(int(pageSize)).
Offset(int(offset)).
Limit(queryPageSize).
Offset(queryOffset).
Order("created_at DESC").
Omit("content").
Find(&fileObjectRes).Error; err != nil {
@ -343,10 +369,23 @@ func (s *sqlDatabase) ListFileObjects(_ context.Context, page, pageSize uint64)
}
offset := (page - 1) * pageSize
queryPageSize := math.MaxInt
if pageSize <= math.MaxInt {
queryPageSize = int(pageSize)
}
var queryOffset int
if offset <= math.MaxInt {
queryOffset = int(offset)
} else {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("offset excedes max int size: %d", math.MaxInt)
}
var fileObjs []FileObject
if err := s.conn.Preload("TagsList").Omit("content").
Limit(int(pageSize)).
Offset(int(offset)).
Limit(queryPageSize).
Offset(queryOffset).
Order("created_at DESC").
Find(&fileObjs).Error; err != nil {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("failed to list file objects: %w", err)
@ -384,13 +423,14 @@ func (s *sqlDatabase) sqlFileObjectToCommonParams(obj FileObject) params.FileObj
tags[idx] = val.Tag
}
return params.FileObject{
ID: obj.ID,
CreatedAt: obj.CreatedAt,
UpdatedAt: obj.UpdatedAt,
Name: obj.Name,
Size: obj.Size,
FileType: obj.FileType,
SHA256: obj.SHA256,
Tags: tags,
ID: obj.ID,
CreatedAt: obj.CreatedAt,
UpdatedAt: obj.UpdatedAt,
Name: obj.Name,
Size: obj.Size,
FileType: obj.FileType,
SHA256: obj.SHA256,
Description: obj.Description,
Tags: tags,
}
}

View file

@ -67,7 +67,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 1: Small text file with tags
content1 := []byte("Hello, World! This is test file 1.")
fileObj1, err := s.Store.CreateFileObject(s.ctx, "test-file-1.txt", int64(len(content1)), []string{"test", "text"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "test-file-1.txt",
Size: int64(len(content1)),
Tags: []string{"test", "text"},
}
fileObj1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 1: %s", err))
}
@ -75,7 +80,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 2: Binary-like content with different tags
content2 := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00} // PNG header-like
fileObj2, err := s.Store.CreateFileObject(s.ctx, "test-image.png", int64(len(content2)), []string{"image", "binary"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "test-image.png",
Size: int64(len(content2)),
Tags: []string{"image", "binary"},
}
fileObj2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 2: %s", err))
}
@ -83,7 +93,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 3: No tags
content3 := []byte("File without tags.")
fileObj3, err := s.Store.CreateFileObject(s.ctx, "no-tags.txt", int64(len(content3)), []string{}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "no-tags.txt",
Size: int64(len(content3)),
Tags: []string{},
}
fileObj3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 3: %s", err))
}
@ -98,7 +113,12 @@ func (s *FileStoreTestSuite) TestCreateFileObject() {
content := []byte("New test file content")
tags := []string{"new", "test"}
fileObj, err := s.Store.CreateFileObject(s.ctx, "new-file.txt", int64(len(content)), tags, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "new-file.txt",
Size: int64(len(content)),
Tags: tags,
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().NotZero(fileObj.ID)
s.Require().Equal("new-file.txt", fileObj.Name)
@ -115,7 +135,12 @@ func (s *FileStoreTestSuite) TestCreateFileObject() {
func (s *FileStoreTestSuite) TestCreateFileObjectEmpty() {
content := []byte{}
fileObj, err := s.Store.CreateFileObject(s.ctx, "empty-file.txt", 0, []string{}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "empty-file.txt",
Size: 0,
Tags: []string{},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().NotZero(fileObj.ID)
s.Require().Equal("empty-file.txt", fileObj.Name)
@ -141,7 +166,12 @@ func (s *FileStoreTestSuite) TestGetFileObjectNotFound() {
func (s *FileStoreTestSuite) TestOpenFileObjectContent() {
// Create a file with known content
content := []byte("Test content for reading")
fileObj, err := s.Store.CreateFileObject(s.ctx, "read-test.txt", int64(len(content)), []string{"read"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "read-test.txt",
Size: int64(len(content)),
Tags: []string{"read"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Open and read the content
@ -182,7 +212,12 @@ func (s *FileStoreTestSuite) TestListFileObjectsPagination() {
// Create more files to test pagination
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("File %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("page-test-%d.txt", i), int64(len(content)), []string{"pagination"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("page-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"pagination"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -313,7 +348,12 @@ func (s *FileStoreTestSuite) TestUpdateFileObjectEmptyName() {
func (s *FileStoreTestSuite) TestDeleteFileObject() {
// Create a file to delete
content := []byte("To be deleted")
fileObj, err := s.Store.CreateFileObject(s.ctx, "delete-me.txt", int64(len(content)), []string{"delete"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "delete-me.txt",
Size: int64(len(content)),
Tags: []string{"delete"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Delete the file
@ -339,8 +379,12 @@ func (s *FileStoreTestSuite) TestCreateFileObjectLargeContent() {
for i := range content {
content[i] = byte(i % 256)
}
fileObj, err := s.Store.CreateFileObject(s.ctx, "large-file.bin", int64(size), []string{"large", "binary"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "large-file.bin",
Size: int64(size),
Tags: []string{"large", "binary"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().Equal(int64(size), fileObj.Size)
@ -357,7 +401,12 @@ func (s *FileStoreTestSuite) TestCreateFileObjectLargeContent() {
func (s *FileStoreTestSuite) TestFileObjectImmutableFields() {
// Create a file
content := []byte("Immutable test content")
fileObj, err := s.Store.CreateFileObject(s.ctx, "immutable-test.txt", int64(len(content)), []string{"original"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "immutable-test.txt",
Size: int64(len(content)),
Tags: []string{"original"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
originalSize := fileObj.Size
@ -390,19 +439,39 @@ func (s *FileStoreTestSuite) TestFileObjectImmutableFields() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTags() {
// Create files with specific tags for searching
content1 := []byte("File with tag1 and tag2")
file1, err := s.Store.CreateFileObject(s.ctx, "search-file-1.txt", int64(len(content1)), []string{"tag1", "tag2"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "search-file-1.txt",
Size: int64(len(content1)),
Tags: []string{"tag1", "tag2"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with tag1, tag2, and tag3")
file2, err := s.Store.CreateFileObject(s.ctx, "search-file-2.txt", int64(len(content2)), []string{"tag1", "tag2", "tag3"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "search-file-2.txt",
Size: int64(len(content2)),
Tags: []string{"tag1", "tag2", "tag3"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with only tag1")
file3, err := s.Store.CreateFileObject(s.ctx, "search-file-3.txt", int64(len(content3)), []string{"tag1"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "search-file-3.txt",
Size: int64(len(content3)),
Tags: []string{"tag1"},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
content4 := []byte("File with tag3 only")
_, err = s.Store.CreateFileObject(s.ctx, "search-file-4.txt", int64(len(content4)), []string{"tag3"}, bytes.NewReader(content4))
param = params.CreateFileObjectParams{
Name: "search-file-4.txt",
Size: int64(len(content4)),
Tags: []string{"tag3"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content4))
s.Require().Nil(err)
// Search for files with tag1 - should return 3 files
@ -423,15 +492,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTags() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsMultipleTags() {
// Create files with various tag combinations
content1 := []byte("File with search1 and search2")
file1, err := s.Store.CreateFileObject(s.ctx, "multi-search-1.txt", int64(len(content1)), []string{"search1", "search2"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "multi-search-1.txt",
Size: int64(len(content1)),
Tags: []string{"search1", "search2"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with search1, search2, and search3")
file2, err := s.Store.CreateFileObject(s.ctx, "multi-search-2.txt", int64(len(content2)), []string{"search1", "search2", "search3"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "multi-search-2.txt",
Size: int64(len(content2)),
Tags: []string{"search1", "search2", "search3"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with only search1")
_, err = s.Store.CreateFileObject(s.ctx, "multi-search-3.txt", int64(len(content3)), []string{"search1"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "multi-search-3.txt",
Size: int64(len(content3)),
Tags: []string{"search1"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search for files with both search1 AND search2 - should return only 2 files
@ -469,7 +553,12 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsPagination() {
// Create multiple files with the same tag
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("Pagination test file %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("page-search-%d.txt", i), int64(len(content)), []string{"pagination-test"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("page-search-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"pagination-test"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -497,7 +586,12 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsPagination() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsDefaultPagination() {
// Create a file with a unique tag
content := []byte("Default pagination test")
_, err := s.Store.CreateFileObject(s.ctx, "default-page-search.txt", int64(len(content)), []string{"default-pagination"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "default-page-search.txt",
Size: int64(len(content)),
Tags: []string{"default-pagination"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Test default values (page 0 should become 1, pageSize 0 should become 20)
@ -511,19 +605,39 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsDefaultPagination() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsAllTagsRequired() {
// Test that search requires ALL specified tags (AND logic, not OR)
content1 := []byte("Has A and B")
file1, err := s.Store.CreateFileObject(s.ctx, "and-test-1.txt", int64(len(content1)), []string{"tagA", "tagB"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "and-test-1.txt",
Size: int64(len(content1)),
Tags: []string{"tagA", "tagB"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("Has A, B, and C")
file2, err := s.Store.CreateFileObject(s.ctx, "and-test-2.txt", int64(len(content2)), []string{"tagA", "tagB", "tagC"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "and-test-2.txt",
Size: int64(len(content2)),
Tags: []string{"tagA", "tagB", "tagC"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("Has only A")
_, err = s.Store.CreateFileObject(s.ctx, "and-test-3.txt", int64(len(content3)), []string{"tagA"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "and-test-3.txt",
Size: int64(len(content3)),
Tags: []string{"tagA"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
content4 := []byte("Has only B")
_, err = s.Store.CreateFileObject(s.ctx, "and-test-4.txt", int64(len(content4)), []string{"tagB"}, bytes.NewReader(content4))
param = params.CreateFileObjectParams{
Name: "and-test-4.txt",
Size: int64(len(content4)),
Tags: []string{"tagB"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content4))
s.Require().Nil(err)
// Search for files with BOTH tagA AND tagB
@ -546,15 +660,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsAllTagsRequired() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsCaseInsensitive() {
// Test case insensitivity of tag search (COLLATE NOCASE)
content1 := []byte("File with lowercase tag")
file1, err := s.Store.CreateFileObject(s.ctx, "case-test-1.txt", int64(len(content1)), []string{"TestTag"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "case-test-1.txt",
Size: int64(len(content1)),
Tags: []string{"TestTag"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with UPPERCASE tag")
file2, err := s.Store.CreateFileObject(s.ctx, "case-test-2.txt", int64(len(content2)), []string{"TESTTAG"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "case-test-2.txt",
Size: int64(len(content2)),
Tags: []string{"TESTTAG"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with MixedCase tag")
file3, err := s.Store.CreateFileObject(s.ctx, "case-test-3.txt", int64(len(content3)), []string{"testTAG"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "case-test-3.txt",
Size: int64(len(content3)),
Tags: []string{"testTAG"},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search for lowercase - should return all files (case insensitive)
@ -586,15 +715,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsOrderByCreatedAt() {
tag := "order-test"
content1 := []byte("First file")
file1, err := s.Store.CreateFileObject(s.ctx, "order-1.txt", int64(len(content1)), []string{tag}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "order-1.txt",
Size: int64(len(content1)),
Tags: []string{tag},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("Second file")
file2, err := s.Store.CreateFileObject(s.ctx, "order-2.txt", int64(len(content2)), []string{tag}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "order-2.txt",
Size: int64(len(content2)),
Tags: []string{tag},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("Third file")
file3, err := s.Store.CreateFileObject(s.ctx, "order-3.txt", int64(len(content3)), []string{tag}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "order-3.txt",
Size: int64(len(content3)),
Tags: []string{tag},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search and verify order (should be DESC by created_at, so newest first)
@ -629,7 +773,12 @@ func (s *FileStoreTestSuite) TestPaginationFieldsLastPage() {
// Create exactly 5 files
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("Last page test %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("last-page-test-%d.txt", i), int64(len(content)), []string{"last-page"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("last-page-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"last-page"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -649,7 +798,12 @@ func (s *FileStoreTestSuite) TestPaginationFieldsLastPage() {
func (s *FileStoreTestSuite) TestPaginationFieldsSinglePage() {
// Test when all results fit in a single page
content := []byte("Single page test")
_, err := s.Store.CreateFileObject(s.ctx, "single-page-test.txt", int64(len(content)), []string{"single-page-unique-tag"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "single-page-test.txt",
Size: int64(len(content)),
Tags: []string{"single-page-unique-tag"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
result, err := s.Store.SearchFileObjectByTags(s.ctx, []string{"single-page-unique-tag"}, 1, 20)

View file

@ -51,20 +51,20 @@ type GithubTestSuite struct {
db common.Store
}
func (s *GithubTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *GithubTestSuite) SetupTest() {
ctx := context.Background()
watcher.InitWatcher(ctx)
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
db, err := NewSQLDatabase(ctx, garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.db = db
}
func (s *GithubTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *GithubTestSuite) TestDefaultEndpointGetsCreatedAutomaticallyIfNoOtherEndpointExists() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
endpoint, err := s.db.GetGithubEndpoint(ctx, defaultGithubEndpoint)
@ -953,9 +953,11 @@ func TestCredentialsAndEndpointMigration(t *testing.T) {
// Set the config credentials in the cfg. This is what happens in the main function.
// of GARM as well.
cfg.MigrateCredentials = credentials
ctx := context.Background()
watcher.InitWatcher(ctx)
defer watcher.CloseWatcher()
db, err := NewSQLDatabase(ctx, cfg)
if err != nil {
t.Fatalf("failed to create db connection: %s", err)

View file

@ -79,13 +79,13 @@ func (s *InstancesTestSuite) SetupTest() {
ctx := context.Background()
watcher.InitWatcher(ctx)
// create testing sqlite database
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
db, err := NewSQLDatabase(ctx, garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
adminCtx := garmTesting.ImpersonateAdminContext(ctx, db, s.T())
s.adminCtx = adminCtx
githubEndpoint := garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())

View file

@ -462,7 +462,9 @@ type GiteaCredentials struct {
type FileObject struct {
gorm.Model
// Name is the name of the file
Name string `gotm:"type:text,index:idx_fo_name"`
Name string `gorm:"type:text;index:idx_fo_name"`
// Description is a description for the file
Description string `gorm:"type:text"`
// FileType holds the MIME type or file type description
FileType string `gorm:"type:text"`
// Size is the file size in bytes

View file

@ -88,13 +88,13 @@ func (s *OrgTestSuite) SetupTest() {
watcher.InitWatcher(ctx)
// create testing sqlite database
dbConfig := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := NewSQLDatabase(context.Background(), dbConfig)
db, err := NewSQLDatabase(ctx, dbConfig)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
adminCtx := garmTesting.ImpersonateAdminContext(ctx, db, s.T())
s.adminCtx = adminCtx
s.adminUserID = auth.UserID(adminCtx)
s.Require().NotEmpty(s.adminUserID)