From ded186295d6eeaab6801742806545f0e0681cd56 Mon Sep 17 00:00:00 2001 From: Dylan Ross Date: Fri, 13 Feb 2026 17:42:04 -0600 Subject: [PATCH 1/3] feat(ci): added actions for ci and release --- .claude/skills/development-workflow/SKILL.md | 49 ++ .github/workflows/ci.yml | 48 ++ .github/workflows/release.yml | 41 ++ .gitignore | 1 + .goreleaser.yml | 69 +++ LICENSE.md | 21 + README.md | 598 +++++-------------- cmd/barnacle/main.go | 7 + cmd/barnacle/serve.go | 6 +- 9 files changed, 391 insertions(+), 449 deletions(-) create mode 100644 .claude/skills/development-workflow/SKILL.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yml create mode 100644 LICENSE.md diff --git a/.claude/skills/development-workflow/SKILL.md b/.claude/skills/development-workflow/SKILL.md new file mode 100644 index 0000000..3d34e25 --- /dev/null +++ b/.claude/skills/development-workflow/SKILL.md @@ -0,0 +1,49 @@ +--- +name: development-workflow +description: Branch naming conventions, PR workflow, and development process for this repository +--- + +## Branch Naming Conventions + +When starting work, create a branch from `main` using the appropriate prefix: + +* `feat/` - For new features (e.g., `feat/blob-replication`) +* `bug/` - For bug fixes (e.g., `bug/redis-connection-timeout`) +* `doc/` - For documentation changes (e.g., `doc/api-examples`) + +## Development Workflow + +1. **Create branch** from `main` with the appropriate prefix +2. **Do the work** - implement the feature, fix, or documentation +3. **Commit and push** - commit changes with descriptive messages, push to remote +4. **Create PR** - open a pull request targeting `main` +5. **Wait for CI** - ensure all pipelines pass +6. **Get review** - wait for code review approval +7. **Squash merge** - merge to `main` using squash merge + +## Git Commands Reference + +```bash +# Start a new feature +git checkout main +git pull origin main +git checkout -b feat/ + +# After work is complete +git add +git commit -m "feat: description of changes" +git push -u origin feat/ + +# Create PR via GitHub CLI +gh pr create --base main --title "feat: description" --body "..." +``` + +## Commit Message Style + +Follow conventional commits where appropriate: +* `feat:` - New feature +* `fix:` - Bug fix +* `doc:` or `docs:` - Documentation +* `refactor:` - Code refactoring +* `test:` - Adding or updating tests +* `chore:` - Maintenance tasks \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..af9efe9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Run unit tests + run: go test -v $(go list ./... | grep -v /test/e2e) -race + + build: + name: Docker Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0e6cea4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + packages: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - uses: ko-build/setup-ko@v0.9 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + KO_DOCKER_REPO: ghcr.io/pdylanross/barnacle diff --git a/.gitignore b/.gitignore index ed20272..c0f6246 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +dist/ # Test binary, built with `go test -c` *.test diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..71a981f --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json + +version: 2 + +project_name: barnacle + +builds: + - id: barnacle + main: ./cmd/barnacle + binary: barnacle + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + +kos: + - id: barnacle + main: ./cmd/barnacle + base_image: gcr.io/distroless/static-debian12:nonroot + repositories: + - ghcr.io/pdylanross/barnacle + platforms: + - linux/amd64 + - linux/arm64 + tags: + - "{{.Tag}}" + - "{{.Major}}.{{.Minor}}" + - "{{.Major}}" + - latest + labels: + org.opencontainers.image.title: barnacle + org.opencontainers.image.description: Distributed tiered OCI registry caching proxy + org.opencontainers.image.source: https://github.com/pdylanross/barnacle + org.opencontainers.image.version: "{{.Version}}" + org.opencontainers.image.revision: "{{.Commit}}" + org.opencontainers.image.licenses: Apache-2.0 + creation_time: "{{.CommitTimestamp}}" + ko_data_creation_time: "{{.CommitTimestamp}}" + sbom: none + +archives: + - id: default + formats: + - tar.gz + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +checksum: + name_template: "checksums.txt" + +changelog: + use: github + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" + +release: + github: + owner: pdylanross + name: barnacle + draft: false + prerelease: auto + name_template: "{{ .Tag }}" diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..77cc6f1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2026 Carlos Alexandro Becker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7353c50..16d35fd 100644 --- a/README.md +++ b/README.md @@ -1,463 +1,165 @@ -# Barnacle - -A distributed, tiered OCI registry caching proxy designed for terabyte-scale container images. - -## Overview - -Barnacle is a Kubernetes-native caching proxy for OCI registries that solves the challenges of managing extremely large container images (multi-terabyte blobs) across distributed infrastructure. Like its namesake that attaches to larger vessels, Barnacle attaches to upstream registries and intelligently caches their content across a distributed fleet of storage pods. - -### Why Barnacle? - -Modern container workloads increasingly include massive images containing ML models, scientific datasets, and other large artifacts. Traditional registry caching solutions face several challenges: - -- **Fixed placement**: Consistent hashing locks blobs to specific nodes, preventing optimization -- **Inefficient storage**: All blobs treated equally regardless of access patterns, wasting expensive fast storage -- **Poor scalability**: Large blobs overwhelm memory-based caching systems -- **Limited capacity**: Cannot efficiently handle terabyte-scale individual blobs - -Barnacle addresses these challenges through intelligent metadata-driven placement, automatic storage tiering, and streaming-first architecture. - -## Key Features - -- **Terabyte-Scale Blob Support**: Stream-based architecture handles blobs of any size without memory constraints -- **Metadata-Driven Sharding**: Intelligent placement based on capacity, load, and access patterns rather than static hashing -- **Automatic Storage Tiering**: Cost optimization through hot/warm/cold/archive tiers based on access patterns -- **Horizontal Scalability**: Add capacity by adding pods; no complex resharding required -- **Multiple Upstream Registries**: Proxy to Docker Hub, GCR, ECR, Harbor, and private registries simultaneously -- **Flexible Authentication**: Basic auth, bearer token, and anonymous authentication for upstream registries -- **Thundering Herd Prevention**: Distributed locking ensures single upstream fetch per blob across the cluster -- **Read-Only Safe**: Production-ready read-only mode with architecture supporting write operations - -## Architecture - -### High-Level Design - -``` -┌─────────────────────────────────────────────────────────┐ -│ Client Requests │ -│ (docker pull, containerd) │ -└────────────────────┬────────────────────────────────────┘ - │ - ▼ - ┌───────────────────────┐ - │ Load Balancer/ │ - │ Ingress │ - └───────────┬───────────┘ - │ - ┌────────────┴────────────┐ - ▼ ▼ -┌───────────────┐ ┌───────────────┐ -│ Cache Pod 0 │ ◄────► │ Cache Pod N │ -│ (Router) │ │ (Router) │ -└───────┬───────┘ └───────┬───────┘ - │ │ - ▼ ▼ -┌───────────────┐ ┌───────────────┐ -│ Storage Tier │ │ Storage Tier │ -│ (NVMe/SSD/ │ │ (NVMe/SSD/ │ -│ HDD/S3) │ │ HDD/S3) │ -└───────────────┘ └───────────────┘ - │ │ - └────────────┬────────────┘ - ▼ - ┌─────────────┐ - │ Redis │ - │ (Metadata) │ - └─────────────┘ -``` - -### Core Components - -#### 1. Router Component -Each cache pod runs a router that: -- Determines blob ownership via metadata lookup (not hashing) -- Issues 307 redirects to the appropriate storage pod -- Handles cache misses by fetching from upstream -- Manages distributed locks during fetch operations - -#### 2. Redis Metadata Store -Redis serves as the source of truth for: -- **Blob locations**: Maps digests to storage pods and tiers -- **Node capacity**: Tracks available space, IOPS, and load per pod -- **Distributed locks**: Prevents duplicate fetches (thundering herd protection) -- **Access patterns**: Records access frequency for tier migration decisions -- **Membership**: Tracks active pods for placement decisions - -**Why Redis over other options?** -- Fast metadata lookups (critical path for all requests) -- Built-in distributed locking with TTL (via Redlock/Redsync) -- Sorted sets for efficient access pattern tracking -- Pub/Sub for membership changes -- AOF persistence provides durability without Raft overhead -- Simple operations compared to etcd or Consul -- Single dependency for both metadata and coordination - -#### 3. Metadata-Based Sharding - -**Why metadata-based instead of consistent hashing?** - -Consistent hashing (`digest → hash → pod`) is simple but inflexible: -- Cannot move blobs after initial placement -- No consideration of pod capacity or load -- Wastes space (some pods fill while others sit empty) -- Makes storage tiering impossible - -Metadata-based sharding (`digest → Redis lookup → pod(s)`) provides: -- Dynamic placement based on current capacity -- Load-aware distribution -- Support for storage tiering (move blobs between storage classes) -- Graceful rebalancing without rehashing -- Multi-replica support with configurable redundancy - -**Placement Algorithm:** - -*Current placement (v0.1.0):* -``` -For new blob: -1. Query node capacities from Redis -2. Iterate through storage tiers in order (hot → warm → cold) -3. Prefer local node if it has sufficient space in current tier -4. Fall back to first remote node with capacity in current tier -5. Store metadata mapping digest → {node, tier} -``` - -The current algorithm prioritizes storage tiers in order, placing blobs on the fastest -available tier with sufficient capacity. Local node preference reduces network hops -for subsequent reads. - -*Planned placement (v0.4.0+):* -``` -For new blob: -1. Query all pod capacities from Redis -2. Filter pods by available space -3. Score by: available_space (50%) + load (30%) + blob_count (20%) -4. Select N pods with best scores (N = replication factor) -5. Store metadata mapping digest → [pod1, pod2, ...] -``` - -#### 4. Storage Tiering - -Blobs automatically migrate between storage tiers based on access patterns: - -| Tier | Storage | Cost/GB | Use Case | Demotion After | -|---------|-------------|---------|----------------------------|----------------| -| Hot | NVMe SSD | $0.50 | Recently accessed (10+ hits) | 24 hours | -| Warm | SSD | $0.15 | Moderate access (3+ hits) | 7 days | -| Cold | HDD | $0.05 | Occasional access | 30 days | -| Archive | S3 Glacier | $0.004 | Rarely accessed | 90+ days | - -**Background workers continuously:** -- Monitor access patterns -- Promote frequently-accessed blobs to faster tiers -- Demote cold blobs to cheaper storage -- Optimize cost vs. performance automatically - -**Example cost savings for 100TB:** -- All NVMe: $50,000/month -- Tiered (10% hot, 20% warm, 40% cold, 30% archive): **$10,120/month** (80% reduction) - -#### 5. Storage Backend - -**Filesystem-based, not key-value stores** - -**Why filesystem instead of BadgerDB/Pebble/etc?** -- No size limits (TB+ blobs) -- Native streaming via `io.Reader`/`io.Writer` -- OS page cache provides automatic hot-data caching -- Standard tools work (`du`, `find`, `rsync`) -- Simple debugging and operations -- Perfect range request support -- Works identically across all storage tiers - -Blobs stored content-addressed: -``` -/var/cache/oci/ - sha256/ - abc123def456... # blob content - abc123def456....descriptor.json # metadata -``` - -Each storage tier (hot/warm/cold) is a separate filesystem mount with different backing storage. - -### Request Flow - -#### Cache Hit (Blob Exists) -``` -1. Client → Load Balancer → Any Cache Pod -2. Pod queries Redis: "Where is sha256:abc...?" -3. Redis returns: [pod-2 (hot tier), pod-5 (warm tier)] -4. Pod issues 307 redirect to pod-2 -5. Client fetches directly from pod-2 -6. Pod-2 streams from local NVMe -7. Access recorded in Redis (async) -``` - -#### Cache Miss (Fetch from Upstream) -``` -1. Client → Cache Pod → Redis lookup → Not found -2. Pod acquires distributed lock in Redis for digest -3. Lock acquired → fetch from upstream begins -4. Redis metadata queried for optimal placement -5. Selected pods: [pod-7 (hot), pod-3 (hot)] based on capacity -6. Blob streams from upstream → client (via pod) -7. Simultaneously streams to pod-7 and pod-3 storage -8. Metadata written to Redis on completion -9. Lock released -10. Future requests redirect to pod-7 or pod-3 -``` - -#### Thundering Herd Prevention -``` -1. Pod A: Acquires lock for sha256:abc... → SUCCESS -2. Pod B: Attempts lock for sha256:abc... → FAIL -3. Pod B: Returns 503 Retry-After: 5 -4. Client waits 5 seconds, retries -5. Blob now cached, Pod B redirects to owner -``` - -The lock uses heartbeat extension: acquired with 30s TTL, extended every 10s while download progresses. If the downloading pod crashes, lock expires automatically. - -### Multiple Upstream Registries - -Barnacle proxies to any number of upstream registries simultaneously: - -**Supported upstreams:** -- Docker Hub (`registry-1.docker.io`) -- Google Container Registry (`gcr.io`, `us.gcr.io`, etc.) -- AWS Elastic Container Registry (ECR) -- Azure Container Registry (ACR) -- GitHub Container Registry (`ghcr.io`) -- Harbor -- Quay.io -- Any OCI-compliant registry - -**Configuration:** - -Upstreams are configured as a map where each key is an alias used in the URL path: - -```yaml -upstreams: - dockerhub: - registry: registry-1.docker.io - authentication: - basic: - username: "company-bot" - password: "secret123" - - gcr: - registry: gcr.io - authentication: - anonymous: {} - - private-registry: - registry: registry.example.com - authentication: - bearer: - token: "my-token" -``` - -**Authentication types:** - -- **Basic auth**: Username and password credentials (`authentication.basic`) -- **Bearer token**: Static bearer token (`authentication.bearer.token`) -- **Anonymous**: No authentication required (`authentication.anonymous: {}`) - -The upstream alias (e.g., `dockerhub`) is used in request URLs: `/v2/dockerhub/library/nginx/manifests/latest` - -### Distributed Locking - -Redis-based distributed locking prevents multiple pods from fetching the same blob: - -**Lock characteristics:** -- 30-second TTL with 10-second heartbeat extension -- Progress tracking: only extends if bytes are flowing -- Automatic expiration if holder crashes -- Waiters poll and retry with exponential backoff - -**Lock lifecycle:** -```go -1. Acquire lock with 30s TTL -2. Start download -3. Heartbeat goroutine extends every 10s -4. Track progress (bytes read) -5. Only extend if making progress -6. On completion or error: release lock -7. If crash: lock expires in 30s -``` - -Stalled downloads (network issue, upstream timeout) automatically release locks when progress stops, allowing other pods to retry. - -### Scalability - -**Horizontal scaling:** -- Add pods → increase capacity and throughput -- New pods register in Redis membership -- Placement algorithm automatically uses new capacity -- No rebalancing required (optional background optimization) - -**Storage capacity scaling:** -``` -3 pods × 5TB PVCs = 15TB total -Replication factor = 2 -Effective capacity = 7.5TB - -Add 2 pods (5 total): -5 pods × 5TB = 25TB total -Effective capacity = 12.5TB -``` - -**Performance characteristics:** -- Most requests: Single redirect (307) → direct to owner -- Cache miss: Single fetch → distributed write → cached -- Lock contention: Only during cache misses for same digest -- Metadata lookups: Redis (sub-millisecond) - -## Roadmap - -### Current Status: v0.1.0 (Alpha) - Feature Complete - -The v0.1.0 milestone is feature complete. The project remains in alpha status as we continue stabilization and testing. - -✅ **v0.1.0 (Feature Complete):** -- Read-only proxy mode -- Metadata-based sharding -- Redis metadata store -- Distributed locking -- Multiple upstream support -- Hardcoded credentials -- Storage tiering architecture -- Background tier migration - -**v0.2.0 - Authentication Rework** -- Credential passthrough (forward client credentials to upstream) -- GCR service account authentication -- ECR IAM authentication -- Pluggable auth provider interface -- Helm chart - -**v0.3.0 - Monitoring & Observability** -- Prometheus metrics -- Grafana dashboards -- Distributed tracing (OpenTelemetry) -- Structured logging improvements -- Health check enhancements - -**v0.4.0 - Image Optimization** -- eStargz lazy-pulling support -- eStargz rewriting for non-optimized images -- Range request support (HTTP 206 Partial Content) -- Accept header content negotiation -- Full OCI Distribution Spec conformance - -**v0.5.0 - Operations & Beta Stabilization** -- Write support (push to cache) -- Garbage collection -- Admin API -- Beta release milestone - -**v0.6.0 - Advanced Placement** -- Multi-factor placement scoring (capacity, load, blob count) -- Configurable replication factor -- Multi-cluster federation -- Cross-region replication -- Advanced placement policies (cost optimization) -- Webhook for external placement decisions - -**v1.0.0:** -- Production hardening -- Performance optimization -- Comprehensive documentation -- Reference architectures - -## Design Philosophy - -### Why These Choices? - -**Metadata-driven over consistent hashing:** -- Flexibility > Simplicity -- Enables tiering, rebalancing, and capacity-aware placement -- Slight complexity increase worth the operational benefits - -**Redis over etcd/Consul:** -- Performance: Faster for hot-path lookups -- Simplicity: Single dependency for metadata + locking -- Familiarity: Most teams already run Redis - -**Filesystem over key-value stores:** -- No size limits for TB-scale blobs -- Streaming without memory pressure -- Operational simplicity (standard tools work) - -**Tiering over uniform storage:** -- 80% cost savings in practice -- Performance where it matters (hot tier) -- Automatic optimization reduces ops burden - -**Read-only first:** -- Safer for production introduction -- Most use cases are pull-heavy -- Architecture supports writes when ready - -## Contributing - -Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. - -**Areas we'd love help:** -- Performance testing at scale -- Additional upstream registry integrations -- Metrics and dashboards -- Documentation improvements -- Bug reports and feature requests - -## License - -Apache License 2.0 - See [LICENSE](LICENSE) for details. - -## Acknowledgments - -Inspired by: -- [Docker Distribution](https://github.com/distribution/distribution) - Registry implementation -- [Groupcache](https://github.com/golang/groupcache) - Distributed caching concepts -- [Harbor](https://goharbor.io/) - Enterprise registry features -- The barnacle - Nature's original attachment-based caching system +

+ GoReleaser Logo +

GoReleaser

+

Release engineering, simplified.

+

+ Go + Go + Rust + Rust + Zig + Zig + Bun + Bun + Deno + Deno + Python + Python +

+

--- -**Project Status:** Alpha (v0.1.0 feature complete) - Not recommended for production use yet. APIs may change. Beta planned for v0.5.0. +We handle the complexities of releasing so you can focus in building what really +matters: **your software**. -**Questions?** Open an issue. +![](https://goreleaser.com/static/goreleaser.png) --- -## TODO: OCI Distribution Spec Conformance +## Get GoReleaser -The current distribution API implementation is **partially compliant** with the OCI Distribution Specification for read-only operations. +- [On your machine](https://goreleaser.com/install/); +- [On CI/CD systems](https://goreleaser.com/ci/). -### Compliant +## Documentation -- HTTP Methods (GET/HEAD) - Correct -- Error Response Format - OCI-compliant JSON structure with code, message, detail -- Error Codes - All standard codes defined (BLOB_UNKNOWN, MANIFEST_UNKNOWN, etc.) -- Status Codes - Correct (200, 400, 404) -- Tag vs Digest Handling - Correctly uses `:` for tags, `@` for digests -- Blob Streaming - Efficient io.ReadCloser streaming -- Required Headers - Docker-Distribution-API-Version, Docker-Content-Digest, Content-Type, Content-Length +Documentation is hosted live at https://goreleaser.com -### Intentional Deviation +## Community -**Non-Standard URL Pattern:** -``` -Current: /v2/:upstream/:repository/manifests/:reference -OCI Spec: /v2//manifests/ -``` +You have questions, need support and or just want to talk about GoReleaser? -This is **by design** - Barnacle routes to multiple upstream registries, requiring the upstream parameter. Standard OCI clients (docker, containerd, skopeo) cannot use this API directly without configuration. +Here are ways to get in touch with the GoReleaser community: -### Missing Features (TODO) +[![Join Discord](https://img.shields.io/badge/Join_our_Discord_server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/RGEBtg8vQ6) +[![Follow Twitter](https://img.shields.io/badge/follow_on_twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/goreleaser) +[![GitHub Discussions](https://img.shields.io/badge/GITHUB_DISCUSSION-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/goreleaser/goreleaser/discussions) -| Feature | Impact | Priority | -|---------|--------|----------| -| Accept header content negotiation | Can't negotiate manifest format (image vs list) | Medium | -| Range request support (206 Partial Content) | Can't resume interrupted blob downloads | Low | -| Accept-Ranges header on blob endpoints | Clients don't know ranges are unsupported | Low | -| Content-Type header on HEAD blob | Minor, not strictly required by spec | Low | +You can find the links above and all others [here](https://goreleaser.com/links/). + +### Code of Conduct + +This project adheres to the Contributor Covenant [code of conduct](https://github.com/goreleaser/.github/blob/main/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. +We appreciate your contribution. +Please refer to our [contributing guidelines](CONTRIBUTING.md) for further information. + +## Badges + +[![Release](https://img.shields.io/github/release/goreleaser/goreleaser.svg?style=for-the-badge)](https://github.com/goreleaser/goreleaser/releases/latest) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=for-the-badge)](/LICENSE.md) +[![Build status](https://img.shields.io/github/actions/workflow/status/goreleaser/goreleaser/build.yml?style=for-the-badge&branch=main)](https://github.com/goreleaser/goreleaser/actions?workflow=build) +[![Codecov branch](https://img.shields.io/codecov/c/github/goreleaser/goreleaser/main.svg?style=for-the-badge)](https://codecov.io/gh/goreleaser/goreleaser) +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/goreleaser&style=for-the-badge)](https://artifacthub.io/packages/search?repo=goreleaser) +[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](http://godoc.org/github.com/goreleaser/goreleaser) +[![Powered By: GoReleaser](https://img.shields.io/badge/powered%20by-goreleaser-green.svg?style=for-the-badge)](https://github.com/goreleaser) +[![Backers on Open Collective](https://opencollective.com/goreleaser/backers/badge.svg?style=for-the-badge)](https://opencollective.com/goreleaser/backers/) +[![Sponsors on Open Collective](https://opencollective.com/goreleaser/sponsors/badge.svg?style=for-the-badge)](https://opencollective.com/goreleaser/sponsors/) +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?style=for-the-badge)](https://conventionalcommits.org) +[![CII Best Practices](https://img.shields.io/cii/summary/5420?label=openssf%20best%20practices&style=for-the-badge)](https://bestpractices.coreinfrastructure.org/projects/5420) +[![GoReportCard](https://goreportcard.com/badge/github.com/goreleaser/goreleaser?style=for-the-badge)](https://goreportcard.com/report/github.com/goreleaser/goreleaser) + +## Sponsors + +Does you or your company use GoReleaser? + +You can help keep the project bug-free and feature rich by sponsoring the +project and its maintainers. + +You can sponsor GoReleaser via: + +- **[GitHub Sponsors](https://github.com/sponsors/caarlos0)** +- **[OpenCollective](https://opencollective.com/goreleaser)** + +A big **thank you** to all current, past, and future sponsors! + + + + +### Gold Sponsors + +
+ + Mercedes-Benz Group + nitric + +
+ +### Silver Sponsors + +
+ + Datadog, Inc. + +
+ +### Bronze Sponsors + +
+ + Lea Anthony + conet cloud + Encore + Numary + Gitea + +
+ +### Backers + +- [Marcel Eichner](https://github.com/Ephigenia) +- [Chatpong Voranartaksorn](https://github.com/psychvc) +- [Bruno Paz](https://brunopaz.dev/) +- [Guest](https://opencollective.com/guest-341ba997) +- [Automatio AI](https://automatio.ai/) +- [Bileta Avioni](https://biletaavioni.al/) +- [Jared Allard](https://github.com/jaredallard) +- [joe miller](https://github.com/joemiller) +- [Ryan Nixon](https://github.com/taiidani) +- [Lawrence Gripper](https://github.com/lawrencegripper) +- [Francis Lavoie](https://github.com/francislavoie) +- [Nicolas Gotchac](https://github.com/ngotchac) +- [Ben](https://github.com/iwpnd) +- [KEINOS](https://github.com/KEINOS) +- [Eden Zimbelman](https://github.com/zimeg) +- [Ben Lechlitner](https://github.com/asphaltbuffet) +- [Santosh Yadav](https://github.com/santoshyadavdev) +- [Alexey Palazhchenko](https://github.com/AlekSi) +- [David Birks](https://github.com/dbirks) +- [Alex Viscreanu](https://github.com/aexvir) +- [Ethan Li](https://github.com/ethanjli) +- [Benjamin Kane](https://github.com/bbkane) +- [Carl Tsai](https://github.com/moonape1226) +- [David Dymko](https://github.com/ddymko) +- [Jan De Dobbeleer](https://github.com/JanDeDobbeleer) +- [Paul Greenberg](https://github.com/greenpau) +- [Baptiste Canton](https://github.com/batmac) +- [Andrew](https://github.com/wobondar) +- [Sidartha Karna](https://github.com/sidarthakarna) +- [Kazuma Watanabe](https://github.com/wata727) +- [Joseph Sirianni](https://github.com/jsirianni) +- [Oleg Balunenko](https://github.com/obalunenko) +- [^.{5}\s.{2}ram.{3}$](https://github.com/umatare5) +- [Ethan Troy](https://github.com/ethanolivertroy) + + + +### Contributors + +This project exists thanks to all the people who contribute. +[Contribution guide](CONTRIBUTING.md). + +## Stars + +[![Stargazers over time](https://starchart.cc/goreleaser/goreleaser.svg?variant=adaptive)](https://starchart.cc/goreleaser/goreleaser) diff --git a/cmd/barnacle/main.go b/cmd/barnacle/main.go index faaa9ae..54bdb31 100644 --- a/cmd/barnacle/main.go +++ b/cmd/barnacle/main.go @@ -6,6 +6,13 @@ import ( "github.com/joho/godotenv" ) +// Build-time variables injected via ldflags by goreleaser. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + //go:generate swag init -g main.go --parseInternal -d ./cmd/barnacle/,./internal/routes/,./pkg/api/,./internal/tk/httptk/ -o docs // @title Barnacle API diff --git a/cmd/barnacle/serve.go b/cmd/barnacle/serve.go index d0354ab..fd925b3 100644 --- a/cmd/barnacle/serve.go +++ b/cmd/barnacle/serve.go @@ -25,7 +25,11 @@ var serveCmd = &cobra.Command{ } defer tk.IgnoreDeferError(logger.Sync) - logger.Info("Starting barnacle server") + logger.Info("Starting barnacle server", + zap.String("version", version), + zap.String("commit", commit), + zap.String("date", date), + ) // Load configuration config, err := configloader.LoadConfig(configDir, logger) From 0828e98afc30e0a7d6a15a6695eb2063a1716c21 Mon Sep 17 00:00:00 2001 From: Dylan Ross Date: Fri, 13 Feb 2026 17:45:05 -0600 Subject: [PATCH 2/3] fix(ci): updated linter action to v9 --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af9efe9..5d423ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: go-version: "1.25" - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: version: latest @@ -36,13 +36,16 @@ jobs: run: go test -v $(go list ./... | grep -v /test/e2e) -race build: - name: Docker Build + name: Container Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Build Docker image - uses: docker/build-push-action@v6 + - uses: actions/setup-go@v5 with: - context: . - push: false + go-version: "1.25" + + - uses: ko-build/setup-ko@v0.9 + + - name: Build container image + run: ko build --local ./cmd/barnacle From cac4e96a9f2de586192fc59115ba879d263a925f Mon Sep 17 00:00:00 2001 From: Dylan Ross Date: Fri, 13 Feb 2026 17:49:31 -0600 Subject: [PATCH 3/3] fix: added redis service for tests to pass --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d423ee..2d735df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,16 @@ jobs: test: name: Test runs-on: ubuntu-latest + services: + redis: + image: redis:7 + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - uses: actions/checkout@v4