# How Aqua Manages Tool Versions: A Deep Dive into the Modular VersionGetter Pipeline

> Learn how aqua manages tool versions using a modular VersionGetter pipeline that pulls from GitHub tags releases Cargo and Go modules and extracts semantic versions.

- Repository: [aquaproj/aqua](https://github.com/aquaproj/aqua)
- Tags: deep-dive
- Published: 2026-02-25

---

**Aqua determines tool versions through a modular `VersionGetter` pipeline that aggregates multiple source-specific getters (GitHub tags, releases, Cargo, Go modules) and parses semantic versions using regex-based extraction.**

Understanding how aquaproj/aqua resolves tool versions requires examining its pluggable architecture. The system delegates version discovery to a chain of specialized getters that handle different package registries, normalizing raw tags into clean semantic versions for installation and updates.

## The VersionGetter Architecture

At the core of aqua’s version management lies the **`VersionGetter`** interface defined in [[`pkg/versiongetter/version_getter.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/version_getter.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/version_getter.go). This abstraction allows the CLI to treat GitHub tags, Cargo crates, and Go modules as interchangeable version sources.

### The Interface Contract

The `VersionGetter` interface declares two essential methods:

```go
type VersionGetter interface {
    Get(ctx context.Context, pkg *registry.PackageInfo, currentVersion string) (string, error)
    List(ctx context.Context, pkg *registry.PackageInfo) ([]string, error)
}

```

Concrete implementations handle the specifics of querying their respective upstream APIs, while consumers remain agnostic to the underlying registry type.

### The Fuzzy Finder Wrapper

Before reaching the concrete getters, version requests pass through **`FuzzyGetter`** located in [[`pkg/versiongetter/fuzzy_getter.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/fuzzy_getter.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/fuzzy_getter.go). This wrapper conditionally launches a fuzzy-finder UI when `useFinder` is enabled, or directly delegates to the embedded `VersionGetter` during automated operations:

```go
func (f *FuzzyGetter) Get(... ) string {
    if !useFinder { return f.getter.Get(...) }
    // … fuzzy UI logic …
}

```

This separation ensures that interactive and programmatic workflows share the same resolution logic.

## The Version Resolution Flow

When you run `aqua update` or `aqua install`, the system triggers a coordinated lookup process across multiple files.

### Controller Entry Point

The journey begins in [[`pkg/controller/update/update.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/update/update.go)](https://github.com/aquaproj/aqua/blob/main/pkg/controller/update/update.go) at line 95, where the update controller invokes:

```go
newVersion := c.fuzzyGetter.Get(ctx, logger, pkg.PackageInfo,
    pkg.Package.Version, param.SelectVersion, param.Limit)

```

This single call initiates the entire pipeline, passing the package metadata and current version constraints down the stack.

### GeneralVersionGetter Routing

The `FuzzyGetter` forwards requests to **`GeneralVersionGetter`** ([[`pkg/versiongetter/general.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/general.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/general.go)), which acts as a router. Its internal `get` method selects the appropriate concrete implementation based on the package's `Type` field:

```go
func (g *GeneralVersionGetter) get(pkg *registry.PackageInfo) VersionGetter {
    switch pkg.Type {
    case "cargo":    return g.cargo
    case "github":   // tag vs release decided later
    case "go":       return g.goGetter
    default:         return nil
    }
}

```

This design aggregates **cargo**, **GitHub tag**, **GitHub release**, and **Go proxy** getters into a unified interface.

### Concrete Getter Implementations

Each concrete getter implements registry-specific logic:

- **`GitHubTagVersionGetter`** ([[`pkg/versiongetter/github_tag.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/github_tag.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/github_tag.go)): Queries repository tags via the GitHub API
- **`GitHubReleaseVersionGetter`** ([[`pkg/versiongetter/github_release.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/github_release.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/github_release.go)): Fetches release metadata instead of raw tags
- **`CargoVersionGetter`** ([[`pkg/versiongetter/cargo.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/cargo.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/cargo.go)): Retrieves version information from crates.io
- **`GoGetter`** ([[`pkg/versiongetter/go.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/go.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/go.go)): Resolves versions through the Go module proxy

For example, `GitHubTagVersionGetter.Get` lists tags, applies filters, and extracts the version using `GetVersionAndPrefix` (line 37 in [`github_tag.go`](https://github.com/aquaproj/aqua/blob/main/github_tag.go)).

### Semantic Version Extraction

Raw tags often contain prefixes or non-standard formatting. The function **`GetVersionAndPrefix`** in [[`pkg/versiongetter/parse.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/parse.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/parse.go) handles normalization using the regex:

```

^(.*?)v?((?:\d+)(?:\.\d+)?(?:\.\d+)?(?:(\.|-).+)?)$

```

This pattern splits strings like `"release-v1.2.3"` into an optional prefix (`release-`) and a clean semantic version (`1.2.3`). The implementation uses `github.com/hashicorp/go-version` to parse the extracted version string:

```go
a := versionPattern.FindStringSubmatch(tag)
if a == nil { return nil, "", nil }
v, err := version.NewVersion(a[2])

```

Errors propagate only when a version pattern is detected but malformed, ensuring that non-version tags are silently skipped rather than causing failures.

## Handling Prefixed Versions

Some ecosystems embed contextual prefixes in tags (e.g., `kubernetes-1.28.0` or `release-v2.0.0`). Aqua preserves these prefixes during parsing so that template variables like `{{.Version}}` can reconstruct the original tag format when necessary. The prefix (`a[1]` from the regex match) travels alongside the parsed version through the entire pipeline, enabling tools that require specific tag naming conventions while still supporting semantic version comparisons.

## Code Examples

### Parsing a Raw Tag Directly

You can utilize the parsing utility independently to extract versions from arbitrary strings:

```go
import (
    "fmt"
    "github.com/aquaproj/aqua/v2/pkg/versiongetter"
)

func main() {
    version, prefix, err := versiongetter.GetVersionAndPrefix("release-v1.2.3")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Version: %s, Prefix: %s\n", version, prefix)
    // Output: Version: 1.2.3, Prefix: release-
}

```

### Resolving Versions Programmatically

In a controller-like context, you interact with the getter chain as follows:

```go
func resolveVersion(ctx context.Context, logger *slog.Logger, pkgInfo *registry.PackageInfo, curVer string) (string, error) {
    // fuzzyGetter is typically injected via Wire (see pkg/controller/wire.go)
    version := fuzzyGetter.Get(ctx, logger, pkgInfo, curVer, false, 0) // no UI, unlimited limit
    if version == "" {
        return "", fmt.Errorf("could not determine version for %s", pkgInfo.Name)
    }
    return version, nil
}

```

### Dependency Injection Configuration

The concrete getter chain is wired together using Google Wire. The generated [[`pkg/controller/wire_gen.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/wire_gen.go)](https://github.com/aquaproj/aqua/blob/main/pkg/controller/wire_gen.go) (lines 115-121) illustrates the assembly:

```go
func InitializeController(...) (*Controller, error) {
    cargoVersionGetter := versiongetter.NewCargo(client)
    gitHubTagVersionGetter := versiongetter.NewGitHubTag(repositoriesService)
    gitHubReleaseVersionGetter := versiongetter.NewGitHubRelease(repositoriesService)
    goGetter := versiongetter.NewGoGetter(goproxyClient)

    generalVersionGetter := versiongetter.NewGeneralVersionGetter(
        cargoVersionGetter,
        gitHubTagVersionGetter,
        gitHubReleaseVersionGetter,
        goGetter,
    )
    fuzzyGetter := versiongetter.NewFuzzy(fuzzyfinderFinder, generalVersionGetter)
    // … controller assembly continues …
}

```

## Summary

- Aqua manages tool versions through a **layered `VersionGetter` pipeline** that abstracts multiple registry types behind a unified interface.
- The **`FuzzyGetter`** wrapper enables both interactive fuzzy-finding and automated resolution via the same underlying logic.
- **`GeneralVersionGetter`** routes requests to specialized getters for **Cargo**, **GitHub tags**, **GitHub releases**, and **Go modules** based on package type.
- **Regex-based parsing** in `GetVersionAndPrefix` extracts semantic versions from raw tags while preserving optional prefixes for template rendering.
- The update controller in [[`pkg/controller/update/update.go`](https://github.com/aquaproj/aqua/blob/main/pkg/controller/update/update.go)](https://github.com/aquaproj/aqua/blob/main/pkg/controller/update/update.go) orchestrates the entire flow during `aqua update` operations.

## Frequently Asked Questions

### What is the VersionGetter interface in Aqua?

The `VersionGetter` interface ([[`pkg/versiongetter/version_getter.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/version_getter.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/version_getter.go)) defines the contract `Get` and `List` methods that all version sources must implement. It allows Aqua to treat GitHub repositories, Cargo crates, and Go modules interchangeably when resolving tool versions.

### How does Aqua handle non-semver tags like "release-v1.2.3"?

Aqua uses the `GetVersionAndPrefix` function in [[`pkg/versiongetter/parse.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/parse.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/parse.go) to split raw tags into an optional prefix and a clean semantic version using a capturing regex. The prefix is preserved for template rendering while the version component undergoes strict semantic versioning checks via `hashicorp/go-version`.

### Which package registries does Aqua support for version lookup?

According to the source code in [[`pkg/versiongetter/general.go`](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/general.go)](https://github.com/aquaproj/aqua/blob/main/pkg/versiongetter/general.go), Aqua supports **Cargo** (crates.io), **GitHub tags**, **GitHub releases**, and **Go module proxy** sources. The system routes to the appropriate concrete getter based on the package's `type` field in the registry configuration.

### How does Aqua decide between GitHub tags and releases?

When processing GitHub-based packages, the `GeneralVersionGetter` may utilize both `GitHubTagVersionGetter` and `GitHubReleaseVersionGetter` depending on the package configuration. The specific logic determines whether to query the tags API or the releases API, allowing packages that distribute assets exclusively through releases to resolve correctly while others may use lightweight tag-based versioning.