Harness Open Source (Drone/Gitness) — Interfaces#
Interface catalog#
git.Interface#
- Package:
github.com/harness/gitness/git - File:
git/interface.go - Methods: ~50 methods spanning repository CRUD (
CreateRepository,DeleteRepository), tree/blob operations (GetTreeNode,ListTreeNodes,GetBlob), refs (CreateBranch,DeleteBranch,UpdateRef,GetRef), commits (GetCommit,ListCommits,CommitFiles,GetCommitDivergences), diffs (Diff,RawDiff,DiffStats,DiffCut,GetDiffHunkHeaders), merges (Merge,Revert), blame, protocol (GetInfoRefs,ServicePack), and utilities (ScanSecrets,GeneratePipeline,OptimizeRepository,Archive) - Purpose: The single abstraction behind the entire native git engine. All consumers (the
repocontroller, githook controller, SSH server, pipeline subsystem) depend only on this interface, not on the concrete implementation. - Implementations: One: the concrete
git.Implementationstruct ingit/git.go, which delegates throughgit/api/to the systemgitbinary viagit/command/. - Design quality: Intentionally broad — this is a facade interface over an entire subsystem, not an ISP-compliant narrow contract. The tradeoff is explicit: it allows the git engine to be swapped as a unit (e.g., replacing exec-based with libgit2) without touching callers. The breadth is justified by the domain scope.
authn.Authenticator#
- Package:
github.com/harness/gitness/app/auth/authn - File:
app/auth/authn/authenticator.go - Methods:
Authenticate(r *http.Request) (*auth.Session, error) - Purpose: Extracts and validates a principal identity from an HTTP request. Returns a Session on success,
ErrNoAuthDatawhen no credentials are present (so a chain of authenticators can be tried), or a hard error when credentials are present but invalid. - Implementations: Multiple: JWT bearer token, PAT (personal access token), service account token, cookie-based. Composed via a chain/first-match pattern at the middleware level.
- Design quality: Textbook minimal interface. One method, one concern. The
ErrNoAuthDatasentinel value is an elegant way to support authenticator chains without requiring a separate “can handle?” check.
authz.Authorizer#
- Package:
github.com/harness/gitness/app/auth/authz - File:
app/auth/authz/authz.go - Methods:
Check(ctx, session, scope, resource, permission) (bool, error)— checks a single permissionCheckAll(ctx, session, ...permissionChecks) (bool, error)— checks multiple permissions atomically
- Purpose: Authorization boundary for all domain controllers. Every controller method that touches a protected resource calls
CheckorCheckAllbefore proceeding. Scope includes space/repository hierarchy. - Implementations:
MembershipAuthorizer(in-process RBAC against the membership store),PublicAccessAuthorizer(wrapper that short-circuitsCheckfor public resources). In Harness Enterprise, an additional implementation delegates to a remote policy engine. - Design quality: Well-designed. The
CheckAllmethod avoids N sequential permission checks for multi-operation handlers (e.g., creating a repo requires checking both space write and repo create). The bool+error return avoids a panic-on-deny pattern.
store.*Store family#
- Package:
github.com/harness/gitness/app/store - File:
app/store/database.go - Key interfaces:
PrincipalStore(~30 methods): users, service accounts, and services CRUD;FindByEmail,FindManyByUID, etc.SpaceStore(~20 methods): space hierarchy CRUD;GetAncestorIDs,GetDescendantsIDs, soft delete, restoreRepoStore(~20 methods): repository CRUD;UpdateOptLock(optimistic locking),SoftDelete,Purge,RestorePullReqStore,PipelineStore,ExecutionStore,StageStore,StepStore,WebhookStore,MembershipStore,TokenStore,PublicKeyStore,JobStore,LabelStore,RuleStore— each domain entity has its own store interface
- Purpose: All persistence is behind these interfaces. The architecture ensures that no business logic package directly imports a database driver or SQL query.
- Implementations: Two layers per store: (1)
database.*Storeinapp/store/database/— rawsqlxSQL, no ORM; (2)cache.*wrappers inapp/store/cache/— Redis-backed TTL cache that decorates the SQL implementation for hot paths (space and repo lookups). The cache layer implements the same store interface, so callers are unaware of caching. - Design quality: Strong separation. The dual-layer implementation (SQL + cache via same interface) is a clean decorator pattern. Each store interface is well-segregated by domain entity.
UpdateOptLockis a notably practical pattern: it takes amutateFn func(*T) errorclosure, wraps the read-modify-write in a transaction, and retries on conflict — hiding optimistic locking complexity from callers.
router.Interface#
- Package:
github.com/harness/gitness/app/router - File:
app/router/interface.go - Methods:
Handle(w http.ResponseWriter, req *http.Request)IsEligibleTraffic(req *http.Request) boolName() string
- Purpose: Contract for pluggable HTTP sub-routers. The outer
Routeriterates a[]Interfaceslice, callsIsEligibleTrafficon each in priority order, and dispatches to the first match. This allows completely independent routing strategies per traffic class. - Implementations:
APIRouter(prefix/api/),GitRouter(repository path prefix, not/api/),RegistryRouter(OCI registry paths),WebRouter(catch-all SPA). The registry sub-module also provides its ownAppRouterthat satisfies this interface. - Design quality: Elegant. Three methods, zero coupling between sub-routers. Adding a new traffic class (e.g., a metrics router) requires only implementing this interface and inserting it in the
wire.goslice — no changes to existing routers.
events.StreamProducer / events.StreamConsumer / events.Reader#
- Package:
github.com/harness/gitness/events - Files:
events/stream.go,events/reader.go - Methods:
StreamProducer:Send(ctx, streamID, payload) (string, error)— single methodStreamConsumer:Register(streamID, handler, ...opts) error,Configure(...opts),Start(ctx) error,Errors() <-chan error,Infos() <-chan stringReader:Configure(opts ...ReaderOption)— minimal marker interface; theGenericReaderconcrete type adds event registration via the package-levelReaderRegisterEvent[T]generic function
- Purpose: The event bus abstraction.
StreamProduceris the publisher side;StreamConsumeris the Redis Streams consumer (with consumer groups, delivery guarantees, error channels).Readeris the high-level typed interface that domain event packages expose to subscribers. - Implementations:
StreamProducer→stream.RedisProducer.StreamConsumer→stream.RedisConsumer. TheReaderFactory[R Reader]generic struct acts as the factory that wires these together for a given event category (e.g.,gitevents,pullreqevents). - Design quality: Sophisticated. The use of Go generics (
ReaderFactory[R],HandlerFunc[T],ReaderRegisterEvent[T]) achieves type-safe event payload deserialization without reflection at call sites. TheStreamConsumerFactoryFuncis a function type used as a factory — a clean alternative to an interface with one method.
infraprovider.InfraProvider#
- Package:
github.com/harness/gitness/infraprovider - File:
infraprovider/infra_provider.go - Methods:
Provision,Find,FindInfraStatus,Stop,CleanupInstanceResources,Deprovision,AvailableParams,UpdateParams,ValidateParams,TemplateParams,ProvisioningType,UpdateConfig,ValidateConfig,GenerateSetupYAML(14 methods) - Purpose: The primary extension point for gitspace infrastructure backends. Abstracts the lifecycle of cloud dev environments: provisioning containers (or VMs), finding existing infrastructure, stopping and deprovisioning. The
AvailableParams/ValidateParams/TemplateParamsgroup supports dynamic configuration schemas per provider. - Implementations:
DockerInfraProvider(provisions Docker containers on the local daemon), with the interface designed to support cloud VM providers (AWS, GCP) as future implementations. - Design quality: Broad but internally coherent. The 14 methods cover the full infrastructure lifecycle. The schema-reflection methods (
AvailableParams,TemplateParams) are somewhat unusual — they expose a[]InfraProviderParameterSchemathat lets the UI render provider-specific configuration forms dynamically, which is a thoughtful extensibility mechanism.
livelog.LogStream#
- Package:
github.com/harness/gitness/livelog - File:
livelog/livelog.go - Methods:
Create(ctx, stepID),Delete(ctx, stepID),Write(ctx, stepID, line),Tail(ctx, stepID) (<-chan *Line, <-chan error),Info(ctx) *LogStreamInfo - Purpose: Real-time log streaming for CI pipeline step execution.
Tailreturns a pair of channels — the standard Go idiom for streaming with error propagation. Consumed by SSE handlers that push log lines to browser clients. - Implementations: Redis-backed pub/sub implementation. Log lines are written by the drone runner (via the manager RPC) and tailed by browser clients.
- Design quality: Clean 5-method interface. The dual-channel
Tailreturn (<-chan *Line, <-chan error) is idiomatic Go for streaming with cancellation.
cache.Cache[K,V] / cache.ExtendedCache[K,V]#
- Package:
github.com/harness/gitness/cache - File:
cache/cache.go - Methods:
Cache[K any, V any]:Stats() (int64, int64),Get(ctx, key K) (V, error),Evict(ctx, key K)ExtendedCache[K comparable, V Identifiable[K]]: embedsCache[K,V]+Map(ctx, keys []K) (map[K]V, error)- Supporting:
Identifiable[K](Identifier() K),Getter[K,V](Find),ExtendedGetter[K,V](Find+FindMany)
- Purpose: Generic cache layer for hot-path lookups (space IDs, repo refs). The
Getter/ExtendedGetterinterfaces define the data source that backs a cache instance. - Implementations:
RedisCache(incache/redis_cache.go), withEncoder[V]/Decoder[V]/Codec[V]interfaces for serialization. - Design quality: Excellent use of Go 1.18 generics. The
Identifiable[K]constraint enablesExtendedCache.Mapto build a result map without requiring a separate key-extraction function. TheGetter/Cacheseparation (data source vs cache behavior) is clean composition.
pipeline.Scheduler#
- Package:
github.com/harness/gitness/app/pipeline/scheduler - File:
app/pipeline/scheduler/scheduler.go - Methods:
Schedule(ctx, stage) error,Request(ctx, filter) (*Stage, error),Cancel(ctx, buildID) error,Cancelled(ctx, buildID) (bool, error) - Purpose: Assigns CI pipeline stages to available drone runners.
Requestis a long-polling method called by runners; it blocks until a matching stage is available.Cancelledis similarly blocking — runners poll this to detect cancellation mid-execution. - Implementations: Internal
schedulerstruct (queue + canceler composition) in the same package. - Design quality: Well-designed. The
Filterstruct (OS, arch, labels) supports heterogeneous runner pools. The blockingRequest/Cancelledmethods expose the long-poll pattern explicitly in the interface — a rare but honest design choice.
ide.IDE#
- Package:
github.com/harness/gitness/app/gitspace/orchestrator/ide - File:
app/gitspace/orchestrator/ide/ide.go - Methods:
Setup(ctx, exec, args, logger) error,Run(ctx, exec, args, logger) error,Port() *GitspacePort,Type() enum.IDEType,GenerateURL(...) string,GeneratePluginURL(...) string - Purpose: Abstracts IDE lifecycle management inside gitspace containers. Each IDE type (VS Code Web, Cursor, JetBrains) implements
Setup(install/configure) andRun(start the IDE server process) differently, but the orchestrator interacts with all through this interface. - Implementations:
VSCode,Cursor,JetBrainsconcrete types in sibling packages underide/. - Design quality: Clean. The
args map[IDEArg]anypattern forSetup/Runallows IDE-specific arguments without requiring interface changes per IDE. TheGitspaceLoggerparameter is passed through for container-side log capture.
Interface patterns#
Size distribution: Heavily bimodal. Infrastructure/extension-point interfaces (
git.Interface,InfraProvider,PrincipalStore) have 14–50 methods because they are facades over entire subsystems. Cross-cutting security interfaces (Authenticator,router.Interface) and infrastructure abstractions (LogStream,Scheduler) have 1–5 methods. The majority of interfaces fall in the 3–10 method range.Embedding:
cache.ExtendedCacheembedscache.Cache.cache.ExtendedGetterembedscache.Getter. This is the only significant interface embedding in the core codebase; most interfaces are self-contained.Implicit satisfaction: Consistently defined by consumers, not providers. The
git.Interfaceis defined in thegit/package but consumed byapp/api/controller/repo/; thestore.*Storeinterfaces are defined inapp/store/but implemented byapp/store/database/. This is idiomatic Go and enables clean layering.Stdlib interfaces used:
io.Writerappears ingit.Interface(RawDiff,CommitDiff,Archive— streaming large outputs directly to a writer avoids buffering entire diffs in memory).net/http.Handlersemantics flow throughrouter.Interface.Handle.context.Contextis universal — every interface method that does I/O takes actxfirst argument.Generics: Three interface families use Go 1.18+ type parameters:
cache.Cache[K,V]/cache.ExtendedCache[K,V],events.ReaderFactory[R Reader], andevents.HandlerFunc[T]. The cache generics are the cleanest example — they eliminate the per-type cache boilerplate that pre-generics Go required.
Key abstractions#
git.Interface— The single facade that hides the entire native git engine. Every controller that touches git goes through this interface. It is the largest interface in the codebase and the hardest to split further without forcing callers to manage multiple dependencies.authn.Authenticator+authz.Authorizer— Together these form the security boundary. Every HTTP request passes throughAuthenticator(authentication middleware), and every controller action callsAuthorizer.Checkbefore touching data. Their separation (who are you? vs. what can you do?) is architecturally clean.store.*Storefamily — The persistence boundary. ~15 domain-specific store interfaces ensure that no business logic package directly touches a database driver. The decorator pattern (SQL store wrapped by cache store, same interface) is one of the most elegant patterns in the codebase.router.Interface— The HTTP dispatch strategy. Three methods, zero coupling between sub-routers, trivially extensible. This interface embodies the four-router dispatch pattern that is one of the project’s defining architectural decisions.infraprovider.InfraProvider— The primary extension point for the gitspace feature. Adding support for a new cloud provider (AWS, GCP) means implementing this interface — no other changes required. The schema-introspection methods (AvailableParams,TemplateParams) make this a self-describing plugin interface.
Interface-driven extensibility#
The project uses interfaces for extensibility in three distinct patterns:
Backend swapping (store + cache): The store.*Store interfaces allow the SQL implementation to be transparently wrapped by a Redis cache layer. The DI system (Wire) selects which implementation to inject. Adding a new persistence backend (e.g., CockroachDB) requires only a new *Store implementation — all controllers remain unchanged.
Sub-router plugins (router.Interface): New traffic classes can be added by implementing the three-method router.Interface and registering it in the Wire set. The registry sub-module uses this to inject its own AppRouter alongside the core routers.
IDE and infra provider plugins: The ide.IDE and infraprovider.InfraProvider interfaces are explicit plugin contracts. The gitspace feature is designed around the expectation that multiple IDE types and infrastructure providers will coexist. The parameter schema methods (AvailableParams) make these interfaces self-describing, enabling dynamic UI configuration without code changes.
Event bus abstraction: The events.StreamConsumer / events.StreamProducer interfaces isolate the Redis Streams implementation from the domain event packages. In principle, a different streaming backend could be substituted by providing alternative implementations of these interfaces — the domain event code and the service subscribers would be unaffected.