mirror of
https://github.com/replicatedhq/ttl.sh.git
synced 2026-08-25 03:07:15 +00:00
Merge pull request #191 from replicatedhq/joshs/sidecar-followups
CI runs and metadata corrections
This commit is contained in:
@@ -8,6 +8,11 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/sidecar"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
# sidecar/ is the only Go module in this repo; skip CI for changes that
|
||||
# cannot affect it (web/, ansible/, docs).
|
||||
paths:
|
||||
- "sidecar/**"
|
||||
- ".github/workflows/ci.yaml"
|
||||
|
||||
# Cancel in-progress runs for the same ref (PR or branch) when new commits land.
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: sidecar
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint & format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: sidecar/go.mod
|
||||
check-latest: true
|
||||
cache-dependency-path: sidecar/go.sum
|
||||
|
||||
- name: Verify gofmt
|
||||
run: |
|
||||
unformatted=$(gofmt -l .)
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "::error::The following files are not gofmt-formatted:"
|
||||
echo "$unformatted"
|
||||
echo "Run 'gofmt -w .' to fix."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Verify go.mod is tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
if ! git diff --exit-code -- go.mod go.sum; then
|
||||
echo "::error::go.mod/go.sum are not tidy. Run 'go mod tidy' and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.12.2
|
||||
# `defaults.run` does not apply to actions, so point it at the module.
|
||||
working-directory: sidecar
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# No service container: internal/redistest starts its own Redis on the
|
||||
# runner's Docker daemon. GitHub sets CI=true, which makes it fail rather
|
||||
# than skip, so the persistence layer cannot drop out of this job.
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: sidecar/go.mod
|
||||
check-latest: true
|
||||
cache-dependency-path: sidecar/go.sum
|
||||
|
||||
- name: Run tests (race + coverage)
|
||||
run: go test -race -coverprofile=cover.out -covermode=atomic ./...
|
||||
|
||||
- name: Coverage summary
|
||||
run: go tool cover -func=cover.out | tail -1
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: sidecar/go.mod
|
||||
check-latest: true
|
||||
cache-dependency-path: sidecar/go.sum
|
||||
|
||||
- name: go build
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
run: go build -ldflags="-s -w" ./...
|
||||
@@ -101,22 +101,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
steps:
|
||||
- name: Test push and pull
|
||||
- name: Test push, pull, and expiry
|
||||
run: |
|
||||
IMAGE="ttl.sh/smoke-${{ github.sha }}:5m"
|
||||
echo "🧪 Testing with image: $IMAGE"
|
||||
|
||||
# Use busybox - tiny image (~1MB)
|
||||
IMAGE="ttl.sh/smoke-${{ github.sha }}:1m"
|
||||
|
||||
docker pull busybox:latest
|
||||
docker tag busybox:latest "$IMAGE"
|
||||
|
||||
echo "📤 Pushing image..."
|
||||
docker push "$IMAGE"
|
||||
|
||||
# Remove local copy to force pull from registry
|
||||
docker rmi "$IMAGE"
|
||||
|
||||
echo "📥 Pulling image back..."
|
||||
|
||||
docker rmi "$IMAGE" busybox:latest
|
||||
docker pull "$IMAGE"
|
||||
|
||||
echo "✅ Push/pull test passed!"
|
||||
echo "✅ Push/pull passed"
|
||||
|
||||
docker rmi "$IMAGE"
|
||||
sleep 90
|
||||
|
||||
if docker pull "$IMAGE"; then
|
||||
echo "❌ image still pullable after TTL"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Image expired as expected"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Vulnerability check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
# sidecar/ is the only Go module in this repo.
|
||||
paths:
|
||||
- "sidecar/**"
|
||||
- ".github/workflows/govulncheck.yaml"
|
||||
schedule:
|
||||
# Weekly scan so newly disclosed CVEs in pinned deps surface even
|
||||
# without a code change. Mondays at 07:00 UTC.
|
||||
- cron: "0 7 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: govulncheck-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: govulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# The action still installs govulncheck itself at @latest.
|
||||
- name: Run govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
go-version-file: sidecar/go.mod
|
||||
check-latest: true
|
||||
cache-dependency-path: sidecar/go.sum
|
||||
work-dir: sidecar
|
||||
repo-checkout: false
|
||||
+1
-1
@@ -65,7 +65,7 @@ ARG BUILD_DATE
|
||||
|
||||
LABEL org.opencontainers.image.title="zot-ephemeral-ttl" \
|
||||
org.opencontainers.image.description="Tag-driven TTL sidecar for zot. Subscribes to image.updated CloudEvents and DELETEs expired manifests." \
|
||||
org.opencontainers.image.source="https://github.com/nullbytelabs/zot-ephemeral-ttl" \
|
||||
org.opencontainers.image.source="https://github.com/replicatedhq/ttl.sh" \
|
||||
org.opencontainers.image.licenses="Apache-2.0" \
|
||||
org.opencontainers.image.version="${VERSION}" \
|
||||
org.opencontainers.image.revision="${COMMIT}" \
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/app"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/config"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/app"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/config"
|
||||
)
|
||||
|
||||
// Build metadata, set via -ldflags "-X main.version=..." in the Dockerfile.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
module github.com/nullbytelabs/zot-ephemeral-ttl
|
||||
module github.com/replicatedhq/ttl.sh/sidecar
|
||||
|
||||
go 1.26
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/config"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/reaper"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/registry"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/server"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/store"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/config"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/reaper"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/registry"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/server"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/store"
|
||||
)
|
||||
|
||||
// storeBackend is the union of persistence capabilities app injects into the
|
||||
@@ -56,8 +56,18 @@ func Run(ctx context.Context, cfg config.Config) error {
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// The sweep loop gets its own cancellable context so it can be stopped on
|
||||
// the way out even when shutdown was triggered by a server error rather
|
||||
// than by ctx.
|
||||
reaperCtx, stopReaper := context.WithCancel(ctx)
|
||||
defer stopReaper()
|
||||
|
||||
rp := reaper.New(cfg.SweepInterval, st, registry.New(cfg.ZotURL))
|
||||
go rp.Run(ctx)
|
||||
reaperDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(reaperDone)
|
||||
rp.Run(reaperCtx)
|
||||
}()
|
||||
|
||||
serveErr := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -76,5 +86,13 @@ func Run(ctx context.Context, cfg config.Config) error {
|
||||
log.Printf("shutting down")
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
return httpSrv.Shutdown(shutdownCtx)
|
||||
shutdownErr := httpSrv.Shutdown(shutdownCtx)
|
||||
|
||||
// Wait for an in-flight sweep to unwind before returning, so it cannot
|
||||
// still be talking to Redis when the deferred Close runs. Cancelling
|
||||
// reaperCtx aborts the delete request in flight, so this does not block for
|
||||
// long.
|
||||
stopReaper()
|
||||
<-reaperDone
|
||||
return shutdownErr
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/config"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/redistest"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/config"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/redistest"
|
||||
)
|
||||
|
||||
// The startup-path test needs a real Redis; redistest starts one per package.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/store"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/store"
|
||||
)
|
||||
|
||||
// Store is the subset of the persistence layer the reaper needs.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/store"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/store"
|
||||
)
|
||||
|
||||
// In-memory fakes for the reaper's two dependencies. No real DB, no real HTTP.
|
||||
|
||||
@@ -21,7 +21,11 @@ func New(baseURL string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
http: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
// A manifest delete is a metadata operation against a zot running
|
||||
// alongside this process, so it should be fast. Failing quickly
|
||||
// keeps one wedged tag from stalling the rest of the sweep; the row
|
||||
// stays in the store and is retried on the next tick.
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -41,11 +45,12 @@ func (c *Client) DeleteManifest(ctx context.Context, repo, tag string) error {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK, http.StatusAccepted, http.StatusNoContent, http.StatusNotFound:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("DELETE %s -> %d", endpoint, resp.StatusCode)
|
||||
// zot describes the failure in the body; %q keeps it on one log line.
|
||||
return fmt.Errorf("DELETE %s -> %d: %q", endpoint, resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/events"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/ttl"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/events"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/ttl"
|
||||
)
|
||||
|
||||
// Store is the subset of the persistence layer the server needs.
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/events"
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/store"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/events"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/store"
|
||||
)
|
||||
|
||||
// Server.Store is documented as safe for concurrent use, so the fake locks its
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"github.com/nullbytelabs/zot-ephemeral-ttl/internal/redistest"
|
||||
"github.com/replicatedhq/ttl.sh/sidecar/internal/redistest"
|
||||
)
|
||||
|
||||
// These tests run against a real Redis, started for the package by redistest.
|
||||
|
||||
Reference in New Issue
Block a user