Public Go libraries shared across this repository's modules. Following the golang-standards/project-layout convention, only externally-usable code lives here; private implementation details live in the top-level /internal tree.
Module root: github.com/apache/airavata-custos (root go.mod).
| Package | Import path | Purpose |
|---|---|---|
models | github.com/apache/airavata-custos/pkg/models | Shared domain types (User, Organization, Project, allocations) |
service | github.com/apache/airavata-custos/pkg/service | High-level API for creating, reading, updating, and deleting entities |
The supporting internal/db and internal/store packages are private to this module and are not importable from outside the repository.
Every service method takes a context.Context as its first argument. The context carries cancellation, deadlines, and request-scoped values down into the database driver, so an in-flight query is aborted if the caller goes away or a deadline passes. You typically derive it from one of:
context.Background() — root context for main, init, tests, scripts.r.Context() — inside an HTTP handler; cancelled when the client disconnects.ctx argument of a gRPC handler.context.WithTimeout(parent, d) — adds a deadline; always defer cancel().signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) — cancelled on Ctrl-C / SIGTERM.Never pass nil; use context.Background() (or context.TODO()) if you have nothing better.
import ( "context" "time" "github.com/apache/airavata-custos/pkg/models" "github.com/apache/airavata-custos/pkg/service" ) svc := service.New(database) // *sqlx.DB // Derive a context with a 10s budget for this call chain. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() org, err := svc.CreateOrganization(ctx, &models.Organization{ Name: "University of Example", OriginatedID: "ACCESS-ORG-001", }) user, err := svc.CreateUser(ctx, &models.User{ OrganizationID: org.ID, FirstName: "Ada", LastName: "Lovelace", Email: "ada@example.edu", }) project, err := svc.CreateProject(ctx, &models.Project{ Title: "Climate Simulation 2026", Origination: "ACCESS", OriginatedID: "ACCESS-PRJ-9000", ProjectPIID: user.ID, })
ID is left empty the service generates a UUID.CreatedTime on a project is zero, the service sets it to time.Now().UTC().org, err := svc.GetOrganization(ctx, orgID)
user, err := svc.GetUserByEmail(ctx, "ada@example.edu")
proj, err := svc.GetProject(ctx, projID)
users, err := svc.ListUsersByOrganization(ctx, orgID)
projects, err := svc.ListProjectsByPI(ctx, userID)
err = svc.UpdateUser(ctx, user)
err = svc.DeleteProject(ctx, projID)
| Sentinel | Returned when |
|---|---|
service.ErrNotFound | A GetX call finds no matching record |
service.ErrAlreadyExists | Duplicate email or duplicate originated_id |
service.ErrInvalidInput | Missing required field, or unknown FK reference |
Use errors.Is(err, service.ErrNotFound) to check.
Tables created by the embedded migrations in /internal/db/migrations/:
organizations users (FK → organizations.id) projects (FK → users.id via project_pi_id)
parseTime=true must be set in the DSN so MySQL TIMESTAMP columns scan into time.Time correctly.
The service package exposes a CoreService interface (see pkg/service/interface.go) that the concrete *service.Service satisfies. Callers should depend on this interface — or one of the narrower per-domain interfaces in the same file — so tests can swap in a mock without a database.
A mock implementation, CoreServiceMock, is generated by matryer/moq into pkg/service/mock.go. Each method has a corresponding XxxFunc field; set only the funcs your test needs, and use the matching XxxCalls() helper to assert how the code under test invoked the service.
import ( "context" "testing" "github.com/apache/airavata-custos/pkg/models" "github.com/apache/airavata-custos/pkg/service" ) func TestSomething(t *testing.T) { svc := &service.CoreServiceMock{ GetComputeClusterFunc: func(ctx context.Context, id string) (*models.ComputeCluster, error) { return &models.ComputeCluster{ID: id, Name: "test-cluster"}, nil }, GetProjectFunc: func(ctx context.Context, id string) (*models.Project, error) { return &models.Project{ID: id, ProjectPIID: "pi-id"}, nil }, } // ... exercise the code under test, passing svc where a service.CoreService is expected ... if got := len(svc.GetComputeClusterCalls()); got != 1 { t.Fatalf("GetComputeCluster called %d times, want 1", got) } }
Methods whose Func field is left nil will panic when invoked, surfacing unexpected calls loudly.
After adding or changing a method on any interface in interface.go, regenerate mock.go:
go generate ./pkg/service/...
The //go:generate directive at the top of interface.go invokes moq via go run, so no project-wide install is required. The Go toolchain will fetch the version of moq it needs on demand.