mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-11-14 14:47:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f04d2e390e | ||
|
|
1c1c131b95 |
No files matched your search
+21
-18
@@ -24,7 +24,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# fetch the whole repo for `git describe` to work
|
||||
fetch-depth: 0
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
make docker-unit-test
|
||||
mkdir -p out/coverage
|
||||
mv unit.out out/coverage/
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unit-tests-coverage
|
||||
path: out/
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8 faketime dput-ng
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# fetch the whole repo for `git describe` to work
|
||||
fetch-depth: 0
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
id: goversion
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
mkdir -p out/coverage
|
||||
COVERAGE_DIR=$PWD/out/coverage make system-test
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: system-tests-coverage
|
||||
path: out/
|
||||
@@ -123,15 +123,15 @@ jobs:
|
||||
- test
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Download Unit Test Coverage"
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unit-tests-coverage
|
||||
|
||||
- name: "Download System Test Coverage"
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: system-tests-coverage
|
||||
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name: ["Debian 13/trixie", "Debian 12/bookworm", "Ubuntu 26.04", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||
name: ["Debian 13/trixie", "Debian 12/bookworm", "Debian 11/bullseye", "Ubuntu 26.04", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"]
|
||||
arch: ["amd64", "i386" , "arm64" , "armhf"]
|
||||
include:
|
||||
- name: "Debian 13/trixie"
|
||||
@@ -167,6 +167,9 @@ jobs:
|
||||
- name: "Debian 12/bookworm"
|
||||
suite: bookworm
|
||||
image: debian:bookworm-slim
|
||||
- name: "Debian 11/bullseye"
|
||||
suite: bullseye
|
||||
image: debian:bullseye-slim
|
||||
- name: "Ubuntu 26.04"
|
||||
suite: resolute
|
||||
image: ubuntu:26.04
|
||||
@@ -196,7 +199,7 @@ jobs:
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# fetch the whole repo for `git describe` to work
|
||||
fetch-depth: 0
|
||||
@@ -209,7 +212,7 @@ jobs:
|
||||
id: goversion
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||
|
||||
@@ -265,7 +268,7 @@ jobs:
|
||||
|
||||
- name: "Upload CI Artifacts"
|
||||
if: github.ref != 'refs/heads/master' && !startsWith(github.event.ref, 'refs/tags')
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.suite }}_${{ matrix.arch }}
|
||||
path: build/
|
||||
@@ -287,7 +290,7 @@ jobs:
|
||||
goarch: arm
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# fetch the whole repo for `git describe` to work
|
||||
fetch-depth: 0
|
||||
@@ -298,7 +301,7 @@ jobs:
|
||||
id: goversion
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||
|
||||
@@ -325,7 +328,7 @@ jobs:
|
||||
make binaries GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }}
|
||||
|
||||
- name: "Upload Artifacts"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: startsWith(github.event.ref, 'refs/tags')
|
||||
with:
|
||||
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
@@ -333,7 +336,7 @@ jobs:
|
||||
compression-level: 0 # no compression
|
||||
|
||||
- name: "Upload CI Artifacts"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
if: "!startsWith(github.event.ref, 'refs/tags')"
|
||||
with:
|
||||
name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
@@ -349,7 +352,7 @@ jobs:
|
||||
if: startsWith(github.event.ref, 'refs/tags')
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Get aptly version"
|
||||
env:
|
||||
@@ -361,7 +364,7 @@ jobs:
|
||||
id: releaseversion
|
||||
|
||||
- name: "Download Artifacts"
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: out/
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: "Read go version from go.mod"
|
||||
run: |
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
id: goversion
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.GOVER }}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: test aptly upload-lint
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
aptly-upload:
|
||||
name: upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish to aptly server
|
||||
uses: neolynx/aptly-github-action/publish@v0.0.15
|
||||
with:
|
||||
directory: buildout
|
||||
aptly_server: https://aptly.info
|
||||
aptly_user: ${{ secrets.APTLY_USER }}
|
||||
aptly_password: ${{ secrets.APTLY_PASSWORD }}
|
||||
|
||||
@@ -84,8 +84,3 @@ List of contributors, in chronological order:
|
||||
* Zhang Xiao (https://github.com/xzhang1)
|
||||
* Tom Nguyen (https://github.com/lecafard)
|
||||
* Philip Cramer (https://github.com/PhilipCramer)
|
||||
* Kerem Gur (https://github.com/KereMath)
|
||||
* Phil Dibowitz (https://github.com/jaymzh)
|
||||
* James Munson (https://github.com/jmunson)
|
||||
* Luan Dang (https://github.com/luanmdang)
|
||||
* Martin Simon (https://github.com/barnumbirr)
|
||||
@@ -126,10 +126,6 @@ GUI for aptly API:
|
||||
|
||||
- `Python aptly GUI (via pyqt5) <https://github.com/chnyda/python-aptly-gui>`_ by Cedric Hnyda
|
||||
|
||||
Repository management:
|
||||
|
||||
- `AptlyPilot <https://github.com/mmayabi/AptlyPilot>`_ by Maryam Mayabi - Web-based management platform for Aptly repositories with Repository as Code workflows and lifecycle automation.
|
||||
|
||||
Scala sbt:
|
||||
|
||||
- `sbt aptly plugin <https://github.com/amalakar/sbt-aptly>`_ by Arup Malakar
|
||||
|
||||
@@ -16,4 +16,3 @@ git push origin v$version master
|
||||
- update version in content/download.md
|
||||
- push commit to master
|
||||
- create release announcement on https://github.com/aptly-dev/aptly/discussions
|
||||
- update aptly infrastructure
|
||||
+27
-5
@@ -285,11 +285,33 @@ func showPackages(c *gin.Context, reflist *deb.PackageRefList, collectionFactory
|
||||
|
||||
// filter packages by version
|
||||
if c.Request.URL.Query().Get("maximumVersion") == "1" {
|
||||
list, err = list.FilterLatest(1)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to filter latest packages: %s", err))
|
||||
return
|
||||
}
|
||||
list.PrepareIndex()
|
||||
_ = list.ForEach(func(p *deb.Package) error {
|
||||
versionQ, err := query.Parse(fmt.Sprintf("Name (%s), $Version (<= %s)", p.Name, p.Version))
|
||||
if err != nil {
|
||||
fmt.Println("filter packages by version, query string parse err: ", err)
|
||||
_ = c.AbortWithError(500, fmt.Errorf("unable to parse %s maximum version query string: %s", p.Name, err))
|
||||
} else {
|
||||
tmpList, err := list.Filter(deb.FilterOptions{
|
||||
Queries: []deb.PackageQuery{versionQ},
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
if tmpList.Len() > 0 {
|
||||
_ = tmpList.ForEach(func(tp *deb.Package) error {
|
||||
list.Remove(tp)
|
||||
return nil
|
||||
})
|
||||
_ = list.Add(p)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("filter packages by version, filter err: ", err)
|
||||
_ = c.AbortWithError(500, fmt.Errorf("unable to get %s maximum version: %s", p.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if c.Request.URL.Query().Get("format") == "details" {
|
||||
|
||||
@@ -42,7 +42,6 @@ func createTestConfig() *os.File {
|
||||
jsonString, err := json.Marshal(gin.H{
|
||||
"architectures": []string{},
|
||||
"enableMetricsEndpoint": true,
|
||||
"enablePprofEndpoint": false,
|
||||
"S3PublishEndpoints": map[string]map[string]string{
|
||||
"test-s3": {
|
||||
"region": "us-east-1",
|
||||
@@ -159,23 +158,6 @@ func (s *APISuite) TestGetMetrics(c *C) {
|
||||
c.Check(b, Matches, ".*aptly_build_info.*version=\"testVersion\".*")
|
||||
}
|
||||
|
||||
func (s *APISuite) TestHeadMetrics(c *C) {
|
||||
server := httptest.NewServer(s.router)
|
||||
defer server.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodHead, server.URL+"/api/metrics", nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
resp, err := server.Client().Do(req)
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
c.Check(resp.StatusCode, Equals, 200)
|
||||
body, readErr := io.ReadAll(resp.Body)
|
||||
c.Assert(readErr, IsNil)
|
||||
c.Check(len(body), Equals, 0)
|
||||
}
|
||||
|
||||
func (s *APISuite) TestRepoCreate(c *C) {
|
||||
body, err := json.Marshal(gin.H{
|
||||
"Name": "dummy",
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ func apiGPGAddKey(c *gin.Context) {
|
||||
// @Description Returns all public keys currently installed in the aptly GPG keyring.
|
||||
// @Description
|
||||
// @Tags Mirrors
|
||||
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)"
|
||||
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)" example(trustedkeys.gpg)
|
||||
// @Produce json
|
||||
// @Success 200 {object} gpgKeyListResponse "OK"
|
||||
// @Failure 400 {object} Error "Bad Request"
|
||||
|
||||
+2
-20
@@ -520,10 +520,8 @@ type mirrorUpdateParams struct {
|
||||
ForceUpdate bool ` json:"ForceUpdate"`
|
||||
// Set "true" to skip downloading already downloaded packages
|
||||
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
||||
// Set "true" to download only the latest version per package/architecture (same as `MaxVersions`: 1)
|
||||
// Set "true" to download only the latest version per package/architecture
|
||||
LatestOnly bool ` json:"LatestOnly"`
|
||||
// Download only the N most recent versions per package/architecture, 0 means all versions
|
||||
MaxVersions int ` json:"MaxVersions" example:"5"`
|
||||
}
|
||||
|
||||
// @Summary Update Mirror
|
||||
@@ -575,14 +573,6 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if b.MaxVersions < 0 {
|
||||
AbortWithJSONError(c, 400, fmt.Errorf("MaxVersions should be a positive number"))
|
||||
return
|
||||
}
|
||||
if b.LatestOnly && b.MaxVersions == 0 {
|
||||
b.MaxVersions = 1
|
||||
}
|
||||
|
||||
verifier, err := getVerifier(b.Keyrings)
|
||||
if err != nil {
|
||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
||||
@@ -607,7 +597,6 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
if err == nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)
|
||||
}
|
||||
remote.Name = b.Name
|
||||
}
|
||||
|
||||
downloader := context.NewDownloader(out)
|
||||
@@ -650,15 +639,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if b.MaxVersions > 0 {
|
||||
_, _, err := remote.FilterLatest(b.MaxVersions)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), taskCollectionFactory.PackageCollection(),
|
||||
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
|
||||
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages, b.LatestOnly)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Exercise the common API listing handler with persisted packages, checking
|
||||
// both reference and detailed JSON responses independently of response order.
|
||||
func TestPackagesMaximumVersionArchitectureVariant(t *testing.T) {
|
||||
pkg := func(arch, variant, version string) *deb.Package {
|
||||
stanza := deb.Stanza{"Package": "example", "Version": version, "Architecture": arch}
|
||||
if variant != "" {
|
||||
stanza["Architecture-Variant"] = variant
|
||||
}
|
||||
return deb.NewPackageFromControlFile(stanza)
|
||||
}
|
||||
n1, n3 := pkg("amd64", "", "1"), pkg("amd64", "", "3")
|
||||
v1, v2 := pkg("amd64", "amd64v3", "1"), pkg("amd64", "amd64v3", "2")
|
||||
a1, a4 := pkg("arm64", "", "1"), pkg("arm64", "", "4")
|
||||
all := []*deb.Package{n1, n3, v1, v2, a1, a4}
|
||||
cases := []struct {
|
||||
name, option string
|
||||
input, want []*deb.Package
|
||||
}{
|
||||
{"same_version", "1", []*deb.Package{n1, v1}, []*deb.Package{n1, v1}},
|
||||
{"independent_latest", "1", []*deb.Package{n1, n3, v1, v2}, []*deb.Package{n3, v2}},
|
||||
{"ordinary_multi_architecture", "1", []*deb.Package{n1, n3, a1, a4}, []*deb.Package{n3, a4}},
|
||||
{"ordinary_single_architecture", "1", []*deb.Package{n1, n3}, []*deb.Package{n3}},
|
||||
{"disabled_absent", "", all, all},
|
||||
{"disabled_zero", "0", all, all},
|
||||
{"empty", "1", nil, nil},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
for _, format := range []string{"refs", "details"} {
|
||||
t.Run(tc.name+"/"+format, func(t *testing.T) {
|
||||
db, err := goleveldb.NewOpenDB(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := db.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
factory := deb.NewCollectionFactory(db)
|
||||
list := deb.NewPackageList()
|
||||
for _, p := range tc.input {
|
||||
// Update offloads metadata; use a fresh package for each database.
|
||||
p := pkg(p.Architecture, p.ArchitectureVariant, p.Version)
|
||||
if err := factory.PackageCollection().Update(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := list.Add(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
refs := deb.NewPackageRefListFromPackageList(list)
|
||||
url := "/api/packages?format=" + format
|
||||
if tc.option != "" {
|
||||
url += "&maximumVersion=" + tc.option
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(response)
|
||||
c.Request = httptest.NewRequest("GET", url, nil)
|
||||
showPackages(c, refs, deb.NewCollectionFactory(db))
|
||||
if response.Code != 200 {
|
||||
t.Fatalf("status %d: %s", response.Code, response.Body.String())
|
||||
}
|
||||
got := []string{}
|
||||
if format == "details" {
|
||||
var details []map[string]string
|
||||
if err := json.Unmarshal(response.Body.Bytes(), &details); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, detail := range details {
|
||||
got = append(got, detail["Key"])
|
||||
// Metadata must remain separate from the identity encoded in Key.
|
||||
for _, p := range tc.want {
|
||||
if detail["Key"] == string(p.Key("")) {
|
||||
if detail["Architecture"] != p.Architecture || detail["Architecture-Variant"] != p.ArchitectureVariant || detail["ShortKey"] != string(p.ShortKey("")) {
|
||||
t.Errorf("incorrect package metadata: %v", detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err := json.Unmarshal(response.Body.Bytes(), &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []string{}
|
||||
for _, p := range tc.want {
|
||||
want = append(want, string(p.Key("")))
|
||||
}
|
||||
sort.Strings(got)
|
||||
sort.Strings(want)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("package references:\n got %v\nwant %v", got, want)
|
||||
}
|
||||
if !reflect.DeepEqual(refs.Strings(), deb.NewPackageRefListFromPackageList(list).Strings()) {
|
||||
t.Error("listing mutated source references")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// pprofTestServer starts a real httptest.Server with the aptly router configured
|
||||
// with enablePprofEndpoint: true. The caller is responsible for calling
|
||||
// server.Close() and aptlyCtx.Shutdown().
|
||||
func pprofTestServer(t *testing.T) (*httptest.Server, *ctx.AptlyContext) {
|
||||
t.Helper()
|
||||
|
||||
aptly.Version = "testVersion"
|
||||
|
||||
file, err := os.CreateTemp("", "aptly-pprof-integration")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = os.Remove(file.Name()) })
|
||||
|
||||
jsonString, err := json.Marshal(gin.H{
|
||||
"architectures": []string{},
|
||||
"enablePprofEndpoint": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = file.Write(jsonString); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = file.Close()
|
||||
|
||||
aptlyCtx, router, err := newPprofContext(file.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return httptest.NewServer(router), aptlyCtx
|
||||
}
|
||||
|
||||
// runPprof runs "go tool pprof -top <url>" and returns the combined output.
|
||||
func runPprof(t *testing.T, url string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command("go", "tool", "pprof", "-top", url)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("go tool pprof -top %s failed: %v\noutput:\n%s", url, err, out)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// TestPprofToolGoroutine verifies that go tool pprof can fetch and parse the
|
||||
// goroutine profile from the /api/debug/pprof/goroutine endpoint.
|
||||
func TestPprofToolGoroutine(t *testing.T) {
|
||||
server, aptlyCtx := pprofTestServer(t)
|
||||
defer server.Close()
|
||||
defer aptlyCtx.Shutdown()
|
||||
|
||||
url := fmt.Sprintf("%s/api/debug/pprof/goroutine", server.URL)
|
||||
out := runPprof(t, url)
|
||||
t.Logf("go tool pprof goroutine output:\n%s", out)
|
||||
|
||||
if !strings.Contains(out, "Type: goroutine") {
|
||||
t.Errorf("expected 'Type: goroutine' in output, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "flat") {
|
||||
t.Errorf("expected 'flat' column header in output, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPprofToolHeap verifies that go tool pprof can fetch and parse the heap
|
||||
// profile from the /api/debug/pprof/heap endpoint.
|
||||
func TestPprofToolHeap(t *testing.T) {
|
||||
server, aptlyCtx := pprofTestServer(t)
|
||||
defer server.Close()
|
||||
defer aptlyCtx.Shutdown()
|
||||
|
||||
url := fmt.Sprintf("%s/api/debug/pprof/heap", server.URL)
|
||||
out := runPprof(t, url)
|
||||
t.Logf("go tool pprof heap output:\n%s", out)
|
||||
|
||||
// Heap profiles are typed as "inuse_space" by default.
|
||||
if !strings.Contains(out, "Type: inuse_space") {
|
||||
t.Errorf("expected 'Type: inuse_space' in output, got:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "flat") {
|
||||
t.Errorf("expected 'flat' column header in output, got:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPprofToolCmdline verifies that the cmdline endpoint is reachable.
|
||||
// cmdline returns plain text (the process argv), not a pprof binary profile,
|
||||
// so we confirm the HTTP fetch succeeds rather than parsing it as a profile.
|
||||
func TestPprofToolCmdline(t *testing.T) {
|
||||
server, aptlyCtx := pprofTestServer(t)
|
||||
defer server.Close()
|
||||
defer aptlyCtx.Shutdown()
|
||||
|
||||
url := fmt.Sprintf("%s/api/debug/pprof/cmdline", server.URL)
|
||||
|
||||
resp, err := server.Client().Get(url)
|
||||
if err != nil {
|
||||
t.Fatalf("GET %s: %v", url, err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Errorf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
t.Logf("cmdline endpoint status: %d", resp.StatusCode)
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/smira/flag"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// newPprofContext builds an aptly context and router from the given config file
|
||||
// path. It returns an error if context initialisation fails. The caller is
|
||||
// responsible for calling aptlyCtx.Shutdown() when done.
|
||||
func newPprofContext(configPath string) (*ctx.AptlyContext, http.Handler, error) {
|
||||
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
|
||||
flags.Bool("no-lock", false, "dummy")
|
||||
flags.Int("db-open-attempts", 3, "dummy")
|
||||
flags.String("config", configPath, "dummy")
|
||||
flags.String("architectures", "", "dummy")
|
||||
|
||||
aptlyCtx, err := ctx.NewContext(flags)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return aptlyCtx, Router(aptlyCtx), nil
|
||||
}
|
||||
|
||||
// PprofEnabledSuite tests pprof endpoints when EnablePprofEndpoint is true.
|
||||
// It uses a dedicated router built from a config that enables only the pprof
|
||||
// endpoint, ensuring no interaction with other optional features.
|
||||
type PprofEnabledSuite struct {
|
||||
configFile *os.File
|
||||
aptlyCtx *ctx.AptlyContext
|
||||
router http.Handler
|
||||
}
|
||||
|
||||
var _ = Suite(&PprofEnabledSuite{})
|
||||
|
||||
func (s *PprofEnabledSuite) SetUpSuite(c *C) {
|
||||
aptly.Version = "testVersion"
|
||||
|
||||
file, err := os.CreateTemp("", "aptly-pprof")
|
||||
c.Assert(err, IsNil)
|
||||
s.configFile = file
|
||||
|
||||
jsonString, err := json.Marshal(gin.H{
|
||||
"architectures": []string{},
|
||||
"enablePprofEndpoint": true,
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
_, err = file.Write(jsonString)
|
||||
c.Assert(err, IsNil)
|
||||
_ = file.Close()
|
||||
|
||||
aptlyCtx, router, err := newPprofContext(file.Name())
|
||||
c.Assert(err, IsNil)
|
||||
s.aptlyCtx = aptlyCtx
|
||||
s.router = router
|
||||
}
|
||||
|
||||
func (s *PprofEnabledSuite) TearDownSuite(c *C) {
|
||||
if s.configFile != nil {
|
||||
_ = os.Remove(s.configFile.Name())
|
||||
}
|
||||
if s.aptlyCtx != nil {
|
||||
s.aptlyCtx.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PprofEnabledSuite) request(method, url string) *httptest.ResponseRecorder {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.router.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
// TestPprofIndexReturns200 verifies the pprof index page is served when the
|
||||
// endpoint is enabled.
|
||||
func (s *PprofEnabledSuite) TestPprofIndexReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
c.Check(w.Header().Get("Content-Type"), Matches, "text/html.*")
|
||||
}
|
||||
|
||||
// TestPprofGoroutineReturns200 verifies the goroutine sub-profile is served.
|
||||
func (s *PprofEnabledSuite) TestPprofGoroutineReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/goroutine?debug=1")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// TestPprofHeapReturns200 verifies the heap sub-profile is served.
|
||||
func (s *PprofEnabledSuite) TestPprofHeapReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/heap?debug=1")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// TestPprofCmdlineReturns200 verifies the cmdline handler is served.
|
||||
func (s *PprofEnabledSuite) TestPprofCmdlineReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/cmdline")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// TestPprofSymbolReturns200 verifies the symbol lookup handler responds (GET
|
||||
// with no addresses returns 200 with a count of 0 symbols found).
|
||||
func (s *PprofEnabledSuite) TestPprofSymbolReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/symbol")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// TestPprofTraceReturns200 verifies the execution trace handler is wired
|
||||
// correctly. The minimum trace duration is 1 second, so this test blocks
|
||||
// briefly but confirms end-to-end handler registration.
|
||||
func (s *PprofEnabledSuite) TestPprofTraceReturns200(c *C) {
|
||||
w := s.request("GET", "/api/debug/pprof/trace?seconds=1")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
c.Check(w.Header().Get("Content-Type"), Equals, "application/octet-stream")
|
||||
}
|
||||
|
||||
// TestNonPprofRoutesUnaffectedWhenEnabled verifies that existing API routes
|
||||
// continue to work correctly when the pprof endpoint is enabled.
|
||||
func (s *PprofEnabledSuite) TestNonPprofRoutesUnaffectedWhenEnabled(c *C) {
|
||||
w := s.request("GET", "/api/version")
|
||||
c.Check(w.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// PprofDisabledSuite tests that pprof endpoints are NOT exposed when the config
|
||||
// option is off. It embeds APISuite, whose config does not set
|
||||
// enablePprofEndpoint, confirming the default state is safe. The inherited
|
||||
// APISuite tests also run here, verifying that normal API operation is
|
||||
// unaffected when pprof is disabled.
|
||||
type PprofDisabledSuite struct {
|
||||
APISuite
|
||||
}
|
||||
|
||||
var _ = Suite(&PprofDisabledSuite{})
|
||||
|
||||
// TestPprofIndexNotExposed verifies the pprof index is not reachable when
|
||||
// enablePprofEndpoint is false (the default).
|
||||
func (s *PprofDisabledSuite) TestPprofIndexNotExposed(c *C) {
|
||||
response, err := s.HTTPRequest("GET", "/api/debug/pprof/", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(response.Code, Equals, 404)
|
||||
}
|
||||
|
||||
// TestPprofGoroutineNotExposed verifies the goroutine profile is not reachable
|
||||
// when disabled.
|
||||
func (s *PprofDisabledSuite) TestPprofGoroutineNotExposed(c *C) {
|
||||
response, err := s.HTTPRequest("GET", "/api/debug/pprof/goroutine", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(response.Code, Equals, 404)
|
||||
}
|
||||
|
||||
// TestPprofHeapNotExposed verifies the heap profile is not reachable when
|
||||
// disabled.
|
||||
func (s *PprofDisabledSuite) TestPprofHeapNotExposed(c *C) {
|
||||
response, err := s.HTTPRequest("GET", "/api/debug/pprof/heap", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(response.Code, Equals, 404)
|
||||
}
|
||||
|
||||
// TestPprofCmdlineNotExposed verifies the cmdline endpoint is not reachable
|
||||
// when disabled.
|
||||
func (s *PprofDisabledSuite) TestPprofCmdlineNotExposed(c *C) {
|
||||
response, err := s.HTTPRequest("GET", "/api/debug/pprof/cmdline", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(response.Code, Equals, 404)
|
||||
}
|
||||
|
||||
// TestPprofTraceNotExposed verifies the trace endpoint is not reachable when
|
||||
// disabled.
|
||||
func (s *PprofDisabledSuite) TestPprofTraceNotExposed(c *C) {
|
||||
response, err := s.HTTPRequest("GET", "/api/debug/pprof/trace", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(response.Code, Equals, 404)
|
||||
}
|
||||
@@ -160,8 +160,6 @@ type publishedRepoCreateParams struct {
|
||||
Sources []sourceParams `binding:"required" json:"Sources"`
|
||||
// Distribution name, if missing Aptly would try to guess from sources
|
||||
Distribution string ` json:"Distribution" example:"bookworm"`
|
||||
// Value of Codename: field in published repository stanza, if missing equals Distribution
|
||||
Codename string ` json:"Codename" example:"bookworm"`
|
||||
// Value of Label: field in published repository stanza
|
||||
Label string ` json:"Label" example:""`
|
||||
// Value of Origin: field in published repository stanza
|
||||
@@ -356,7 +354,6 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
if b.ButAutomaticUpgrades != "" {
|
||||
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
|
||||
}
|
||||
published.Codename = b.Codename
|
||||
published.Label = b.Label
|
||||
|
||||
published.SkipContents = context.Config().SkipContentsPublishing
|
||||
@@ -387,9 +384,6 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||
@@ -581,9 +575,6 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
@@ -1251,9 +1242,6 @@ func apiPublishUpdate(c *gin.Context) {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||
|
||||
+1
-26
@@ -2,8 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
@@ -84,27 +82,6 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
MetricsCollectorRegistrar.Register(router)
|
||||
}
|
||||
|
||||
if c.Config().EnablePprofEndpoint {
|
||||
pprofMux := http.NewServeMux()
|
||||
pprofMux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
pprofMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
pprofMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
pprofMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
pprofMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
|
||||
router.Any("/api/debug/pprof/*any", func(c *gin.Context) {
|
||||
origPath := c.Request.URL.Path
|
||||
origRawPath := c.Request.URL.RawPath
|
||||
c.Request.URL.Path = strings.TrimPrefix(origPath, "/api")
|
||||
if origRawPath != "" {
|
||||
c.Request.URL.RawPath = strings.TrimPrefix(origRawPath, "/api")
|
||||
}
|
||||
pprofMux.ServeHTTP(c.Writer, c.Request)
|
||||
c.Request.URL.Path = origPath
|
||||
c.Request.URL.RawPath = origRawPath
|
||||
})
|
||||
}
|
||||
|
||||
if c.Config().ServeInAPIMode {
|
||||
router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots))
|
||||
router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode)
|
||||
@@ -145,9 +122,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
|
||||
{
|
||||
if c.Config().EnableMetricsEndpoint {
|
||||
metricsHandler := apiMetricsGet()
|
||||
api.GET("/metrics", metricsHandler)
|
||||
api.HEAD("/metrics", metricsHandler)
|
||||
api.GET("/metrics", apiMetricsGet())
|
||||
}
|
||||
api.GET("/version", apiVersion)
|
||||
api.GET("/storage", apiDiskFree)
|
||||
|
||||
+1
-11
@@ -36,9 +36,6 @@ func apiSnapshotsList(c *gin.Context) {
|
||||
err := collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
|
||||
err := collection.LoadComplete(snapshot)
|
||||
if err != nil {
|
||||
if err == database.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -803,10 +800,6 @@ func apiSnapshotsPull(c *gin.Context) {
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
err = taskCollectionFactory.SnapshotCollection().LoadComplete(freshToSnapshot)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
freshSourceSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(body.Source)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
@@ -880,7 +873,7 @@ func apiSnapshotsPull(c *gin.Context) {
|
||||
alreadySeen := map[string]bool{}
|
||||
|
||||
_ = destinationPackageList.ForEachIndexed(func(pkg *deb.Package) error {
|
||||
key := pkg.IndexArchitecture() + "_" + pkg.Name
|
||||
key := pkg.Architecture + "_" + pkg.Name
|
||||
_, seen := alreadySeen[key]
|
||||
|
||||
// If we haven't seen such name-architecture pair and were instructed to remove, remove it
|
||||
@@ -888,9 +881,6 @@ func apiSnapshotsPull(c *gin.Context) {
|
||||
// Remove all packages with the same name and architecture
|
||||
packageSearchResults := toPackageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false)
|
||||
for _, p := range packageSearchResults {
|
||||
if p.ArchitectureVariant != pkg.ArchitectureVariant {
|
||||
continue
|
||||
}
|
||||
toPackageList.Remove(p)
|
||||
removedPackages = append(removedPackages, p.String())
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
// Exercise the real pull entry point and persisted references, without package
|
||||
// files or mirrors. Architecture selection remains base-architecture selection.
|
||||
func TestSnapshotPullArchitectureVariant(t *testing.T) {
|
||||
normal := &deb.Package{Name: "pull-test", Version: "1", Architecture: "amd64"}
|
||||
variant := &deb.Package{Name: "pull-test", Version: "1", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
normal2 := &deb.Package{Name: "pull-test", Version: "2", Architecture: "amd64"}
|
||||
variant2 := &deb.Package{Name: "pull-test", Version: "2", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
cases := []struct {
|
||||
name string
|
||||
dest, source, want []*deb.Package
|
||||
noRemove, allMatches bool
|
||||
}{
|
||||
{"normal", nil, []*deb.Package{normal}, []*deb.Package{normal}, false, false},
|
||||
{"variant", nil, []*deb.Package{variant}, []*deb.Package{variant}, false, false},
|
||||
{"both", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, false, false},
|
||||
{"variant_into_normal", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, false, false},
|
||||
{"normal_into_variant", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, false, false},
|
||||
{"both_all_matches", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, false, true},
|
||||
{"both_no_remove", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, true, false},
|
||||
{"variant_into_normal_no_remove", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, true, false},
|
||||
{"normal_into_variant_no_remove", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, true, false},
|
||||
{"variant_into_normal_all_matches", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, false, true},
|
||||
{"normal_into_variant_all_matches", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, false, true},
|
||||
{"normal_replace", []*deb.Package{normal}, []*deb.Package{normal2}, []*deb.Package{normal2}, false, false},
|
||||
{"normal_no_remove", []*deb.Package{normal}, []*deb.Package{normal2}, []*deb.Package{normal, normal2}, true, false},
|
||||
{"normal_latest", nil, []*deb.Package{normal, normal2}, []*deb.Package{normal2}, false, false},
|
||||
{"normal_all_matches", nil, []*deb.Package{normal, normal2}, []*deb.Package{normal, normal2}, false, true},
|
||||
{"variant_replace_preserves_normal", []*deb.Package{normal, variant}, []*deb.Package{variant2}, []*deb.Package{normal, variant2}, false, false},
|
||||
{"latest_per_identity", nil, []*deb.Package{normal, normal2, variant, variant2}, []*deb.Package{normal2, variant2}, false, false},
|
||||
{"all_versions_both_identities", nil, []*deb.Package{normal, normal2, variant, variant2}, []*deb.Package{normal, normal2, variant, variant2}, false, true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
for _, mode := range []string{"inferred", "explicit"} {
|
||||
t.Run(tc.name+"/"+mode, func(t *testing.T) {
|
||||
savedConfig := utils.Config
|
||||
t.Cleanup(func() { utils.Config = savedConfig })
|
||||
dir := t.TempDir()
|
||||
configPath := filepath.Join(dir, "aptly.conf")
|
||||
configData, err := json.Marshal(map[string]interface{}{"rootDir": dir, "architectures": []string{}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = os.WriteFile(configPath, configData, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flags := flag.NewFlagSet("pull-test", flag.ContinueOnError)
|
||||
flags.String("config", configPath, "")
|
||||
// Empty destinations need explicit architecture; populated destinations
|
||||
// exercise automatic base-architecture discovery.
|
||||
arch := ""
|
||||
if len(tc.dest) == 0 || mode == "explicit" {
|
||||
arch = "amd64"
|
||||
}
|
||||
flags.String("architectures", arch, "")
|
||||
flags.Bool("no-lock", false, "")
|
||||
flags.Int("db-open-attempts", 1, "")
|
||||
flags.Bool("no-deps", true, "")
|
||||
flags.Bool("no-remove", tc.noRemove, "")
|
||||
flags.Bool("all-matches", tc.allMatches, "")
|
||||
flags.Bool("dry-run", false, "")
|
||||
testContext, err := ctx.NewContext(flags)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(testContext.Shutdown)
|
||||
factory := testContext.NewCollectionFactory()
|
||||
refs := func(packages []*deb.Package) *deb.PackageRefList {
|
||||
list := deb.NewPackageList()
|
||||
for _, p := range packages {
|
||||
if err := list.Add(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return deb.NewPackageRefListFromPackageList(list)
|
||||
}
|
||||
for _, p := range append(append([]*deb.Package{}, tc.dest...), tc.source...) {
|
||||
if err := factory.PackageCollection().Update(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for name, packages := range map[string][]*deb.Package{"base": tc.dest, "source": tc.source} {
|
||||
if err := factory.SnapshotCollection().Add(deb.NewSnapshotFromRefList(name, nil, refs(packages), "")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
body, err := json.Marshal(snapshotsPullParams{Source: "source", Destination: "result", Queries: []string{"pull-test"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
url := "/api/snapshots/base/pull?no-deps=1"
|
||||
if tc.noRemove {
|
||||
url += "&no-remove=1"
|
||||
}
|
||||
if tc.allMatches {
|
||||
url += "&all-matches=1"
|
||||
}
|
||||
req := httptest.NewRequest("POST", url, bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
response := httptest.NewRecorder()
|
||||
savedContext := context
|
||||
t.Cleanup(func() { context = savedContext })
|
||||
Router(testContext).ServeHTTP(response, req)
|
||||
if response.Code != 201 {
|
||||
t.Fatalf("pull status %d: %s", response.Code, response.Body.String())
|
||||
}
|
||||
snapshots := testContext.NewCollectionFactory().SnapshotCollection()
|
||||
result, err := snapshots.ByName("result")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = snapshots.LoadComplete(result); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, want := result.RefList().Strings(), refs(tc.want).Strings()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("persisted package identities:\n got %v\nwant %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/smira/flag"
|
||||
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
. "gopkg.in/check.v1"
|
||||
@@ -58,70 +51,3 @@ func (s *SnapshotsSuite) TestGetSnapshotsReturns500OnCorruptRefList(c *C) {
|
||||
c.Assert(response.Code, Equals, 500)
|
||||
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
|
||||
}
|
||||
|
||||
func (s *SnapshotsSuite) TestGetSnapshotsHandlesConcurrentDeletion(c *C) {
|
||||
collection := s.context.NewCollectionFactory().SnapshotCollection()
|
||||
snapshot := deb.NewSnapshotFromRefList("deleted-snapshot-list", nil, makePackageRefList(c), "")
|
||||
c.Assert(collection.Add(snapshot), IsNil)
|
||||
// Simulate concurrent deletion by dropping the ref key or entire snapshot record
|
||||
c.Assert(collection.Drop(snapshot), IsNil)
|
||||
|
||||
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(response.Code, Equals, 200)
|
||||
}
|
||||
|
||||
// Pull must load the destination references before inferring architectures or
|
||||
// preserving existing packages. Both fixtures are ordinary amd64 packages.
|
||||
func (*SnapshotsSuite) TestPullPreservesDestinationPackages(c *C) {
|
||||
savedConfig, savedContext := utils.Config, context
|
||||
defer func() { utils.Config, context = savedConfig, savedContext }()
|
||||
dir := c.MkDir()
|
||||
configPath := filepath.Join(dir, "aptly.conf")
|
||||
configData, err := json.Marshal(map[string]interface{}{"rootDir": dir, "architectures": []string{}})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(os.WriteFile(configPath, configData, 0600), IsNil)
|
||||
flags := flag.NewFlagSet("pull-test", flag.ContinueOnError)
|
||||
flags.String("config", configPath, "")
|
||||
flags.String("architectures", "", "")
|
||||
flags.Bool("no-lock", false, "")
|
||||
flags.Int("db-open-attempts", 1, "")
|
||||
testContext, err := ctx.NewContext(flags)
|
||||
c.Assert(err, IsNil)
|
||||
defer testContext.Shutdown()
|
||||
s := &APISuite{context: testContext, router: Router(testContext)}
|
||||
factory := s.context.NewCollectionFactory()
|
||||
for _, mode := range []string{"inferred", "explicit"} {
|
||||
a := &deb.Package{Name: "package-a", Version: "1", Architecture: "amd64"}
|
||||
b := &deb.Package{Name: "package-b", Version: "1", Architecture: "amd64"}
|
||||
for name, pkg := range map[string]*deb.Package{"base": a, "source": b} {
|
||||
c.Assert(factory.PackageCollection().Update(pkg), IsNil)
|
||||
list := deb.NewPackageList()
|
||||
c.Assert(list.Add(pkg), IsNil)
|
||||
snapshot := deb.NewSnapshotFromPackageList("pull-"+name+"-"+mode, nil, list, "")
|
||||
c.Assert(factory.SnapshotCollection().Add(snapshot), IsNil)
|
||||
}
|
||||
params := snapshotsPullParams{
|
||||
Source: "pull-source-" + mode,
|
||||
Destination: "pull-result-" + mode,
|
||||
Queries: []string{"package-b"},
|
||||
}
|
||||
if mode == "explicit" {
|
||||
params.Architectures = []string{"amd64"}
|
||||
}
|
||||
body, err := json.Marshal(params)
|
||||
c.Assert(err, IsNil)
|
||||
response, err := s.HTTPRequest("POST", "/api/snapshots/pull-base-"+mode+"/pull?no-remove=1&no-deps=1", bytes.NewReader(body))
|
||||
c.Assert(err, IsNil)
|
||||
if !c.Check(response.Code, Equals, 201, Commentf("%s: %s", mode, response.Body.String())) {
|
||||
continue
|
||||
}
|
||||
snapshots := s.context.NewCollectionFactory().SnapshotCollection()
|
||||
result, err := snapshots.ByName(params.Destination)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(snapshots.LoadComplete(result), IsNil)
|
||||
c.Check(result.RefList().Strings(), DeepEquals, []string{
|
||||
string(a.Key("")), string(b.Key("")),
|
||||
}, Commentf("%s: preserve A and add B", mode))
|
||||
}
|
||||
}
|
||||
+11
-241
@@ -9,9 +9,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aptly-dev/aptly/api"
|
||||
"github.com/aptly-dev/aptly/systemd/activation"
|
||||
@@ -20,214 +18,6 @@ import (
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
const (
|
||||
metricsReadHeaderTimeout = 5 * time.Second
|
||||
httpShutdownTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type apiHTTPServer struct {
|
||||
name string
|
||||
server *http.Server
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
type apiServeResult struct {
|
||||
name string
|
||||
err error
|
||||
}
|
||||
|
||||
func metricsOnlyHandler(apiHandler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.EscapedPath() != "/metrics" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
request := r.Clone(r.Context())
|
||||
requestURL := *r.URL
|
||||
requestURL.Path = "/api/metrics"
|
||||
requestURL.RawPath = ""
|
||||
request.URL = &requestURL
|
||||
request.RequestURI = requestURL.RequestURI()
|
||||
apiHandler.ServeHTTP(w, request)
|
||||
})
|
||||
}
|
||||
|
||||
func validateMetricsListener(address string, metricsEnabled bool) error {
|
||||
if address != "" && !metricsEnabled {
|
||||
return errors.New("-metrics-listen requires enableMetricsEndpoint to be true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func selectActivatedAPIListener(listeners []net.Listener) (net.Listener, error) {
|
||||
switch len(listeners) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
if listeners[0] == nil {
|
||||
return nil, errors.New("systemd file descriptor is not a supported network listener")
|
||||
}
|
||||
return listeners[0], nil
|
||||
default:
|
||||
return nil, fmt.Errorf("got %d listeners from systemd; only one API listener is supported", len(listeners))
|
||||
}
|
||||
}
|
||||
|
||||
func listenForAPI(address string) (net.Listener, error) {
|
||||
listenURL, err := url.Parse(address)
|
||||
if err == nil && listenURL.Scheme == "unix" {
|
||||
file := listenURL.Path
|
||||
_ = os.Remove(file)
|
||||
|
||||
listener, err := net.Listen("unix", file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen on API Unix socket %q: %w", file, err)
|
||||
}
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to listen on API address %q: %w", address, err)
|
||||
}
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func shutdownHTTPServers(servers []apiHTTPServer, timeout time.Duration) {
|
||||
shutdownContext, cancel := stdcontext.WithTimeout(stdcontext.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
failed := make(chan *http.Server, len(servers))
|
||||
var waitGroup sync.WaitGroup
|
||||
for _, httpServer := range servers {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
if err := httpServer.server.Shutdown(shutdownContext); err != nil {
|
||||
failed <- httpServer.server
|
||||
}
|
||||
}()
|
||||
}
|
||||
waitGroup.Wait()
|
||||
close(failed)
|
||||
|
||||
for server := range failed {
|
||||
_ = server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func serveHTTPServers(servers []apiHTTPServer, sigchan <-chan os.Signal, restoreSignals func(), waitForTasks func(), shutdownTimeout time.Duration) error {
|
||||
results := make(chan apiServeResult, len(servers))
|
||||
for _, httpServer := range servers {
|
||||
go func() {
|
||||
results <- apiServeResult{
|
||||
name: httpServer.name,
|
||||
err: httpServer.server.Serve(httpServer.listener),
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
serveResults := make([]apiServeResult, 0, len(servers))
|
||||
shutdownFromSignal := false
|
||||
select {
|
||||
case result := <-results:
|
||||
serveResults = append(serveResults, result)
|
||||
case <-sigchan:
|
||||
shutdownFromSignal = true
|
||||
}
|
||||
|
||||
if restoreSignals != nil {
|
||||
restoreSignals()
|
||||
}
|
||||
|
||||
if shutdownFromSignal {
|
||||
fmt.Printf("\nShutdown signal received, stopping HTTP servers...\n")
|
||||
} else {
|
||||
fmt.Printf("\nHTTP server stopped unexpectedly, stopping sibling server...\n")
|
||||
}
|
||||
shutdownHTTPServers(servers, shutdownTimeout)
|
||||
|
||||
for len(serveResults) < len(servers) {
|
||||
serveResults = append(serveResults, <-results)
|
||||
}
|
||||
|
||||
fmt.Printf("Waiting for background tasks...\n")
|
||||
waitForTasks()
|
||||
|
||||
if shutdownFromSignal {
|
||||
for _, result := range serveResults {
|
||||
if result.err != nil && !errors.Is(result.err, http.ErrServerClosed) {
|
||||
return fmt.Errorf("%s server failed during shutdown: %w", result.name, result.err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
result := serveResults[0]
|
||||
if result.err == nil {
|
||||
return fmt.Errorf("%s server stopped unexpectedly", result.name)
|
||||
}
|
||||
return fmt.Errorf("%s server stopped unexpectedly: %w", result.name, result.err)
|
||||
}
|
||||
|
||||
func serveAPIWithMetrics(apiListener net.Listener, activated bool, apiAddress, metricsAddress string) error {
|
||||
var err error
|
||||
if apiListener == nil {
|
||||
apiListener, err = listenForAPI(apiAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer func() { _ = apiListener.Close() }()
|
||||
|
||||
metricsListener, err := net.Listen("tcp", metricsAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on metrics address %q: %w", metricsAddress, err)
|
||||
}
|
||||
defer func() { _ = metricsListener.Close() }()
|
||||
|
||||
apiHandler := api.Router(context)
|
||||
servers := []apiHTTPServer{
|
||||
{
|
||||
name: "API",
|
||||
server: &http.Server{Handler: apiHandler},
|
||||
listener: apiListener,
|
||||
},
|
||||
{
|
||||
name: "metrics",
|
||||
server: &http.Server{
|
||||
Handler: metricsOnlyHandler(apiHandler),
|
||||
ReadHeaderTimeout: metricsReadHeaderTimeout,
|
||||
},
|
||||
listener: metricsListener,
|
||||
},
|
||||
}
|
||||
|
||||
if activated {
|
||||
fmt.Printf("\nTaking over API web server at: %s (press Ctrl+C to quit)...\n", apiListener.Addr().String())
|
||||
} else {
|
||||
fmt.Printf("\nStarting API web server at: %s (press Ctrl+C to quit)...\n", apiListener.Addr().String())
|
||||
}
|
||||
fmt.Printf("Starting metrics web server at: %s...\n", metricsListener.Addr().String())
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(sigchan)
|
||||
|
||||
restoreSignals := func() {
|
||||
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
return serveHTTPServers(servers, sigchan, restoreSignals, context.TaskList().Wait, httpShutdownTimeout)
|
||||
}
|
||||
|
||||
func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
||||
var (
|
||||
err error
|
||||
@@ -249,37 +39,24 @@ func aptlyAPIServe(cmd *commander.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
metricsListen := context.Flags().Lookup("metrics-listen").Value.String()
|
||||
if err = validateMetricsListener(metricsListen, context.Config().EnableMetricsEndpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Try to recycle systemd fds for listening
|
||||
listeners, err := activation.Listeners(true)
|
||||
if err == nil {
|
||||
listener, listenerErr := selectActivatedAPIListener(listeners)
|
||||
if listenerErr != nil {
|
||||
return listenerErr
|
||||
}
|
||||
if listener != nil && metricsListen != "" {
|
||||
return serveAPIWithMetrics(listener, true, "", metricsListen)
|
||||
}
|
||||
if listener != nil {
|
||||
defer func() { _ = listener.Close() }()
|
||||
fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String())
|
||||
err = http.Serve(listener, api.Router(context))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to serve: %s", err)
|
||||
}
|
||||
return nil
|
||||
if len(listeners) > 1 {
|
||||
panic("Got more than 1 listener from systemd. This is currently not supported!")
|
||||
}
|
||||
if err == nil && len(listeners) == 1 {
|
||||
listener := listeners[0]
|
||||
defer func() { _ = listener.Close() }()
|
||||
fmt.Printf("\nTaking over web server at: %s (press Ctrl+C to quit)...\n", listener.Addr().String())
|
||||
err = http.Serve(listener, api.Router(context))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to serve: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If there are none: use the listen argument.
|
||||
listen := context.Flags().Lookup("listen").Value.String()
|
||||
if metricsListen != "" {
|
||||
return serveAPIWithMetrics(nil, false, listen, metricsListen)
|
||||
}
|
||||
fmt.Printf("\nStarting web server at: %s (press Ctrl+C to quit)...\n", listen)
|
||||
|
||||
server := http.Server{Handler: api.Router(context)}
|
||||
@@ -331,22 +108,15 @@ or Unix domain socket. When using a socket, Aptly will fully manage the socket
|
||||
file. This command also supports taking over from a systemd file descriptors to
|
||||
enable systemd socket activation.
|
||||
|
||||
An optional metrics-only TCP listener can be enabled with -metrics-listen. It
|
||||
serves GET and HEAD requests at /metrics and requires enableMetricsEndpoint in
|
||||
the aptly configuration. This listener uses plain HTTP; provide access control
|
||||
and TLS through network policy or a proxy when required.
|
||||
|
||||
Example:
|
||||
|
||||
$ aptly api serve -listen=:8080
|
||||
$ aptly api serve -listen=unix:///tmp/aptly.sock
|
||||
$ aptly api serve -listen=:8080 -metrics-listen=127.0.0.1:9090
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("aptly-serve", flag.ExitOnError),
|
||||
}
|
||||
|
||||
cmd.Flag.String("listen", ":8080", "host:port for HTTP listening or unix://path to listen on a Unix domain socket")
|
||||
cmd.Flag.String("metrics-listen", "", "host:port for optional metrics-only HTTP listening (requires enableMetricsEndpoint)")
|
||||
cmd.Flag.Bool("no-lock", false, "don't lock the database")
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
check.TestingT(t)
|
||||
}
|
||||
|
||||
func TestMetricsOnlyHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
target string
|
||||
wantStatus int
|
||||
wantForwarded bool
|
||||
wantQuery string
|
||||
wantRequestURI string
|
||||
wantAllowMethods string
|
||||
}{
|
||||
{
|
||||
name: "get",
|
||||
method: http.MethodGet,
|
||||
target: "/metrics?_async=true&value=a%2Bb",
|
||||
wantStatus: http.StatusNoContent,
|
||||
wantForwarded: true,
|
||||
wantQuery: "_async=true&value=a%2Bb",
|
||||
wantRequestURI: "/api/metrics?_async=true&value=a%2Bb",
|
||||
},
|
||||
{
|
||||
name: "head",
|
||||
method: http.MethodHead,
|
||||
target: "/metrics",
|
||||
wantStatus: http.StatusNoContent,
|
||||
wantForwarded: true,
|
||||
wantRequestURI: "/api/metrics",
|
||||
},
|
||||
{
|
||||
name: "api metrics path",
|
||||
method: http.MethodGet,
|
||||
target: "/api/metrics",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "trailing slash",
|
||||
method: http.MethodGet,
|
||||
target: "/metrics/",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "encoded alias",
|
||||
method: http.MethodGet,
|
||||
target: "/met%72ics",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "case variant",
|
||||
method: http.MethodGet,
|
||||
target: "/METRICS",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "unsupported method",
|
||||
method: http.MethodPost,
|
||||
target: "/metrics",
|
||||
wantStatus: http.StatusMethodNotAllowed,
|
||||
wantAllowMethods: "GET, HEAD",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
forwarded := false
|
||||
var forwardedMethod, forwardedPath, forwardedQuery, forwardedRequestURI string
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
forwarded = true
|
||||
forwardedMethod = r.Method
|
||||
forwardedPath = r.URL.Path
|
||||
forwardedQuery = r.URL.RawQuery
|
||||
forwardedRequestURI = r.RequestURI
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
request := httptest.NewRequest(test.method, test.target, nil)
|
||||
originalPath := request.URL.Path
|
||||
originalRawPath := request.URL.RawPath
|
||||
originalRequestURI := request.RequestURI
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
metricsOnlyHandler(apiHandler).ServeHTTP(response, request)
|
||||
|
||||
if response.Code != test.wantStatus {
|
||||
t.Fatalf("status = %d, want %d", response.Code, test.wantStatus)
|
||||
}
|
||||
if forwarded != test.wantForwarded {
|
||||
t.Fatalf("forwarded = %t, want %t", forwarded, test.wantForwarded)
|
||||
}
|
||||
if got := response.Header().Get("Allow"); got != test.wantAllowMethods {
|
||||
t.Errorf("Allow = %q, want %q", got, test.wantAllowMethods)
|
||||
}
|
||||
if request.URL.Path != originalPath || request.URL.RawPath != originalRawPath || request.RequestURI != originalRequestURI {
|
||||
t.Errorf("original request was mutated")
|
||||
}
|
||||
|
||||
if test.wantForwarded {
|
||||
if forwardedMethod != test.method {
|
||||
t.Errorf("forwarded method = %q, want %q", forwardedMethod, test.method)
|
||||
}
|
||||
if forwardedPath != "/api/metrics" {
|
||||
t.Errorf("forwarded path = %q, want /api/metrics", forwardedPath)
|
||||
}
|
||||
if forwardedQuery != test.wantQuery {
|
||||
t.Errorf("forwarded query = %q, want %q", forwardedQuery, test.wantQuery)
|
||||
}
|
||||
if forwardedRequestURI != test.wantRequestURI {
|
||||
t.Errorf("forwarded request URI = %q, want %q", forwardedRequestURI, test.wantRequestURI)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMetricsListener(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
address string
|
||||
metricsEnabled bool
|
||||
wantError bool
|
||||
}{
|
||||
{name: "disabled listener", metricsEnabled: false},
|
||||
{name: "existing API metrics only", metricsEnabled: true},
|
||||
{name: "enabled listener", address: "127.0.0.1:9090", metricsEnabled: true},
|
||||
{name: "listener requires metrics", address: "127.0.0.1:9090", metricsEnabled: false, wantError: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := validateMetricsListener(test.address, test.metricsEnabled)
|
||||
if (err != nil) != test.wantError {
|
||||
t.Fatalf("error = %v, wantError %t", err, test.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectActivatedAPIListener(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = listener.Close() })
|
||||
|
||||
selected, err := selectActivatedAPIListener(nil)
|
||||
if err != nil || selected != nil {
|
||||
t.Fatalf("no listeners: selected = %v, error = %v", selected, err)
|
||||
}
|
||||
|
||||
selected, err = selectActivatedAPIListener([]net.Listener{listener})
|
||||
if err != nil || selected != listener {
|
||||
t.Fatalf("one listener: selected = %v, error = %v", selected, err)
|
||||
}
|
||||
|
||||
if _, err = selectActivatedAPIListener([]net.Listener{nil}); err == nil {
|
||||
t.Fatal("nil listener did not return an error")
|
||||
}
|
||||
if _, err = selectActivatedAPIListener([]net.Listener{listener, listener}); err == nil {
|
||||
t.Fatal("multiple listeners did not return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeAPIWithMetricsClosesAPIListenerOnMetricsBindFailure(t *testing.T) {
|
||||
apiListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = apiListener.Close() })
|
||||
apiAddress := apiListener.Addr().String()
|
||||
|
||||
occupiedMetricsListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
_ = apiListener.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = occupiedMetricsListener.Close() }()
|
||||
|
||||
err = serveAPIWithMetrics(apiListener, false, "", occupiedMetricsListener.Addr().String())
|
||||
if err == nil || !strings.Contains(err.Error(), "failed to listen on metrics address") {
|
||||
t.Fatalf("error = %v, want metrics bind failure", err)
|
||||
}
|
||||
|
||||
connection, dialErr := net.DialTimeout("tcp", apiAddress, 100*time.Millisecond)
|
||||
if dialErr == nil {
|
||||
_ = connection.Close()
|
||||
t.Fatal("API listener still accepts connections after metrics bind failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShutdownHTTPServersForcesCloseAfterTimeout(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requestStarted := make(chan struct{})
|
||||
releaseRequest := make(chan struct{})
|
||||
var releaseOnce sync.Once
|
||||
t.Cleanup(func() {
|
||||
releaseOnce.Do(func() { close(releaseRequest) })
|
||||
})
|
||||
|
||||
server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
close(requestStarted)
|
||||
<-releaseRequest
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})}
|
||||
serveResult := make(chan error, 1)
|
||||
go func() { serveResult <- server.Serve(listener) }()
|
||||
|
||||
clientResult := make(chan error, 1)
|
||||
go func() {
|
||||
response, requestErr := http.Get("http://" + listener.Addr().String())
|
||||
if response != nil {
|
||||
_ = response.Body.Close()
|
||||
}
|
||||
clientResult <- requestErr
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-requestStarted:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("request did not reach server")
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
shutdownHTTPServers([]apiHTTPServer{{name: "test", server: server, listener: listener}}, 50*time.Millisecond)
|
||||
if elapsed := time.Since(started); elapsed < 40*time.Millisecond {
|
||||
t.Fatalf("shutdown closed an active request immediately after %s", elapsed)
|
||||
}
|
||||
if elapsed := time.Since(started); elapsed > time.Second {
|
||||
t.Fatalf("forced shutdown took %s", elapsed)
|
||||
}
|
||||
|
||||
select {
|
||||
case requestErr := <-clientResult:
|
||||
if requestErr == nil {
|
||||
t.Error("client request unexpectedly completed during forced shutdown")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("client connection was not closed")
|
||||
}
|
||||
|
||||
select {
|
||||
case serveErr := <-serveResult:
|
||||
if serveErr != http.ErrServerClosed {
|
||||
t.Errorf("Serve error = %v, want %v", serveErr, http.ErrServerClosed)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Serve did not return")
|
||||
}
|
||||
|
||||
releaseOnce.Do(func() { close(releaseRequest) })
|
||||
}
|
||||
|
||||
func TestServeHTTPServersStopsSiblingOnFailure(t *testing.T) {
|
||||
apiListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metricsListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
_ = apiListener.Close()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
servers := []apiHTTPServer{
|
||||
{name: "API", server: &http.Server{Handler: handler}, listener: apiListener},
|
||||
{name: "metrics", server: &http.Server{Handler: handler}, listener: metricsListener},
|
||||
}
|
||||
for _, server := range servers {
|
||||
server := server
|
||||
t.Cleanup(func() {
|
||||
_ = server.server.Close()
|
||||
_ = server.listener.Close()
|
||||
})
|
||||
}
|
||||
|
||||
sigchan := make(chan os.Signal)
|
||||
signalsRestored := make(chan struct{})
|
||||
tasksWaited := make(chan struct{})
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
result <- serveHTTPServers(
|
||||
servers,
|
||||
sigchan,
|
||||
func() { close(signalsRestored) },
|
||||
func() { close(tasksWaited) },
|
||||
time.Second,
|
||||
)
|
||||
}()
|
||||
|
||||
waitForHTTP(t, apiListener.Addr().String())
|
||||
waitForHTTP(t, metricsListener.Addr().String())
|
||||
if err = apiListener.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case err = <-result:
|
||||
if err == nil || !strings.Contains(err.Error(), "API server stopped unexpectedly") {
|
||||
t.Fatalf("error = %v, want unexpected API server failure", err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("server coordinator did not return")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-signalsRestored:
|
||||
default:
|
||||
t.Error("signal handling was not restored")
|
||||
}
|
||||
select {
|
||||
case <-tasksWaited:
|
||||
default:
|
||||
t.Error("background tasks were not awaited")
|
||||
}
|
||||
|
||||
connection, dialErr := net.DialTimeout("tcp", metricsListener.Addr().String(), 100*time.Millisecond)
|
||||
if dialErr == nil {
|
||||
_ = connection.Close()
|
||||
t.Error("metrics sibling still accepts connections")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeHTTPServersShutsDownOnSignal(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})}
|
||||
t.Cleanup(func() {
|
||||
_ = server.Close()
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signalsRestored := make(chan struct{})
|
||||
tasksWaitedAfterShutdown := make(chan bool, 1)
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
result <- serveHTTPServers(
|
||||
[]apiHTTPServer{{name: "API", server: server, listener: listener}},
|
||||
sigchan,
|
||||
func() { close(signalsRestored) },
|
||||
func() {
|
||||
connection, dialErr := net.DialTimeout("tcp", listener.Addr().String(), 100*time.Millisecond)
|
||||
if dialErr == nil {
|
||||
_ = connection.Close()
|
||||
}
|
||||
tasksWaitedAfterShutdown <- dialErr != nil
|
||||
},
|
||||
time.Second,
|
||||
)
|
||||
}()
|
||||
|
||||
waitForHTTP(t, listener.Addr().String())
|
||||
sigchan <- syscall.SIGTERM
|
||||
|
||||
select {
|
||||
case err = <-result:
|
||||
if err != nil {
|
||||
t.Fatalf("signal shutdown returned error: %v", err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("server coordinator did not return")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-signalsRestored:
|
||||
default:
|
||||
t.Error("signal handling was not restored")
|
||||
}
|
||||
select {
|
||||
case listenerClosed := <-tasksWaitedAfterShutdown:
|
||||
if !listenerClosed {
|
||||
t.Error("background tasks were awaited before HTTP shutdown")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Error("background tasks were not awaited")
|
||||
}
|
||||
}
|
||||
|
||||
func waitForHTTP(t *testing.T, address string) {
|
||||
t.Helper()
|
||||
client := &http.Client{Timeout: 100 * time.Millisecond}
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
response, err := client.Get("http://" + address)
|
||||
if err == nil {
|
||||
_, _ = io.Copy(io.Discard, response.Body)
|
||||
_ = response.Body.Close()
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("HTTP server at %s did not become ready", address)
|
||||
}
|
||||
+3
-24
@@ -23,14 +23,6 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
|
||||
name := args[0]
|
||||
|
||||
maxVersions := context.Flags().Lookup("max-versions").Value.Get().(int)
|
||||
if maxVersions < 0 {
|
||||
return fmt.Errorf("unable to update: -max-versions should be a positive number")
|
||||
}
|
||||
if maxVersions == 0 && context.Flags().Lookup("latest").Value.Get().(bool) {
|
||||
maxVersions = 1
|
||||
}
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
repo, err := collectionFactory.RemoteRepoCollection().ByName(name)
|
||||
if err != nil {
|
||||
@@ -98,29 +90,17 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
||||
context.Progress().Printf("Packages filtered: %d -> %d.\n", oldLen, newLen)
|
||||
}
|
||||
|
||||
if maxVersions > 0 {
|
||||
var oldLen, newLen int
|
||||
oldLen, newLen, err = repo.FilterLatest(maxVersions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
}
|
||||
versionWord := "versions"
|
||||
if maxVersions == 1 {
|
||||
versionWord = "version"
|
||||
}
|
||||
context.Progress().Printf("Keeping at most %d %s of each package: %d -> %d.\n", maxVersions, versionWord, oldLen, newLen)
|
||||
}
|
||||
|
||||
var (
|
||||
downloadSize int64
|
||||
queue []deb.PackageDownloadTask
|
||||
)
|
||||
|
||||
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
|
||||
latestOnly := context.Flags().Lookup("latest").Value.Get().(bool)
|
||||
|
||||
context.Progress().Printf("Building download queue...\n")
|
||||
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
||||
collectionFactory.ChecksumCollection(nil), skipExistingPackages)
|
||||
collectionFactory.ChecksumCollection(nil), skipExistingPackages, latestOnly)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update: %s", err)
|
||||
@@ -322,8 +302,7 @@ Example:
|
||||
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
|
||||
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture), same as -max-versions=1")
|
||||
cmd.Flag.Int("max-versions", 0, "download only the N most recent versions of each package (per architecture), 0 means all versions")
|
||||
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture)")
|
||||
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
||||
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
|
||||
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
||||
|
||||
@@ -178,9 +178,6 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
||||
context.Progress().ColoredPrintf("@rWARNING@|: force overwrite mode enabled, aptly might corrupt other published repositories sharing the same package pool.\n")
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
|
||||
@@ -111,9 +111,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
|
||||
@@ -80,9 +80,6 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
||||
published.Version = context.Flags().Lookup("version").Value.String()
|
||||
}
|
||||
|
||||
// Which hash algorithms get Acquire-By-Hash entries. Each one is a full
|
||||
// set of index copies per publish, which is felt most on S3-like storage.
|
||||
published.AcquireByHashAlgorithms = context.Config().AcquireByHashAlgorithms
|
||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to publish: %s", err)
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/smira/flag"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type RepoAddVariantSuite struct{}
|
||||
|
||||
var _ = check.Suite(&RepoAddVariantSuite{})
|
||||
|
||||
// Every fixture has the same uninformative basename. Architecture-Variant is
|
||||
// supplied exclusively by control metadata, and rebuilt packages change payload.
|
||||
func (*RepoAddVariantSuite) TestRepoAddArchitectureVariant(c *check.C) {
|
||||
builder, err := exec.LookPath("dpkg-deb")
|
||||
c.Assert(err, check.IsNil, check.Commentf("these real .deb import tests require dpkg-deb"))
|
||||
type fixture struct {
|
||||
path string
|
||||
pkg *deb.Package
|
||||
data []byte
|
||||
}
|
||||
fixtures := map[string]fixture{}
|
||||
for _, spec := range []struct{ id, variant, version, payload string }{
|
||||
{"normal", "", "1", "original"}, {"variant", "amd64v3", "1", "original"},
|
||||
{"normal-rebuilt", "", "1", "rebuilt"}, {"variant-rebuilt", "amd64v3", "1", "rebuilt"},
|
||||
{"normal-v2", "", "2", "new version"}, {"variant-v2", "amd64v3", "2", "new version"},
|
||||
} {
|
||||
dir := c.MkDir()
|
||||
root := filepath.Join(dir, "root")
|
||||
c.Assert(os.MkdirAll(filepath.Join(root, "DEBIAN"), 0755), check.IsNil)
|
||||
control := fmt.Sprintf("Package: import-test\nVersion: %s\nArchitecture: amd64\nMaintainer: Test <test@example.invalid>\nDescription: tiny import fixture\n", spec.version)
|
||||
if spec.variant != "" {
|
||||
control += "Architecture-Variant: " + spec.variant + "\n"
|
||||
}
|
||||
c.Assert(os.WriteFile(filepath.Join(root, "DEBIAN", "control"), []byte(control), 0644), check.IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(root, "payload"), []byte(spec.payload), 0644), check.IsNil)
|
||||
path := filepath.Join(dir, "payload.deb")
|
||||
output, err := exec.Command(builder, "--build", "--root-owner-group", root, path).CombinedOutput()
|
||||
c.Assert(err, check.IsNil, check.Commentf("build fixture: %s", output))
|
||||
checksums, err := utils.ChecksumsForFile(path)
|
||||
c.Assert(err, check.IsNil)
|
||||
pkg := &deb.Package{Name: "import-test", Version: spec.version, Architecture: "amd64", ArchitectureVariant: spec.variant, V06Plus: true}
|
||||
pkg.UpdateFiles(deb.PackageFiles{{Filename: "payload.deb", Checksums: checksums}})
|
||||
data, err := os.ReadFile(path)
|
||||
c.Assert(err, check.IsNil)
|
||||
fixtures[spec.id] = fixture{path, pkg, data}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
initial []string
|
||||
incoming string
|
||||
force, reject bool
|
||||
want []string
|
||||
}{
|
||||
{"normal_empty", nil, "normal", false, false, []string{"normal"}},
|
||||
{"variant_empty", nil, "variant", false, false, []string{"variant"}},
|
||||
{"normal_then_variant", []string{"normal"}, "variant", false, false, []string{"normal", "variant"}},
|
||||
{"variant_then_normal", []string{"variant"}, "normal", false, false, []string{"normal", "variant"}},
|
||||
{"identical_normal", []string{"normal", "variant"}, "normal", false, false, []string{"normal", "variant"}},
|
||||
{"identical_variant", []string{"normal", "variant"}, "variant", false, false, []string{"normal", "variant"}},
|
||||
{"conflicting_normal_without_force", []string{"normal", "variant"}, "normal-rebuilt", false, true, []string{"normal", "variant"}},
|
||||
{"conflicting_variant_without_force", []string{"normal", "variant"}, "variant-rebuilt", false, true, []string{"normal", "variant"}},
|
||||
{"force_normal_preserves_variant", []string{"normal", "variant"}, "normal-rebuilt", true, false, []string{"normal-rebuilt", "variant"}},
|
||||
{"force_variant_preserves_normal", []string{"normal", "variant"}, "variant-rebuilt", true, false, []string{"normal", "variant-rebuilt"}},
|
||||
{"force_identical_normal", []string{"normal", "variant"}, "normal", true, false, []string{"normal", "variant"}},
|
||||
{"force_identical_variant", []string{"normal", "variant"}, "variant", true, false, []string{"normal", "variant"}},
|
||||
{"force_new_variant_family", []string{"normal"}, "variant", true, false, []string{"normal", "variant"}},
|
||||
{"force_new_normal_family", []string{"variant"}, "normal", true, false, []string{"normal", "variant"}},
|
||||
{"force_normal_only", []string{"normal"}, "normal-rebuilt", true, false, []string{"normal-rebuilt"}},
|
||||
{"force_variant_only", []string{"variant"}, "variant-rebuilt", true, false, []string{"variant-rebuilt"}},
|
||||
{"force_normal_new_version", []string{"normal", "variant"}, "normal-v2", true, false, []string{"normal", "variant", "normal-v2"}},
|
||||
{"force_variant_new_version", []string{"normal", "variant"}, "variant-v2", true, false, []string{"normal", "variant", "variant-v2"}},
|
||||
} {
|
||||
c.Logf("case: %s", tc.name)
|
||||
func() {
|
||||
savedConfig, savedContext := utils.Config, context
|
||||
defer func() { utils.Config, context = savedConfig, savedContext }()
|
||||
dir := c.MkDir()
|
||||
configPath := filepath.Join(dir, "aptly.conf")
|
||||
config, err := json.Marshal(map[string]interface{}{"rootDir": dir, "gpgProvider": "internal"})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(os.WriteFile(configPath, config, 0600), check.IsNil)
|
||||
flags := flag.NewFlagSet("repo-add-test", flag.ContinueOnError)
|
||||
flags.String("config", configPath, "")
|
||||
flags.Bool("no-lock", false, "")
|
||||
flags.Int("db-open-attempts", 1, "")
|
||||
flags.Bool("force-replace", false, "")
|
||||
flags.Bool("remove-files", false, "")
|
||||
testContext, err := ctx.NewContext(flags)
|
||||
c.Assert(err, check.IsNil)
|
||||
context = testContext
|
||||
defer testContext.Shutdown()
|
||||
factory := testContext.NewCollectionFactory()
|
||||
c.Assert(factory.LocalRepoCollection().Add(deb.NewLocalRepo("test", "")), check.IsNil)
|
||||
add := func(id string) error { return aptlyRepoAdd(makeCmdRepoAdd(), []string{"test", fixtures[id].path}) }
|
||||
for _, id := range tc.initial {
|
||||
c.Assert(add(id), check.IsNil)
|
||||
}
|
||||
c.Assert(flags.Set("force-replace", fmt.Sprint(tc.force)), check.IsNil)
|
||||
err = add(tc.incoming)
|
||||
c.Assert(err != nil, check.Equals, tc.reject, check.Commentf("case %s: repo add error: %v", tc.name, err))
|
||||
// Reload persisted references and metadata rather than checking an in-memory list.
|
||||
factory = testContext.NewCollectionFactory()
|
||||
repo, err := factory.LocalRepoCollection().ByName("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(factory.LocalRepoCollection().LoadComplete(repo), check.IsNil)
|
||||
got := map[string]string{}
|
||||
for _, key := range repo.RefList().Refs {
|
||||
p, err := factory.PackageCollection().ByKey(key)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Check(p.Architecture, check.Equals, "amd64", check.Commentf("case %s", tc.name))
|
||||
c.Assert(p.Files(), check.HasLen, 1)
|
||||
got[string(p.ShortKey(""))] = p.Files()[0].Checksums.SHA256
|
||||
}
|
||||
want := map[string]string{}
|
||||
for _, id := range tc.want {
|
||||
p := fixtures[id].pkg
|
||||
want[string(p.ShortKey(""))] = p.Files()[0].Checksums.SHA256
|
||||
}
|
||||
c.Check(got, check.DeepEquals, want, check.Commentf("case %s: persisted package identities", tc.name))
|
||||
// Import stores files and full DB records before resolving repo conflicts.
|
||||
// Neither rejection nor replacement should corrupt/remove these pool blobs.
|
||||
for _, id := range append(append([]string{}, tc.initial...), tc.incoming) {
|
||||
f := fixtures[id]
|
||||
p, err := factory.PackageCollection().ByKey(f.pkg.Key(""))
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Check(p.ArchitectureVariant, check.Equals, f.pkg.ArchitectureVariant, check.Commentf("case %s", tc.name))
|
||||
stream, err := testContext.PackagePool().Open(p.Files()[0].PoolPath)
|
||||
c.Assert(err, check.IsNil)
|
||||
data, err := io.ReadAll(stream)
|
||||
closeErr := stream.Close()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(closeErr, check.IsNil)
|
||||
c.Check(data, check.DeepEquals, f.data, check.Commentf("case %s: pool content for %s", tc.name, id))
|
||||
_, err = os.Stat(f.path)
|
||||
c.Check(err, check.IsNil, check.Commentf("case %s: input file %s", tc.name, f.path))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -60,10 +60,10 @@ func aptlySnapshotDiff(cmd *commander.Command, args []string) error {
|
||||
verA = "-"
|
||||
verB = pdiff.Right.Version
|
||||
pkg = pdiff.Right.Name
|
||||
arch = pdiff.Right.IndexArchitecture()
|
||||
arch = pdiff.Right.Architecture
|
||||
} else {
|
||||
pkg = pdiff.Left.Name
|
||||
arch = pdiff.Left.IndexArchitecture()
|
||||
arch = pdiff.Left.Architecture
|
||||
verA = pdiff.Left.Version
|
||||
if pdiff.Right == nil {
|
||||
verB = "-"
|
||||
|
||||
@@ -117,7 +117,7 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
alreadySeen := map[string]bool{}
|
||||
|
||||
_ = result.ForEachIndexed(func(pkg *deb.Package) error {
|
||||
key := pkg.IndexArchitecture() + "_" + pkg.Name
|
||||
key := pkg.Architecture + "_" + pkg.Name
|
||||
_, seen := alreadySeen[key]
|
||||
|
||||
// If we haven't seen such name-architecture pair and were instructed to remove, remove it
|
||||
@@ -125,9 +125,6 @@ func aptlySnapshotPull(cmd *commander.Command, args []string) error {
|
||||
// Remove all packages with the same name and architecture
|
||||
pS := packageList.Search(deb.Dependency{Architecture: pkg.Architecture, Pkg: pkg.Name}, true, false)
|
||||
for _, p := range pS {
|
||||
if p.ArchitectureVariant != pkg.ArchitectureVariant {
|
||||
continue
|
||||
}
|
||||
packageList.Remove(p)
|
||||
context.Progress().ColoredPrintf("@r[-]@| %s removed", p)
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/smira/flag"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type SnapshotPullVariantSuite struct{}
|
||||
|
||||
var _ = check.Suite(&SnapshotPullVariantSuite{})
|
||||
|
||||
// Exercise the real pull entry point and persisted references, without package
|
||||
// files or mirrors. Architecture selection remains base-architecture selection.
|
||||
func (*SnapshotPullVariantSuite) TestSnapshotPullArchitectureVariant(c *check.C) {
|
||||
normal := &deb.Package{Name: "pull-test", Version: "1", Architecture: "amd64"}
|
||||
variant := &deb.Package{Name: "pull-test", Version: "1", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
normal2 := &deb.Package{Name: "pull-test", Version: "2", Architecture: "amd64"}
|
||||
variant2 := &deb.Package{Name: "pull-test", Version: "2", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
cases := []struct {
|
||||
name string
|
||||
dest, source, want []*deb.Package
|
||||
noRemove, allMatches bool
|
||||
}{
|
||||
{"normal", nil, []*deb.Package{normal}, []*deb.Package{normal}, false, false},
|
||||
{"variant", nil, []*deb.Package{variant}, []*deb.Package{variant}, false, false},
|
||||
{"both", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, false, false},
|
||||
{"variant_into_normal", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, false, false},
|
||||
{"normal_into_variant", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, false, false},
|
||||
{"both_all_matches", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, false, true},
|
||||
{"both_no_remove", nil, []*deb.Package{normal, variant}, []*deb.Package{normal, variant}, true, false},
|
||||
{"variant_into_normal_no_remove", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, true, false},
|
||||
{"normal_into_variant_no_remove", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, true, false},
|
||||
{"variant_into_normal_all_matches", []*deb.Package{normal}, []*deb.Package{variant}, []*deb.Package{normal, variant}, false, true},
|
||||
{"normal_into_variant_all_matches", []*deb.Package{variant}, []*deb.Package{normal}, []*deb.Package{normal, variant}, false, true},
|
||||
{"normal_replace", []*deb.Package{normal}, []*deb.Package{normal2}, []*deb.Package{normal2}, false, false},
|
||||
{"normal_no_remove", []*deb.Package{normal}, []*deb.Package{normal2}, []*deb.Package{normal, normal2}, true, false},
|
||||
{"normal_latest", nil, []*deb.Package{normal, normal2}, []*deb.Package{normal2}, false, false},
|
||||
{"normal_all_matches", nil, []*deb.Package{normal, normal2}, []*deb.Package{normal, normal2}, false, true},
|
||||
{"variant_replace_preserves_normal", []*deb.Package{normal, variant}, []*deb.Package{variant2}, []*deb.Package{normal, variant2}, false, false},
|
||||
{"latest_per_identity", nil, []*deb.Package{normal, normal2, variant, variant2}, []*deb.Package{normal2, variant2}, false, false},
|
||||
{"all_versions_both_identities", nil, []*deb.Package{normal, normal2, variant, variant2}, []*deb.Package{normal, normal2, variant, variant2}, false, true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
c.Logf("case: %s", tc.name)
|
||||
func() {
|
||||
savedConfig := utils.Config
|
||||
defer func() { utils.Config = savedConfig }()
|
||||
dir := c.MkDir()
|
||||
configPath := filepath.Join(dir, "aptly.conf")
|
||||
configData, err := json.Marshal(map[string]interface{}{"rootDir": dir, "architectures": []string{}})
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(os.WriteFile(configPath, configData, 0600), check.IsNil)
|
||||
flags := flag.NewFlagSet("pull-test", flag.ContinueOnError)
|
||||
flags.String("config", configPath, "")
|
||||
// Empty destinations need explicit architecture; populated destinations
|
||||
// exercise automatic base-architecture discovery.
|
||||
arch := ""
|
||||
if len(tc.dest) == 0 {
|
||||
arch = "amd64"
|
||||
}
|
||||
flags.String("architectures", arch, "")
|
||||
flags.Bool("no-lock", false, "")
|
||||
flags.Int("db-open-attempts", 1, "")
|
||||
flags.Bool("no-deps", true, "")
|
||||
flags.Bool("no-remove", tc.noRemove, "")
|
||||
flags.Bool("all-matches", tc.allMatches, "")
|
||||
flags.Bool("dry-run", false, "")
|
||||
testContext, err := ctx.NewContext(flags)
|
||||
c.Assert(err, check.IsNil)
|
||||
defer testContext.Shutdown()
|
||||
factory := testContext.NewCollectionFactory()
|
||||
refs := func(packages []*deb.Package) *deb.PackageRefList {
|
||||
list := deb.NewPackageList()
|
||||
for _, p := range packages {
|
||||
c.Assert(list.Add(p), check.IsNil)
|
||||
}
|
||||
return deb.NewPackageRefListFromPackageList(list)
|
||||
}
|
||||
for _, p := range append(append([]*deb.Package{}, tc.dest...), tc.source...) {
|
||||
c.Assert(factory.PackageCollection().Update(p), check.IsNil)
|
||||
}
|
||||
for name, packages := range map[string][]*deb.Package{"base": tc.dest, "source": tc.source} {
|
||||
c.Assert(factory.SnapshotCollection().Add(deb.NewSnapshotFromRefList(name, nil, refs(packages), "")), check.IsNil)
|
||||
}
|
||||
savedContext := context
|
||||
context = testContext
|
||||
defer func() { context = savedContext }()
|
||||
c.Assert(aptlySnapshotPull(makeCmdSnapshotPull(), []string{"base", "source", "result", "pull-test"}), check.IsNil)
|
||||
snapshots := testContext.NewCollectionFactory().SnapshotCollection()
|
||||
result, err := snapshots.ByName("result")
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(snapshots.LoadComplete(result), check.IsNil)
|
||||
got, want := result.RefList().Strings(), refs(tc.want).Strings()
|
||||
c.Check(got, check.DeepEquals, want, check.Commentf("case %s: persisted package identities", tc.name))
|
||||
}()
|
||||
}
|
||||
}
|
||||
+1
-3
@@ -212,8 +212,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
||||
$keyring \
|
||||
"-max-tries=[max download tries till process fails with download error]:number: " \
|
||||
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
|
||||
"-latest=[download only latest version of each package (per architecture), same as -max-versions=1]:$bool" \
|
||||
"-max-versions=[download only the N most recent versions of each package (per architecture), 0 means all versions]:number: " \
|
||||
"-latest=[download only latest version of each package (per architecture)]:$bool" \
|
||||
"(-)2:mirror name:$mirrors"
|
||||
;;
|
||||
rename)
|
||||
@@ -538,7 +537,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
||||
serve)
|
||||
_arguments '1:: :' \
|
||||
"-listen=[host:port for HTTP listening or unix://path to listen on a Unix domain socket]:host\:port or unix\://path: " \
|
||||
"-metrics-listen=[host:port for optional metrics-only HTTP listening]:host\:port: " \
|
||||
"-no-lock=[don’t lock the database]:$bool"
|
||||
;;
|
||||
esac
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ _aptly()
|
||||
"update")
|
||||
if [[ $numargs -eq 0 ]]; then
|
||||
if [[ "$cur" == -* ]]; then
|
||||
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages -latest -max-versions=" -- ${cur}))
|
||||
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages -latest" -- ${cur}))
|
||||
else
|
||||
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
||||
fi
|
||||
@@ -674,7 +674,7 @@ _aptly()
|
||||
"serve")
|
||||
if [[ $numargs -eq 0 ]]; then
|
||||
if [[ "$cur" == -* ]]; then
|
||||
COMPREPLY=($(compgen -W "-listen= -metrics-listen=" -- ${cur}))
|
||||
COMPREPLY=($(compgen -W "-listen=" -- ${cur}))
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -124,12 +124,6 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
|
||||
continue
|
||||
}
|
||||
|
||||
if !isValidVersion(p.Version) {
|
||||
reporter.Warning("Version number ('%s') for the '%s' package is invalid", p.Version, p.Name)
|
||||
failedFiles = append(failedFiles, file)
|
||||
continue
|
||||
}
|
||||
|
||||
if p.Architecture == "" {
|
||||
reporter.Warning("Empty architecture on %s", file)
|
||||
failedFiles = append(failedFiles, file)
|
||||
@@ -217,9 +211,6 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
|
||||
if forceReplace {
|
||||
conflictingPackages := list.Search(Dependency{Pkg: p.Name, Version: p.Version, Relation: VersionEqual, Architecture: p.Architecture}, true, false)
|
||||
for _, cp := range conflictingPackages {
|
||||
if cp.ArchitectureVariant != p.ArchitectureVariant {
|
||||
continue
|
||||
}
|
||||
reporter.Removed("%s removed due to conflict with package being added", cp)
|
||||
list.Remove(cp)
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type ImportSuite struct {
|
||||
db database.Storage
|
||||
packageCollection *PackageCollection
|
||||
reporter *aptly.RecordingResultReporter
|
||||
}
|
||||
|
||||
var _ = Suite(&ImportSuite{})
|
||||
|
||||
func (s *ImportSuite) SetUpTest(c *C) {
|
||||
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
||||
s.packageCollection = NewPackageCollection(s.db)
|
||||
s.reporter = &aptly.RecordingResultReporter{
|
||||
Warnings: []string{},
|
||||
AddedLines: []string{},
|
||||
RemovedLines: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TearDownTest(c *C) {
|
||||
_ = s.db.Close()
|
||||
}
|
||||
|
||||
func (s *ImportSuite) TestImportPackageFilesRejectsInvalidVersion(c *C) {
|
||||
list := NewPackageList()
|
||||
|
||||
processedFiles, failedFiles, err := ImportPackageFiles(
|
||||
list, []string{"testdata/import/invalid-version.dsc"}, false, &NullVerifier{},
|
||||
nil, s.packageCollection, s.reporter, nil,
|
||||
func(database.ReaderWriter) aptly.ChecksumStorage { return nil })
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(processedFiles, HasLen, 0)
|
||||
c.Check(failedFiles, DeepEquals, []string{"testdata/import/invalid-version.dsc"})
|
||||
c.Check(s.reporter.Warnings, DeepEquals, []string{
|
||||
"Version number ('1.2.3-') for the 'aptly-test-invalid-version' package is invalid",
|
||||
})
|
||||
}
|
||||
+15
-100
@@ -7,7 +7,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/pgp"
|
||||
@@ -23,7 +22,6 @@ type indexFiles struct {
|
||||
suffix string
|
||||
indexes map[string]*indexFile
|
||||
acquireByHash bool
|
||||
byHashAlgorithms []string
|
||||
skipBz2 bool
|
||||
}
|
||||
|
||||
@@ -113,11 +111,7 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
||||
}
|
||||
|
||||
if file.acquireByHash {
|
||||
algorithms, algErr := file.parent.hashAlgorithms()
|
||||
if algErr != nil {
|
||||
return algErr
|
||||
}
|
||||
for _, hash := range algorithms {
|
||||
for _, hash := range []string{"MD5Sum", "SHA1", "SHA256", "SHA512"} {
|
||||
err = file.parent.publishedStorage.MkDir(filepath.Join(filedir, "by-hash", hash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create dir: %s", err)
|
||||
@@ -139,13 +133,8 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
||||
|
||||
if file.acquireByHash {
|
||||
sums := file.parent.generatedFiles[file.relativePath+ext]
|
||||
all := map[string]string{"SHA512": sums.SHA512, "SHA256": sums.SHA256, "SHA1": sums.SHA1, "MD5Sum": sums.MD5}
|
||||
algorithms, algErr := file.parent.hashAlgorithms()
|
||||
if algErr != nil {
|
||||
return algErr
|
||||
}
|
||||
for _, hash := range algorithms {
|
||||
err = packageIndexByHash(file, ext, hash, all[hash])
|
||||
for hash, sum := range map[string]string{"SHA512": sums.SHA512, "SHA256": sums.SHA256, "SHA1": sums.SHA1, "MD5Sum": sums.MD5} {
|
||||
err = packageIndexByHash(file, ext, hash, sum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build hash file: %s", err)
|
||||
}
|
||||
@@ -196,51 +185,6 @@ func (file *indexFile) Finalize(signer pgp.Signer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// knownHashAlgorithms are the algorithms aptly can write Acquire-By-Hash
|
||||
// entries for, in the order the entries are written.
|
||||
var knownHashAlgorithms = []string{"MD5Sum", "SHA1", "SHA256", "SHA512"}
|
||||
|
||||
// hashAlgorithms is the set of Acquire-By-Hash algorithms to write entries for.
|
||||
// Empty means all of them, which is what aptly did before the set was
|
||||
// configurable, so an existing config behaves exactly as before.
|
||||
//
|
||||
// The order is knownHashAlgorithms' rather than the config's, so that the
|
||||
// directories created in one loop and the entries written in another always
|
||||
// agree whatever order the user listed.
|
||||
//
|
||||
// An unrecognised name is an error rather than something to skip. Skipping it
|
||||
// would turn a typo into "no by-hash entries for that algorithm", which looks
|
||||
// identical to having configured it away.
|
||||
func (files *indexFiles) hashAlgorithms() ([]string, error) {
|
||||
if len(files.byHashAlgorithms) == 0 {
|
||||
return knownHashAlgorithms, nil
|
||||
}
|
||||
|
||||
wanted := make(map[string]bool, len(files.byHashAlgorithms))
|
||||
for _, want := range files.byHashAlgorithms {
|
||||
known := false
|
||||
for _, k := range knownHashAlgorithms {
|
||||
if k == want {
|
||||
known = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !known {
|
||||
return nil, fmt.Errorf("unknown Acquire-By-Hash algorithm %q, expected one of %v",
|
||||
want, knownHashAlgorithms)
|
||||
}
|
||||
wanted[want] = true
|
||||
}
|
||||
|
||||
ordered := make([]string, 0, len(wanted))
|
||||
for _, known := range knownHashAlgorithms {
|
||||
if wanted[known] {
|
||||
ordered = append(ordered, known)
|
||||
}
|
||||
}
|
||||
return ordered, nil
|
||||
}
|
||||
|
||||
func packageIndexByHash(file *indexFile, ext string, hash string, sum string) error {
|
||||
src := filepath.Join(file.parent.basePath, file.relativePath)
|
||||
indexfile := path.Base(src + ext)
|
||||
@@ -291,7 +235,7 @@ func packageIndexByHash(file *indexFile, ext string, hash string, sum string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string, acquireByHash bool, byHashAlgorithms []string, skipBz2 bool) *indexFiles {
|
||||
func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, suffix string, acquireByHash bool, skipBz2 bool) *indexFiles {
|
||||
return &indexFiles{
|
||||
publishedStorage: publishedStorage,
|
||||
basePath: basePath,
|
||||
@@ -301,7 +245,6 @@ func newIndexFiles(publishedStorage aptly.PublishedStorage, basePath, tempDir, s
|
||||
suffix: suffix,
|
||||
indexes: make(map[string]*indexFile),
|
||||
acquireByHash: acquireByHash,
|
||||
byHashAlgorithms: byHashAlgorithms,
|
||||
skipBz2: skipBz2,
|
||||
}
|
||||
}
|
||||
@@ -454,12 +397,11 @@ func (files *indexFiles) SkelIndex(component, path string) *indexFile {
|
||||
relativePath := filepath.Join(component, path)
|
||||
|
||||
file = &indexFile{
|
||||
parent: files,
|
||||
discardable: false,
|
||||
compressable: false,
|
||||
onlyGzip: false,
|
||||
acquireByHash: files.acquireByHash,
|
||||
relativePath: relativePath,
|
||||
parent: files,
|
||||
discardable: false,
|
||||
compressable: false,
|
||||
onlyGzip: false,
|
||||
relativePath: relativePath,
|
||||
}
|
||||
|
||||
files.indexes[key] = file
|
||||
@@ -501,41 +443,14 @@ func (files *indexFiles) FinalizeAll(progress aptly.Progress, signer pgp.Signer)
|
||||
}
|
||||
|
||||
func (files *indexFiles) RenameFiles() error {
|
||||
type renamePair struct{ oldName, newName string }
|
||||
var err error
|
||||
|
||||
pairs := make([]renamePair, 0, len(files.renameMap))
|
||||
for oldName, newName := range files.renameMap {
|
||||
pairs = append(pairs, renamePair{oldName, newName})
|
||||
err = files.publishedStorage.RenameFile(oldName, newName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
firstErr error
|
||||
)
|
||||
// Below 1 the semaphore would admit nothing, so treat it as serial.
|
||||
sem := make(chan struct{}, max(utils.Config.PublishRenameConcurrency, 1))
|
||||
|
||||
for _, pair := range pairs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(pair renamePair) {
|
||||
defer wg.Done()
|
||||
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
if err := files.publishedStorage.RenameFile(pair.oldName, pair.newName); err != nil {
|
||||
mu.Lock()
|
||||
if firstErr == nil {
|
||||
firstErr = fmt.Errorf("unable to rename: %s", err)
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}(pair)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return firstErr
|
||||
return nil
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type RenameFilesSuite struct {
|
||||
savedRenameConcurrency int
|
||||
}
|
||||
|
||||
var _ = Suite(&RenameFilesSuite{})
|
||||
|
||||
func (s *RenameFilesSuite) SetUpTest(c *C) {
|
||||
s.savedRenameConcurrency = utils.Config.PublishRenameConcurrency
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TearDownTest(c *C) {
|
||||
utils.Config.PublishRenameConcurrency = s.savedRenameConcurrency
|
||||
}
|
||||
|
||||
// Only RenameFile is exercised by RenameFiles, so the rest of the interface is
|
||||
// embedded and left nil: a call to anything else would panic, which is the
|
||||
// loudest way to notice this fake drifting from what the code under test does.
|
||||
type renameRecordingStorage struct {
|
||||
aptly.PublishedStorage
|
||||
|
||||
mu sync.Mutex
|
||||
renamed map[string]string
|
||||
failOn string
|
||||
inFlight int
|
||||
maxSeen int
|
||||
|
||||
// With waitFor set, every rename blocks until that many run at once, so a
|
||||
// test proves the limit is reached rather than only respected.
|
||||
waitFor int
|
||||
released bool
|
||||
release chan struct{}
|
||||
}
|
||||
|
||||
func (s *renameRecordingStorage) RenameFile(oldName, newName string) error {
|
||||
s.mu.Lock()
|
||||
s.inFlight++
|
||||
if s.inFlight > s.maxSeen {
|
||||
s.maxSeen = s.inFlight
|
||||
}
|
||||
if s.waitFor > 0 && s.inFlight == s.waitFor && !s.released {
|
||||
s.released = true
|
||||
close(s.release)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.mu.Lock()
|
||||
s.inFlight--
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
|
||||
if s.waitFor > 0 {
|
||||
select {
|
||||
case <-s.release:
|
||||
case <-time.After(5 * time.Second):
|
||||
return fmt.Errorf("never saw %d renames in flight at once", s.waitFor)
|
||||
}
|
||||
}
|
||||
|
||||
if oldName == s.failOn {
|
||||
return fmt.Errorf("rename refused for %s", oldName)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.renamed[oldName] = newName
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newRenameFiles(storage aptly.PublishedStorage, n int) (*indexFiles, *renameRecordingStorage) {
|
||||
rec, ok := storage.(*renameRecordingStorage)
|
||||
if !ok {
|
||||
rec = &renameRecordingStorage{renamed: make(map[string]string), release: make(chan struct{})}
|
||||
}
|
||||
files := &indexFiles{
|
||||
publishedStorage: rec,
|
||||
renameMap: make(map[string]string, n),
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
files.renameMap[fmt.Sprintf("dists/x/Packages.tmp.%d", i)] = fmt.Sprintf("dists/x/Packages.%d", i)
|
||||
}
|
||||
return files, rec
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TestRenameFilesRenamesEveryEntry(c *C) {
|
||||
files, rec := newRenameFiles(nil, 20)
|
||||
|
||||
c.Assert(files.RenameFiles(), IsNil)
|
||||
|
||||
c.Check(len(rec.renamed), Equals, 20)
|
||||
for i := 0; i < 20; i++ {
|
||||
c.Check(rec.renamed[fmt.Sprintf("dists/x/Packages.tmp.%d", i)], Equals,
|
||||
fmt.Sprintf("dists/x/Packages.%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TestRenameFilesRunsUpToTheConfiguredLimit(c *C) {
|
||||
// Above the default of 4, so a limit that ignored the config would time out.
|
||||
utils.Config.PublishRenameConcurrency = 6
|
||||
files, rec := newRenameFiles(nil, 50)
|
||||
rec.waitFor = 6
|
||||
|
||||
c.Assert(files.RenameFiles(), IsNil)
|
||||
|
||||
c.Check(rec.maxSeen, Equals, 6)
|
||||
c.Check(len(rec.renamed), Equals, 50)
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TestRenameFilesIsSerialAtALimitOfOneOrLess(c *C) {
|
||||
for _, limit := range []int{1, 0, -3} {
|
||||
utils.Config.PublishRenameConcurrency = limit
|
||||
files, rec := newRenameFiles(nil, 10)
|
||||
|
||||
c.Assert(files.RenameFiles(), IsNil, Commentf("limit %d", limit))
|
||||
|
||||
c.Check(rec.maxSeen, Equals, 1, Commentf("limit %d", limit))
|
||||
c.Check(len(rec.renamed), Equals, 10, Commentf("limit %d", limit))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TestRenameFilesReportsAFailure(c *C) {
|
||||
files, rec := newRenameFiles(nil, 10)
|
||||
rec.failOn = "dists/x/Packages.tmp.4"
|
||||
|
||||
err := files.RenameFiles()
|
||||
|
||||
c.Assert(err, NotNil)
|
||||
c.Check(err, ErrorMatches, "unable to rename: rename refused for dists/x/Packages.tmp.4")
|
||||
// The failure is reported, and it does not abandon the rest: every other
|
||||
// entry is still renamed rather than left staged.
|
||||
c.Check(len(rec.renamed), Equals, 9)
|
||||
}
|
||||
|
||||
func (s *RenameFilesSuite) TestRenameFilesWithNothingToDo(c *C) {
|
||||
files, _ := newRenameFiles(nil, 0)
|
||||
|
||||
c.Assert(files.RenameFiles(), IsNil)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type IndexFilesSuite struct{}
|
||||
|
||||
var _ = Suite(&IndexFilesSuite{})
|
||||
|
||||
func (s *IndexFilesSuite) TestHashAlgorithmsDefaultsToAll(c *C) {
|
||||
// Unset must behave exactly as aptly did before the set was configurable,
|
||||
// so an existing config keeps writing all four.
|
||||
for _, files := range []*indexFiles{{}, {byHashAlgorithms: []string{}}} {
|
||||
got, err := files.hashAlgorithms()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(got, DeepEquals, []string{"MD5Sum", "SHA1", "SHA256", "SHA512"})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestHashAlgorithmsHonoursTheConfiguredSet(c *C) {
|
||||
got, err := (&indexFiles{byHashAlgorithms: []string{"SHA256"}}).hashAlgorithms()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(got, DeepEquals, []string{"SHA256"})
|
||||
|
||||
got, err = (&indexFiles{byHashAlgorithms: []string{"SHA256", "SHA512"}}).hashAlgorithms()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(got, DeepEquals, []string{"SHA256", "SHA512"})
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestHashAlgorithmsIsOrderedIndependentlyOfConfig(c *C) {
|
||||
// The directories are created in one loop and the entries written in
|
||||
// another. If the order came from the config those two could disagree,
|
||||
// so it is fixed here whatever order the user listed.
|
||||
got, err := (&indexFiles{byHashAlgorithms: []string{"SHA512", "MD5Sum", "SHA256"}}).hashAlgorithms()
|
||||
c.Check(err, IsNil)
|
||||
c.Check(got, DeepEquals, []string{"MD5Sum", "SHA256", "SHA512"})
|
||||
}
|
||||
|
||||
func (s *IndexFilesSuite) TestHashAlgorithmsRejectsUnknownNames(c *C) {
|
||||
// Loudly, not by skipping. Filtering a typo would produce "no by-hash
|
||||
// entries for that algorithm", which is indistinguishable from having
|
||||
// deliberately configured it away.
|
||||
_, err := (&indexFiles{byHashAlgorithms: []string{"SHA256", "SHA3"}}).hashAlgorithms()
|
||||
c.Check(err, ErrorMatches, `unknown Acquire-By-Hash algorithm "SHA3".*`)
|
||||
|
||||
_, err = (&indexFiles{byHashAlgorithms: []string{"sha256"}}).hashAlgorithms()
|
||||
c.Check(err, NotNil)
|
||||
}
|
||||
+13
-71
@@ -173,21 +173,20 @@ func (l *PackageList) ForEach(handler func(*Package) error) error {
|
||||
}
|
||||
|
||||
// FilterLatest creates a copy of the package list containing only the
|
||||
// count latest versions for each package name/architecture pair.
|
||||
func (l *PackageList) FilterLatest(count int) (*PackageList, error) {
|
||||
// latest version for each package name/architecture pair.
|
||||
func (l *PackageList) FilterLatest() (*PackageList, error) {
|
||||
if l == nil {
|
||||
return nil, fmt.Errorf("package list is nil")
|
||||
}
|
||||
|
||||
if count < 1 {
|
||||
return nil, fmt.Errorf("number of latest versions should be at least 1, got %d", count)
|
||||
}
|
||||
|
||||
groups := make(map[string][]*Package, l.Len())
|
||||
filtered := make(map[string]*Package, l.Len())
|
||||
|
||||
err := l.ForEach(func(p *Package) error {
|
||||
key := p.IndexArchitecture() + "|" + p.Name
|
||||
groups[key] = append(groups[key], p)
|
||||
key := p.Architecture + "|" + p.Name
|
||||
|
||||
if existing, found := filtered[key]; !found || CompareVersions(p.Version, existing.Version) > 0 {
|
||||
filtered[key] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -195,49 +194,11 @@ func (l *PackageList) FilterLatest(count int) (*PackageList, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := NewPackageListWithDuplicates(l.duplicatesAllowed, len(groups)*count)
|
||||
result := NewPackageListWithDuplicates(l.duplicatesAllowed, len(filtered))
|
||||
|
||||
for _, pkgs := range groups {
|
||||
// sort from latest to oldest
|
||||
sort.SliceStable(pkgs, func(i, j int) bool {
|
||||
cmp, cmpErr := CompareVersions(pkgs[i].Version, pkgs[j].Version)
|
||||
switch cmpErr {
|
||||
case LeftInvalid, BothInvalid:
|
||||
return false
|
||||
case RightInvalid:
|
||||
return true
|
||||
}
|
||||
return cmp > 0
|
||||
})
|
||||
|
||||
versions := 0
|
||||
for i, p := range pkgs {
|
||||
if i == 0 {
|
||||
// first element always included
|
||||
} else {
|
||||
cmp, cmpErr := CompareVersions(p.Version, pkgs[i-1].Version)
|
||||
switch cmpErr {
|
||||
case LeftInvalid, BothInvalid:
|
||||
// invalid version is different from previous
|
||||
versions++
|
||||
case RightInvalid:
|
||||
// previous was invalid, current is valid — different
|
||||
versions++
|
||||
default:
|
||||
if cmp == 0 {
|
||||
// same version, skip
|
||||
} else {
|
||||
versions++
|
||||
}
|
||||
}
|
||||
if versions >= count {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err = result.Add(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pkg := range filtered {
|
||||
if err = result.Add(pkg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,19 +287,6 @@ func (l *PackageList) Architectures(includeSource bool) (result []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// IndexArchitectures returns repository index architectures present in the list.
|
||||
// If includeSource is true, the source meta-architecture is included.
|
||||
func (l *PackageList) IndexArchitectures(includeSource bool) (result []string) {
|
||||
result = make([]string, 0, 10)
|
||||
for _, pkg := range l.packages {
|
||||
arch := pkg.IndexArchitecture()
|
||||
if arch != ArchitectureAll && (arch != ArchitectureSource || includeSource) && !utils.StrSliceHasItem(result, arch) {
|
||||
result = append(result, arch)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Strings builds list of strings with package keys
|
||||
func (l *PackageList) Strings() []string {
|
||||
result := make([]string, l.Len())
|
||||
@@ -479,13 +427,7 @@ func (l *PackageList) Swap(i, j int) {
|
||||
|
||||
func (l *PackageList) lessPackages(iPkg, jPkg *Package) bool {
|
||||
if iPkg.Name == jPkg.Name {
|
||||
cmp, err := CompareVersions(iPkg.Version, jPkg.Version)
|
||||
switch err {
|
||||
case LeftInvalid, BothInvalid:
|
||||
return false
|
||||
case RightInvalid:
|
||||
return true
|
||||
}
|
||||
cmp := CompareVersions(iPkg.Version, jPkg.Version)
|
||||
if cmp == 0 {
|
||||
return iPkg.Architecture < jPkg.Architecture
|
||||
}
|
||||
|
||||
+3
-166
@@ -134,66 +134,6 @@ func (s *PackageListSuite) TestAddLen(c *C) {
|
||||
c.Check(s.list.Add(s.p4), ErrorMatches, "package already exists and is different: .*")
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantAdd(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64"}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
|
||||
c.Assert(s.list.Add(normal), IsNil)
|
||||
c.Assert(s.list.Add(variant), IsNil)
|
||||
c.Check(s.list.Len(), Equals, 2)
|
||||
var packages []*Package
|
||||
c.Assert(s.list.ForEach(func(p *Package) error {
|
||||
packages = append(packages, p)
|
||||
return nil
|
||||
}), IsNil)
|
||||
c.Check(packages, HasLen, 2)
|
||||
found := map[*Package]bool{}
|
||||
for _, p := range packages {
|
||||
found[p] = true
|
||||
}
|
||||
c.Check(found[normal], Equals, true)
|
||||
c.Check(found[variant], Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantHas(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64"}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
|
||||
c.Check(s.list.Has(normal), Equals, false)
|
||||
c.Check(s.list.Has(variant), Equals, false)
|
||||
c.Assert(s.list.Add(normal), IsNil)
|
||||
c.Check(s.list.Has(normal), Equals, true)
|
||||
c.Check(s.list.Has(variant), Equals, false)
|
||||
c.Assert(s.list.Add(variant), IsNil)
|
||||
c.Check(s.list.Has(normal), Equals, true)
|
||||
c.Check(s.list.Has(variant), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantRemove(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64"}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
|
||||
c.Assert(s.list.Add(normal), IsNil)
|
||||
c.Assert(s.list.Add(variant), IsNil)
|
||||
s.list.Remove(variant)
|
||||
c.Check(s.list.Len(), Equals, 1)
|
||||
c.Check(s.list.Has(normal), Equals, true)
|
||||
c.Check(s.list.Has(variant), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantFilterLatest(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64"}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
|
||||
c.Assert(s.list.Add(normal), IsNil)
|
||||
c.Assert(s.list.Add(variant), IsNil)
|
||||
filtered, err := s.list.FilterLatest(1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(filtered.Len(), Equals, 2)
|
||||
c.Check(filtered.Has(normal), Equals, true)
|
||||
c.Check(filtered.Has(variant), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestRemove(c *C) {
|
||||
c.Check(s.list.Add(s.p1), IsNil)
|
||||
c.Check(s.list.Add(s.p3), IsNil)
|
||||
@@ -584,85 +524,13 @@ func (s *PackageListSuite) TestFilterLatest(c *C) {
|
||||
sharedPkg := NewPackageFromControlFile(shared)
|
||||
_ = list.Add(sharedPkg)
|
||||
|
||||
filtered, err := list.FilterLatest(1)
|
||||
filtered, err := list.FilterLatest()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(filtered.Len(), Equals, 2)
|
||||
c.Check(filtered.Has(newerPkg), Equals, true)
|
||||
c.Check(filtered.Has(sharedPkg), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestFilterLatestMultiple(c *C) {
|
||||
list := NewPackageList()
|
||||
|
||||
pkgs := make([]*Package, 0, 4)
|
||||
for _, version := range []string{"2.0", "1.0", "10.0", "1:0.1"} {
|
||||
stanza := packageStanza.Copy()
|
||||
stanza["Version"] = version
|
||||
pkg := NewPackageFromControlFile(stanza)
|
||||
_ = list.Add(pkg)
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
|
||||
shared := packageStanza.Copy()
|
||||
shared["Architecture"] = ArchitectureAll
|
||||
shared["Version"] = "3.0"
|
||||
shared["Package"] = "shared"
|
||||
sharedPkg := NewPackageFromControlFile(shared)
|
||||
_ = list.Add(sharedPkg)
|
||||
|
||||
filtered, err := list.FilterLatest(3)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(filtered.Len(), Equals, 4)
|
||||
c.Check(filtered.Has(pkgs[0]), Equals, true)
|
||||
c.Check(filtered.Has(pkgs[1]), Equals, false)
|
||||
c.Check(filtered.Has(pkgs[2]), Equals, true)
|
||||
c.Check(filtered.Has(pkgs[3]), Equals, true)
|
||||
c.Check(filtered.Has(sharedPkg), Equals, true)
|
||||
|
||||
// fewer versions available than requested: everything is kept
|
||||
filtered, err = list.FilterLatest(5)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(filtered.Len(), Equals, 5)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestFilterLatestInvalidCount(c *C) {
|
||||
list := NewPackageList()
|
||||
|
||||
filtered, err := list.FilterLatest(0)
|
||||
c.Assert(err, ErrorMatches, "number of latest versions should be at least 1, got 0")
|
||||
c.Assert(filtered, IsNil)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestFilterLatestInvalidVersions(c *C) {
|
||||
list := NewPackageList()
|
||||
valid := &Package{Name: "pkg", Version: "1.0", Architecture: "amd64"}
|
||||
invalid := &Package{Name: "pkg", Version: "2.0-", Architecture: "amd64"}
|
||||
_ = list.Add(valid)
|
||||
_ = list.Add(invalid)
|
||||
|
||||
filtered, err := list.FilterLatest(1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(filtered.Len(), Equals, 1)
|
||||
c.Check(filtered.Has(valid), Equals, true)
|
||||
|
||||
invalidOnly := NewPackageList()
|
||||
_ = invalidOnly.Add(&Package{Name: "pkg", Version: "1.0-", Architecture: "amd64"})
|
||||
_ = invalidOnly.Add(&Package{Name: "pkg", Version: "2.0-", Architecture: "amd64"})
|
||||
filtered, err = invalidOnly.FilterLatest(1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(filtered.Len(), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestLessPackagesInvalidVersions(c *C) {
|
||||
list := NewPackageList()
|
||||
valid := &Package{Name: "pkg", Version: "1.0", Architecture: "amd64"}
|
||||
invalid := &Package{Name: "pkg", Version: "2.0-", Architecture: "amd64"}
|
||||
|
||||
c.Check(list.lessPackages(valid, invalid), Equals, true)
|
||||
c.Check(list.lessPackages(invalid, valid), Equals, false)
|
||||
c.Check(list.lessPackages(invalid, invalid), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
|
||||
list := NewPackageListWithDuplicates(true, 2)
|
||||
|
||||
@@ -672,7 +540,7 @@ func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
|
||||
another["Version"] = "7.41-1"
|
||||
_ = list.Add(NewPackageFromControlFile(another))
|
||||
|
||||
filtered, err := list.FilterLatest(1)
|
||||
filtered, err := list.FilterLatest()
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(filtered.duplicatesAllowed, Equals, true)
|
||||
}
|
||||
@@ -680,38 +548,7 @@ func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
|
||||
func (s *PackageListSuite) TestFilterLatestNil(c *C) {
|
||||
var list *PackageList
|
||||
|
||||
filtered, err := list.FilterLatest(1)
|
||||
filtered, err := list.FilterLatest()
|
||||
c.Assert(err, ErrorMatches, "package list is nil")
|
||||
c.Assert(filtered, IsNil)
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantIndexArchitectures(c *C) {
|
||||
for _, p := range []*Package{
|
||||
{Name: "example", Version: "1.0", Architecture: "amd64"},
|
||||
{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"},
|
||||
{Name: "example", Version: "2.0", Architecture: "amd64", ArchitectureVariant: "amd64v3"},
|
||||
{Name: "example", Version: "1.0", Architecture: "i386"},
|
||||
{Name: "data", Version: "1.0", Architecture: "all"},
|
||||
{Name: "example", Version: "1.0", Architecture: "source", IsSource: true},
|
||||
} {
|
||||
c.Assert(s.list.Add(p), IsNil)
|
||||
}
|
||||
indexes := s.list.IndexArchitectures(false)
|
||||
sort.Strings(indexes)
|
||||
c.Check(indexes, DeepEquals, []string{"amd64", "amd64v3", "i386"})
|
||||
indexes = s.list.IndexArchitectures(true)
|
||||
sort.Strings(indexes)
|
||||
c.Check(indexes, DeepEquals, []string{"amd64", "amd64v3", "i386", "source"})
|
||||
base := s.list.Architectures(false)
|
||||
sort.Strings(base)
|
||||
c.Check(base, DeepEquals, []string{"amd64", "i386"})
|
||||
}
|
||||
|
||||
func (s *PackageListSuite) TestArchitectureVariantFullNames(c *C) {
|
||||
for _, variant := range []string{"", "amd64v3"} {
|
||||
c.Assert(s.list.Add(&Package{Name: "test-package", Version: "1.0", Architecture: "amd64", ArchitectureVariant: variant}), IsNil)
|
||||
}
|
||||
names := s.list.FullNames()
|
||||
sort.Strings(names)
|
||||
c.Check(names, DeepEquals, []string{"test-package_1.0_amd64", "test-package_1.0_amd64v3"})
|
||||
}
|
||||
+19
-43
@@ -16,10 +16,9 @@ import (
|
||||
// Package is single instance of Debian package
|
||||
type Package struct {
|
||||
// Basic package properties
|
||||
Name string
|
||||
Version string
|
||||
Architecture string
|
||||
ArchitectureVariant string
|
||||
Name string
|
||||
Version string
|
||||
Architecture string
|
||||
// If this source package, this field holds "real" architecture value,
|
||||
// while Architecture would be equal to "source"
|
||||
SourceArchitecture string
|
||||
@@ -69,18 +68,16 @@ var (
|
||||
// NewPackageFromControlFile creates Package from parsed Debian control file
|
||||
func NewPackageFromControlFile(input Stanza) *Package {
|
||||
result := &Package{
|
||||
Name: input["Package"],
|
||||
Version: input["Version"],
|
||||
Architecture: input["Architecture"],
|
||||
ArchitectureVariant: input["Architecture-Variant"],
|
||||
Source: input["Source"],
|
||||
V06Plus: true,
|
||||
Name: input["Package"],
|
||||
Version: input["Version"],
|
||||
Architecture: input["Architecture"],
|
||||
Source: input["Source"],
|
||||
V06Plus: true,
|
||||
}
|
||||
|
||||
delete(input, "Package")
|
||||
delete(input, "Version")
|
||||
delete(input, "Architecture")
|
||||
delete(input, "Architecture-Variant")
|
||||
delete(input, "Source")
|
||||
|
||||
filesize, _ := strconv.ParseInt(input["Size"], 10, 64)
|
||||
@@ -216,18 +213,10 @@ func NewInstallerPackageFromControlFile(input Stanza, repo *RemoteRepo, componen
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// IndexArchitecture returns the variant when present, otherwise the base architecture.
|
||||
func (p *Package) IndexArchitecture() string {
|
||||
if p.ArchitectureVariant != "" {
|
||||
return p.ArchitectureVariant
|
||||
}
|
||||
return p.Architecture
|
||||
}
|
||||
|
||||
// Key returns unique key identifying package
|
||||
func (p *Package) Key(prefix string) []byte {
|
||||
if p.V06Plus {
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s %08x", prefix, p.IndexArchitecture(), p.Name, p.Version, p.FilesHash))
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s %08x", prefix, p.Architecture, p.Name, p.Version, p.FilesHash))
|
||||
}
|
||||
|
||||
return p.ShortKey(prefix)
|
||||
@@ -235,12 +224,12 @@ func (p *Package) Key(prefix string) []byte {
|
||||
|
||||
// ShortKey returns key for the package that should be unique in one list
|
||||
func (p *Package) ShortKey(prefix string) []byte {
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s", prefix, p.IndexArchitecture(), p.Name, p.Version))
|
||||
return []byte(fmt.Sprintf("%sP%s %s %s", prefix, p.Architecture, p.Name, p.Version))
|
||||
}
|
||||
|
||||
// String creates readable representation
|
||||
func (p *Package) String() string {
|
||||
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.IndexArchitecture())
|
||||
return fmt.Sprintf("%s_%s_%s", p.Name, p.Version, p.Architecture)
|
||||
}
|
||||
|
||||
// ExtendedStanza returns package stanza enhanced with aptly-specific fields
|
||||
@@ -303,8 +292,6 @@ func (p *Package) GetField(name string) string {
|
||||
return p.SourceArchitecture
|
||||
}
|
||||
return p.Architecture
|
||||
case "Architecture-Variant":
|
||||
return p.ArchitectureVariant
|
||||
case "Source":
|
||||
return p.Source
|
||||
case "Depends":
|
||||
@@ -352,14 +339,6 @@ func (p *Package) MatchesArchitecture(arch string) bool {
|
||||
return p.Architecture == arch
|
||||
}
|
||||
|
||||
// MatchesIndexArchitecture checks membership in a repository index architecture.
|
||||
func (p *Package) MatchesIndexArchitecture(arch string) bool {
|
||||
if p.Architecture == ArchitectureAll && arch != ArchitectureSource {
|
||||
return true
|
||||
}
|
||||
return p.IndexArchitecture() == arch
|
||||
}
|
||||
|
||||
func JoinErrors(errs ...error) error {
|
||||
var combinedErr error
|
||||
for _, err := range errs {
|
||||
@@ -426,19 +405,19 @@ func (p *Package) MatchesDependency(dep Dependency) bool {
|
||||
}
|
||||
|
||||
func versionSatisfiesDependency(version string, dep Dependency) bool {
|
||||
r, err := CompareVersions(version, dep.Version)
|
||||
r := CompareVersions(version, dep.Version)
|
||||
|
||||
switch dep.Relation {
|
||||
case VersionEqual:
|
||||
return err == nil && r == 0
|
||||
return r == 0
|
||||
case VersionLess:
|
||||
return err == nil && r < 0
|
||||
return r < 0
|
||||
case VersionGreater:
|
||||
return err == nil && r > 0
|
||||
return r > 0
|
||||
case VersionLessOrEqual:
|
||||
return err == nil && r <= 0
|
||||
return r <= 0
|
||||
case VersionGreaterOrEqual:
|
||||
return err == nil && r >= 0
|
||||
return r >= 0
|
||||
case VersionPatternMatch:
|
||||
matched, err := filepath.Match(dep.Version, version)
|
||||
return err == nil && matched
|
||||
@@ -458,7 +437,7 @@ func (p *Package) GetName() string {
|
||||
|
||||
// GetFullName returns the package full name
|
||||
func (p *Package) GetFullName() string {
|
||||
return strings.Join([]string{p.Name, p.Version, p.IndexArchitecture()}, "_")
|
||||
return strings.Join([]string{p.Name, p.Version, p.Architecture}, "_")
|
||||
}
|
||||
|
||||
// GetVersion returns package version
|
||||
@@ -615,9 +594,6 @@ func (p *Package) Stanza() (result Stanza) {
|
||||
result["Architecture"] = p.SourceArchitecture
|
||||
} else {
|
||||
result["Architecture"] = p.Architecture
|
||||
if p.ArchitectureVariant != "" {
|
||||
result["Architecture-Variant"] = p.ArchitectureVariant
|
||||
}
|
||||
if p.Source != "" {
|
||||
result["Source"] = p.Source
|
||||
}
|
||||
@@ -706,7 +682,7 @@ func (p *Package) Stanza() (result Stanza) {
|
||||
func (p *Package) Equals(p2 *Package) bool {
|
||||
return p.Name == p2.Name && p.Version == p2.Version && p.SourceArchitecture == p2.SourceArchitecture &&
|
||||
p.Architecture == p2.Architecture && p.Source == p2.Source && p.IsSource == p2.IsSource &&
|
||||
p.ArchitectureVariant == p2.ArchitectureVariant && p.FilesHash == p2.FilesHash
|
||||
p.FilesHash == p2.FilesHash
|
||||
}
|
||||
|
||||
// LinkFromPool links package file from pool to dist's pool location
|
||||
|
||||
@@ -336,7 +336,7 @@ func (collection *PackageCollection) SearchByKey(arch, name, version string) (re
|
||||
panic(fmt.Sprintf("unable to load package: %s", err))
|
||||
}
|
||||
|
||||
if pkg.IndexArchitecture() == arch && pkg.Name == name && pkg.Version == version {
|
||||
if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
|
||||
_ = result.Add(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,106 +65,6 @@ func (s *PackageCollectionSuite) TestByKey(c *C) {
|
||||
c.Check(p2.Files()[0].Filename, Equals, "alien-arena-common_7.40-2_i386.deb")
|
||||
}
|
||||
|
||||
func architectureVariantCollectionPackage(variant string) *Package {
|
||||
stanza := Stanza{
|
||||
"Package": "example", "Version": "1.0", "Architecture": "amd64",
|
||||
"Filename": "pool/example_1.0_amd64.deb", "Size": "123",
|
||||
"Depends": "test-dependency (>= 1.0)", "Priority": "optional",
|
||||
}
|
||||
if variant != "" {
|
||||
stanza["Architecture-Variant"] = variant
|
||||
stanza["Filename"] = "pool/example_1.0_" + variant + ".deb"
|
||||
}
|
||||
return NewPackageFromControlFile(stanza)
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestArchitectureVariantNormalRoundTrip(c *C) {
|
||||
p := architectureVariantCollectionPackage("")
|
||||
key := p.Key("")
|
||||
c.Assert(s.collection.Update(p), IsNil)
|
||||
loaded, err := s.collection.ByKey(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(loaded.Name, Equals, "example")
|
||||
c.Check(loaded.Version, Equals, "1.0")
|
||||
c.Check(loaded.Architecture, Equals, "amd64")
|
||||
c.Check(loaded.ArchitectureVariant, Equals, "")
|
||||
c.Check(loaded.IndexArchitecture(), Equals, "amd64")
|
||||
c.Check(loaded.Key(""), DeepEquals, key)
|
||||
c.Check(loaded.Equals(p), Equals, true)
|
||||
c.Check(loaded.Files()[0].Filename, Equals, "example_1.0_amd64.deb")
|
||||
c.Check(loaded.GetDependencies(0), DeepEquals, []string{"test-dependency (>= 1.0)"})
|
||||
c.Check(loaded.Extra()["Priority"], Equals, "optional")
|
||||
_, hasVariant := loaded.Stanza()["Architecture-Variant"]
|
||||
c.Check(hasVariant, Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestArchitectureVariantRoundTrip(c *C) {
|
||||
p := architectureVariantCollectionPackage("amd64v3")
|
||||
key := p.Key("")
|
||||
c.Assert(s.collection.Update(p), IsNil)
|
||||
loaded, err := s.collection.ByKey(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(loaded.Name, Equals, "example")
|
||||
c.Check(loaded.Version, Equals, "1.0")
|
||||
c.Check(loaded.Architecture, Equals, "amd64")
|
||||
c.Check(loaded.ArchitectureVariant, Equals, "amd64v3")
|
||||
c.Check(loaded.IndexArchitecture(), Equals, "amd64v3")
|
||||
c.Check(loaded.Key(""), DeepEquals, key)
|
||||
c.Check(loaded.Equals(p), Equals, true)
|
||||
// Exercise offloaded records using the reloaded variant-aware key.
|
||||
c.Check(loaded.Files()[0].Filename, Equals, "example_1.0_amd64v3.deb")
|
||||
c.Check(loaded.GetDependencies(0), DeepEquals, []string{"test-dependency (>= 1.0)"})
|
||||
c.Check(loaded.Extra()["Priority"], Equals, "optional")
|
||||
stanza := loaded.Stanza()
|
||||
c.Check(stanza["Architecture"], Equals, "amd64")
|
||||
c.Check(stanza["Architecture-Variant"], Equals, "amd64v3")
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestArchitectureVariantCoexistence(c *C) {
|
||||
normal := architectureVariantCollectionPackage("")
|
||||
variant := architectureVariantCollectionPackage("amd64v3")
|
||||
c.Assert(s.collection.Update(normal), IsNil)
|
||||
c.Assert(s.collection.Update(variant), IsNil)
|
||||
refs := s.collection.AllPackageRefs()
|
||||
c.Check(refs.Len(), Equals, 2)
|
||||
c.Check(refs.Has(normal), Equals, true)
|
||||
c.Check(refs.Has(variant), Equals, true)
|
||||
for _, p := range []*Package{normal, variant} {
|
||||
loaded, err := s.collection.ByKey(p.Key(""))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(loaded.Equals(p), Equals, true)
|
||||
c.Check(loaded.Architecture, Equals, "amd64")
|
||||
c.Check(loaded.ArchitectureVariant, Equals, p.ArchitectureVariant)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestArchitectureVariantFullKeyLookup(c *C) {
|
||||
normal := architectureVariantCollectionPackage("")
|
||||
variant := architectureVariantCollectionPackage("amd64v3")
|
||||
// Keep the file hash identical so the architecture token alone must
|
||||
// distinguish the full keys and stored records.
|
||||
variant.UpdateFiles(normal.Files())
|
||||
normalKey, variantKey := normal.Key(""), variant.Key("")
|
||||
c.Assert(normalKey, Not(DeepEquals), variantKey)
|
||||
c.Assert(s.collection.Update(normal), IsNil)
|
||||
_, err := s.collection.ByKey(variantKey)
|
||||
c.Assert(err, Equals, database.ErrNotFound)
|
||||
c.Assert(s.collection.Update(variant), IsNil)
|
||||
|
||||
loaded, err := s.collection.ByKey(variantKey)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(loaded.Key(""), DeepEquals, variantKey)
|
||||
c.Check(loaded.Architecture, Equals, "amd64")
|
||||
c.Check(loaded.ArchitectureVariant, Equals, "amd64v3")
|
||||
c.Check(loaded.IndexArchitecture(), Equals, "amd64v3")
|
||||
c.Check(loaded.Equals(variant), Equals, true)
|
||||
c.Check(loaded.Equals(normal), Equals, false)
|
||||
loadedNormal, err := s.collection.ByKey(normalKey)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(loadedNormal.Equals(normal), Equals, true)
|
||||
c.Check(loadedNormal.ArchitectureVariant, Equals, "")
|
||||
}
|
||||
|
||||
func (s *PackageCollectionSuite) TestByKeyOld0_3(c *C) {
|
||||
key := []byte("Pi386 vmware-view-open-client 4.5.0-297975+dfsg-4+b1")
|
||||
_ = s.db.Put(key, old0_3Package)
|
||||
|
||||
@@ -43,66 +43,6 @@ func (s *PackageSuite) TestNewFromPara(c *C) {
|
||||
c.Check(p.deps.Depends, DeepEquals, []string{"libc6 (>= 2.7)", "alien-arena-data (>= 7.40)"})
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantParsedSeparately(c *C) {
|
||||
input, err := NewControlFileReader(bytes.NewBufferString("Package: example\nVersion: 1.0\nArchitecture: amd64\nArchitecture-Variant: amd64v3\nFilename: pool/example_1.0_amd64v3.deb\n"), false, false).ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
p := NewPackageFromControlFile(input)
|
||||
|
||||
c.Check(p.Architecture, Equals, "amd64")
|
||||
c.Check(p.ArchitectureVariant, Equals, "amd64v3")
|
||||
_, inExtra := p.Extra()["Architecture-Variant"]
|
||||
c.Check(inExtra, Equals, false)
|
||||
c.Check(p.GetField("Architecture"), Equals, "amd64")
|
||||
c.Check(p.GetField("Architecture-Variant"), Equals, "amd64v3")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantShortKey(c *C) {
|
||||
input := Stanza{"Package": "example", "Version": "1.0", "Architecture": "amd64"}
|
||||
normal := NewPackageFromControlFile(input.Copy())
|
||||
input["Architecture-Variant"] = "amd64v3"
|
||||
variant := NewPackageFromControlFile(input)
|
||||
|
||||
c.Check(normal.Architecture, Equals, "amd64")
|
||||
c.Check(variant.Architecture, Equals, "amd64")
|
||||
c.Check(string(normal.ShortKey("")), Equals, "Pamd64 example 1.0")
|
||||
c.Check(string(variant.ShortKey("")), Equals, "Pamd64v3 example 1.0")
|
||||
c.Check(string(variant.ShortKey("")), Not(Equals), string(normal.ShortKey("")))
|
||||
c.Check(string(variant.ShortKey("xD")), Equals, "xDPamd64v3 example 1.0")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantKeyAndEquals(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64", V06Plus: true, FilesHash: 0x12345678}
|
||||
variant := *normal
|
||||
variant.ArchitectureVariant = "amd64v3"
|
||||
|
||||
c.Check(normal.IndexArchitecture(), Equals, "amd64")
|
||||
c.Check(variant.IndexArchitecture(), Equals, "amd64v3")
|
||||
c.Check(string(normal.Key("")), Equals, "Pamd64 example 1.0 12345678")
|
||||
c.Check(string(variant.Key("")), Equals, "Pamd64v3 example 1.0 12345678")
|
||||
c.Check(string(variant.Key("xD")), Equals, "xDPamd64v3 example 1.0 12345678")
|
||||
c.Check(normal.Equals(&variant), Equals, false)
|
||||
c.Check(variant.Equals(normal), Equals, false)
|
||||
otherVariant := variant
|
||||
c.Check(variant.Equals(&otherVariant), Equals, true)
|
||||
otherVariant.ArchitectureVariant = "amd64v4"
|
||||
c.Check(variant.Equals(&otherVariant), Equals, false)
|
||||
|
||||
variant.V06Plus = false
|
||||
c.Check(string(variant.Key("")), Equals, "Pamd64v3 example 1.0")
|
||||
c.Check(string(variant.Key("xD")), Equals, "xDPamd64v3 example 1.0")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantStanza(c *C) {
|
||||
s.stanza["Architecture"] = "amd64"
|
||||
s.stanza["Architecture-Variant"] = "amd64v3"
|
||||
p := NewPackageFromControlFile(s.stanza)
|
||||
stanza := p.Stanza()
|
||||
|
||||
c.Check(p.Architecture, Equals, "amd64")
|
||||
c.Check(stanza["Architecture"], Equals, "amd64")
|
||||
c.Check(stanza["Architecture-Variant"], Equals, "amd64v3")
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestNewUdebFromPara(c *C) {
|
||||
stanza, _ := NewControlFileReader(bytes.NewBufferString(udebPackageMeta), false, false).ReadStanza()
|
||||
p := NewUdebPackageFromControlFile(stanza)
|
||||
@@ -580,39 +520,3 @@ SHA256: bbb3a2cb07f741c3995b6d4bb08d772d83582b93a0236d4ea7736bc0370fc320`
|
||||
|
||||
const installerPackageMeta = `9d8bb14044dee520f4706ab197dfff10e9e39ecb3c1a402331712154e8284b2e ./MANIFEST.udebs
|
||||
dab96042d8e25e0f6bbb8d7c5bd78543afb5eb31a4a8b122ece68ab197228028 ./udeb.list`
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantMatchesIndexArchitecture(c *C) {
|
||||
for _, tc := range []struct {
|
||||
base, variant, target string
|
||||
matches bool
|
||||
}{
|
||||
{"amd64", "", "amd64", true},
|
||||
{"amd64", "", "amd64v3", false},
|
||||
{"amd64", "amd64v3", "amd64", false},
|
||||
{"amd64", "amd64v3", "amd64v3", true},
|
||||
{"all", "", "amd64", true},
|
||||
{"all", "", "amd64v3", true},
|
||||
{"all", "", "source", false},
|
||||
{"source", "", "source", true},
|
||||
{"source", "", "amd64v3", false},
|
||||
} {
|
||||
p := &Package{Architecture: tc.base, ArchitectureVariant: tc.variant, IsSource: tc.base == "source"}
|
||||
c.Check(p.MatchesIndexArchitecture(tc.target), Equals, tc.matches, Commentf("base=%s variant=%s target=%s", tc.base, tc.variant, tc.target))
|
||||
}
|
||||
p := &Package{Architecture: "amd64", ArchitectureVariant: "amd64v3"}
|
||||
c.Check(p.MatchesArchitecture("amd64"), Equals, true)
|
||||
c.Check(p.MatchesArchitecture("amd64v3"), Equals, false)
|
||||
}
|
||||
|
||||
func (s *PackageSuite) TestArchitectureVariantDisplayNames(c *C) {
|
||||
for _, variant := range []string{"", "amd64v3"} {
|
||||
p := &Package{Name: "test-package", Version: "1.0", Architecture: "amd64", ArchitectureVariant: variant}
|
||||
expected := "test-package_1.0_amd64"
|
||||
if variant != "" {
|
||||
expected = "test-package_1.0_amd64v3"
|
||||
}
|
||||
c.Check(p.String(), Equals, expected)
|
||||
c.Check(p.GetFullName(), Equals, expected)
|
||||
c.Check(p.GetArchitecture(), Equals, "amd64")
|
||||
}
|
||||
}
|
||||
+63
-71
@@ -76,10 +76,6 @@ type PublishedRepo struct {
|
||||
|
||||
// Skip bz2 compression for index files
|
||||
SkipBz2 bool
|
||||
// AcquireByHashAlgorithms limits which hash algorithms get Acquire-By-Hash
|
||||
// entries. Set per invocation from the config rather than persisted, the
|
||||
// same way SkipBz2 is. Empty means all four.
|
||||
AcquireByHashAlgorithms []string
|
||||
|
||||
// True if repo is being re-published
|
||||
rePublishing bool
|
||||
@@ -883,7 +879,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
if !p.rePublishing {
|
||||
if len(p.Architectures) == 0 {
|
||||
for _, list := range lists {
|
||||
p.Architectures = append(p.Architectures, list.IndexArchitectures(true)...)
|
||||
p.Architectures = append(p.Architectures, list.Architectures(true)...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,7 +893,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
|
||||
var suffix string
|
||||
if p.rePublishing {
|
||||
suffix = ".tmp." + uuid.NewString()
|
||||
suffix = ".tmp"
|
||||
}
|
||||
|
||||
if progress != nil {
|
||||
@@ -911,7 +907,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
}
|
||||
defer func() { _ = os.RemoveAll(tempDir) }()
|
||||
|
||||
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix, p.AcquireByHash, p.AcquireByHashAlgorithms, p.SkipBz2)
|
||||
indexes := newIndexFiles(publishedStorage, basePath, tempDir, suffix, p.AcquireByHash, p.SkipBz2)
|
||||
|
||||
legacyContentIndexes := map[string]*ContentsIndex{}
|
||||
var count int64
|
||||
@@ -941,7 +937,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
}
|
||||
|
||||
for _, arch := range p.Architectures {
|
||||
if pkg.MatchesIndexArchitecture(arch) {
|
||||
if pkg.MatchesArchitecture(arch) {
|
||||
hadUdebs = hadUdebs || pkg.IsUdeb
|
||||
|
||||
var relPath string
|
||||
@@ -979,7 +975,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
batch := tempDB.CreateBatch()
|
||||
|
||||
for _, arch := range p.Architectures {
|
||||
if pkg.MatchesIndexArchitecture(arch) {
|
||||
if pkg.MatchesArchitecture(arch) {
|
||||
var bufWriter *bufio.Writer
|
||||
|
||||
if !p.SkipContents && !pkg.IsInstaller {
|
||||
@@ -1047,6 +1043,62 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
}
|
||||
}
|
||||
|
||||
for component := range p.sourceItems {
|
||||
skelFiles, err := p.GetSkelFiles(skelDir, component)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get skeleton files: %v", err)
|
||||
}
|
||||
|
||||
for relPath, absPath := range skelFiles {
|
||||
bufWriter, err := indexes.SkelIndex(component, relPath).BufWriter()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate skeleton index: %v", err)
|
||||
}
|
||||
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read skeleton file: %v", err)
|
||||
}
|
||||
|
||||
_, err = bufio.NewReader(file).WriteTo(bufWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write skeleton file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass-through AppStream (DEP-11) files from snapshots
|
||||
for component, item := range p.sourceItems {
|
||||
if item.snapshot == nil || len(item.snapshot.AppStreamFiles) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := component + "/"
|
||||
for relPath, poolPath := range item.snapshot.AppStreamFiles {
|
||||
if !strings.HasPrefix(relPath, prefix) {
|
||||
continue
|
||||
}
|
||||
withinComponent := strings.TrimPrefix(relPath, prefix)
|
||||
|
||||
poolFile, err := packagePool.Open(poolPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open AppStream file from pool: %v", err)
|
||||
}
|
||||
|
||||
bufWriter, err := indexes.SkelIndex(component, withinComponent).BufWriter()
|
||||
if err != nil {
|
||||
_ = poolFile.Close()
|
||||
return fmt.Errorf("unable to generate AppStream index: %v", err)
|
||||
}
|
||||
|
||||
_, err = bufio.NewReader(poolFile).WriteTo(bufWriter)
|
||||
_ = poolFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write AppStream file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
udebs := []bool{false}
|
||||
if hadUdebs {
|
||||
udebs = append(udebs, true)
|
||||
@@ -1092,66 +1144,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton and AppStream (DEP-11) files are keyed by component and already
|
||||
// iterate over every component, so they must be emitted once for the whole
|
||||
// publish -- not once per component, which would append each payload again.
|
||||
for component := range p.sourceItems {
|
||||
skelFiles, err := p.GetSkelFiles(skelDir, component)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get skeleton files: %v", err)
|
||||
}
|
||||
|
||||
for relPath, absPath := range skelFiles {
|
||||
bufWriter, err := indexes.SkelIndex(component, relPath).BufWriter()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate skeleton index: %v", err)
|
||||
}
|
||||
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read skeleton file: %v", err)
|
||||
}
|
||||
|
||||
_, err = bufio.NewReader(file).WriteTo(bufWriter)
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write skeleton file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass-through AppStream (DEP-11) files from snapshots
|
||||
for component, item := range p.sourceItems {
|
||||
if item.snapshot == nil || len(item.snapshot.AppStreamFiles) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := component + "/"
|
||||
for relPath, poolPath := range item.snapshot.AppStreamFiles {
|
||||
if !strings.HasPrefix(relPath, prefix) {
|
||||
continue
|
||||
}
|
||||
withinComponent := strings.TrimPrefix(relPath, prefix)
|
||||
|
||||
poolFile, err := packagePool.Open(poolPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open AppStream file from pool: %v", err)
|
||||
}
|
||||
|
||||
bufWriter, err := indexes.SkelIndex(component, withinComponent).BufWriter()
|
||||
if err != nil {
|
||||
_ = poolFile.Close()
|
||||
return fmt.Errorf("unable to generate AppStream index: %v", err)
|
||||
}
|
||||
|
||||
_, err = bufio.NewReader(poolFile).WriteTo(bufWriter)
|
||||
_ = poolFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write AppStream file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arch := range p.Architectures {
|
||||
for _, udeb := range []bool{true, false} {
|
||||
index := legacyContentIndexes[fmt.Sprintf("%s-%v", arch, udeb)]
|
||||
@@ -1216,8 +1208,8 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
||||
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetimeformat)
|
||||
}
|
||||
if p.Version != "" {
|
||||
release["Version"] = p.Version
|
||||
}
|
||||
release["Version"] = p.Version
|
||||
}
|
||||
release["Description"] = " Generated by aptly\n"
|
||||
release["MD5Sum"] = ""
|
||||
release["SHA1"] = ""
|
||||
|
||||
@@ -6,11 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
@@ -382,83 +379,6 @@ func (s *PublishedRepoSuite) TestUpdate(c *C) {
|
||||
c.Assert(result.RemovedComponents(), DeepEquals, []string{})
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) checkArchitectureVariantPublication(c *C, architectures []string) {
|
||||
list := NewPackageList()
|
||||
for _, arch := range []string{"amd64", "amd64v3"} {
|
||||
filename := "example_1.0_" + arch + ".deb"
|
||||
stanza := Stanza{"Package": "example", "Version": "1.0", "Architecture": "amd64", "Filename": filename}
|
||||
if arch == "amd64v3" {
|
||||
stanza["Architecture-Variant"] = "amd64v3"
|
||||
}
|
||||
p := NewPackageFromControlFile(stanza)
|
||||
inputPath := filepath.Join(c.MkDir(), filename)
|
||||
c.Assert(os.WriteFile(inputPath, nil, 0644), IsNil)
|
||||
var err error
|
||||
p.Files()[0].PoolPath, err = s.packagePool.Import(inputPath, filename, &p.Files()[0].Checksums, false, s.cs)
|
||||
c.Assert(err, IsNil)
|
||||
p.UpdateFiles(p.Files())
|
||||
c.Assert(s.packageCollection.Update(p), IsNil)
|
||||
c.Assert(list.Add(p), IsNil)
|
||||
}
|
||||
local := NewLocalRepo("architecture-variant", "")
|
||||
local.packageRefs = NewPackageRefListFromPackageList(list)
|
||||
c.Assert(s.factory.LocalRepoCollection().Add(local), IsNil)
|
||||
repo, err := NewPublishedRepo("", "variant-test", "test", architectures, []string{"main"}, []interface{}{local}, s.factory, false)
|
||||
c.Assert(err, IsNil)
|
||||
repo.SkipContents = true
|
||||
c.Assert(repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, ""), IsNil)
|
||||
|
||||
root := filepath.Join(s.publishedStorage.PublicPath(), "variant-test")
|
||||
releaseData, err := os.ReadFile(filepath.Join(root, "dists/test/Release"))
|
||||
c.Assert(err, IsNil)
|
||||
release, err := NewControlFileReader(bytes.NewReader(releaseData), true, false).ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(release["Architectures"], Equals, "amd64 amd64v3")
|
||||
|
||||
for _, arch := range []string{"amd64", "amd64v3"} {
|
||||
filename := "example_1.0_" + arch + ".deb"
|
||||
c.Check(filepath.Join(root, "pool/main/e/example", filename), PathExists)
|
||||
indexPath := filepath.Join(root, "dists/test/main", "binary-"+arch, "Packages")
|
||||
data, err := os.ReadFile(indexPath)
|
||||
if !c.Check(err, IsNil, Commentf("index %s must exist", arch)) {
|
||||
continue
|
||||
}
|
||||
reader := NewControlFileReader(bytes.NewReader(data), false, false)
|
||||
var entries []Stanza
|
||||
for {
|
||||
entry, err := reader.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
if entry == nil {
|
||||
break
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
c.Check(entries, HasLen, 1, Commentf("index %s must contain exactly one package", arch))
|
||||
var filenames []string
|
||||
for _, entry := range entries {
|
||||
filenames = append(filenames, entry["Filename"])
|
||||
c.Check(entry["Package"], Equals, "example")
|
||||
c.Check(entry["Version"], Equals, "1.0")
|
||||
c.Check(entry["Architecture"], Equals, "amd64")
|
||||
if entry["Filename"] == "pool/main/e/example/example_1.0_amd64v3.deb" {
|
||||
c.Check(entry["Architecture-Variant"], Equals, "amd64v3")
|
||||
} else {
|
||||
_, hasVariant := entry["Architecture-Variant"]
|
||||
c.Check(hasVariant, Equals, false)
|
||||
}
|
||||
}
|
||||
c.Check(filenames, DeepEquals, []string{"pool/main/e/example/" + filename}, Commentf("index %s package membership", arch))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestArchitectureVariantPublishAutoArchitectures(c *C) {
|
||||
s.checkArchitectureVariantPublication(c, nil)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestArchitectureVariantPublishExplicitArchitectures(c *C) {
|
||||
s.checkArchitectureVariantPublication(c, []string{"amd64", "amd64v3"})
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublish(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -581,240 +501,6 @@ func (s *PublishedRepoSuite) TestPublishAppStream(c *C) {
|
||||
c.Assert(err, ErrorMatches, "unable to open AppStream file from pool.*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) importAppStreamFile(c *C, name string, content []byte) string {
|
||||
tmpFile := filepath.Join(c.MkDir(), name)
|
||||
c.Assert(os.WriteFile(tmpFile, content, 0644), IsNil)
|
||||
|
||||
checksums := utils.ChecksumInfo{Size: int64(len(content))}
|
||||
poolPath, err := s.packagePool.Import(tmpFile, name, &checksums, false, s.cs)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
return poolPath
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishAppStreamMultipleComponents(c *C) {
|
||||
mainContent := []byte("DEP-11 content for main\n")
|
||||
contribContent := []byte("DEP-11 content for contrib\n")
|
||||
|
||||
s.snapshot.AppStreamFiles = map[string]string{
|
||||
"main/dep11/Components-amd64.yml.gz": s.importAppStreamFile(c, "Components-main.yml.gz", mainContent),
|
||||
}
|
||||
s.snapshot2.AppStreamFiles = map[string]string{
|
||||
"contrib/dep11/Components-amd64.yml.gz": s.importAppStreamFile(c, "Components-contrib.yml.gz", contribContent),
|
||||
}
|
||||
|
||||
err := s.repo3.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
base := filepath.Join(s.publishedStorage.PublicPath(), "linux/dists/natty")
|
||||
|
||||
published, err := os.ReadFile(filepath.Join(base, "main/dep11/Components-amd64.yml.gz"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(published, DeepEquals, mainContent)
|
||||
|
||||
published, err = os.ReadFile(filepath.Join(base, "contrib/dep11/Components-amd64.yml.gz"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(published, DeepEquals, contribContent)
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishAppStreamAcquireByHash(c *C) {
|
||||
s.snapshot.AppStreamFiles = map[string]string{
|
||||
"main/dep11/Components-amd64.yml.gz": s.importAppStreamFile(c, "Components-amd64.yml.gz", []byte("DEP-11 content\n")),
|
||||
}
|
||||
|
||||
s.repo.AcquireByHash = true
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
base := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze")
|
||||
c.Check(filepath.Join(base, "main/dep11/by-hash/SHA256"), PathExists)
|
||||
}
|
||||
|
||||
// setupSmallTempFS points TMPDIR at a ~1MB filesystem so that Publish's
|
||||
// temporary index files run out of space mid-write. Follows the DiskFullSuite
|
||||
// convention: /smallfs is pre-mounted in CI (see docker-unit-test), a loopback
|
||||
// mount is created when running as root, otherwise the test is skipped.
|
||||
func (s *PublishedRepoSuite) setupSmallTempFS(c *C) func() {
|
||||
if runtime.GOOS != "linux" {
|
||||
c.Skip("disk full tests only run on Linux")
|
||||
}
|
||||
|
||||
mountPoint := "/smallfs"
|
||||
mounted := false
|
||||
if os.Geteuid() == 0 {
|
||||
mountPoint = filepath.Join(c.MkDir(), "smallfs")
|
||||
c.Assert(os.MkdirAll(mountPoint, 0777), IsNil)
|
||||
fsImage := filepath.Join(c.MkDir(), "small.img")
|
||||
c.Assert(exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1").Run(), IsNil)
|
||||
c.Assert(exec.Command("mkfs.ext4", "-F", fsImage).Run(), IsNil)
|
||||
c.Assert(exec.Command("mount", "-o", "loop", fsImage, mountPoint).Run(), IsNil)
|
||||
mounted = true
|
||||
} else if _, err := os.Stat(mountPoint); err != nil {
|
||||
c.Skip("/smallfs is not mounted")
|
||||
}
|
||||
|
||||
// remove leftovers from other tests sharing the mount
|
||||
entries, err := os.ReadDir(mountPoint)
|
||||
c.Assert(err, IsNil)
|
||||
for _, entry := range entries {
|
||||
if entry.Name() == "lost+found" {
|
||||
continue
|
||||
}
|
||||
c.Assert(os.RemoveAll(filepath.Join(mountPoint, entry.Name())), IsNil)
|
||||
}
|
||||
|
||||
oldTMPDIR, hadTMPDIR := os.LookupEnv("TMPDIR")
|
||||
c.Assert(os.Setenv("TMPDIR", mountPoint), IsNil)
|
||||
|
||||
return func() {
|
||||
if hadTMPDIR {
|
||||
_ = os.Setenv("TMPDIR", oldTMPDIR)
|
||||
} else {
|
||||
_ = os.Unsetenv("TMPDIR")
|
||||
}
|
||||
if mounted {
|
||||
_ = exec.Command("umount", mountPoint).Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFiles(c *C) {
|
||||
skelDir := c.MkDir()
|
||||
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
|
||||
c.Assert(os.MkdirAll(filepath.Join(dir, "extra"), 0755), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(dir, "top.txt"), []byte("top-level skel file\n"), 0644), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(dir, "extra", "metadata.json"), []byte("{\"nested\": true}\n"), 0644), IsNil)
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
base := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main")
|
||||
|
||||
published, err := os.ReadFile(filepath.Join(base, "top.txt"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(published), Equals, "top-level skel file\n")
|
||||
|
||||
published, err = os.ReadFile(filepath.Join(base, "extra", "metadata.json"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(published), Equals, "{\"nested\": true}\n")
|
||||
|
||||
// Release file should reference skel files
|
||||
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = rf.Close() }()
|
||||
|
||||
cfr := NewControlFileReader(rf, true, false)
|
||||
st, err := cfr.ReadStanza()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(st["SHA256"], Matches, "(?s).*main/top\\.txt.*")
|
||||
c.Check(st["SHA256"], Matches, "(?s).*main/extra/metadata\\.json.*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFilesMultipleComponents(c *C) {
|
||||
// regression test: skel files used to be emitted once per component,
|
||||
// appending each payload to the index again (issue fixed in this change)
|
||||
skelDir := c.MkDir()
|
||||
mainDir := filepath.Join(skelDir, "linux", "dists", "natty", "main")
|
||||
contribDir := filepath.Join(skelDir, "linux", "dists", "natty", "contrib")
|
||||
c.Assert(os.MkdirAll(mainDir, 0755), IsNil)
|
||||
c.Assert(os.MkdirAll(contribDir, 0755), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(mainDir, "main.txt"), []byte("skel for main\n"), 0644), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(contribDir, "contrib.txt"), []byte("skel for contrib\n"), 0644), IsNil)
|
||||
|
||||
err := s.repo3.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
base := filepath.Join(s.publishedStorage.PublicPath(), "linux/dists/natty")
|
||||
|
||||
// each payload must be published exactly once, not duplicated per component
|
||||
published, err := os.ReadFile(filepath.Join(base, "main/main.txt"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(published), Equals, "skel for main\n")
|
||||
|
||||
published, err = os.ReadFile(filepath.Join(base, "contrib/contrib.txt"))
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(string(published), Equals, "skel for contrib\n")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFilesWalkError(c *C) {
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Skip("a file in the middle of a path is reported as not-exist on Windows")
|
||||
}
|
||||
|
||||
// "dists" is a regular file, so walking skelDir/ppa/dists/squeeze/main
|
||||
// fails with ENOTDIR, which is not swallowed as a not-exist error
|
||||
skelDir := c.MkDir()
|
||||
c.Assert(os.MkdirAll(filepath.Join(skelDir, "ppa"), 0755), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(skelDir, "ppa", "dists"), []byte("not a directory"), 0644), IsNil)
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, ErrorMatches, "unable to get skeleton files: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFilesIndexError(c *C) {
|
||||
// each name stays within NAME_MAX, but BufWriter flattens the relative
|
||||
// path into a single temporary file name that exceeds it
|
||||
skelDir := c.MkDir()
|
||||
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main", strings.Repeat("a", 200))
|
||||
c.Assert(os.MkdirAll(dir, 0755), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(dir, strings.Repeat("b", 200)), []byte("skel content"), 0644), IsNil)
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, ErrorMatches, "unable to generate skeleton index: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFilesOpenError(c *C) {
|
||||
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
|
||||
c.Skip("requires POSIX permissions and a non-root user")
|
||||
}
|
||||
|
||||
skelDir := c.MkDir()
|
||||
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
|
||||
c.Assert(os.MkdirAll(dir, 0755), IsNil)
|
||||
skelFile := filepath.Join(dir, "InRelease")
|
||||
c.Assert(os.WriteFile(skelFile, []byte("skel content"), 0644), IsNil)
|
||||
c.Assert(os.Chmod(skelFile, 0), IsNil)
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, ErrorMatches, "unable to read skeleton file: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishSkelFilesWriteError(c *C) {
|
||||
cleanup := s.setupSmallTempFS(c)
|
||||
defer cleanup()
|
||||
|
||||
skelDir := c.MkDir()
|
||||
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
|
||||
c.Assert(os.MkdirAll(dir, 0755), IsNil)
|
||||
c.Assert(os.WriteFile(filepath.Join(dir, "Contents-huge"), bytes.Repeat([]byte{'x'}, 2*1024*1024), 0644), IsNil)
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
|
||||
c.Assert(err, ErrorMatches, "unable to write skeleton file: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishAppStreamIndexError(c *C) {
|
||||
// flattened temporary file name for the index exceeds NAME_MAX
|
||||
s.snapshot.AppStreamFiles = map[string]string{
|
||||
"main/" + strings.Repeat("a", 200) + "/" + strings.Repeat("b", 200): s.importAppStreamFile(c, "Components-amd64.yml.gz", []byte("DEP-11 content\n")),
|
||||
}
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||
c.Assert(err, ErrorMatches, "unable to generate AppStream index: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishAppStreamWriteError(c *C) {
|
||||
cleanup := s.setupSmallTempFS(c)
|
||||
defer cleanup()
|
||||
|
||||
s.snapshot.AppStreamFiles = map[string]string{
|
||||
"main/dep11/Components-amd64.yml.gz": s.importAppStreamFile(c, "Components-amd64.yml.gz", bytes.Repeat([]byte{'x'}, 2*1024*1024)),
|
||||
}
|
||||
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
||||
c.Assert(err, ErrorMatches, "unable to write AppStream file: .*")
|
||||
}
|
||||
|
||||
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
+6
-15
@@ -168,20 +168,15 @@ func (q *FieldQuery) Matches(pkg PackageLike) bool {
|
||||
case VersionDontCare:
|
||||
return field != ""
|
||||
case VersionEqual:
|
||||
result, err := CompareVersions(field, q.Value)
|
||||
return err == nil && result == 0
|
||||
return CompareVersions(field, q.Value) == 0
|
||||
case VersionGreater:
|
||||
result, err := CompareVersions(field, q.Value)
|
||||
return err == nil && result > 0
|
||||
return CompareVersions(field, q.Value) > 0
|
||||
case VersionGreaterOrEqual:
|
||||
result, err := CompareVersions(field, q.Value)
|
||||
return err == nil && result >= 0
|
||||
return CompareVersions(field, q.Value) >= 0
|
||||
case VersionLess:
|
||||
result, err := CompareVersions(field, q.Value)
|
||||
return err == nil && result < 0
|
||||
return CompareVersions(field, q.Value) < 0
|
||||
case VersionLessOrEqual:
|
||||
result, err := CompareVersions(field, q.Value)
|
||||
return err == nil && result <= 0
|
||||
return CompareVersions(field, q.Value) <= 0
|
||||
case VersionPatternMatch:
|
||||
matched, err := filepath.Match(q.Value, field)
|
||||
return err == nil && matched
|
||||
@@ -266,11 +261,7 @@ func (q *DependencyQuery) String() string {
|
||||
|
||||
// Matches on specific properties
|
||||
func (q *PkgQuery) Matches(pkg PackageLike) bool {
|
||||
arch := pkg.GetArchitecture()
|
||||
if indexed, ok := pkg.(interface{ IndexArchitecture() string }); ok {
|
||||
arch = indexed.IndexArchitecture()
|
||||
}
|
||||
return pkg.GetName() == q.Pkg && pkg.GetVersion() == q.Version && arch == q.Arch
|
||||
return pkg.GetName() == q.Pkg && pkg.GetVersion() == q.Version && pkg.GetArchitecture() == q.Arch
|
||||
}
|
||||
|
||||
// Fast is always true for package query
|
||||
|
||||
@@ -21,43 +21,3 @@ func (s *QuerySuite) TestVersionCompare(c *C) {
|
||||
c.Check(q.Matches(&p100), Equals, false)
|
||||
c.Check(q.Matches(&p1), Equals, true)
|
||||
}
|
||||
|
||||
func (s *QuerySuite) TestVersionCompareGreater(c *C) {
|
||||
q := FieldQuery{"Version", VersionGreater, "5.0.0.2", nil}
|
||||
|
||||
p100 := Package{}
|
||||
p100.Version = "5.0.0.100"
|
||||
|
||||
p1 := Package{}
|
||||
p1.Version = "5.0.0.1"
|
||||
|
||||
c.Check(q.Matches(&p100), Equals, true)
|
||||
c.Check(q.Matches(&p1), Equals, false)
|
||||
|
||||
// invalid version on either side of the comparison must not match
|
||||
pInvalid := Package{}
|
||||
pInvalid.Version = "1.2.3-"
|
||||
c.Check(q.Matches(&pInvalid), Equals, false)
|
||||
}
|
||||
|
||||
func (s *QuerySuite) TestVersionCompareGreaterOrEqual(c *C) {
|
||||
q := FieldQuery{"Version", VersionGreaterOrEqual, "5.0.0.2", nil}
|
||||
|
||||
p100 := Package{}
|
||||
p100.Version = "5.0.0.100"
|
||||
|
||||
pEqual := Package{}
|
||||
pEqual.Version = "5.0.0.2"
|
||||
|
||||
p1 := Package{}
|
||||
p1.Version = "5.0.0.1"
|
||||
|
||||
c.Check(q.Matches(&p100), Equals, true)
|
||||
c.Check(q.Matches(&pEqual), Equals, true)
|
||||
c.Check(q.Matches(&p1), Equals, false)
|
||||
|
||||
// invalid version on either side of the comparison must not match
|
||||
pInvalid := Package{}
|
||||
pInvalid.Version = "1.2.3-"
|
||||
c.Check(q.Matches(&pInvalid), Equals, false)
|
||||
}
|
||||
+19
-23
@@ -240,7 +240,7 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle
|
||||
if rel < 0 {
|
||||
// compaction: +(,A) -(B,) --> !(A,B)
|
||||
if len(result) > 0 && result[len(result)-1].Left == nil && result[len(result)-1].Right.Name == pl.Name &&
|
||||
result[len(result)-1].Right.IndexArchitecture() == pl.IndexArchitecture() {
|
||||
result[len(result)-1].Right.Architecture == pl.Architecture {
|
||||
result[len(result)-1] = PackageDiff{Left: pl, Right: result[len(result)-1].Right}
|
||||
} else {
|
||||
result = append(result, PackageDiff{Left: pl, Right: nil})
|
||||
@@ -250,7 +250,7 @@ func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageColle
|
||||
} else {
|
||||
// compaction: -(A,) +(,B) --> !(A,B)
|
||||
if len(result) > 0 && result[len(result)-1].Right == nil && result[len(result)-1].Left.Name == pr.Name &&
|
||||
result[len(result)-1].Left.IndexArchitecture() == pr.IndexArchitecture() {
|
||||
result[len(result)-1].Left.Architecture == pr.Architecture {
|
||||
result[len(result)-1] = PackageDiff{Left: result[len(result)-1].Left, Right: pr}
|
||||
} else {
|
||||
result = append(result, PackageDiff{Left: nil, Right: pr})
|
||||
@@ -365,36 +365,32 @@ func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConfli
|
||||
func (l *PackageRefList) FilterLatestRefs() {
|
||||
var (
|
||||
lastArch, lastName, lastVer []byte
|
||||
arch, name, ver []byte
|
||||
parts [][]byte
|
||||
)
|
||||
|
||||
filtered := l.Refs[:0]
|
||||
for _, ref := range l.Refs {
|
||||
parts := bytes.Split(ref[1:], []byte(" "))
|
||||
arch, name, ver := parts[0], parts[1], parts[2]
|
||||
for i := 0; i < len(l.Refs); i++ {
|
||||
parts = bytes.Split(l.Refs[i][1:], []byte(" "))
|
||||
arch, name, ver = parts[0], parts[1], parts[2]
|
||||
|
||||
if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) {
|
||||
// The previous ref is valid, so this also validates the current ref.
|
||||
vres, err := CompareVersions(string(ver), string(lastVer))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Two packages are identical, check version and only one wins
|
||||
vres := CompareVersions(string(ver), string(lastVer))
|
||||
|
||||
// Replace the older ref in the result.
|
||||
// Remove the older refs from the result
|
||||
if vres > 0 {
|
||||
filtered[len(filtered)-1] = ref
|
||||
lastArch, lastName, lastVer = arch, name, ver
|
||||
// ver[i] > ver[i-1], remove element i-1
|
||||
l.Refs = append(l.Refs[:i-1], l.Refs[i:]...)
|
||||
} else {
|
||||
// ver[i] < ver[i-1], remove element i
|
||||
l.Refs = append(l.Refs[:i], l.Refs[i+1:]...)
|
||||
arch, name, ver = lastArch, lastName, lastVer
|
||||
}
|
||||
continue
|
||||
|
||||
// Compensate for the reduced set
|
||||
i--
|
||||
}
|
||||
|
||||
// Comparing a version with itself validates singleton refs.
|
||||
if _, err := CompareVersions(string(ver), string(ver)); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
filtered = append(filtered, ref)
|
||||
lastArch, lastName, lastVer = arch, name, ver
|
||||
}
|
||||
|
||||
l.Refs = filtered
|
||||
}
|
||||
@@ -398,118 +398,3 @@ func (s *PackageRefListSuite) TestFilterLatestRefs(c *C) {
|
||||
c.Check(toStrSlice(result), DeepEquals,
|
||||
[]string{"Pi386 dpkg 1.6", "Pi386 lib 1.2"})
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestFilterLatestRefsRemovesInvalidVersions(c *C) {
|
||||
result := &PackageRefList{Refs: [][]byte{
|
||||
[]byte("Pi386 app 1.0-"),
|
||||
[]byte("Pi386 dpkg 1.0"),
|
||||
[]byte("Pi386 dpkg 2.0-"),
|
||||
[]byte("Pi386 lib 1.0-"),
|
||||
[]byte("Pi386 lib 2.0-"),
|
||||
}}
|
||||
|
||||
result.FilterLatestRefs()
|
||||
|
||||
c.Check(toStrSlice(result), DeepEquals, []string{"Pi386 dpkg 1.0"})
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestArchitectureVariantCoexistence(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64", V06Plus: true}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3", V06Plus: true}
|
||||
c.Assert(s.list.Add(normal), IsNil)
|
||||
c.Assert(s.list.Add(variant), IsNil)
|
||||
|
||||
refs := NewPackageRefListFromPackageList(s.list)
|
||||
c.Check(toStrSlice(refs), DeepEquals, []string{
|
||||
"Pamd64 example 1.0 00000000",
|
||||
"Pamd64v3 example 1.0 00000000",
|
||||
})
|
||||
c.Check(refs.Has(normal), Equals, true)
|
||||
c.Check(refs.Has(variant), Equals, true)
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestArchitectureVariantMerge(c *C) {
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64", V06Plus: true}
|
||||
variant := &Package{Name: "example", Version: "1.0", Architecture: "amd64", ArchitectureVariant: "amd64v3", V06Plus: true}
|
||||
left, right := NewPackageList(), NewPackageList()
|
||||
c.Assert(left.Add(normal), IsNil)
|
||||
c.Assert(right.Add(variant), IsNil)
|
||||
leftRefs := NewPackageRefListFromPackageList(left)
|
||||
rightRefs := NewPackageRefListFromPackageList(right)
|
||||
|
||||
for _, overrideMatching := range []bool{false, true} {
|
||||
for _, ignoreConflicting := range []bool{false, true} {
|
||||
for _, reverse := range []bool{false, true} {
|
||||
l, r := leftRefs, rightRefs
|
||||
if reverse {
|
||||
l, r = r, l
|
||||
}
|
||||
merged := l.Merge(r, overrideMatching, ignoreConflicting)
|
||||
c.Check(toStrSlice(merged), DeepEquals, []string{
|
||||
"Pamd64 example 1.0 00000000",
|
||||
"Pamd64v3 example 1.0 00000000",
|
||||
}, Commentf("overrideMatching=%v ignoreConflicting=%v reverse=%v", overrideMatching, ignoreConflicting, reverse))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestArchitectureVariantFilterLatestRefs(c *C) {
|
||||
for _, variant := range []string{"", "amd64v3"} {
|
||||
for _, version := range []string{"1.0", "2.0"} {
|
||||
c.Assert(s.list.Add(&Package{
|
||||
Name: "example", Version: version, Architecture: "amd64", ArchitectureVariant: variant, V06Plus: true,
|
||||
}), IsNil)
|
||||
}
|
||||
}
|
||||
refs := NewPackageRefListFromPackageList(s.list)
|
||||
c.Assert(refs.Len(), Equals, 4)
|
||||
refs.FilterLatestRefs()
|
||||
c.Check(toStrSlice(refs), DeepEquals, []string{
|
||||
"Pamd64 example 2.0 00000000",
|
||||
"Pamd64v3 example 2.0 00000000",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PackageRefListSuite) TestArchitectureVariantDiffCompaction(c *C) {
|
||||
db, err := goleveldb.NewOpenDB(c.MkDir())
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { c.Check(db.Close(), IsNil) }()
|
||||
coll := NewPackageCollection(db)
|
||||
|
||||
normal := &Package{Name: "example", Version: "1.0", Architecture: "amd64", V06Plus: true}
|
||||
// Equal and differing versions must both remain separate additions/removals.
|
||||
for _, version := range []string{"1.0", "2.0"} {
|
||||
variant := &Package{Name: "example", Version: version, Architecture: "amd64", ArchitectureVariant: "amd64v3", V06Plus: true}
|
||||
c.Assert(coll.Update(normal), IsNil)
|
||||
c.Assert(coll.Update(variant), IsNil)
|
||||
left, right := NewPackageList(), NewPackageList()
|
||||
c.Assert(left.Add(normal), IsNil)
|
||||
c.Assert(right.Add(variant), IsNil)
|
||||
leftRefs := NewPackageRefListFromPackageList(left)
|
||||
rightRefs := NewPackageRefListFromPackageList(right)
|
||||
|
||||
for _, reverse := range []bool{false, true} {
|
||||
l, r := leftRefs, rightRefs
|
||||
if reverse {
|
||||
l, r = r, l
|
||||
}
|
||||
diff, err := l.Diff(r, coll)
|
||||
c.Assert(err, IsNil)
|
||||
context := Commentf("variant version=%s reverse=%v", version, reverse)
|
||||
c.Check(diff, HasLen, 2, context)
|
||||
var removed, added []string
|
||||
for _, entry := range diff {
|
||||
c.Check(entry.Left != nil && entry.Right != nil, Equals, false, context)
|
||||
if entry.Left != nil {
|
||||
removed = append(removed, string(entry.Left.Key("")))
|
||||
}
|
||||
if entry.Right != nil {
|
||||
added = append(added, string(entry.Right.Key("")))
|
||||
}
|
||||
}
|
||||
c.Check(removed, DeepEquals, toStrSlice(l), context)
|
||||
c.Check(added, DeepEquals, toStrSlice(r), context)
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-22
@@ -682,11 +682,6 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
|
||||
emptyList := NewPackageList()
|
||||
emptyList.PrepareIndex()
|
||||
|
||||
architectures := repo.packageList.Architectures(false)
|
||||
if len(architectures) == 0 {
|
||||
architectures = repo.Architectures
|
||||
}
|
||||
|
||||
oldLen = repo.packageList.Len()
|
||||
repo.packageList, err = repo.packageList.Filter(FilterOptions{
|
||||
Queries: []PackageQuery{filterQuery},
|
||||
@@ -694,7 +689,7 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
|
||||
Source: emptyList,
|
||||
WithSources: repo.DownloadSources,
|
||||
DependencyOptions: dependencyOptions,
|
||||
Architectures: architectures,
|
||||
Architectures: repo.Architectures,
|
||||
Progress: progress,
|
||||
})
|
||||
if repo.packageList != nil {
|
||||
@@ -703,28 +698,20 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
|
||||
return
|
||||
}
|
||||
|
||||
// FilterLatest keeps only count latest versions of each package (per architecture)
|
||||
func (repo *RemoteRepo) FilterLatest(count int) (oldLen, newLen int, err error) {
|
||||
if repo.packageList == nil {
|
||||
err = fmt.Errorf("package list is empty, please (re)download package indexes")
|
||||
return
|
||||
}
|
||||
|
||||
oldLen = repo.packageList.Len()
|
||||
repo.packageList, err = repo.packageList.FilterLatest(count)
|
||||
if repo.packageList != nil {
|
||||
newLen = repo.packageList.Len()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BuildDownloadQueue builds queue, discards current PackageList
|
||||
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
|
||||
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages, latestOnly bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
|
||||
if repo.packageList == nil {
|
||||
err = fmt.Errorf("package list is empty, please (re)download package indexes")
|
||||
return
|
||||
}
|
||||
|
||||
if latestOnly {
|
||||
repo.packageList, err = repo.packageList.FilterLatest()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
|
||||
seen := make(map[string]int, repo.packageList.Len())
|
||||
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ApplyFilter operates on the package list already parsed from downloaded
|
||||
// indexes. All architecture information here comes from control metadata.
|
||||
func TestRemoteRepoApplyFilterArchitectureVariant(t *testing.T) {
|
||||
binary := func(name, variant, arch, depends, source string) *Package {
|
||||
control := "Package: " + name + "\nVersion: 1\nArchitecture: " + arch + "\n"
|
||||
if variant != "" {
|
||||
control += "Architecture-Variant: " + variant + "\n"
|
||||
}
|
||||
if depends != "" {
|
||||
control += "Depends: " + depends + "\n"
|
||||
}
|
||||
if source != "" {
|
||||
control += "Source: " + source + "\n"
|
||||
}
|
||||
stanza, err := NewControlFileReader(strings.NewReader(control), false, false).ReadStanza()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return NewPackageFromControlFile(stanza)
|
||||
}
|
||||
source, err := NewSourcePackageFromControlFile(Stanza{
|
||||
"Package": "source-app", "Version": "1", "Architecture": "any all", "Build-Depends": "build-tool",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
appNormal := binary("app", "", "amd64", "helper", "")
|
||||
appVariant := binary("app", "amd64v3", "amd64", "helper", "")
|
||||
helperNormal := binary("helper", "", "amd64", "", "")
|
||||
helperVariant := binary("helper", "amd64v3", "amd64", "", "")
|
||||
helperAll := binary("helper", "", "all", "", "")
|
||||
appAll := binary("app", "", "all", "helper", "")
|
||||
sourceVariant := binary("app", "amd64v3", "amd64", "", "source-app")
|
||||
sourceNormal := binary("app", "", "amd64", "", "source-app")
|
||||
buildTool := binary("build-tool", "", "amd64", "", "")
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
indexes []string
|
||||
input, want []*Package
|
||||
queryName string
|
||||
withDeps, withSources bool
|
||||
options int
|
||||
}{
|
||||
{"variant_index_normal_dependency", []string{"amd64v3"}, []*Package{appVariant, helperNormal}, []*Package{appVariant, helperNormal}, "app", true, false, 0},
|
||||
{"variant_index_variant_dependency", []string{"amd64v3"}, []*Package{appVariant, helperVariant}, []*Package{appVariant, helperVariant}, "app", true, false, 0},
|
||||
{"variant_index_all_dependency", []string{"amd64v3"}, []*Package{appVariant, helperAll}, []*Package{appVariant, helperAll}, "app", true, false, 0},
|
||||
{"variant_index_all_selected", []string{"amd64v3"}, []*Package{appAll, helperVariant}, []*Package{appAll, helperVariant}, "app", true, false, 0},
|
||||
{"mixed_indexes_shared_dependency", []string{"amd64", "amd64v3"}, []*Package{appNormal, appVariant, helperNormal}, []*Package{appNormal, appVariant, helperNormal}, "app", true, false, 0},
|
||||
{"mixed_indexes_shared_all_dependency", []string{"amd64", "amd64v3"}, []*Package{appNormal, appVariant, helperAll}, []*Package{appNormal, appVariant, helperAll}, "app", true, false, 0},
|
||||
{"normal_index_binary_dependency", []string{"amd64"}, []*Package{appNormal, helperNormal}, []*Package{appNormal, helperNormal}, "app", true, false, 0},
|
||||
{"normal_index_all_dependency", []string{"amd64"}, []*Package{appNormal, helperAll}, []*Package{appNormal, helperAll}, "app", true, false, 0},
|
||||
{"dependencies_disabled", []string{"amd64v3"}, []*Package{appVariant, helperVariant}, []*Package{appVariant}, "app", false, false, 0},
|
||||
{"variant_follow_source", []string{"amd64v3"}, []*Package{sourceVariant, source}, []*Package{sourceVariant, source}, "app", true, false, DepFollowSource},
|
||||
{"normal_follow_source", []string{"amd64"}, []*Package{sourceNormal, source}, []*Package{sourceNormal, source}, "app", true, false, DepFollowSource},
|
||||
{"download_sources_explicit_name", []string{"amd64v3"}, []*Package{sourceVariant, source}, []*Package{sourceVariant, source}, "app", true, true, 0},
|
||||
// These protect existing behavior against blindly passing Architectures(false).
|
||||
{"only_all_normal_index", []string{"amd64"}, []*Package{appAll, helperAll}, []*Package{appAll, helperAll}, "app", true, false, 0},
|
||||
{"only_all_variant_index", []string{"amd64v3"}, []*Package{appAll, helperAll}, []*Package{appAll, helperAll}, "app", true, false, 0},
|
||||
{"source_only", []string{"amd64"}, []*Package{source}, []*Package{source}, "source-app", true, true, DepFollowBuild},
|
||||
// Mirrors do not currently walk source-package build dependencies. Retaining
|
||||
// that behavior avoids treating the source pseudo-architecture as a CPU arch.
|
||||
{"source_build_dependencies_unchanged", []string{"amd64"}, []*Package{source, buildTool}, []*Package{source}, "source-app", true, true, DepFollowBuild},
|
||||
{"empty_selection", []string{"amd64v3"}, []*Package{appVariant, helperVariant}, nil, "absent", true, false, 0},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
list := NewPackageList()
|
||||
for _, p := range tc.input {
|
||||
if err := list.Add(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
bases := list.Architectures(false)
|
||||
sort.Strings(bases)
|
||||
t.Logf("index architectures=%v; metadata base architectures=%v", tc.indexes, bases)
|
||||
repo := &RemoteRepo{Architectures: append([]string{}, tc.indexes...), FilterWithDeps: tc.withDeps, DownloadSources: tc.withSources, packageList: list}
|
||||
oldLen, newLen, err := repo.ApplyFilter(tc.options, &FieldQuery{Field: "Name", Relation: VersionEqual, Value: tc.queryName}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if oldLen != len(tc.input) {
|
||||
t.Errorf("oldLen=%d, want %d", oldLen, len(tc.input))
|
||||
}
|
||||
got := repo.packageList.FullNames()
|
||||
want := make([]string, 0, len(tc.want))
|
||||
for _, p := range tc.want {
|
||||
want = append(want, p.GetFullName())
|
||||
}
|
||||
sort.Strings(got)
|
||||
sort.Strings(want)
|
||||
if newLen != len(tc.want) || !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("filtered packages (newLen=%d):\n got %v\nwant %v", newLen, got, want)
|
||||
}
|
||||
if !reflect.DeepEqual(repo.Architectures, tc.indexes) {
|
||||
t.Errorf("index configuration changed: %v", repo.Architectures)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+15
-123
@@ -1,7 +1,6 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -309,97 +308,6 @@ func (s *RemoteRepoSuite) TestRefKey(c *C) {
|
||||
c.Assert(s.repo.RefKey()[1:], DeepEquals, s.repo.Key()[1:])
|
||||
}
|
||||
|
||||
type architectureVariantRemoteProgress struct {
|
||||
aptly.Progress
|
||||
messages []string
|
||||
}
|
||||
|
||||
func (p *architectureVariantRemoteProgress) ColoredPrintf(format string, args ...interface{}) {
|
||||
p.messages = append(p.messages, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) loadArchitectureVariantIndexes(c *C) {
|
||||
var err error
|
||||
s.repo, err = NewRemoteRepo("variant-test", "http://mirror.example.invalid/repo", "test", []string{"main"}, nil, false, false, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
const normalIndex = `Package: example
|
||||
Version: 1.0
|
||||
Architecture: amd64
|
||||
Filename: pool/main/e/example/example_1.0_amd64.deb
|
||||
Size: 3
|
||||
MD5sum: 900150983cd24fb0d6963f7d28e17f72
|
||||
|
||||
`
|
||||
const variantIndex = `Package: example
|
||||
Version: 1.0
|
||||
Architecture: amd64
|
||||
Architecture-Variant: amd64v3
|
||||
Filename: pool/main/e/example/example_1.0_amd64v3.deb
|
||||
Size: 4
|
||||
MD5sum: e2fc714c4727ee9395f324cd2e7f331f
|
||||
|
||||
`
|
||||
const root = "http://mirror.example.invalid/repo/dists/test/"
|
||||
release := fmt.Sprintf("Architectures: amd64 amd64v3\nComponents: main\nSHA256:\n %x %d main/binary-amd64/Packages\n %x %d main/binary-amd64v3/Packages\n",
|
||||
sha256.Sum256([]byte(normalIndex)), len(normalIndex), sha256.Sum256([]byte(variantIndex)), len(variantIndex))
|
||||
s.downloader = http.NewFakeDownloader().ExpectResponse(root+"Release", release)
|
||||
s.downloader.ExpectResponse(root+"main/binary-amd64/Packages", normalIndex)
|
||||
s.downloader.ExpectResponse(root+"main/binary-amd64v3/Packages", variantIndex)
|
||||
c.Assert(s.repo.Fetch(s.downloader, nil, true), IsNil)
|
||||
c.Check(s.repo.Architectures, DeepEquals, []string{"amd64", "amd64v3"})
|
||||
|
||||
progress := &architectureVariantRemoteProgress{Progress: s.progress}
|
||||
c.Assert(s.repo.DownloadPackageIndexes(progress, s.downloader, nil, s.collectionFactory, true, false), IsNil)
|
||||
c.Check(s.downloader.Empty(), Equals, true)
|
||||
// Duplicate rejection is reported through ColoredPrintf, not as an error.
|
||||
c.Check(progress.messages, HasLen, 0)
|
||||
c.Assert(s.repo.packageList.Len(), Equals, 2)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestArchitectureVariantPackageIndexes(c *C) {
|
||||
s.loadArchitectureVariantIndexes(c)
|
||||
packages := map[string]*Package{}
|
||||
c.Assert(s.repo.packageList.ForEach(func(p *Package) error {
|
||||
packages[p.IndexArchitecture()] = p
|
||||
return nil
|
||||
}), IsNil)
|
||||
c.Assert(packages, HasLen, 2)
|
||||
for _, arch := range []string{"amd64", "amd64v3"} {
|
||||
p := packages[arch]
|
||||
c.Assert(p, NotNil)
|
||||
c.Check(p.Name, Equals, "example")
|
||||
c.Check(p.Version, Equals, "1.0")
|
||||
c.Check(p.Architecture, Equals, "amd64")
|
||||
variant := ""
|
||||
if arch == "amd64v3" {
|
||||
variant = "amd64v3"
|
||||
}
|
||||
c.Check(p.ArchitectureVariant, Equals, variant)
|
||||
c.Check(p.IndexArchitecture(), Equals, arch)
|
||||
c.Assert(p.Files(), HasLen, 1)
|
||||
c.Check(p.Files()[0].Filename, Equals, "example_1.0_"+arch+".deb")
|
||||
c.Check(p.Files()[0].DownloadURL(), Equals, "pool/main/e/example/example_1.0_"+arch+".deb")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestArchitectureVariantDownloadQueue(c *C) {
|
||||
s.loadArchitectureVariantIndexes(c)
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(queue, HasLen, 2)
|
||||
c.Check(size, Equals, int64(7))
|
||||
var urls []string
|
||||
for _, task := range queue {
|
||||
urls = append(urls, task.File.DownloadURL())
|
||||
c.Check(task.Additional, HasLen, 0)
|
||||
}
|
||||
sort.Strings(urls)
|
||||
c.Check(urls, DeepEquals, []string{
|
||||
"pool/main/e/example/example_1.0_amd64.deb",
|
||||
"pool/main/e/example/example_1.0_amd64v3.deb",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
s.repo.Architectures = []string{"i386"}
|
||||
|
||||
@@ -414,7 +322,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
@@ -441,7 +349,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(0))
|
||||
c.Check(queue, HasLen, 0)
|
||||
@@ -462,7 +370,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
@@ -489,7 +397,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(3)+int64(len(exampleInstallerManifestFile)))
|
||||
c.Check(queue, HasLen, 2)
|
||||
@@ -515,7 +423,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
|
||||
c.Check(pkg.Name, Equals, "installer")
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFilterLatest(c *C) {
|
||||
func (s *RemoteRepoSuite) TestBuildDownloadQueueLatestOnly(c *C) {
|
||||
s.repo.Architectures = []string{"i386"}
|
||||
|
||||
err := s.repo.Fetch(s.downloader, nil, true)
|
||||
@@ -537,29 +445,13 @@ func (s *RemoteRepoSuite) TestFilterLatest(c *C) {
|
||||
newest := NewPackageFromControlFile(stanza)
|
||||
_ = s.repo.packageList.Add(newest)
|
||||
|
||||
oldLen, newLen, err := s.repo.FilterLatest(1)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(oldLen, Equals, 2)
|
||||
c.Check(newLen, Equals, 1)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, true)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(queue, HasLen, 1)
|
||||
c.Check(queue[0].File.DownloadURL(), Equals, "pool/main/a/amanda/amanda-client_3.4.0-1_i386.deb")
|
||||
c.Check(size, Equals, int64(187518))
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestFilterLatestErrors(c *C) {
|
||||
_, _, err := s.repo.FilterLatest(1)
|
||||
c.Assert(err, ErrorMatches, "package list is empty, please \\(re\\)download package indexes")
|
||||
|
||||
s.repo.packageList = NewPackageList()
|
||||
oldLen, newLen, err := s.repo.FilterLatest(0)
|
||||
c.Assert(err, ErrorMatches, "number of latest versions should be at least 1, got 0")
|
||||
c.Check(oldLen, Equals, 0)
|
||||
c.Check(newLen, Equals, 0)
|
||||
}
|
||||
|
||||
func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
s.repo.Architectures = []string{"i386"}
|
||||
s.repo.DownloadSources = true
|
||||
@@ -578,7 +470,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
@@ -622,7 +514,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(0))
|
||||
c.Check(queue, HasLen, 0)
|
||||
@@ -647,7 +539,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(s.downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
@@ -671,7 +563,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
@@ -699,7 +591,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(0))
|
||||
c.Check(queue, HasLen, 0)
|
||||
@@ -721,7 +613,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(3))
|
||||
c.Check(queue, HasLen, 1)
|
||||
@@ -752,7 +644,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
@@ -798,7 +690,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(0))
|
||||
c.Check(queue, HasLen, 0)
|
||||
@@ -824,7 +716,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(downloader.Empty(), Equals, true)
|
||||
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(size, Equals, int64(15))
|
||||
c.Check(queue, HasLen, 4)
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
Format: 1.0
|
||||
Source: aptly-test-invalid-version
|
||||
Binary: aptly-test-invalid-version
|
||||
Architecture: any
|
||||
Version: 1.2.3-
|
||||
Maintainer: Aptly Test <test@example.com>
|
||||
Standards-Version: 3.9.3
|
||||
+8
-92
@@ -6,86 +6,38 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
upstreamVersionRegex = regexp.MustCompile(`^[0-9][A-Za-z0-9.+~\-]*$`)
|
||||
debianRevisionRegex = regexp.MustCompile(`^[A-Za-z0-9.+~]*$`)
|
||||
)
|
||||
|
||||
// VersionCompareError identifies an invalid operand passed to CompareVersions.
|
||||
type VersionCompareError string
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e VersionCompareError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// CompareVersions errors.
|
||||
const (
|
||||
LeftInvalid VersionCompareError = "left version is invalid"
|
||||
RightInvalid VersionCompareError = "right version is invalid"
|
||||
BothInvalid VersionCompareError = "both versions are invalid"
|
||||
)
|
||||
|
||||
// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
||||
|
||||
// CompareVersions compares two package versions
|
||||
func CompareVersions(ver1, ver2 string) (int, error) {
|
||||
e1, u1, d1, err1 := parseVersion(ver1)
|
||||
e2, u2, d2, err2 := parseVersion(ver2)
|
||||
|
||||
if err1 != nil {
|
||||
log.Warn().Err(err1).Str("version", ver1).Msg("Invalid package version")
|
||||
}
|
||||
if err2 != nil && (err1 == nil || ver1 != ver2) {
|
||||
log.Warn().Err(err2).Str("version", ver2).Msg("Invalid package version")
|
||||
}
|
||||
|
||||
if err1 != nil && err2 != nil {
|
||||
return 0, BothInvalid
|
||||
}
|
||||
if err1 != nil {
|
||||
return 0, LeftInvalid
|
||||
}
|
||||
if err2 != nil {
|
||||
return 0, RightInvalid
|
||||
}
|
||||
func CompareVersions(ver1, ver2 string) int {
|
||||
e1, u1, d1 := parseVersion(ver1)
|
||||
e2, u2, d2 := parseVersion(ver2)
|
||||
|
||||
r := compareVersionPart(e1, e2)
|
||||
if r != 0 {
|
||||
return r, nil
|
||||
return r
|
||||
}
|
||||
|
||||
r = compareVersionPart(u1, u2)
|
||||
if r != 0 {
|
||||
return r, nil
|
||||
return r
|
||||
}
|
||||
|
||||
return compareVersionPart(d1, d2), nil
|
||||
return compareVersionPart(d1, d2)
|
||||
}
|
||||
|
||||
// parseVersions breaks down full version to components (possibly empty)
|
||||
func parseVersion(ver string) (epoch, upstream, debian string, err error) {
|
||||
func parseVersion(ver string) (epoch, upstream, debian string) {
|
||||
i := strings.Index(ver, ":")
|
||||
if i != -1 {
|
||||
epoch, ver = ver[:i], ver[i+1:]
|
||||
}
|
||||
|
||||
// Debian policy specifies that the upstream_version and debian_revision
|
||||
// are separated by the LAST hyphen in the string, since upstream_version
|
||||
// itself may legitimately contain hyphens.
|
||||
i = strings.LastIndex(ver, "-")
|
||||
i = strings.Index(ver, "-")
|
||||
if i != -1 {
|
||||
debian, ver = ver[i+1:], ver[:i]
|
||||
if debian == "" {
|
||||
// if a hyphen is detected in the upstream version
|
||||
// string without a debian revision following it, the
|
||||
// version is invalid
|
||||
return "", "", "", fmt.Errorf("could not parse version: version string ('%s-') includes hyphen without Debian revision", ver)
|
||||
}
|
||||
}
|
||||
|
||||
upstream = ver
|
||||
@@ -93,42 +45,6 @@ func parseVersion(ver string) (epoch, upstream, debian string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// isValidVersion checks whether package version is compliant with control field spec
|
||||
// source: https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version
|
||||
func isValidVersion(ver string) bool {
|
||||
epoch, upstream, deb, err := parseVersion(ver)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// validate epoch component
|
||||
if epoch != "" {
|
||||
// uint64 for unexpectedly high epoch values
|
||||
_, err := strconv.ParseUint(epoch, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if upstream == "" || !isValidUpstreamVersion(upstream) {
|
||||
return false
|
||||
}
|
||||
|
||||
if deb != "" && !isValidDebianRevision(deb) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isValidUpstreamVersion(upstream string) bool {
|
||||
return upstreamVersionRegex.MatchString(upstream)
|
||||
}
|
||||
|
||||
func isValidDebianRevision(debian string) bool {
|
||||
return debianRevisionRegex.MatchString(debian)
|
||||
}
|
||||
|
||||
// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
|
||||
func compareLexicographic(s1, s2 string) int {
|
||||
i := 0
|
||||
|
||||
+18
-108
@@ -10,82 +10,20 @@ type VersionSuite struct {
|
||||
var _ = Suite(&VersionSuite{})
|
||||
|
||||
func (s *VersionSuite) TestParseVersion(c *C) {
|
||||
e, u, d, err := parseVersion("1.3.4")
|
||||
e, u, d := parseVersion("1.3.4")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", ""})
|
||||
c.Check(err, Equals, nil)
|
||||
|
||||
e, u, d, err = parseVersion("4:1.3:4")
|
||||
e, u, d = parseVersion("4:1.3:4")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3:4", ""})
|
||||
c.Check(err, Equals, nil)
|
||||
|
||||
e, u, d, err = parseVersion("1.3.4-1")
|
||||
e, u, d = parseVersion("1.3.4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", "1"})
|
||||
c.Check(err, Equals, nil)
|
||||
|
||||
// upstream_version and debian_revision are separated by the LAST
|
||||
// hyphen, since upstream_version may itself contain hyphens.
|
||||
e, u, d, err = parseVersion("1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3-pre4", "1"})
|
||||
c.Check(err, Equals, nil)
|
||||
e, u, d = parseVersion("1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3", "pre4-1"})
|
||||
|
||||
e, u, d, err = parseVersion("4:1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3-pre4", "1"})
|
||||
c.Check(err, Equals, nil)
|
||||
|
||||
e, u, d, err = parseVersion("1:2026.07.07-0325-54-a773b756")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"1", "2026.07.07-0325-54", "a773b756"})
|
||||
c.Check(err, Equals, nil)
|
||||
|
||||
e, u, d, err = parseVersion("1:1.2024-")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "", ""})
|
||||
c.Check(err.Error(), Equals, "could not parse version: version string ('1.2024-') includes hyphen without Debian revision")
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestIsValidVersion(c *C) {
|
||||
// valid cases
|
||||
valid := isValidVersion("1.2.3-abc")
|
||||
c.Check(valid, Equals, true)
|
||||
|
||||
valid = isValidVersion("1.0.1337~rc2-3")
|
||||
c.Check(valid, Equals, true)
|
||||
|
||||
valid = isValidVersion("1.2.3+fdsfgs")
|
||||
c.Check(valid, Equals, true)
|
||||
|
||||
valid = isValidVersion("1:2.3~4-5six")
|
||||
c.Check(valid, Equals, true)
|
||||
|
||||
// upstream_version containing multiple hyphens (e.g. date/build-number/
|
||||
// git-sha style versioning) is valid as long as it's split on the LAST
|
||||
// hyphen for the debian_revision.
|
||||
valid = isValidVersion("1:2026.07.07-0325-54-a773b756")
|
||||
c.Check(valid, Equals, true)
|
||||
|
||||
// invalid cases
|
||||
valid = isValidVersion("1:1.2.3-")
|
||||
c.Check(valid, Equals, false)
|
||||
|
||||
valid = isValidVersion("42:")
|
||||
c.Check(valid, Equals, false)
|
||||
|
||||
valid = isValidVersion("1:a.1.2.3~-4")
|
||||
c.Check(valid, Equals, false)
|
||||
|
||||
// non-numeric epoch
|
||||
valid = isValidVersion("abc:1.2.3")
|
||||
c.Check(valid, Equals, false)
|
||||
|
||||
// valid upstream_version, but debian_revision contains a disallowed character
|
||||
valid = isValidVersion("1.2.3-abc!")
|
||||
c.Check(valid, Equals, false)
|
||||
|
||||
e, u, d, err := parseVersion("1.3-pre4-1")
|
||||
c.Check(err, IsNil)
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3-pre4", "1"})
|
||||
|
||||
e, u, d, err = parseVersion("4:1.3-pre4-1")
|
||||
c.Check(err, IsNil)
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3-pre4", "1"})
|
||||
e, u, d = parseVersion("4:1.3-pre4-1")
|
||||
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3", "pre4-1"})
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareLexicographic(c *C) {
|
||||
@@ -148,49 +86,21 @@ func (s *VersionSuite) TestCompareVersionPart(c *C) {
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestCompareVersions(c *C) {
|
||||
check := func(left, right string, expectedCmp int, expectedErr error) {
|
||||
cmp, err := CompareVersions(left, right)
|
||||
c.Check(cmp, Equals, expectedCmp)
|
||||
c.Check(err, Equals, expectedErr)
|
||||
}
|
||||
c.Check(CompareVersions("3:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1"), Equals, 0)
|
||||
|
||||
check("3:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1", 0, nil)
|
||||
c.Check(CompareVersions("1:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1"), Equals, -1)
|
||||
c.Check(CompareVersions("1:1.0~beta1~svn1245-1", "1.0~beta1~svn1245-1"), Equals, 1)
|
||||
c.Check(CompareVersions("1.0~beta1~svn1245-1", "1.0~beta1~svn1245-2"), Equals, -1)
|
||||
c.Check(CompareVersions("3:1.0~beta1~svn1245-1", "3:1.0~beta1-1"), Equals, -1)
|
||||
|
||||
check("1:1.0~beta1~svn1245-1", "3:1.0~beta1~svn1245-1", -1, nil)
|
||||
check("1:1.0~beta1~svn1245-1", "1.0~beta1~svn1245-1", 1, nil)
|
||||
check("1.0~beta1~svn1245-1", "1.0~beta1~svn1245-2", -1, nil)
|
||||
check("3:1.0~beta1~svn1245-1", "3:1.0~beta1-1", -1, nil)
|
||||
c.Check(CompareVersions("1.0~beta1~svn1245", "1.0~beta1"), Equals, -1)
|
||||
c.Check(CompareVersions("1.0~beta1", "1.0"), Equals, -1)
|
||||
|
||||
check("1.0~beta1~svn1245", "1.0~beta1", -1, nil)
|
||||
check("1.0~beta1", "1.0", -1, nil)
|
||||
c.Check(CompareVersions("1.0-133-avc", "1.1"), Equals, -1)
|
||||
c.Check(CompareVersions("1.0-133-avc", "1.0"), Equals, 1)
|
||||
|
||||
check("1.0-133-avc", "1.1", -1, nil)
|
||||
check("1.0-133-avc", "1.0", 1, nil)
|
||||
|
||||
check("5.2.0.3", "5.2.0.283", -1, nil)
|
||||
// upstream_version/debian_revision split on the LAST hyphen: "4.3.5-rc3-1"
|
||||
// is upstream="4.3.5-rc3", debian="1", so it's actually greater than "4.3.5a"
|
||||
// (confirmed against `dpkg --compare-versions`).
|
||||
check("4.3.5a", "4.3.5-rc3-1", -1, nil)
|
||||
|
||||
// Version validation happens independently, so these exercise parse failures.
|
||||
check("1:abc~1.2.3-", "1:1.2.3~abc-good", 0, LeftInvalid)
|
||||
check("1:1.2.3~abc-good", "1:abc~1.2.3-", 0, RightInvalid)
|
||||
check("1:abc~1.2.3-", "2:def~4.5.6-", 0, BothInvalid)
|
||||
|
||||
// The following tests exercise the last-hyphen split and additional
|
||||
// comparison edge cases (added by PR #1646, rebased onto master).
|
||||
check("1.6.0-rc1+really1.6.0-0", "1.6.0-rc1-0", 1, nil)
|
||||
check("1.2-3-4", "1.2-3-5", -1, nil)
|
||||
check("1.0-1-1", "1.0-1", 1, nil)
|
||||
check("1.0+1", "1.0", 1, nil)
|
||||
check("1.0+a-1", "1.0-a-1", -1, nil)
|
||||
check("1.0+1-1", "1.0-1", 1, nil)
|
||||
check("1.0-1+b1", "1.0-1", 1, nil)
|
||||
check("1.0-1+b1", "1.0-1~bpo1", 1, nil)
|
||||
check("1.0-1+b2", "1.0-1+b10", -1, nil)
|
||||
check("1.0.1+dfsg-1", "1.0.1-1", 1, nil)
|
||||
check("1.0-1+b1", "1.0-2", -1, nil)
|
||||
c.Check(CompareVersions("5.2.0.3", "5.2.0.283"), Equals, -1)
|
||||
c.Check(CompareVersions("4.3.5a", "4.3.5-rc3-1"), Equals, 1)
|
||||
}
|
||||
|
||||
func (s *VersionSuite) TestParseDependency(c *C) {
|
||||
|
||||
Vendored
+14
-35
@@ -32,17 +32,6 @@ database_open_attempts: -1
|
||||
architectures:
|
||||
# - amd64
|
||||
|
||||
# Hash algorithms to write Acquire-By-Hash entries for, when a publish
|
||||
# has Acquire-By-Hash enabled. Each algorithm costs a full set of index
|
||||
# copies on every publish, which is felt most on S3-like storage where
|
||||
# every write is a round trip. An empty list defaults to all four.
|
||||
# Modern apt uses SHA256; MD5Sum and SHA1 are deprecated.
|
||||
acquire_by_hash_algorithms:
|
||||
- MD5Sum
|
||||
- SHA1
|
||||
- SHA256
|
||||
- SHA512
|
||||
|
||||
# OBSOLETE
|
||||
# in aptly up to version 1.0.0, package files were stored in internal package pool
|
||||
# with MD5-dervied path, since 1.1.0 package pool layout was changed;
|
||||
@@ -62,13 +51,13 @@ dep_follow_suggests: false
|
||||
dep_follow_recommends: false
|
||||
|
||||
# When dependency looks like `package-a | package-b`, follow both variants always
|
||||
dep_follow_all_variants: false
|
||||
dep_follow_allvariants: false
|
||||
|
||||
# Follow dependency from binary package to source package
|
||||
dep_follow_source: false
|
||||
|
||||
# Log additional details while resolving dependencies (useful for debugging)
|
||||
dep_verboseresolve: false
|
||||
dep_verbose_resolve: false
|
||||
|
||||
|
||||
# PPA
|
||||
@@ -97,9 +86,6 @@ enable_metrics_endpoint: false
|
||||
# Enable API documentation on /docs
|
||||
enable_swagger_endpoint: false
|
||||
|
||||
# Enable pprof profiling endpoints on /api/debug/pprof/*
|
||||
enable_pprof_endpoint: false
|
||||
|
||||
# OBSOLETE: use via url param ?_async=true
|
||||
async_api: false
|
||||
|
||||
@@ -157,10 +143,6 @@ gpg_disable_sign: false
|
||||
# Disable signature verification of remote repositories
|
||||
gpg_disable_verify: false
|
||||
|
||||
# GPG key IDs to sign with when a publish does not name one itself
|
||||
# (the -gpg-key flag, or GpgKey in the API's signing options)
|
||||
gpg_keys: []
|
||||
|
||||
|
||||
# Publishing
|
||||
#############
|
||||
@@ -171,10 +153,6 @@ skip_contents_publishing: false
|
||||
# Do not create bz2 files
|
||||
skip_bz2_publishing: false
|
||||
|
||||
# Number of index files renamed in parallel when a publish finishes
|
||||
# (set to 1 to rename them one at a time)
|
||||
publish_rename_concurrency: 4
|
||||
|
||||
|
||||
# Storage
|
||||
##########
|
||||
@@ -207,6 +185,18 @@ filesystem_publish_endpoints:
|
||||
# # * size (compare file size)
|
||||
# verify_method: md5
|
||||
|
||||
# S3 Endpoint Support
|
||||
#
|
||||
# cloud storage). First, publishing
|
||||
# endpoints should be described in aptly configuration file. Each endpoint has name
|
||||
# and associated settings.
|
||||
#
|
||||
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
|
||||
# publishing prefix on the command line, e.g.:
|
||||
#
|
||||
# `aptly publish snapshot wheezy-main s3:test:`
|
||||
#
|
||||
|
||||
# JFrog Artifactory Endpoint Support
|
||||
#
|
||||
# aptly can be configured to publish repositories directly to JFrog Artifactory. First,
|
||||
@@ -235,17 +225,6 @@ jfrog_publish_endpoints:
|
||||
# api_key: api_key
|
||||
# access_token: access_token
|
||||
|
||||
# S3 Endpoint Support
|
||||
#
|
||||
# cloud storage). First, publishing
|
||||
# endpoints should be described in aptly configuration file. Each endpoint has name
|
||||
# and associated settings.
|
||||
#
|
||||
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
|
||||
# publishing prefix on the command line, e.g.:
|
||||
#
|
||||
# `aptly publish snapshot wheezy-main s3:test:`
|
||||
#
|
||||
s3_publish_endpoints:
|
||||
# # Endpoint Name
|
||||
# test:
|
||||
|
||||
Vendored
-52
@@ -1,55 +1,3 @@
|
||||
aptly (1.6.3) stable; urgency=medium
|
||||
|
||||
* NEW FEATURES:
|
||||
* Google Cloud Storage (GCS) publish backend (https://github.com/aptly-dev/aptly/pull/1550)
|
||||
* dput-compatible file upload API (https://github.com/aptly-dev/aptly/pull/1436)
|
||||
* JFrog Artifactory publish backend (https://github.com/aptly-dev/aptly/pull/1553)
|
||||
* AppStream (DEP-11) mirror support (https://github.com/aptly-dev/aptly/pull/1543)
|
||||
* Multiple GPG keys support (https://github.com/aptly-dev/aptly/pull/1479)
|
||||
* GPG key list & delete API (https://github.com/aptly-dev/aptly/pull/1558)
|
||||
* Edit mirror API endpoint (https://github.com/aptly-dev/aptly/pull/1535)
|
||||
* NumPackages in list responses (https://github.com/aptly-dev/aptly/pull/1559)
|
||||
* Mirror latest packages (https://github.com/aptly-dev/aptly/pull/1513)
|
||||
* Reproducible builds / `SOURCE_DATE_EPOCH` support (https://github.com/aptly-dev/aptly/pull/1537), https://github.com/aptly-dev/aptly/pull/1542)
|
||||
* `Release` file `Version` field support (https://github.com/aptly-dev/aptly/pull/1533)
|
||||
* `InRelease` file `Signed-By` field support (https://github.com/aptly-dev/aptly/pull/1518), https://github.com/aptly-dev/aptly/pull/1519)
|
||||
* GCP / Google Artifact Registry authentication (https://github.com/aptly-dev/aptly/pull/1505)
|
||||
* Update publish label & origin (https://github.com/aptly-dev/aptly/pull/1484)
|
||||
* Ubuntu 26.04 / resolute builds (https://github.com/aptly-dev/aptly/pull/1571)
|
||||
* BUG FIXES:
|
||||
* Race condition & concurrency fixes for the REST API (https://github.com/aptly-dev/aptly/pull/1574)
|
||||
* Fix empty line in `Package-List` for source packages (https://github.com/aptly-dev/aptly/pull/1588)
|
||||
* Publish: check storage exists before publishing (https://github.com/aptly-dev/aptly/pull/1587)
|
||||
* S3 publish race condition (https://github.com/aptly-dev/aptly/pull/1594)
|
||||
* Repo edit name optionally (https://github.com/aptly-dev/aptly/pull/1593)
|
||||
* Fix crash in `aptly db recover` (https://github.com/aptly-dev/aptly/pull/1565)
|
||||
* Fix deadlocks in task list (https://github.com/aptly-dev/aptly/pull/1529)
|
||||
* Fix S3 re-upload issue (https://github.com/aptly-dev/aptly/pull/1480)
|
||||
* Fix `aptly repo edit` API (https://github.com/aptly-dev/aptly/pull/1493)
|
||||
* Fix out-of-disk-space error handling (https://github.com/aptly-dev/aptly/pull/1504)
|
||||
* Fix `aptly mirror update` removing unrelated params (https://github.com/aptly-dev/aptly/pull/1466)
|
||||
* Fix concurrent pool linking race condition (https://github.com/aptly-dev/aptly/pull/1481)
|
||||
* Fix `dpkg`-compliant version comparison (https://github.com/aptly-dev/aptly/pull/1509)
|
||||
* Fix Swagger property casing and spec errors (https://github.com/aptly-dev/aptly/pull/1510), https://github.com/aptly-dev/aptly/pull/1498)
|
||||
* Remove useless nil check (https://github.com/aptly-dev/aptly/pull/1482)
|
||||
* Format Go code with gofmt (https://github.com/aptly-dev/aptly/pull/1483)
|
||||
* DEPENDENCIES CHANGES:
|
||||
* Go toolchain → 1.25.0
|
||||
* `go.opentelemetry.io/otel` → v1.41.0 (https://github.com/aptly-dev/aptly/pull/1586)
|
||||
* `go.opentelemetry.io/otel/sdk` → v1.43.0 (https://github.com/aptly-dev/aptly/pull/1584)
|
||||
* `github.com/go-jose/go-jose/v4` → v4.1.4 (https://github.com/aptly-dev/aptly/pull/1585)
|
||||
* `github.com/go-git/go-git/v5` → v5.19.1 (https://github.com/aptly-dev/aptly/pull/1590)
|
||||
* `github.com/ulikunitz/xz` → v0.5.15 (fixes 32-bit build failures)
|
||||
* `golang.org/x/crypto` → v0.45.0 (https://github.com/aptly-dev/aptly/pull/1506)
|
||||
* `google.golang.org/grpc` → v1.79.3 (https://github.com/aptly-dev/aptly/pull/1546)
|
||||
* `github.com/aws/aws-sdk-go-v2/service/s3` → v1.97.3 (https://github.com/aptly-dev/aptly/pull/1554)
|
||||
* `github.com/cloudflare/circl` → v1.6.3 (https://github.com/aptly-dev/aptly/pull/1461), https://github.com/aptly-dev/aptly/pull/1541)
|
||||
* `requests` (Python, system tests) → 2.33.0 (https://github.com/aptly-dev/aptly/pull/1460), https://github.com/aptly-dev/aptly/pull/1547)
|
||||
* `github.com/ProtonMail/go-crypto` → v1.4.0
|
||||
* `golang.org/x/net` → v0.48.0
|
||||
|
||||
-- André Roth <neolynx@gmail.com> Wed, 24 Jun 2026 18:47:05 +0200
|
||||
|
||||
aptly (1.6.2) stable; urgency=medium
|
||||
|
||||
* doc: add swagger doc for /api/gpg/key (https://github.com/aptly-dev/aptly/pull/1456)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/aptly-dev/aptly
|
||||
|
||||
go 1.26.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/AlekSi/pointer v1.1.0
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/jlaffaye/ftp v0.2.0 // indirect
|
||||
github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2
|
||||
github.com/klauspost/compress v1.18.7
|
||||
github.com/klauspost/compress v1.18.2
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
@@ -32,18 +32,18 @@ require (
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
||||
github.com/ugorji/go/codec v1.2.11
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
golang.org/x/sys v0.48.0
|
||||
golang.org/x/term v0.45.0
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/time v0.14.0
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.2 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
@@ -52,7 +52,7 @@ require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
@@ -78,14 +78,14 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/forPelevin/gomoji v1.3.0 // indirect
|
||||
@@ -93,9 +93,9 @@ require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.19.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.6 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-git/go-git/v5 v5.19.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
@@ -148,7 +148,7 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
@@ -158,27 +158,26 @@ require (
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/log v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.47.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
@@ -201,7 +200,7 @@ require (
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
go.etcd.io/etcd/client/v3 v3.5.15
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/oauth2 v0.35.0
|
||||
google.golang.org/api v0.266.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -1,10 +1,10 @@
|
||||
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
|
||||
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
@@ -44,8 +44,8 @@ github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
||||
@@ -131,8 +131,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
@@ -158,13 +158,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -193,15 +193,15 @@ github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmm
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.6 h1:sNRkftxFJxwsILiQukqp5z83QBSmntVwmgRY7AqBDO4=
|
||||
github.com/go-jose/go-jose/v4 v4.1.6/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@@ -315,8 +315,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 h1:TVZQgMi+I83S3rCuE65HnmDO6+wFPRi3n2LOzr+tr68=
|
||||
github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
@@ -446,8 +446,8 @@ github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d h1:rvtR4+9N2
|
||||
github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d/go.mod h1:Jm7yHrROA5tC42gyJ5EwiR8EWp0PUy0qOc4sE7Y8Uzo=
|
||||
github.com/smira/go-xz v0.1.0 h1:1zVLT1sITUKcWNysfHMLZWJ2Yh7yJfhREsgmUdK4zb0=
|
||||
github.com/smira/go-xz v0.1.0/go.mod h1:OmdEWnIIkuLzRLHGF4YtjDzF9VFUevEcP6YxDPRqVrs=
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -463,8 +463,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
@@ -515,36 +515,32 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
|
||||
go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
|
||||
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
|
||||
go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
|
||||
go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
|
||||
go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
|
||||
go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
|
||||
go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
@@ -553,8 +549,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||
@@ -564,8 +560,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -585,11 +581,11 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -597,8 +593,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -627,20 +623,20 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -653,15 +649,15 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.266.0 h1:hco+oNCf9y7DmLeAtHJi/uBAY7n/7XC9mZPxu1ROiyk=
|
||||
google.golang.org/api v0.266.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -671,17 +667,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
+5
-4
@@ -163,10 +163,11 @@ func (downloader *downloaderImpl) newRequest(ctx context.Context, method, url st
|
||||
req.Close = true
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Some servers (Amazon S3) read a literal "+" in a path as a space, so keep
|
||||
// it percent-encoded. RawPath does this without hiding the host the way
|
||||
// URL.Opaque does, so error messages still show the full URL.
|
||||
req.URL.RawPath = strings.ReplaceAll(req.URL.EscapedPath(), "+", "%2b")
|
||||
proxyURL, _ := downloader.client.Transport.(*http.Transport).Proxy(req)
|
||||
if proxyURL == nil && (req.URL.Scheme == "http" || req.URL.Scheme == "https") {
|
||||
req.URL.Opaque = strings.Replace(req.URL.RequestURI(), "+", "%2b", -1)
|
||||
req.URL.RawQuery = ""
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@@ -109,16 +109,6 @@ func (s *DownloaderSuite) TestDownloadWithChecksum(c *C) {
|
||||
c.Check(checksums.SHA512, Equals, "bac18bf4e564856369acc2ed57300fecba3a2c1af5ae8304021e4252488678feb18118466382ee4e1210fe1f065080210e453a80cfb37ccb8752af3269df160e")
|
||||
}
|
||||
|
||||
func (s *DownloaderSuite) TestNewRequestEscapesPlus(c *C) {
|
||||
d := s.d.(*downloaderImpl)
|
||||
|
||||
req, err := d.newRequest(s.ctx, "GET", s.url+"/test/pkg_1.0+dfsg-1_amd64.deb?a=b")
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(req.URL.RequestURI(), Equals, "/test/pkg_1.0%2bdfsg-1_amd64.deb?a=b")
|
||||
// the host must stay visible, it ends up in error messages
|
||||
c.Check(req.URL.String(), Equals, s.url+"/test/pkg_1.0%2bdfsg-1_amd64.deb?a=b")
|
||||
}
|
||||
|
||||
func (s *DownloaderSuite) TestDownload404(c *C) {
|
||||
c.Assert(s.d.Download(s.ctx, s.url+"/doesntexist", s.tempfile.Name()),
|
||||
ErrorMatches, "HTTP code 404.*")
|
||||
|
||||
+2
-34
@@ -71,13 +71,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// empty array defaults to all available architectures
|
||||
"architectures": [],
|
||||
|
||||
// Hash algorithms to write Acquire\-By\-Hash entries for, when a publish
|
||||
// has Acquire\-By\-Hash enabled. Each algorithm costs a full set of index
|
||||
// copies on every publish, which is felt most on S3\-like storage where
|
||||
// every write is a round trip. An empty array defaults to all four.
|
||||
// Modern apt uses SHA256; MD5Sum and SHA1 are deprecated.
|
||||
"acquireByHashAlgorithms": ["MD5Sum", "SHA1", "SHA256", "SHA512"],
|
||||
|
||||
// Follow contents of `Suggests:` field when processing dependencies for the package
|
||||
"dependencyFollowSuggests": false,
|
||||
|
||||
@@ -100,9 +93,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Codename for short PPA url expansion
|
||||
"ppaCodename": "",
|
||||
|
||||
// PPA Base URL
|
||||
"ppaBaseURL": "http://ppa.launchpad.net",
|
||||
|
||||
// OBSOLETE
|
||||
// in aptly up to version 1\.0\.0, package files were stored in internal package pool
|
||||
// with MD5\-dervied path, since 1\.1\.0 package pool layout was changed;
|
||||
@@ -124,9 +114,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable API documentation on /docs
|
||||
"enableSwaggerEndpoint": false,
|
||||
|
||||
// Enable pprof profiling endpoints on /api/debug/pprof/*
|
||||
"enablePprofEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
|
||||
@@ -187,10 +174,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Disable signature verification of remote repositories
|
||||
"gpgDisableVerify": false,
|
||||
|
||||
// GPG key IDs to sign with when a publish does not name one itself
|
||||
// (the \-gpg\-key flag, or GpgKey in the API's signing options)
|
||||
"gpgKeys": [],
|
||||
|
||||
|
||||
// Publishing
|
||||
//////////////
|
||||
@@ -201,10 +184,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Do not create bz2 files
|
||||
"skipBz2Publishing": false,
|
||||
|
||||
// Number of index files renamed in parallel when a publish finishes
|
||||
// (set to 1 to rename them one at a time)
|
||||
"publishRenameConcurrency": 4,
|
||||
|
||||
|
||||
// Storage
|
||||
///////////
|
||||
@@ -825,11 +804,7 @@ do not check file existence for packages listed in the internal database of the
|
||||
.
|
||||
.TP
|
||||
\-\fBlatest\fR
|
||||
download only latest version of each package (per architecture), same as \-max\-versions=1
|
||||
.
|
||||
.TP
|
||||
\-\fBmax\-versions\fR=0
|
||||
download only the N most recent versions of each package (per architecture), 0 means all versions
|
||||
download only latest version of each package (per architecture)
|
||||
.
|
||||
.SH "RENAMES MIRROR"
|
||||
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR
|
||||
@@ -2462,13 +2437,10 @@ host:port for HTTP listening
|
||||
Start HTTP server with aptly REST API\. The server can listen to either a port or Unix domain socket\. When using a socket, Aptly will fully manage the socket file\. This command also supports taking over from a systemd file descriptors to enable systemd socket activation\.
|
||||
.
|
||||
.P
|
||||
An optional metrics\-only TCP listener can be enabled with \-metrics\-listen\. It serves GET and HEAD requests at /metrics and requires enableMetricsEndpoint in the aptly configuration\. This listener uses plain HTTP; provide access control and TLS through network policy or a proxy when required\.
|
||||
.
|
||||
.P
|
||||
Example:
|
||||
.
|
||||
.P
|
||||
$ aptly api serve \-listen=:8080 $ aptly api serve \-listen=unix:///tmp/aptly\.sock $ aptly api serve \-listen=:8080 \-metrics\-listen=127\.0\.0\.1:9090
|
||||
$ aptly api serve \-listen=:8080 $ aptly api serve \-listen=unix:///tmp/aptly\.sock
|
||||
.
|
||||
.P
|
||||
Options:
|
||||
@@ -2478,10 +2450,6 @@ Options:
|
||||
host:port for HTTP listening or unix://path to listen on a Unix domain socket
|
||||
.
|
||||
.TP
|
||||
\-\fBmetrics\-listen\fR=
|
||||
host:port for optional metrics\-only HTTP listening (requires enableMetricsEndpoint)
|
||||
.
|
||||
.TP
|
||||
\-\fBno\-lock\fR
|
||||
don\(cqt lock the database
|
||||
.
|
||||
|
||||
@@ -60,13 +60,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// empty array defaults to all available architectures
|
||||
"architectures": [],
|
||||
|
||||
// Hash algorithms to write Acquire-By-Hash entries for, when a publish
|
||||
// has Acquire-By-Hash enabled. Each algorithm costs a full set of index
|
||||
// copies on every publish, which is felt most on S3-like storage where
|
||||
// every write is a round trip. An empty array defaults to all four.
|
||||
// Modern apt uses SHA256; MD5Sum and SHA1 are deprecated.
|
||||
"acquireByHashAlgorithms": ["MD5Sum", "SHA1", "SHA256", "SHA512"],
|
||||
|
||||
// Follow contents of `Suggests:` field when processing dependencies for the package
|
||||
"dependencyFollowSuggests": false,
|
||||
|
||||
@@ -89,9 +82,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Codename for short PPA url expansion
|
||||
"ppaCodename": "",
|
||||
|
||||
// PPA Base URL
|
||||
"ppaBaseURL": "http://ppa.launchpad.net",
|
||||
|
||||
// OBSOLETE
|
||||
// in aptly up to version 1.0.0, package files were stored in internal package pool
|
||||
// with MD5-dervied path, since 1.1.0 package pool layout was changed;
|
||||
@@ -113,9 +103,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Enable API documentation on /docs
|
||||
"enableSwaggerEndpoint": false,
|
||||
|
||||
// Enable pprof profiling endpoints on /api/debug/pprof/*
|
||||
"enablePprofEndpoint": false,
|
||||
|
||||
// OBSOLETE: use via url param ?_async=true
|
||||
"AsyncAPI": false,
|
||||
|
||||
@@ -176,10 +163,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Disable signature verification of remote repositories
|
||||
"gpgDisableVerify": false,
|
||||
|
||||
// GPG key IDs to sign with when a publish does not name one itself
|
||||
// (the -gpg-key flag, or GpgKey in the API's signing options)
|
||||
"gpgKeys": [],
|
||||
|
||||
|
||||
// Publishing
|
||||
//////////////
|
||||
@@ -190,10 +173,6 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Do not create bz2 files
|
||||
"skipBz2Publishing": false,
|
||||
|
||||
// Number of index files renamed in parallel when a publish finishes
|
||||
// (set to 1 to rename them one at a time)
|
||||
"publishRenameConcurrency": 4,
|
||||
|
||||
|
||||
// Storage
|
||||
///////////
|
||||
|
||||
+4
-98
@@ -209,40 +209,6 @@ func (g *GpgVerifier) argsKeyrings() (args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
type gpgvSignatureDisposition int
|
||||
|
||||
const (
|
||||
gpgvSignatureOther gpgvSignatureDisposition = iota
|
||||
gpgvSignatureGood
|
||||
gpgvSignatureUnknown
|
||||
gpgvSignatureDisqualified
|
||||
)
|
||||
|
||||
type gpgvSignatureStatus struct {
|
||||
goodSig bool
|
||||
validSig bool
|
||||
missingKeyErrSig bool
|
||||
noPubKey bool
|
||||
disqualified bool
|
||||
otherFailure bool
|
||||
}
|
||||
|
||||
func (status gpgvSignatureStatus) disposition() gpgvSignatureDisposition {
|
||||
if status.disqualified {
|
||||
return gpgvSignatureDisqualified
|
||||
}
|
||||
if status.otherFailure {
|
||||
return gpgvSignatureOther
|
||||
}
|
||||
if status.goodSig && status.validSig {
|
||||
return gpgvSignatureGood
|
||||
}
|
||||
if status.missingKeyErrSig && status.noPubKey && !status.goodSig && !status.validSig {
|
||||
return gpgvSignatureUnknown
|
||||
}
|
||||
return gpgvSignatureOther
|
||||
}
|
||||
|
||||
func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (*KeyInfo, error) {
|
||||
args = append([]string{"--status-fd", "3"}, args...)
|
||||
cmd := exec.Command(g.gpgv, args...)
|
||||
@@ -289,82 +255,22 @@ func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (*
|
||||
statusr := bufio.NewScanner(tempf)
|
||||
|
||||
result := &KeyInfo{}
|
||||
statuses := []gpgvSignatureStatus{}
|
||||
var status *gpgvSignatureStatus
|
||||
flushStatus := func() {
|
||||
if status != nil {
|
||||
statuses = append(statuses, *status)
|
||||
}
|
||||
}
|
||||
|
||||
for statusr.Scan() {
|
||||
line := strings.TrimSpace(statusr.Text())
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 && fields[0] == "[GNUPG:]" && fields[1] == "NEWSIG" {
|
||||
flushStatus()
|
||||
status = &gpgvSignatureStatus{}
|
||||
continue
|
||||
}
|
||||
if status == nil {
|
||||
status = &gpgvSignatureStatus{otherFailure: true}
|
||||
}
|
||||
if len(fields) < 2 || fields[0] != "[GNUPG:]" {
|
||||
status.otherFailure = true
|
||||
continue
|
||||
}
|
||||
|
||||
switch fields[1] {
|
||||
case "GOODSIG":
|
||||
if len(fields) < 3 {
|
||||
status.otherFailure = true
|
||||
continue
|
||||
}
|
||||
status.goodSig = true
|
||||
result.GoodKeys = append(result.GoodKeys, Key(fields[2]))
|
||||
case "VALIDSIG":
|
||||
status.validSig = true
|
||||
case "NO_PUBKEY":
|
||||
if len(fields) < 3 {
|
||||
status.otherFailure = true
|
||||
continue
|
||||
}
|
||||
status.noPubKey = true
|
||||
result.MissingKeys = append(result.MissingKeys, Key(fields[2]))
|
||||
case "ERRSIG":
|
||||
// Field 7 is the gpg-error reason; 9 is GPG_ERR_NO_PUBKEY.
|
||||
if len(fields) > 7 && fields[7] == "9" {
|
||||
status.missingKeyErrSig = true
|
||||
} else {
|
||||
status.otherFailure = true
|
||||
}
|
||||
case "BADSIG", "EXPSIG", "EXPKEYSIG", "REVKEYSIG":
|
||||
status.disqualified = true
|
||||
if strings.HasPrefix(line, "[GNUPG:] GOODSIG ") {
|
||||
result.GoodKeys = append(result.GoodKeys, Key(strings.Fields(line)[2]))
|
||||
} else if strings.HasPrefix(line, "[GNUPG:] NO_PUBKEY ") {
|
||||
result.MissingKeys = append(result.MissingKeys, Key(strings.Fields(line)[2]))
|
||||
}
|
||||
}
|
||||
flushStatus()
|
||||
|
||||
if err = statusr.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cmderr != nil {
|
||||
goodSignatures := 0
|
||||
unknownSignatures := 0
|
||||
failureExplained := cmd.ProcessState != nil && cmd.ProcessState.Exited()
|
||||
for _, signatureStatus := range statuses {
|
||||
switch signatureStatus.disposition() {
|
||||
case gpgvSignatureGood:
|
||||
goodSignatures++
|
||||
case gpgvSignatureUnknown:
|
||||
unknownSignatures++
|
||||
default:
|
||||
failureExplained = false
|
||||
}
|
||||
}
|
||||
if failureExplained && goodSignatures > 0 && unknownSignatures > 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if showKeyTip && len(g.keyRings) == 0 && len(result.MissingKeys) > 0 {
|
||||
fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")
|
||||
|
||||
|
||||
@@ -93,14 +93,8 @@ func (s *Gnupg1VerifierSuite) SetUpTest(c *C) {
|
||||
|
||||
s.verifier = NewGpgVerifier(finder)
|
||||
s.verifier.AddKeyring("./trusted.gpg")
|
||||
s.multisigOneVerifier = NewGpgVerifier(finder)
|
||||
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
|
||||
s.multisigBothVerifier = NewGpgVerifier(finder)
|
||||
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
|
||||
|
||||
c.Assert(s.verifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
|
||||
}
|
||||
|
||||
type Gnupg1SignerSuite struct {
|
||||
@@ -148,191 +142,8 @@ func (s *Gnupg2VerifierSuite) SetUpTest(c *C) {
|
||||
|
||||
s.verifier = NewGpgVerifier(finder)
|
||||
s.verifier.AddKeyring("./trusted.gpg")
|
||||
s.multisigOneVerifier = NewGpgVerifier(finder)
|
||||
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
|
||||
s.multisigBothVerifier = NewGpgVerifier(finder)
|
||||
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
|
||||
|
||||
c.Assert(s.verifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
|
||||
}
|
||||
|
||||
// The status fixtures were generated with GnuPG 2.4 at fixed times so the
|
||||
// expired key and subkey remain expired without making the other keys age out:
|
||||
//
|
||||
// export GNUPGHOME=$(mktemp -d); chmod 700 "$GNUPGHOME"
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status good fixture <good@fixture.invalid>' rsa2048 sign 0
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status expired fixture <expired@fixture.invalid>' rsa2048 sign seconds=5
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status unknown fixture <unknown-status@fixture.invalid>' rsa2048 sign 0
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status revoked fixture <revoked@fixture.invalid>' rsa2048 sign 0
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly unused expired subkey fixture <subkey@fixture.invalid>' rsa2048 cert 0
|
||||
// good=A35DB05543372EE999F6EF74EBC0B25127B9BF5A expired=DA1728275CB25D14DC8B6DC97D0DD4A295844522 unknown=A068D4E79AD454D37C0888F5B316843CAB67504E
|
||||
// revoked=CD11F407D4CEB092E66A83EC402BF908BCB202A9 primary=E1745C372D24AC29BD57FAB094ABAB0251F5E2FC active=724D68416C95AA282D8280DF22EE1E88523FD531
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-add-key "$primary" rsa2048 sign 0
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-add-key "$primary" rsa2048 sign seconds=5
|
||||
// gpg --batch --yes --faked-system-time 20250101T000002 --local-user "$good!" --local-user "$expired!" --local-user "$unknown!" --detach-sign --output status-expired-held.signature multisig.text
|
||||
// gpg --batch --yes --export "$good" "$expired" > status-expired-held.gpg
|
||||
// gpg --batch --yes --faked-system-time 20250101T000002 --local-user "$good!" --local-user "$revoked!" --local-user "$unknown!" --detach-sign --output status-revoked-held.signature multisig.text
|
||||
// sed 's/^://' "$GNUPGHOME/openpgp-revocs.d/$revoked.rev" | gpg --batch --yes --import
|
||||
// gpg --batch --yes --export "$good" "$revoked" > status-revoked-held.gpg
|
||||
// gpg --batch --yes --faked-system-time 20260902T143800 --local-user "$active!" --local-user "$unknown!" --detach-sign --output status-unused-expired-subkey.signature multisig.text
|
||||
// gpg --batch --yes --export "$primary" > status-unused-expired-subkey.gpg
|
||||
|
||||
func verifyGpgStatusFixture(c *C, finder GPGFinder, keyring, signature string) error {
|
||||
verifier := NewGpgVerifier(finder)
|
||||
verifier.AddKeyring(keyring)
|
||||
c.Assert(verifier.InitKeyring(false), IsNil)
|
||||
|
||||
cleartext, err := os.Open("multisig.text")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = cleartext.Close() }()
|
||||
|
||||
signatureFile, err := os.Open(signature)
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = signatureFile.Close() }()
|
||||
|
||||
return verifier.VerifyDetachedSignature(signatureFile, cleartext, false)
|
||||
}
|
||||
|
||||
func (s *Gnupg2VerifierSuite) TestGpgVerifierRejectsExpiredHeldSignatureAlongsideGood(c *C) {
|
||||
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-expired-held.gpg", "status-expired-held.signature")
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *Gnupg2VerifierSuite) TestGpgVerifierRejectsRevokedHeldSignatureAlongsideGood(c *C) {
|
||||
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-revoked-held.gpg", "status-revoked-held.signature")
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *Gnupg2VerifierSuite) TestGpgVerifierIgnoresUnusedExpiredSubkey(c *C) {
|
||||
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-unused-expired-subkey.gpg", "status-unused-expired-subkey.signature")
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
type fixedGPGFinder struct {
|
||||
gpgv string
|
||||
}
|
||||
|
||||
func (f fixedGPGFinder) FindGPG() (string, GPGVersion, error) {
|
||||
return f.gpgv, GPG22xPlus, nil
|
||||
}
|
||||
|
||||
func (f fixedGPGFinder) FindGPGV() (string, GPGVersion, error) {
|
||||
return f.gpgv, GPG22xPlus, nil
|
||||
}
|
||||
|
||||
func (s *GnupgSuite) TestGpgVerifierRejectsUnexplainedFailure(c *C) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
wantError bool
|
||||
goodKeys []Key
|
||||
missingKeys []Key
|
||||
}{
|
||||
{
|
||||
name: "unexplained exit",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
|
||||
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' >&3
|
||||
exit 70
|
||||
`,
|
||||
wantError: true,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
},
|
||||
{
|
||||
name: "unknown co-signer",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
|
||||
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] ERRSIG FEDCBA9876543210 1 10 00 0 9 0000000000000000FEDCBA9876543210' \
|
||||
'[GNUPG:] NO_PUBKEY FEDCBA9876543210' >&3
|
||||
exit 2
|
||||
`,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
missingKeys: []Key{"FEDCBA9876543210"},
|
||||
},
|
||||
{
|
||||
name: "non-missing-key ERRSIG",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
|
||||
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] ERRSIG FEDCBA9876543210 1 10 00 0 1 0000000000000000FEDCBA9876543210' >&3
|
||||
exit 2
|
||||
`,
|
||||
wantError: true,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
},
|
||||
{
|
||||
name: "empty signature block",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
|
||||
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
|
||||
'[GNUPG:] NEWSIG' >&3
|
||||
exit 2
|
||||
`,
|
||||
wantError: true,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
},
|
||||
{
|
||||
name: "GOODSIG without VALIDSIG",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' >&3
|
||||
exit 2
|
||||
`,
|
||||
wantError: true,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
},
|
||||
{
|
||||
name: "signal death",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
|
||||
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' >&3
|
||||
kill -TERM "$$"
|
||||
`,
|
||||
wantError: true,
|
||||
goodKeys: []Key{"0123456789ABCDEF"},
|
||||
},
|
||||
{
|
||||
name: "exit zero preserves upstream behavior",
|
||||
body: `#!/bin/sh
|
||||
printf '%s\n' \
|
||||
'[GNUPG:] NEWSIG' \
|
||||
'[GNUPG:] BADSIG 0123456789ABCDEF held' >&3
|
||||
exit 0
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
directory := c.MkDir()
|
||||
gpgv := filepath.Join(directory, "gpgv")
|
||||
c.Assert(os.WriteFile(gpgv, []byte(test.body), 0o700), IsNil)
|
||||
|
||||
verifier := NewGpgVerifier(fixedGPGFinder{gpgv: gpgv})
|
||||
keyInfo, err := verifier.runGpgv(nil, "detached signature", false)
|
||||
if test.wantError {
|
||||
c.Check(err, NotNil, Commentf(test.name))
|
||||
} else {
|
||||
c.Check(err, IsNil, Commentf(test.name))
|
||||
}
|
||||
c.Check(keyInfo.GoodKeys, DeepEquals, test.goodKeys, Commentf(test.name))
|
||||
c.Check(keyInfo.MissingKeys, DeepEquals, test.missingKeys, Commentf(test.name))
|
||||
}
|
||||
}
|
||||
|
||||
type Gnupg2SignerSuite struct {
|
||||
|
||||
@@ -13,14 +13,8 @@ var _ = Suite(&GoVerifierSuite{})
|
||||
func (s *GoVerifierSuite) SetUpTest(c *C) {
|
||||
s.verifier = &GoVerifier{}
|
||||
s.verifier.AddKeyring("./trusted.gpg")
|
||||
s.multisigOneVerifier = &GoVerifier{}
|
||||
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
|
||||
s.multisigBothVerifier = &GoVerifier{}
|
||||
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
|
||||
|
||||
c.Assert(s.verifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
|
||||
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
|
||||
}
|
||||
|
||||
type GoSignerSuite struct {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
tampered aptly multi-signature verification fixture
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA512
|
||||
|
||||
aptly multi-signature verification fixture
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAEBCgAdFiEEqYoHYeLwl1cZL9VhhT6Gnff0SYYFAmrmaZIACgkQhT6Gnff0
|
||||
SYYKYAgAujy2QKb0Gjg9YSX16Ff5XCkd/XJSVwSHxVfX5TXN2daby+a3fns78Z9u
|
||||
gbNF8qB0n6P8Q8xpPvrvnx125yJidutBR77Y1fjBm5Ks6N/x1kWg9lIXwS5eYglA
|
||||
I7Ctm3vEdD96wyYnHiBOo9sufasrfX1CnCBd1q1MkDrNpaiYmvSMeK+f6Ozs7OK2
|
||||
IfYNB5dNsI/LI65bKbgGFiOXoqMlXYFDn9GA734jrk3CvOImks4Q9GEi/qhaPKHe
|
||||
sU9lZ9KU9Okj9Yy96c7Dk29iGaxhhKTiZ+2UGvyczVRenf2+v5t8/M11CgagApPP
|
||||
nq8MRgPBKk6BdMgMd0SyZZhPj0VEpokBMwQBAQoAHRYhBOyPYRMwLkNvKOuL0hPZ
|
||||
BG62qCWlBQJq5mmSAAoJEBPZBG62qCWlgv8H/1CEchjYZDgPWFu7cZE+abBPByTX
|
||||
VcYU62CQPBpkxJ00pviuKgYRzQX0FCxQxpQOQlHv3ToVzL3a62m1vrTRa3NhT0U8
|
||||
DYdRxcW8o/11hnY3VeiPavOvORhwn70FeBYtYw7BipHHxwOElh7Ey5dkzHS+Z2OL
|
||||
0kCNvltjKEFZM+q4w2yPbSpam7wCS5gYLl0d+99ornPtzDwBOreXFoC1MER3y1hB
|
||||
H3XbLqggE6gfumfiGR9Hljvh5CT/ebk7RwMWCcrp3/MdXwfpH+6JGg1AvF/TwmEi
|
||||
XAl8LljwTybylH+8wKAHYX4ed7GvJBOigJth+3o7sh8n7oqL0YJH0JoSjbM=
|
||||
=ThV5
|
||||
-----END PGP SIGNATURE-----
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
aptly multi-signature verification fixture
|
||||
+1
-3
@@ -63,9 +63,7 @@ func checkDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader
|
||||
for {
|
||||
p, err = packets.Next()
|
||||
if err == io.EOF {
|
||||
// Revocation and expiry of held keys deliberately remain unchanged
|
||||
// from upstream in this provider; only unknown co-signers are ignored.
|
||||
if len(signers) == missingKeys {
|
||||
if len(signers) == 0 || missingKeys > 0 {
|
||||
err = errors.ErrUnknownIssuer
|
||||
} else {
|
||||
err = nil
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14
-99
@@ -10,33 +10,9 @@ import (
|
||||
|
||||
// Common set of tests shared by internal & external GnuPG implementations
|
||||
type VerifierSuite struct {
|
||||
verifier Verifier
|
||||
multisigOneVerifier Verifier
|
||||
multisigBothVerifier Verifier
|
||||
verifier Verifier
|
||||
}
|
||||
|
||||
// The partial-keyring fixtures were generated with GnuPG 2.4 at a fixed time
|
||||
// so they remain valid under the CI faketime window (2025-01-02):
|
||||
//
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' \
|
||||
// --faked-system-time 20250101T000000 \
|
||||
// --quick-generate-key 'Aptly multisign known fixture <known@fixture.invalid>' rsa2048 sign 0
|
||||
// gpg --batch --pinentry-mode loopback --passphrase '' \
|
||||
// --faked-system-time 20250101T000000 \
|
||||
// --quick-generate-key 'Aptly multisign unknown fixture <unknown@fixture.invalid>' rsa2048 sign 0
|
||||
// gpg --export <FPR_known> <FPR_unknown> > multisig-both.gpg
|
||||
// gpg --export <FPR_known> > multisig-one.gpg
|
||||
// gpg --local-user <FPR_known> --local-user <FPR_unknown> \
|
||||
// --detach-sign --output multisig.signature multisig.text
|
||||
// gpg --local-user <FPR_known> --local-user <FPR_unknown> \
|
||||
// --clearsign --output multisig.clearsigned multisig.text
|
||||
// gpg --local-user <FPR_known> --detach-sign --output multisig-known-good.signature multisig.text
|
||||
// gpg --local-user <FPR_unknown> --detach-sign --output multisig-known-bad.signature multisig-bad.text
|
||||
//
|
||||
// Replace <FPR_known> and <FPR_unknown> with the actual fingerprints from step 2.
|
||||
//
|
||||
// multisig-bad.text was made by changing multisig.text after signing it.
|
||||
|
||||
func (s *VerifierSuite) TestVerifyDetached(c *C) {
|
||||
for _, test := range []struct {
|
||||
textName, signatureName string
|
||||
@@ -61,82 +37,21 @@ func (s *VerifierSuite) TestVerifyDetached(c *C) {
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestVerifyClearsigned(c *C) {
|
||||
clearsigned, err := os.Open("multisig.clearsigned")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = clearsigned.Close() }()
|
||||
for _, test := range []struct {
|
||||
clearSignedName string
|
||||
}{
|
||||
{"1.clearsigned"},
|
||||
} {
|
||||
clearsigned, err := os.Open(test.clearSignedName)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
keyInfo, err := s.multisigBothVerifier.VerifyClearsigned(clearsigned, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"853E869DF7F44986", "13D9046EB6A825A5"})
|
||||
c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil))
|
||||
}
|
||||
keyInfo, err := s.verifier.VerifyClearsigned(clearsigned, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"04EE7237B7D453EC", "648ACFD622F3D138", "DCC9EFBF77E11517"})
|
||||
c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil))
|
||||
|
||||
func (s *VerifierSuite) TestVerifyClearsignedPartialKeyring(c *C) {
|
||||
clearsigned, err := os.Open("multisig.clearsigned")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = clearsigned.Close() }()
|
||||
|
||||
keyInfo, err := s.multisigOneVerifier.VerifyClearsigned(clearsigned, false)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"853E869DF7F44986"})
|
||||
c.Check(keyInfo.MissingKeys, DeepEquals, []Key{"13D9046EB6A825A5"})
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestVerifyDetachedPartialKeyring(c *C) {
|
||||
cleartext, err := os.Open("multisig.text")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = cleartext.Close() }()
|
||||
|
||||
signature, err := os.Open("multisig.signature")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = signature.Close() }()
|
||||
|
||||
err = s.multisigOneVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestVerifyPartialKeyringRejectsBadSignature(c *C) {
|
||||
cleartext, err := os.Open("multisig-bad.text")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = cleartext.Close() }()
|
||||
|
||||
signature, err := os.Open("multisig.signature")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = signature.Close() }()
|
||||
|
||||
err = s.multisigOneVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestVerifyRejectsBadSignatureAlongsideGood(c *C) {
|
||||
cleartext, err := os.Open("multisig.text")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = cleartext.Close() }()
|
||||
|
||||
goodSignature, err := os.Open("multisig-known-good.signature")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = goodSignature.Close() }()
|
||||
|
||||
badSignature, err := os.Open("multisig-known-bad.signature")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = badSignature.Close() }()
|
||||
|
||||
signature := io.MultiReader(goodSignature, badSignature)
|
||||
err = s.multisigBothVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
||||
c.Assert(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestVerifyNoKnownSignerStillFails(c *C) {
|
||||
cleartext, err := os.Open("multisig.text")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = cleartext.Close() }()
|
||||
|
||||
signature, err := os.Open("multisig.signature")
|
||||
c.Assert(err, IsNil)
|
||||
defer func() { _ = signature.Close() }()
|
||||
|
||||
err = s.verifier.VerifyDetachedSignature(signature, cleartext, false)
|
||||
c.Assert(err, NotNil)
|
||||
_ = clearsigned.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VerifierSuite) TestExtractClearsigned(c *C) {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
)
|
||||
|
||||
func TestArchitectureVariantQueries(t *testing.T) {
|
||||
db, err := goleveldb.NewOpenDB(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := db.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
collection := deb.NewPackageCollection(db)
|
||||
list := deb.NewPackageList()
|
||||
const normal = "test-package_1.0_amd64"
|
||||
const variant = "test-package_1.0_amd64v3"
|
||||
for _, value := range []string{"", "amd64v3"} {
|
||||
p := deb.NewPackageFromControlFile(deb.Stanza{
|
||||
"Package": "test-package", "Version": "1.0",
|
||||
"Architecture": "amd64", "Architecture-Variant": value,
|
||||
})
|
||||
if err := collection.Update(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := list.Add(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
list.PrepareIndex()
|
||||
for _, catalog := range []struct {
|
||||
name string
|
||||
value deb.PackageCatalog
|
||||
}{{"list", list}, {"collection", collection}} {
|
||||
t.Run(catalog.name, func(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, expression string
|
||||
want []string
|
||||
}{
|
||||
{"coexistence", "Name (= test-package)", []string{normal, variant}},
|
||||
{"exact_normal", normal, []string{normal}},
|
||||
{"exact_variant", variant, []string{variant}},
|
||||
{"base_architecture", "Architecture (= amd64)", []string{normal, variant}},
|
||||
{"variant_is_not_base_architecture", "Architecture (= amd64v3)", []string{}},
|
||||
{"variant_field", "Architecture-Variant (= amd64v3)", []string{variant}},
|
||||
{"special_base_architecture", "$Architecture (= amd64)", []string{normal, variant}},
|
||||
{"special_variant_architecture", "$Architecture (= amd64v3)", []string{}},
|
||||
{"normal_and_field", normal + ", Architecture (= amd64)", []string{normal}},
|
||||
{"variant_and_field", variant + ", Architecture (= amd64)", []string{variant}},
|
||||
// A field OR operand forces scanning, including PkgQuery.Matches.
|
||||
{"scan_normal", normal + " | Name (= absent)", []string{normal}},
|
||||
{"scan_variant", variant + " | Name (= absent)", []string{variant}},
|
||||
{"not_normal", "!(" + normal + ")", []string{variant}},
|
||||
{"not_variant", "!(" + variant + ")", []string{normal}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
q, err := Parse(tc.expression)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := q.Query(catalog.value).FullNames()
|
||||
sort.Strings(got)
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("query %q: got %v; want %v", tc.expression, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestArchitectureVariantQueryParsing(t *testing.T) {
|
||||
for _, arch := range []string{"amd64", "amd64v3"} {
|
||||
q, err := Parse("test-package_1.0_" + arch)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := &deb.PkgQuery{Pkg: "test-package", Version: "1.0", Arch: arch}
|
||||
if !reflect.DeepEqual(q, want) {
|
||||
t.Errorf("got %#v; want %#v", q, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArchitectureVariantArchitectureAllQuerySemantics(t *testing.T) {
|
||||
p := &deb.Package{Name: "data", Architecture: "all"}
|
||||
for _, tc := range []struct {
|
||||
expression string
|
||||
want bool
|
||||
}{
|
||||
{"Architecture (= amd64)", false},
|
||||
{"Architecture (= amd64v3)", false},
|
||||
{"$Architecture (= amd64)", true},
|
||||
{"$Architecture (= amd64v3)", true},
|
||||
{"$Architecture (= source)", false},
|
||||
} {
|
||||
q, err := Parse(tc.expression)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := q.Matches(p); got != tc.want {
|
||||
t.Errorf("query %q: got %v; want %v", tc.expression, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-10
@@ -576,14 +576,5 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// S3 lowercases user metadata keys, so the value SymLink writes as
|
||||
// "SymLink" is returned as "symlink" and a case-sensitive lookup finds
|
||||
// nothing. Matched case-insensitively so either spelling resolves.
|
||||
for key, value := range output.Metadata {
|
||||
if strings.EqualFold(key, "SymLink") {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return output.Metadata["SymLink"], nil
|
||||
}
|
||||
@@ -409,52 +409,6 @@ func (s *PublishedStorageSuite) TestSymLink(c *C) {
|
||||
c.Skip("copy not available in s3test")
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestReadLinkMetadataKeyIsCaseInsensitive(c *C) {
|
||||
// S3 lowercases user metadata keys, so a value written as "SymLink" comes
|
||||
// back as "symlink". A case-sensitive map lookup therefore returns "" with
|
||||
// a nil error, which reads as "this is not a link" rather than as failure.
|
||||
//
|
||||
// deb.packageIndexByHash relies on ReadLink to find the physical file its
|
||||
// .old pointer refers to, and removes it before rotating. With "" returned
|
||||
// the Remove is a no-op whose error is discarded, so every superseded
|
||||
// by-hash index file is left behind. On a live S3-backed archive that is
|
||||
// one leaked object per index per hash algorithm per publish, forever;
|
||||
// filesystem backends are unaffected because they have real symlinks.
|
||||
//
|
||||
// Written with PutObject rather than SymLink because the test server has
|
||||
// no CopyObject, which is why TestSymLink above is skipped and why this
|
||||
// path was never covered.
|
||||
_, err := s.storage.s3.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String("test"),
|
||||
Key: aws.String("a/b.link"),
|
||||
Body: bytes.NewReader([]byte("test")),
|
||||
Metadata: map[string]string{"symlink": "a/b"},
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
link, err := s.storage.ReadLink("a/b.link")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(link, Equals, "a/b")
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestReadLinkOnPlainObject(c *C) {
|
||||
// An object that is not a link has no SymLink metadata, and ReadLink
|
||||
// returns an empty string with a nil error rather than failing.
|
||||
// packageIndexByHash relies on that: it calls ReadLink on a path it has
|
||||
// only just confirmed exists, and treats an error as "leave it alone".
|
||||
s.PutFile(c, "a/plain", []byte("test"))
|
||||
|
||||
link, err := s.storage.ReadLink("a/plain")
|
||||
c.Check(err, IsNil)
|
||||
c.Check(link, Equals, "")
|
||||
|
||||
// A HeadObject that fails is an error, not an empty link. The two are
|
||||
// distinguishable so a caller can tell "this is not a link" from "I could
|
||||
// not find out".
|
||||
_, err = s.noSuchBucketStorage.ReadLink("a/plain")
|
||||
c.Check(err, NotNil)
|
||||
}
|
||||
|
||||
func (s *PublishedStorageSuite) TestFileExists(c *C) {
|
||||
s.PutFile(c, "a/b", []byte("test"))
|
||||
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ FROM debian:trixie-slim
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian trixie-backports main' > /etc/apt/sources.list.d/backports.list && \
|
||||
apt-get update -y && apt-get install -y --no-install-recommends curl gnupg bzip2 xz-utils ca-certificates vim procps \
|
||||
golang-1.26 golang-1.26-go golang-1.26-src \
|
||||
golang-1.25 golang-1.25-go golang-1.25-src \
|
||||
make git python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto3 python3-azure-storage \
|
||||
g++ python3-etcd3 python3-plyvel graphviz devscripts sudo dh-golang binutils-i686-linux-gnu binutils-aarch64-linux-gnu \
|
||||
binutils-arm-linux-gnueabihf bash-completion zip ruby-dev lintian npm \
|
||||
@@ -10,8 +10,8 @@ RUN echo 'deb http://deb.debian.org/debian trixie-backports main' > /etc/apt/sou
|
||||
gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu \
|
||||
faketime dput-ng && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
ln -sf /usr/lib/go-1.26/bin/go /usr/local/bin/go && \
|
||||
ln -sf /usr/lib/go-1.26/bin/gofmt /usr/local/bin/gofmt
|
||||
ln -sf /usr/lib/go-1.25/bin/go /usr/local/bin/go && \
|
||||
ln -sf /usr/lib/go-1.25/bin/gofmt /usr/local/bin/gofmt
|
||||
|
||||
RUN useradd -m --shell /bin/bash --home-dir /var/lib/aptly aptly
|
||||
RUN sed -i 's/#force_color_prompt=yes/force_color_prompt=yes/' /var/lib/aptly/.bashrc
|
||||
|
||||
@@ -25,11 +25,6 @@ from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
def remove_tmp_suffix(_, output):
|
||||
"""Normalize .tmp.<uuid> suffixes to .tmp for deterministic golden output."""
|
||||
return re.sub(r'(\.tmp)\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', r'\1', output)
|
||||
|
||||
|
||||
def ungzip_if_required(output):
|
||||
if isinstance(output, bytes) and output.startswith(b"\x1f\x8b"):
|
||||
return zlib.decompress(output, 16 + zlib.MAX_WBITS).decode('utf-8')
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
"logFormat": "default",
|
||||
"databaseOpenAttempts": 10,
|
||||
"architectures": [],
|
||||
"acquireByHashAlgorithms": [
|
||||
"MD5Sum",
|
||||
"SHA1",
|
||||
"SHA256",
|
||||
"SHA512"
|
||||
],
|
||||
"skipLegacyPool": false,
|
||||
"dependencyFollowSuggests": false,
|
||||
"dependencyFollowRecommends": false,
|
||||
@@ -22,7 +16,6 @@
|
||||
"serveInAPIMode": true,
|
||||
"enableMetricsEndpoint": true,
|
||||
"enableSwaggerEndpoint": false,
|
||||
"enablePprofEndpoint": false,
|
||||
"AsyncAPI": false,
|
||||
"databaseBackend": {
|
||||
"type": "",
|
||||
@@ -40,7 +33,6 @@
|
||||
"gpgKeys": [],
|
||||
"skipContentsPublishing": false,
|
||||
"skipBz2Publishing": false,
|
||||
"publishRenameConcurrency": 4,
|
||||
"FileSystemPublishEndpoints": {},
|
||||
"JFrogPublishEndpoints": null,
|
||||
"S3PublishEndpoints": {},
|
||||
|
||||
@@ -3,11 +3,6 @@ log_level: debug
|
||||
log_format: default
|
||||
database_open_attempts: 10
|
||||
architectures: []
|
||||
acquire_by_hash_algorithms:
|
||||
- MD5Sum
|
||||
- SHA1
|
||||
- SHA256
|
||||
- SHA512
|
||||
skip_legacy_pool: false
|
||||
dep_follow_suggests: false
|
||||
dep_follow_recommends: false
|
||||
@@ -20,7 +15,6 @@ ppa_baseurl: http://ppa.launchpad.net
|
||||
serve_in_api_mode: true
|
||||
enable_metrics_endpoint: true
|
||||
enable_swagger_endpoint: false
|
||||
enable_pprof_endpoint: false
|
||||
async_api: false
|
||||
database_backend:
|
||||
type: ""
|
||||
@@ -37,7 +31,6 @@ gpg_disable_verify: false
|
||||
gpg_keys: []
|
||||
skip_contents_publishing: false
|
||||
skip_bz2_publishing: false
|
||||
publish_rename_concurrency: 4
|
||||
filesystem_publish_endpoints: {}
|
||||
jfrog_publish_endpoints: {}
|
||||
s3_publish_endpoints: {}
|
||||
|
||||
@@ -32,17 +32,6 @@ database_open_attempts: -1
|
||||
architectures:
|
||||
# - amd64
|
||||
|
||||
# Hash algorithms to write Acquire-By-Hash entries for, when a publish
|
||||
# has Acquire-By-Hash enabled. Each algorithm costs a full set of index
|
||||
# copies on every publish, which is felt most on S3-like storage where
|
||||
# every write is a round trip. An empty list defaults to all four.
|
||||
# Modern apt uses SHA256; MD5Sum and SHA1 are deprecated.
|
||||
acquire_by_hash_algorithms:
|
||||
- MD5Sum
|
||||
- SHA1
|
||||
- SHA256
|
||||
- SHA512
|
||||
|
||||
# OBSOLETE
|
||||
# in aptly up to version 1.0.0, package files were stored in internal package pool
|
||||
# with MD5-dervied path, since 1.1.0 package pool layout was changed;
|
||||
@@ -62,13 +51,13 @@ dep_follow_suggests: false
|
||||
dep_follow_recommends: false
|
||||
|
||||
# When dependency looks like `package-a | package-b`, follow both variants always
|
||||
dep_follow_all_variants: false
|
||||
dep_follow_allvariants: false
|
||||
|
||||
# Follow dependency from binary package to source package
|
||||
dep_follow_source: false
|
||||
|
||||
# Log additional details while resolving dependencies (useful for debugging)
|
||||
dep_verboseresolve: false
|
||||
dep_verbose_resolve: false
|
||||
|
||||
|
||||
# PPA
|
||||
@@ -97,9 +86,6 @@ enable_metrics_endpoint: false
|
||||
# Enable API documentation on /docs
|
||||
enable_swagger_endpoint: false
|
||||
|
||||
# Enable pprof profiling endpoints on /api/debug/pprof/*
|
||||
enable_pprof_endpoint: false
|
||||
|
||||
# OBSOLETE: use via url param ?_async=true
|
||||
async_api: false
|
||||
|
||||
@@ -157,10 +143,6 @@ gpg_disable_sign: false
|
||||
# Disable signature verification of remote repositories
|
||||
gpg_disable_verify: false
|
||||
|
||||
# GPG key IDs to sign with when a publish does not name one itself
|
||||
# (the -gpg-key flag, or GpgKey in the API's signing options)
|
||||
gpg_keys: []
|
||||
|
||||
|
||||
# Publishing
|
||||
#############
|
||||
@@ -171,10 +153,6 @@ skip_contents_publishing: false
|
||||
# Do not create bz2 files
|
||||
skip_bz2_publishing: false
|
||||
|
||||
# Number of index files renamed in parallel when a publish finishes
|
||||
# (set to 1 to rename them one at a time)
|
||||
publish_rename_concurrency: 4
|
||||
|
||||
|
||||
# Storage
|
||||
##########
|
||||
@@ -207,6 +185,18 @@ filesystem_publish_endpoints:
|
||||
# # * size (compare file size)
|
||||
# verify_method: md5
|
||||
|
||||
# S3 Endpoint Support
|
||||
#
|
||||
# cloud storage). First, publishing
|
||||
# endpoints should be described in aptly configuration file. Each endpoint has name
|
||||
# and associated settings.
|
||||
#
|
||||
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
|
||||
# publishing prefix on the command line, e.g.:
|
||||
#
|
||||
# `aptly publish snapshot wheezy-main s3:test:`
|
||||
#
|
||||
|
||||
# JFrog Artifactory Endpoint Support
|
||||
#
|
||||
# aptly can be configured to publish repositories directly to JFrog Artifactory. First,
|
||||
@@ -235,17 +225,6 @@ jfrog_publish_endpoints:
|
||||
# api_key: api_key
|
||||
# access_token: access_token
|
||||
|
||||
# S3 Endpoint Support
|
||||
#
|
||||
# cloud storage). First, publishing
|
||||
# endpoints should be described in aptly configuration file. Each endpoint has name
|
||||
# and associated settings.
|
||||
#
|
||||
# In order to publish to S3, specify endpoint as `s3:endpoint-name:` before
|
||||
# publishing prefix on the command line, e.g.:
|
||||
#
|
||||
# `aptly publish snapshot wheezy-main s3:test:`
|
||||
#
|
||||
s3_publish_endpoints:
|
||||
# # Endpoint Name
|
||||
# test:
|
||||
@@ -446,3 +425,5 @@ packagepool_storage:
|
||||
# # defaults to "https://<accountName>.blob.core.windows.net"
|
||||
# endpoint: ""
|
||||
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release
|
||||
Downloading & parsing package files...
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2
|
||||
Keeping at most 2 versions of each package: 52 -> 23.
|
||||
Building download queue...
|
||||
Download queue: 23 items (8.41 MiB)
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.0%29/varnish-agent_3.0.0~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-dbg_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi-dev_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi1_3.0.6-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.0%29/varnish-agent_2.2.0~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi-dev_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish_3.0.6-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi-dev_3.0.6-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.1%29/varnish-agent_3.0.1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish-dbg_3.0.6-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.6-1%29/libvarnishapi-dev_3.0.6-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-dbg_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-doc_3.0.7-1~wheezy_all.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish-doc_3.0.6-1~wheezy_all.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%281.16.0%29/varnish-agent_1.16.0~wheezy_all.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.6-1%29/varnish-dbg_3.0.6-1~wheezy_i386.deb
|
||||
|
||||
Mirror `varnish` has been updated successfully.
|
||||
@@ -1,45 +0,0 @@
|
||||
Name: varnish
|
||||
Archive Root URL: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/
|
||||
Distribution: wheezy
|
||||
Components: main
|
||||
Architectures: i386, amd64
|
||||
Download Sources: no
|
||||
Download .udebs: no
|
||||
Download AppStream: no
|
||||
Number of packages: 23
|
||||
|
||||
Information from release file:
|
||||
Acquire-By-Hash: yes
|
||||
Architectures: amd64 armel armhf i386 ia64 mips mipsel powerpc ppc64el s390 s390x sparc alpha avr32 hppa m32 m68k sh arm64
|
||||
Codename: wheezy
|
||||
Components: main
|
||||
Date: Tue, 22 Jan 2019 00:28:39 +0000
|
||||
Description:
|
||||
Label: varnish30
|
||||
Origin: packagecloud.io/varnishcache/varnish30
|
||||
Suite: wheezy
|
||||
Version: 1
|
||||
Packages:
|
||||
libvarnishapi-dev_3.0.7-1~wheezy_amd64
|
||||
libvarnishapi-dev_3.0.7-1~wheezy_i386
|
||||
libvarnishapi-dev_3.0.6-1~wheezy_amd64
|
||||
libvarnishapi-dev_3.0.6-1~wheezy_i386
|
||||
libvarnishapi1_3.0.7-1~wheezy_amd64
|
||||
libvarnishapi1_3.0.7-1~wheezy_i386
|
||||
libvarnishapi1_3.0.6-1~wheezy_amd64
|
||||
libvarnishapi1_3.0.6-1~wheezy_i386
|
||||
varnish_3.0.7-1~wheezy_amd64
|
||||
varnish_3.0.7-1~wheezy_i386
|
||||
varnish_3.0.6-1~wheezy_amd64
|
||||
varnish_3.0.6-1~wheezy_i386
|
||||
varnish-agent_3.0.1~wheezy_amd64
|
||||
varnish-agent_3.0.0~wheezy_amd64
|
||||
varnish-agent_2.2.1~wheezy_i386
|
||||
varnish-agent_2.2.0~wheezy_i386
|
||||
varnish-agent_1.16.0~wheezy_all
|
||||
varnish-dbg_3.0.7-1~wheezy_amd64
|
||||
varnish-dbg_3.0.7-1~wheezy_i386
|
||||
varnish-dbg_3.0.6-1~wheezy_amd64
|
||||
varnish-dbg_3.0.6-1~wheezy_i386
|
||||
varnish-doc_3.0.7-1~wheezy_all
|
||||
varnish-doc_3.0.6-1~wheezy_all
|
||||
@@ -1,21 +0,0 @@
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/Release
|
||||
Downloading & parsing package files...
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-i386/Packages.bz2
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/dists/wheezy/main/binary-amd64/Packages.bz2
|
||||
Keeping at most 1 version of each package: 52 -> 12.
|
||||
Building download queue...
|
||||
Download queue: 12 items (4.19 MiB)
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-dbg_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%281.16.0%29/varnish-agent_1.16.0~wheezy_all.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%283.0.1%29/varnish-agent_3.0.1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-dbg_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi-dev_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi1_3.0.7-1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/libv/varnish%20%283.0.7-1%29/libvarnishapi-dev_3.0.7-1~wheezy_amd64.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish-agent%20%282.2.1%29/varnish-agent_2.2.1~wheezy_i386.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/pool/wheezy/main/v/varnish%20%283.0.7-1%29/varnish-doc_3.0.7-1~wheezy_all.deb
|
||||
|
||||
Mirror `varnish` has been updated successfully.
|
||||
@@ -1,34 +0,0 @@
|
||||
Name: varnish
|
||||
Archive Root URL: http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/
|
||||
Distribution: wheezy
|
||||
Components: main
|
||||
Architectures: i386, amd64
|
||||
Download Sources: no
|
||||
Download .udebs: no
|
||||
Download AppStream: no
|
||||
Number of packages: 12
|
||||
|
||||
Information from release file:
|
||||
Acquire-By-Hash: yes
|
||||
Architectures: amd64 armel armhf i386 ia64 mips mipsel powerpc ppc64el s390 s390x sparc alpha avr32 hppa m32 m68k sh arm64
|
||||
Codename: wheezy
|
||||
Components: main
|
||||
Date: Tue, 22 Jan 2019 00:28:39 +0000
|
||||
Description:
|
||||
Label: varnish30
|
||||
Origin: packagecloud.io/varnishcache/varnish30
|
||||
Suite: wheezy
|
||||
Version: 1
|
||||
Packages:
|
||||
libvarnishapi-dev_3.0.7-1~wheezy_amd64
|
||||
libvarnishapi-dev_3.0.7-1~wheezy_i386
|
||||
libvarnishapi1_3.0.7-1~wheezy_amd64
|
||||
libvarnishapi1_3.0.7-1~wheezy_i386
|
||||
varnish_3.0.7-1~wheezy_amd64
|
||||
varnish_3.0.7-1~wheezy_i386
|
||||
varnish-agent_3.0.1~wheezy_amd64
|
||||
varnish-agent_2.2.1~wheezy_i386
|
||||
varnish-agent_1.16.0~wheezy_all
|
||||
varnish-dbg_3.0.7-1~wheezy_amd64
|
||||
varnish-dbg_3.0.7-1~wheezy_i386
|
||||
varnish-doc_3.0.7-1~wheezy_all
|
||||
@@ -1 +0,0 @@
|
||||
ERROR: unable to update: -max-versions should be a positive number
|
||||
@@ -1,12 +0,0 @@
|
||||
Downloading: http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/dists/resolute/Release
|
||||
Downloading & parsing package files...
|
||||
Downloading: http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/dists/resolute/main/binary-amd64/Packages.gz
|
||||
Downloading: http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/dists/resolute/main/binary-amd64v3/Packages.gz
|
||||
Applying filter...
|
||||
Packages filtered: 9974 -> 2.
|
||||
Building download queue...
|
||||
Download queue: 2 items (0.54 MiB)
|
||||
Downloading: http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/pool/main/r/rust-threecpio/3cpio_0.14.0-1ubuntu1_amd64v3.deb
|
||||
Downloading: http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/pool/main/r/rust-threecpio/3cpio_0.14.0-1ubuntu1_amd64.deb
|
||||
|
||||
Mirror `arch-variant` has been updated successfully.
|
||||
@@ -1,2 +0,0 @@
|
||||
Pamd64 3cpio 0.14.0-1ubuntu1
|
||||
Pamd64v3 3cpio 0.14.0-1ubuntu1
|
||||
@@ -502,67 +502,3 @@ class UpdateMirror28Test(BaseTest):
|
||||
self.check_output()
|
||||
self.check_cmd_output("aptly mirror show appstream-test", "mirror_show",
|
||||
match_prepare=lambda s: re.sub(r"Last update: [0-9:+A-Za-z -]+\n", "", s))
|
||||
|
||||
|
||||
class UpdateMirror29Test(BaseTest):
|
||||
"""
|
||||
update mirrors: keep only the 2 most recent versions of each package
|
||||
"""
|
||||
sortOutput = True
|
||||
fixtureCmds = [
|
||||
"aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/ wheezy main",
|
||||
]
|
||||
runCmd = "aptly mirror update --ignore-signatures -max-versions=2 varnish"
|
||||
outputMatchPrepare = filterOutRedirects
|
||||
|
||||
def check(self):
|
||||
self.check_output()
|
||||
self.check_cmd_output("aptly mirror show -with-packages varnish", "mirror_show",
|
||||
match_prepare=lambda s: re.sub(r"Last update: [0-9:+A-Za-z -]+\n", "", s))
|
||||
|
||||
|
||||
class UpdateMirror30Test(BaseTest):
|
||||
"""
|
||||
update mirrors: -latest keeps only the most recent version of each package
|
||||
"""
|
||||
sortOutput = True
|
||||
fixtureCmds = [
|
||||
"aptly -architectures=i386,amd64 mirror create --ignore-signatures varnish http://repo.aptly.info/system-tests/packagecloud.io/varnishcache/varnish30/debian/ wheezy main",
|
||||
]
|
||||
runCmd = "aptly mirror update --ignore-signatures -latest varnish"
|
||||
outputMatchPrepare = filterOutRedirects
|
||||
|
||||
def check(self):
|
||||
self.check_output()
|
||||
self.check_cmd_output("aptly mirror show -with-packages varnish", "mirror_show",
|
||||
match_prepare=lambda s: re.sub(r"Last update: [0-9:+A-Za-z -]+\n", "", s))
|
||||
|
||||
|
||||
class UpdateMirror31Test(BaseTest):
|
||||
"""
|
||||
update mirrors: negative -max-versions
|
||||
"""
|
||||
runCmd = "aptly mirror update -max-versions=-1 varnish"
|
||||
expectedCode = 1
|
||||
|
||||
|
||||
class UpdateMirror32Test(BaseTest):
|
||||
"""
|
||||
update mirrors: architecture variants
|
||||
"""
|
||||
sortOutput = True
|
||||
longTest = False
|
||||
fixtureCmds = [
|
||||
"aptly mirror create --ignore-signatures "
|
||||
"-architectures=amd64,amd64v3 "
|
||||
"-filter='Name (3cpio)' "
|
||||
"arch-variant http://repo.aptly.info/system-tests/archive.ubuntu.com/ubuntu/ resolute main",
|
||||
]
|
||||
runCmd = "aptly mirror update --ignore-signatures arch-variant"
|
||||
|
||||
def check(self):
|
||||
self.check_output()
|
||||
self.check_cmd_output(
|
||||
"aptly mirror search -format='{{.ShortKey}}' arch-variant 'Name (3cpio)'",
|
||||
"packages",
|
||||
)
|
||||
@@ -1,6 +1,4 @@
|
||||
import re
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from lib import BaseTest
|
||||
|
||||
|
||||
@@ -82,58 +80,3 @@ class DiffSnapshot6Test(BaseTest):
|
||||
"aptly snapshot create snap2 from mirror wheezy-main",
|
||||
]
|
||||
runCmd = "aptly snapshot diff snap1 snap2"
|
||||
|
||||
|
||||
class DiffSnapshotArchitectureVariantTest(BaseTest):
|
||||
"""
|
||||
snapshot diff: display base and variant architectures independently
|
||||
"""
|
||||
runCmd = "aptly snapshot diff variant-empty variant-normal"
|
||||
|
||||
def prepare_fixture(self):
|
||||
super().prepare_fixture()
|
||||
self.run_cmd("aptly repo create variant-display")
|
||||
self.run_cmd("aptly snapshot create variant-empty empty")
|
||||
with tempfile.TemporaryDirectory(prefix="aptly-diff-variant-") as tmp:
|
||||
for arch in ("amd64", "amd64v3"):
|
||||
package_dir = Path(tmp) / arch
|
||||
control_dir = package_dir / "DEBIAN"
|
||||
control_dir.mkdir(parents=True)
|
||||
control = (
|
||||
"Package: test-package\n"
|
||||
"Version: 1.0\n"
|
||||
"Architecture: amd64\n"
|
||||
"Maintainer: Aptly Test <test@example.com>\n"
|
||||
"Description: Snapshot diff display fixture\n"
|
||||
)
|
||||
if arch == "amd64v3":
|
||||
control += "Architecture-Variant: amd64v3\n"
|
||||
(control_dir / "control").write_text(control)
|
||||
package_file = str(Path(tmp) / (
|
||||
"test-package_1.0_" + arch + ".deb"))
|
||||
self.run_cmd(["dpkg-deb", "--build", str(package_dir), package_file])
|
||||
self.run_cmd(["aptly", "repo", "add", "variant-display", package_file])
|
||||
snapshot = "variant-normal" if arch == "amd64" else "variant-both"
|
||||
self.run_cmd([
|
||||
"aptly", "snapshot", "create", snapshot,
|
||||
"from", "repo", "variant-display",
|
||||
])
|
||||
|
||||
def check(self):
|
||||
def check_row(output, expected):
|
||||
lines = output.strip().splitlines()
|
||||
self.check_equal(len(lines), 2)
|
||||
self.check_equal(" ".join(lines[0].split()),
|
||||
"Arch | Package | Version in A | Version in B")
|
||||
self.check_equal(" ".join(lines[1].split()), expected)
|
||||
|
||||
check_row(self.output, "+ amd64 | test-package | - | 1.0")
|
||||
for left, right, expected in (
|
||||
("variant-normal", "variant-both",
|
||||
"+ amd64v3 | test-package | - | 1.0"),
|
||||
("variant-both", "variant-normal",
|
||||
"- amd64v3 | test-package | 1.0 | -"),
|
||||
("variant-normal", "variant-empty",
|
||||
"- amd64 | test-package | 1.0 | -"),
|
||||
):
|
||||
check_row(self.run_cmd(["aptly", "snapshot", "diff", left, right]), expected)
|
||||
@@ -1,14 +0,0 @@
|
||||
Loading packages...
|
||||
Generating metadata files and linking package files...
|
||||
Finalizing metadata files...
|
||||
Signing file 'Release' with gpg, please enter your passphrase when prompted:
|
||||
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
|
||||
|
||||
Local repos repo1, repo2 have been successfully published.
|
||||
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
|
||||
Now you can add following line to apt sources:
|
||||
deb http://your-server/ maverick contrib main
|
||||
deb-src http://your-server/ maverick contrib main
|
||||
Don't forget to add your GPG key to apt with apt-key.
|
||||
|
||||
You can also use `aptly serve` to publish your repositories over HTTP quickly.
|
||||
@@ -1098,34 +1098,3 @@ class PublishRepo39Test(BaseTest):
|
||||
self.check_equal(first_release, self.read_file('public/dists/maverick/Release'))
|
||||
self.check_equal(first_release_gpg, self.read_file('public/dists/maverick/Release.gpg'))
|
||||
self.check_equal(first_inrelease, self.read_file('public/dists/maverick/InRelease'))
|
||||
|
||||
|
||||
class PublishRepo40Test(BaseTest):
|
||||
"""
|
||||
publish repo: skeleton files with multiple components
|
||||
"""
|
||||
fixtureCmds = [
|
||||
"aptly repo create repo1",
|
||||
"aptly repo create repo2",
|
||||
"aptly repo add repo1 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb",
|
||||
"aptly repo add repo2 ${files}/pyspi-0.6.1-1.3.stripped.dsc",
|
||||
]
|
||||
runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -component=main,contrib -distribution=maverick -skip-contents repo1 repo2"
|
||||
gold_processor = BaseTest.expand_environ
|
||||
|
||||
def prepare_fixture(self):
|
||||
super(PublishRepo40Test, self).prepare_fixture()
|
||||
|
||||
self.write_file(os.path.join('skel', 'dists', 'maverick', 'main', 'dep11',
|
||||
'Components-amd64.yml'), 'main dep11 payload')
|
||||
self.write_file(os.path.join('skel', 'dists', 'maverick', 'contrib', 'dep11',
|
||||
'Components-amd64.yml'), 'contrib dep11 payload')
|
||||
|
||||
def check(self):
|
||||
super(PublishRepo40Test, self).check()
|
||||
|
||||
# each skeleton file is emitted once per publish, not once per component
|
||||
self.check_equal(self.read_file('public/dists/maverick/main/dep11/Components-amd64.yml'),
|
||||
'main dep11 payload')
|
||||
self.check_equal(self.read_file('public/dists/maverick/contrib/dep11/Components-amd64.yml'),
|
||||
'contrib dep11 payload')
|
||||
@@ -1,4 +1,3 @@
|
||||
from lib import remove_tmp_suffix
|
||||
from s3_lib import S3Test
|
||||
|
||||
|
||||
@@ -56,7 +55,6 @@ class S3Publish2Test(S3Test):
|
||||
"aptly repo remove local-repo pyspi"
|
||||
]
|
||||
sortOutput = True
|
||||
outputMatchPrepare = remove_tmp_suffix
|
||||
runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick s3:test1:"
|
||||
|
||||
def check(self):
|
||||
@@ -98,7 +96,6 @@ class S3Publish3Test(S3Test):
|
||||
"aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick snap1 s3:test1:",
|
||||
]
|
||||
sortOutput = True
|
||||
outputMatchPrepare = remove_tmp_suffix
|
||||
runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick s3:test1: snap3"
|
||||
|
||||
def check(self):
|
||||
@@ -181,7 +178,6 @@ class S3Publish6Test(S3Test):
|
||||
"aptly repo remove local-repo pyspi"
|
||||
]
|
||||
sortOutput = True
|
||||
outputMatchPrepare = remove_tmp_suffix
|
||||
runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick s3:test1:"
|
||||
|
||||
def check(self):
|
||||
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user